Home Live demo Blog GitHub ↗
← Home
Reference

Zehrava Gate Docs

Zehrava Gate is the write-path control plane for AI agents. Agents can read systems freely, but any real-world action — sending email, importing CRM records, updating databases, issuing refunds, publishing files — must pass through Gate first. Agents submit an intent, Gate evaluates policy, optionally requests human approval, then issues a signed execution order. Every step is deterministic, auditable, and fail-closed.

Quickstart

Start the Gate server with npx — no install required for the server itself:

npx zehrava-gate --port 4000

Or install globally:

npm install -g zehrava-gate
zehrava-gate --port 4000

Server options:

FlagDefaultDescription
--port4000HTTP port to listen on
--data-dir./dataDirectory for SQLite database and stored payloads
--policy-dir./policiesDirectory containing your YAML policy files

Self-hosting: Gate stores all data locally in SQLite. Nothing leaves your infrastructure. No cloud dependency, no API keys for the server itself.

Register an agent

Before an agent can submit intents, it needs to be registered. Registration returns an API key — store this securely. It cannot be retrieved again.

curl -X POST http://localhost:4000/v1/agents/register \
  -H "Content-Type: application/json" \
  -d '{"name": "my-crm-agent", "riskTier": "standard"}'
{
  "agentId": "agt_a1b2c3d4e5f6",
  "apiKey":  "gate_sk_••••••••••••••••••••",
  "name":    "my-crm-agent",
  "riskTier": "standard"
}

Save the API key immediately. Gate stores only a hash — if you lose it, register a new agent.

The riskTier field is stored for auditing. Current values: standard, elevated, restricted. Future policy enforcement will gate actions by tier.

Your first intent

Create a policy file first. Drop it in your --policy-dir:

# policies/support-reply.yaml
id: support-reply
destinations: [zendesk.reply, intercom.reply]
auto_approve_under: 1
block_if_terms:
  - "refund guaranteed"
  - "legal action"
expiry_minutes: 30

Then submit an intent from your agent:

const { Gate } = require('zehrava-gate')
const gate = new Gate({ endpoint: 'http://localhost:4000', apiKey: 'gate_sk_...' })

const p = await gate.propose({
  payload:     'Thank you — your issue has been resolved.',
  destination: 'zendesk.reply',
  policy:      'support-reply',
  recordCount: 1
})

console.log(p.status) // "approved" | "blocked" | "pending_approval" | "duplicate_blocked"
console.log(p.intentId)
console.log(p.blockReason) // set if status === "blocked" or "duplicate_blocked"

Gate evaluates the policy immediately and returns one of four statuses:

Policy overview

Policies are YAML files stored in your --policy-dir. Gate loads them on startup and caches them. Each policy has an id that your agent references when submitting an intent via gate.propose().

Rules are evaluated top-to-bottom; the first rule that produces a decision ends evaluation:

1

Policy exists?

If the referenced policy ID has no YAML file, Gate returns blocked immediately.

2

Destination allowlist

If destinations is set and the intent's destination isn't in the list, Gate blocks it.

3

Payload type check

If allowed_types is set and the payload's file extension isn't in the list, Gate blocks it.

4

Term scan

If block_if_terms is set, Gate scans the payload content for each term. Matching is normalized — strips punctuation, collapses whitespace, maps leet substitutions. First match blocks the intent.

5

require_approval: always

If set, every intent goes to pending_approval regardless of other rules.

6

Record count thresholds

auto_approve_under: if recordCount ≤ threshold, approve automatically.
require_approval_over: if recordCount > threshold, send to pending.

7

Default

If no rule matched, Gate returns pending_approval. Safe default — when in doubt, ask a human.

All policy fields

