DocumentationReference

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. createApprovalStoreProvider and createHumanTaskProvider persist to @hazeljs/​agent stores; 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' and defaultDecision: 'deny'
  • Trusted identity from runtime context, not tool args
  • Shared auditSink (Kafka / OTEL / log shipper) — not InMemoryAuditSink
  • Shared approvalProvider when running more than one process
  • audit.critical: true (default in enforce) so audit failure fails closed
  • MCP invoke wrapped with protectMcpInvoke if tools are exposed over MCP
  • hazel gatekeeper validate in CI

Incremental adoption

  1. Wrap sensitive functions with fromFunction.
  2. Optionally set authorizationGate on AgentRuntime via createToolExecutorGate.
  3. Opt in MCP with protectMcpInvoke (default MCP invoke still bypasses ToolExecutor).
  4. Load DNA policies with policiesFromDna.
  5. Before more than one replica: swap in Kafka/OTEL audit and Redis approvals.

Mandatory Agent OS enforcement is not enabled in this release.