Agent Gatekeeper — authorize every tool call
HazelJS Agent Gatekeeper is the runtime authorization and policy-enforcement layer that controls every tool action attempted by an agent.
Tagline: Every tool call authorized before execution.
What it is not
- Not a prompt guardrail
- Not Skillgate (Skillgate curates which APIs become tools)
- Not DNA (DNA declares identity and limits)
- Not the Durable Kernel (the kernel executes and records runs)
- Not Control Plane admission (admission is for desired-state resources)
Core API
const gatekeeper = new AgentGatekeeper({
mode: 'enforce',
defaultDecision: 'deny',
policies,
approvalProvider,
auditSink,
});
await gatekeeper.evaluate(context);
await gatekeeper.simulate(context); // never executes
await gatekeeper.execute({ context, tool });
Trusted identity lives on ToolInvocationContext. Never accept tenant/agent/environment from model-generated arguments.
Modes
- enforce — production. Fail-closed. Default deny.
- audit — logs would-be decisions then allows (unsafe for production enforcement).
- disabled — bypass with minimal observability.
Never silently fall back from enforce to audit.
Horizontal scale
InMemoryAuditSink and InMemoryApprovalProvider are process-local. Each replica has its own RAM. Use them in tests, not in a fleet.
Authorization itself is stateless: the same policies + trusted context produce the same decision on every instance. What must be shared is audit and approvals.
import { KafkaAuditTransport } from '@hazeljs/audit';
import {
CompositeAuditSink,
ConsoleAuditSink,
createAuditTransportSink,
createOtelAuditSink,
createRedisApprovalProvider,
} from '@hazeljs/agent-gatekeeper';
import { trace } from '@opentelemetry/api';
const gatekeeper = new AgentGatekeeper({
mode: 'enforce',
defaultDecision: 'deny',
policies,
auditSink: new CompositeAuditSink([
new ConsoleAuditSink(),
createAuditTransportSink(
new KafkaAuditTransport({
sender: kafkaProducer,
topic: 'hazel.gatekeeper.audit',
key: (event) => String(event.resourceId ?? event.actor?.id ?? ''),
})
),
createOtelAuditSink({ trace }),
]),
approvalProvider: createRedisApprovalProvider(redis),
});
- Audit:
ConsoleAuditSink(JSON → log shipper),createAuditTransportSink(Kafka/file, awaited, fail-closed),createOtelAuditSink. - Approvals:
createRedisApprovalProvider(redis)so create / resolve / consume work on any replica.createApprovalStoreProviderandcreateHumanTaskProviderpersist to@hazeljs/agentstores; prefer Redis for atomic consume.
If replica A requests HITL and replica B resumes the run:
await replicaB.resolve(approvalId, 'approved', 'operator-1');
await replicaA.execute({
context: { ...context, approvalToken: approvalId },
tool,
});
Package docs: Agent Gatekeeper. Audit transports: Audit package.
Production checklist
mode: 'enforce'anddefaultDecision: 'deny'- Trusted identity from runtime context, not tool args
- Shared
auditSink(Kafka / OTEL / log shipper) — notInMemoryAuditSink - Shared
approvalProviderwhen running more than one process audit.critical: true(default in enforce) so audit failure fails closed- MCP invoke wrapped with
protectMcpInvokeif tools are exposed over MCP hazel gatekeeper validatein CI
Incremental adoption
- Wrap sensitive functions with
fromFunction. - Optionally set
authorizationGateonAgentRuntimeviacreateToolExecutorGate. - Opt in MCP with
protectMcpInvoke(default MCP invoke still bypasses ToolExecutor). - Load DNA policies with
policiesFromDna. - Before more than one replica: swap in Kafka/OTEL audit and Redis approvals.
Mandatory Agent OS enforcement is not enabled in this release.