FieldTypeDescription
id * string Unique identifier. Must match the filename (without .yaml). Referenced in intent submissions (gate.propose()).
destinations string[] Allowlist of destination identifiers. Intents to any other destination are blocked. Omit to allow any destination.
allowed_types string[] Allowlist of file extensions without dot (e.g. [csv, json, txt]). Only applies when payload is a file path.
block_if_terms string[] List of terms to scan the payload for. Matching is normalized — case-insensitive, punctuation-stripped, leet-decoded. First match blocks with reason: "Payload contains blocked term: <term>"
require_approval string Set to "always" to require human approval on every intent regardless of other rules. Recommended for high-risk destinations.
auto_approve_under number If recordCount is ≤ this value, the intent is auto-approved. Useful for single-record operations that don't need review.
require_approval_over number If recordCount exceeds this value, the intent goes to pending_approval. Use alongside auto_approve_under to define a safe/unsafe threshold.
expiry_minutes number How long a pending intent can wait for approval before it expires. Expired intents cannot be approved. Default: 60.
field_checks object[] Schema-aware JSON field validation. Each check has path (dot path), plus any of: required, min, max, max_length, pattern. If any check fails, intent is blocked with a specific reason.
environments object Environment-specific overrides for thresholds and approval behavior. Gate selects the block using metadata.environment (e.g. production, staging, development) and merges those fields over the base policy.
rate_limits object Per-agent action limits enforced server-side. Supports per_agent_per_hour and per_agent_per_day. Gate counts recent intents for the submitting agent in SQLite. Errors fail closed.

Policy examples

Support reply — auto-approve safe messages, block sensitive terms

id: support-reply
destinations: [zendesk.reply, intercom.reply, freshdesk.reply]
allowed_types: [text, json]
block_if_terms:
  - "refund guaranteed"
  - "legal action"
  - "sue"
auto_approve_under: 1   # single reply → auto-approve
expiry_minutes: 30

CRM import — auto-approve small batches, review large ones

id: crm-low-risk
destinations: [salesforce.import, hubspot.contacts]
allowed_types: [csv, json]
auto_approve_under: 100        # ≤100 records → auto
require_approval_over: 100     # >100 records → human review
expiry_minutes: 60

Finance — always require human approval

id: finance-high-risk
destinations: [stripe.refund, quickbooks.journal, netsuite.payment]
require_approval: always
expiry_minutes: 15              # short window — stale approvals are dangerous

Schema-aware policy — validate fields, rate limit, env overrides

id: schema-checked-email
destinations: [sendgrid.send, loops.send]

field_checks:
  - path: "to"
    required: true
    pattern: "^[^@]+@[^@]+\\.[^@]+$"
  - path: "amount"
    min: 1
    max: 100

rate_limits:
  per_agent_per_hour: 50
  per_agent_per_day: 200

environments:
  production:
    require_approval: always
  development:
    require_approval: never

auto_approve_under: 1

Internal blog publish — scoped destination, always approve

id: blog-publish
destinations: [blog.publish, cms.publish]
require_approval: always
expiry_minutes: 120
V1 backward compat: /v1/propose, /v1/approve, /v1/reject, /v1/deliver, and /v1/proposals still work. Canonical routes are /v1/intents and /v1/executions.

API — Register agent

⚠ Production note: /v1/agents/register is unauthenticated — intentional for local dev bootstrapping. In production, firewall this endpoint or put it behind a VPN. Anyone who can reach it can mint agent credentials.
POST /v1/agents/register
Bootstrap endpoint — no authentication required. Returns an API key for all subsequent calls.

FieldTypeDescription
name *stringHuman-readable agent name. Appears in audit logs and the dashboard.
riskTierstringstandard | elevated | restricted. Default: standard.
ownerstringTeam or system that owns this agent. Stored in registry.
allowed_policiesstring[]Policy IDs this agent is permitted to invoke. Omit to allow all.
allowed_destinationsstring[]Destinations this agent is permitted to target. Omit to allow all.
frameworkstringAgent framework: langgraph, crewai, autogen, custom, etc. Informational.
metadataobjectArbitrary key/value pairs. Stored in registry.
{
  "agentId":  "agt_a1b2c3d4",
  "apiKey":   "gate_sk_••••••••••••",
  "name":     "my-crm-agent",
  "status":   "active"
}

