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:
| Flag | Default | Description |
|---|---|---|
| --port | 4000 | HTTP port to listen on |
| --data-dir | ./data | Directory for SQLite database and stored payloads |
| --policy-dir | ./policies | Directory 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:
- approved — passed all rules, within auto-approve threshold. Safe to request execution.
- blocked — violated a policy rule. Do not proceed. Check
blockReason. - pending_approval — within policy but requires human review. Poll
/v1/intents/:idfor status updates, or register a webhook. - duplicate_blocked — idempotency_key matched an existing intent. The action was already submitted and Gate will not re-evaluate it.
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:
Policy exists?
If the referenced policy ID has no YAML file, Gate returns blocked immediately.
Destination allowlist
If destinations is set and the intent's destination isn't in the list, Gate blocks it.
Payload type check
If allowed_types is set and the payload's file extension isn't in the list, Gate blocks it.
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.
require_approval: always
If set, every intent goes to pending_approval regardless of other rules.
Record count thresholds
auto_approve_under: if recordCount ≤ threshold, approve automatically.require_approval_over: if recordCount > threshold, send to pending.
Default
If no rule matched, Gate returns pending_approval. Safe default — when in doubt, ask a human.
All policy fields
| Field | Type | Description |
|---|---|---|
| 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/propose, /v1/approve, /v1/reject, /v1/deliver, and /v1/proposals still work. Canonical routes are /v1/intents and /v1/executions.
API — Register agent
/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.| Field | Type | Description |
|---|---|---|
| name * | string | Human-readable agent name. Appears in audit logs and the dashboard. |
| riskTier | string | standard | elevated | restricted. Default: standard. |
| owner | string | Team or system that owns this agent. Stored in registry. |
| allowed_policies | string[] | Policy IDs this agent is permitted to invoke. Omit to allow all. |
| allowed_destinations | string[] | Destinations this agent is permitted to target. Omit to allow all. |
| framework | string | Agent framework: langgraph, crewai, autogen, custom, etc. Informational. |
| metadata | object | Arbitrary key/value pairs. Stored in registry. |
{
"agentId": "agt_a1b2c3d4",
"apiKey": "gate_sk_••••••••••••",
"name": "my-crm-agent",
"status": "active"
}
API — Agent registry
Authentication: Authorization: Bearer <apiKey>
Authentication: Authorization: Bearer <apiKey>
401.Authentication: Authorization: Bearer <apiKey>
Authentication: Authorization: Bearer <apiKey>
API — Submit intent
Authentication: Authorization: Bearer <apiKey>
| Field | Type | Description |
|---|---|---|
| destination * | string | Target system (e.g. salesforce.import). Must match your policy's destinations allowlist. |
| policy * | string | Policy ID to evaluate (matches YAML filename without extension). |
| payload | string | Content 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. |
| recordCount | number | Batch size. Used for threshold evaluation (auto_approve_under, require_approval_over). |
| estimated_value_usd | number | Estimated financial value. Contributes to risk score. |
| sensitivity_tags | string[] | Data sensitivity labels: pii, financial, health, legal. Contributes to risk score. |
| idempotency_key | string | If set, duplicate submissions with the same key + agent return status: "duplicate_blocked" instead of re-evaluating. |
| on_behalf_of | string | Agent ID of the orchestrating agent. Logged in audit trail for multi-agent accountability. |
| expiresIn | string | Approval window: 30m, 1h, 2h. Default: 1h. |
| metadata | object | Arbitrary 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
Authentication: Authorization: Bearer <apiKey>
| Param | Type | Description |
|---|---|---|
| status | string | Filter: pending_approval · approved · blocked · scheduled · succeeded · failed · expired. Omit for all. (scheduled = execution order issued, worker not yet reported.) |
| limit | number | Max results. Default: 50. |
Authentication: Authorization: Bearer <apiKey>
API — Approve
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
blocked.Authentication: Authorization: Bearer <apiKey>
| Field | Type | Description |
|---|---|---|
| reason | string | Rejection reason. Stored in audit trail. |
API — Execute
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
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
succeeded or failed. Accepts either the one-time execution token or a valid API key.Authentication: Authorization: Bearer <execution_token> or Bearer <apiKey>
| Field | Type | Description |
|---|---|---|
| status * | string | succeeded or failed |
| report | object | Arbitrary worker report data. Stored with execution record. |
// Worker reports success { "status": "succeeded", "report": { "records_written": 847, "duration_ms": 1240 } }
API — Policy decision
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
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
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:
- Retry (default): proxy returns
202withX-Gate-Intent-IdandRetry-After. Re-send the same request withX-Gate-Intent-Id. - Hold + auto-replay (opt-in): set
X-Gate-Wait: true. Gate holds the connection open and forwards automatically when approved. Timeout usesGATE_HOLD_TIMEOUT_MS(default 15 minutes).
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.
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:
- Agent name, destination, policy, record count
- Time remaining before expiry
- Payload hint (type + size — not full content by default)
- Full audit trail of events so far
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:
- proposed — agent submitted the intent
- policy_checked — Gate evaluated YAML policy + risk score
- approved — auto-approved by policy, or human approved in dashboard
- rejected — human rejected (includes reason)
- blocked — policy blocked it (includes reason_code). Terminal.
- expired — approval window elapsed. Terminal.
- duplicate_blocked — idempotency_key matched. Terminal.
- execution_requested — execution order issued (gex_ token, 15min TTL)
- execution_succeeded — worker reported success
- execution_failed — worker reported failure
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.
- Zehrava Gate vs Tetrate — Tetrate is an Envoy-based AI gateway platform. Gate is a write-path checkpoint. Full breakdown of both platforms.
- Zehrava Gate vs Tetrate Agent Router Service — Exact comparison of the Agent Router product specifically. How enforcement point and approval model differ.
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:
- Forward proxy — set
HTTP_PROXY=http://gate.internal:4001. All outbound HTTP routes through Gate. No code change in the agent. Gate intercepts, evaluates policy, holds or passes. - Reverse proxy — Gate deployed in front of specific APIs. Agent routes to Gate's URL instead of the destination directly.
- Credential vault (gate_exec) — Gate holds no credentials. Fetches ephemerally from 1Password, HashiCorp Vault, or AWS Secrets Manager at execution time. Agent submits payload only — never touches production credentials. Two audit logs: Gate + credential provider.
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:
- Block CRM imports where
emailfield is empty on more than 20% of records - Flag financial writes where
amountexceeds a threshold per transaction - Require approval for database mutations where
DELETEaffects more than N rows
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.