API — Agent registry

GET /v1/agents
List all registered agents and their status.

Authentication: Authorization: Bearer <apiKey>

GET /v1/agents/:id
Get a single agent by ID, including registry metadata and allowed policies/destinations.

Authentication: Authorization: Bearer <apiKey>

POST /v1/agents/:id/revoke
Permanently revoke an agent's API key. All subsequent requests from that agent will return 401.

Authentication: Authorization: Bearer <apiKey>

POST /v1/agents/:id/suspend
Temporarily suspend an agent. Suspended agents cannot submit intents. Reactivate by calling register again.

Authentication: Authorization: Bearer <apiKey>

API — Submit intent

POST /v1/intents
Submit an agent action for policy evaluation. Gate evaluates synchronously and returns a decision immediately.

Authentication: Authorization: Bearer <apiKey>

FieldTypeDescription
destination *stringTarget system (e.g. salesforce.import). Must match your policy's destinations allowlist.
policy *stringPolicy ID to evaluate (matches YAML filename without extension).
payloadstringContent being proposed. Pass the actual text or JSON string. File paths are only meaningful when Gate and the submitting process share a filesystem — in distributed deployments, serialize content before submitting.
recordCountnumberBatch size. Used for threshold evaluation (auto_approve_under, require_approval_over).
estimated_value_usdnumberEstimated financial value. Contributes to risk score.
sensitivity_tagsstring[]Data sensitivity labels: pii, financial, health, legal. Contributes to risk score.
idempotency_keystringIf set, duplicate submissions with the same key + agent return status: "duplicate_blocked" instead of re-evaluating.
on_behalf_ofstringAgent ID of the orchestrating agent. Logged in audit trail for multi-agent accountability.
expiresInstringApproval window: 30m, 1h, 2h. Default: 1h.
metadataobjectArbitrary key/value pairs. Stored with the intent and included in audit events.
{
  "intentId":       "int_a1b2c3d4e5f6",
  "status":         "approved",          // approved | blocked | pending_approval | duplicate_blocked
  "risk_score":     0.42,                // 0–1 composite risk signal
  "risk_level":     "medium",            // low | medium | high | critical
  "reason_code":    "approved_by_policy",
  "blockReason":    null,
  "expiresAt":      "2026-03-08T12:00:00.000Z"
}

Risk score: Composite 0–1 signal. Factors: destination category (0.2–0.4), record count vs threshold (0–0.3), estimated_value_usd > $1,000 (+0.2), sensitivity_tags (+0.1 each). The score informs the approver but does not override policy — require_approval: always wins regardless of score. Auto-approve policy ignores score. Use the score to triage the approval queue.

Status values: approved · blocked · pending_approval · duplicate_blocked

Reason codes: approved_by_policy · record_threshold_exceeded · blocked_term_detected · destination_not_allowed · sensitive_data_detected · manual_review_required

API — List intents

GET /v1/intents?status=pending_approval&limit=50
List intents with optional status filter. Used by the dashboard approval queue.

Authentication: Authorization: Bearer <apiKey>

ParamTypeDescription
statusstringFilter: pending_approval · approved · blocked · scheduled · succeeded · failed · expired. Omit for all. (scheduled = execution order issued, worker not yet reported.)
limitnumberMax results. Default: 50.
GET /v1/intents/:id
Fetch a single intent by ID including full policy decision fields.

Authentication: Authorization: Bearer <apiKey>

API — Approve

POST /v1/intents/:id/approve
Approve a pending intent. Transitions state to approved. The intent can then be executed.

Authentication: Authorization: Bearer <apiKey>

{ "status": "approved", "approvedAt": "2026-03-08T12:00:00.000Z" }

Already-blocked intents return 409.

API — Reject

POST /v1/intents/:id/reject
Reject a pending intent. Sets status to blocked.

Authentication: Authorization: Bearer <apiKey>

FieldTypeDescription
reasonstringRejection reason. Stored in audit trail.

API — Execute

POST /v1/intents/:id/execute
Request a signed execution order for an approved intent. The order authorizes a worker to perform the write inside your infrastructure. Token expires in 15 minutes.

Authentication: Authorization: Bearer <apiKey>

{
  "executionId":    "exe_a1b2c3d4",
  "execution_token": "gex_••••••••••••",  // one-time, 15min TTL
  "intent_id":      "int_a1b2c3d4e5f6",
  "expires_at":     "2026-03-08T12:15:00.000Z",
  "mode":           "runner_exec"
}

Returns 409 if intent is not in approved state. Returns 409 if an active execution already exists for this intent.

API — Execution status

GET /v1/executions/:id
Fetch an execution record including mode, worker report, and outcome.

Authentication: Authorization: Bearer <apiKey>

{
  "executionId":  "exe_a1b2c3d4",
  "intent_id":   "int_a1b2c3d4e5f6",
  "mode":         "runner_exec",
  "status":       "succeeded",          // pending | executing | succeeded | failed | expired
  "worker_report": { "note": "Imported 847 records" },
  "created_at":   "2026-03-08T12:00:00.000Z",
  "completed_at": "2026-03-08T12:00:04.000Z"
}

API — Report outcome

POST /v1/executions/:id/report
Worker calls this after executing the write. Transitions intent to succeeded or failed. Accepts either the one-time execution token or a valid API key.

Authentication: Authorization: Bearer <execution_token> or Bearer <apiKey>

FieldTypeDescription
status *stringsucceeded or failed
reportobjectArbitrary worker report data. Stored with execution record.
// Worker reports success
{
  "status":  "succeeded",
  "report":  { "records_written": 847, "duration_ms": 1240 }
}

API — Policy decision

GET /v1/intents/:id/decision
Fetch the stored policy decision record for an intent — reason code, risk score, risk level, and matched factors.

Authentication: Authorization: Bearer <apiKey>

{
  "intent_id":   "int_a1b2c3d4e5f6",
  "decision":     "approved",
  "reason_code":  "approved_by_policy",
  "risk_score":   0.42,
  "risk_level":   "medium",
  "evaluated_at": "2026-03-08T12:00:00.000Z"
}

API — Audit trail

GET /v1/intents/:id/audit
Full audit trail for an intent — every state transition in order.

Authentication: Authorization: Bearer <apiKey>

[
  { "eventType": "proposed",            "actor": "my-crm-agent", "createdAt": "..." },
  { "eventType": "policy_checked",      "actor": "system",       "metadata": { "result": "approved", "risk_score": 0.42 } },
  { "eventType": "approved",            "actor": "system",       "createdAt": "..." },
  { "eventType": "execution_requested", "actor": "my-crm-agent", "createdAt": "..." },
  { "eventType": "execution_succeeded", "actor": "worker",       "createdAt": "..." }
]

API — Metrics

GET /v1/metrics
Aggregated counters across all intents and executions. Used by the dashboard metrics bar.

Authentication: Authorization: Bearer <apiKey>

{
  "actions_attempted":    142,
  "actions_blocked":      18,
  "actions_pending":      3,
  "actions_approved":     121,
  "actions_succeeded":    115,
  "actions_failed":       6,
  "actions_duplicate":    4,
  "avg_approval_latency_ms": 4320
}

SDK — Install

npm install zehrava-gate
// CommonJS
const { Gate } = require('zehrava-gate')

// ESM
import { Gate } from 'zehrava-gate'
const gate = new Gate({
  endpoint: 'http://localhost:4000',  // your Gate server URL
  apiKey:   'gate_sk_•••••••••••••'    // from /v1/agents/register
})

SDK — Methods

gate.propose(opts)

Submits intent to POST /v1/intents. All V2 fields supported. The /v1/propose alias still works for backward compat.

const p = await gate.propose({
  payload:      './leads.csv',
  destination:  'salesforce.import',
  policy:       'crm-low-risk',
  recordCount:  847,
  on_behalf_of: 'agt_orchestrator_id',  // optional
  expiresIn:    '2h'                      // optional
})
// p.intentId, p.status, p.risk_score, p.risk_level, p.blockReason, p.expiresAt

gate.approve({ intentId })

Calls POST /v1/intents/:id/approve.

await gate.approve({ intentId: p.intentId })

gate.reject({ intentId, reason })

Calls POST /v1/intents/:id/reject.

await gate.reject({ intentId: p.intentId, reason: 'Flagged for review' })

gate.execute({ intentId })

Calls POST /v1/intents/:id/execute. Returns a signed execution order with a 15-minute token.

const order = await gate.execute({ intentId: p.intentId })
// order.executionId, order.execution_token, order.expires_at

gate.verify({ intentId })

Fetch full intent details including decision record and audit trail.

const detail = await gate.verify({ intentId: p.intentId })
// detail.intentId, detail.status, detail.risk_score, detail.decision ...

Python SDK

Install the Python SDK — zero dependencies, works with Python 3.8+. Supports LangGraph, CrewAI, AutoGen, and any custom agent.

pip install zehrava-gate
from zehrava_gate import Gate, GateError

gate = Gate(
    endpoint="http://localhost:4000",
    api_key="gate_sk_..."
)

p = gate.propose(
    payload="Thank you — your issue is resolved.",
    destination="zendesk.reply",
    policy="support-reply",
    record_count=1
)

# p["status"] → "approved" | "blocked" | "pending_approval" | "duplicate_blocked"
if p["status"] in ["blocked", "duplicate_blocked"]:
    raise GateError(p["blockReason"])

LangGraph integration

from langgraph.graph import StateGraph
from zehrava_gate import Gate, GateError

gate = Gate(endpoint="http://localhost:4000", api_key="gate_sk_...")

def send_crm_import(state):
    p = gate.propose(
        payload=state["csv_path"],
        destination="salesforce.import",
        policy="crm-low-risk",
        record_count=state["record_count"],
        on_behalf_of=state.get("orchestrator_id")
    )
    if p["status"] in ["blocked", "duplicate_blocked"]:
        return {**state, "error": p["blockReason"]}
    if p["status"] == "pending_approval":
        return {**state, "intent_id": p["intentId"], "waiting": True}
    return {**state, "sent": True}

Webhooks

Instead of polling, register a webhook URL. Gate fires once — on approved or rejected. Execution outcome events are on the roadmap.

// JavaScript
const p = await gate.propose({ ... })

await gate.registerWebhook({
  intentId: p.intentId,
  url: 'https://your-app.com/gate-callback',
  secret: 'your-secret'           // sent as X-Gate-Secret header
})
# Python
gate.register_webhook(
    intent_id=p["intentId"],
    url="https://your-app.com/gate-callback",
    secret="your-secret"
)

The webhook payload:

{
  "intentId":   "int_a1b2c3d4",
  "event":      "approved",        // approved | rejected
  "actor":      "system",           // system or reviewer that approved/rejected
  "firedAt":    "2026-03-08T11:30:00.000Z"
}

Webhooks are persisted to SQLite. Registrations survive server restarts. Each webhook fires exactly once — Gate marks it fired=1 with a timestamp after delivery. Re-registering after a webhook fires creates a new pending registration.

Proxy (V3)

Gate can govern agents without SDK changes by running as a forward proxy. The proxy submits each outbound request as an intent. Gate forwards only approved requests.

HTTP forward proxy (Phase 1)

Route outbound HTTP traffic through Gate:

export HTTP_PROXY=http://localhost:4001
export HTTPS_PROXY=http://localhost:4001

If a destination is unknown or blocked, the proxy returns 403. This is intentional fail-closed behavior.

HTTPS interception (MITM) (Phase 2)

Enable TLS interception to govern HTTPS requests at the payload level:

export GATE_TLS_INTERCEPT=true
# restart Gate

Trust Gate’s CA certificate in your agent runtime:

# Download CA
curl -o gate-ca.crt http://localhost:3001/v1/proxy/ca.crt

# Node
export NODE_EXTRA_CA_CERTS=/path/to/gate-ca.crt

# curl
curl --cacert /path/to/gate-ca.crt https://api.example.com

Pending approvals: retry or hold

When Gate returns pending_approval, the proxy supports two patterns:

Inspect held connections

GET /v1/proxy/held  # auth required

Destination naming gotcha

The proxy can emit host-based destinations like api.stripe.com.http. If your policy allowlist expects logical names like stripe.refund, add an override in config/destinations.yaml so host+path maps to your logical destination. Otherwise Gate will block due to allowlist mismatch.

LangChain + LangGraph

Gate integrates cleanly with LangChain tools. The pattern is simple: submit an intent before tool execution. Run the tool only if Gate approves.

Wrap tools (GateTool)

The repo contains packages/langchain-gate, which provides GateTool and GateToolkit. It wraps any Tool with a Gate checkpoint:

const { Gate } = require('zehrava-gate')
const { GateTool, GateBlockedError } = require('@zehrava/langchain-gate')

const gate = new Gate({ endpoint: 'http://localhost:3001', apiKey: 'gate_sk_...' })

const governed = new GateTool({ tool: emailTool, gate, policy: 'outbound-email', destination: 'sendgrid.send' })

try {
  await governed._call(JSON.stringify({ to: 'user@co.com', subject: '...' }))
} catch (err) {
  if (err instanceof GateBlockedError) console.error(err.blockReason)
}

Branch graphs (gateNode + gateRouteAfter)

LangGraph graphs can branch on Gate’s decision. Use gateNode() to submit intent and write gateStatus into state. Use gateRouteAfter() to route edges.

Works With

Zehrava Gate is designed to complement existing agent governance and security tooling. It focuses on one thing: human approval gates for high-consequence writes. Other tools handle autonomous policy enforcement, output validation, and observability. Together, they create a complete governance stack.

Microsoft Agent Governance Toolkit

Microsoft's Agent Governance Toolkit provides deterministic policy enforcement (<0.1ms), zero-trust agent identity, execution sandboxing, and SRE tooling. It handles autonomous governance at scale — 95% of agent actions auto-governed via policy.

Zehrava Gate sits on top as the human-in-the-loop approval layer for the 5% of actions that always need human eyes: email blasts, database deletes, financial transactions, customer-facing content.

Integration pattern:

from agent_os import PolicyEngine
from zehrava import Gate

# Microsoft's policy engine handles auto-allow/deny
policy = PolicyEngine(capabilities=...)
decision = policy.evaluate(agent_id, action, tool)

if decision.allowed:
    # Auto-allowed by policy — proceed
    execute_action()
elif decision.require_human_approval:
    # Route to Zehrava for visual approval
    intent = gate.propose(action, payload)
    if intent.status == "approved":
        execute_action()

Microsoft's toolkit includes an EscalationHandler for human approval, but it's backend-only (webhook or in-memory queue). Zehrava provides the approval dashboard, persistent history, and async intent lifecycle that teams need in production.

Read the full comparison →

Other Tools

NeMo Guardrails (NVIDIA) and Guardrails AI validate LLM outputs. Gate validates agent actions. Use both: Guardrails ensures the model's words are safe, Gate ensures the agent's actions are approved.

LiteLLM and Portkey provide LLM gateway routing, caching, and observability. Gate works transparently behind them — it sees tool calls, not model calls.

Dashboard

The dashboard at /dashboard on your Gate server shows every pending intent. No code required to review and approve — it's the interface for humans in the loop.

For each intent you can see:

Try it at zehrava.com/dashboard →

Approval is one click. The reviewer still needs a reviewer/admin API key. Gate does not embed approval keys in the dashboard UI. Put /dashboard behind your normal auth (VPN, basic auth, SSO) in production.

Audit trail

Every intent generates an immutable sequence of audit events, written server-side. Agents have no API route to write or modify audit events.

Event types in order:

Intent lifecycle

POST /v1/intents
  ↓
intent → policy_checked
  ├── blocked              → terminal
  ├── duplicate_blocked    → terminal (idempotency key matched)
  ├── approved             → auto-approved by policy or human approval; eligible for execution
  └── pending_approval     → human review required
        ├── approved        → ready for execution
        ├── rejected        → terminal
        └── expired         → terminal

approved
  ↓
execution_requested (POST /v1/intents/:id/execute)
  ↓
worker executes in your VPC (runner_exec)
  ↓
POST /v1/executions/:id/report
  ├── execution_succeeded  → terminal
  └── execution_failed     → terminal

Fail closed

If Gate is unreachable, your agent must halt and never perform the write outside Gate.

Gate is middleware. If the HTTP call to /v1/intents (or gate.propose()) fails with a network error, the safe behavior is to treat it as a block and not execute the write. Proceeding silently bypasses Gate entirely.

try {
  const p = await gate.propose({ ... })
  if (p.status === 'blocked' || p.status === 'duplicate_blocked')
    throw new Error(p.blockReason || 'Duplicate intent blocked')
  if (p.status === 'pending_approval') return // wait — do not proceed
  // status === 'approved' → safe to request execution
} catch (err) {
  // Gate unreachable or returned error — fail closed
  console.error('Gate check failed — halting write', err.message)
  throw err // do NOT proceed
}

Multi-agent pipelines

In pipelines where one agent orchestrates others, use on_behalf_of to record the full chain of accountability in the audit trail:

// Leaf agent submitting an intent on behalf of the orchestrator
await gate.propose({
  payload:      data,
  destination:  'salesforce.import',
  policy:       'crm-low-risk',
  on_behalf_of: 'agt_orchestrator_abc123'
})

The audit trail will record both the calling agent and the orchestrating agent. Without on_behalf_of, only the leaf agent appears — making the orchestrator's role invisible in the audit.

Best practice: Wire Gate at the orchestrator level, not just leaf agents. If the orchestrator calls Gate before dispatching to sub-agents, the accountability chain is cleaner.

Comparisons

Gate occupies a specific layer in the agent infrastructure stack. Two comparison pages explain how Gate fits with other tools teams use in the same category.

Roadmap

Gate today is cooperative — agents call the SDK voluntarily. The roadmap closes that gap. Here is what is being built, in priority order.

1. Network-level enforcement — Gate V3 (top priority)

The current model requires agents to call Gate. A cooperative agent that skips the SDK call bypasses governance entirely. Gate V3 closes this with a forward proxy and credential vault integration.

Three deployment modes in V3:

The 1Password integration uses Service Account JWTs scoped to a single vault. Gate fetches credentials at execution time, uses them, then discards them from memory. The agent process never holds a production credential. See the full V3 spec at /v3/.

Until enforcement ships, Gate is most effective for teams that control the agent code and commit to the SDK integration pattern.

2. Schema-aware policies

Current policy evaluation is structural — record count, destination, substring terms. Schema-aware policies evaluate the content of the write against a defined schema. Examples:

3. Environment-aware evaluation

Policies will support environment flags. A write that auto-approves in staging requires human approval in production. Environment is passed at intent submission and changes which policy thresholds apply.

4. Cryptographically signed audit log

Current audit events are append-only in SQLite. A database admin can edit them. The signed audit log adds an HMAC signature to every event at write time. Tampering becomes detectable. This is the gap between Gate's current audit trail and compliance-grade immutability.

5. External log drain

Export audit events to S3, Splunk, or any SIEM via webhook stream. Required for teams that need audit log retention outside the Gate database.

Current limitation: Gate is cooperative infrastructure. An agent that does not call the SDK bypasses governance. If your threat model includes agents that actively circumvent controls, wait for network-level enforcement before relying on Gate as your only safety layer.