ES

The governance boundary

The boundary sits between your agent and its tools, and it runs in your network. It is the only deployment shape that can reach GOVERNED: a gate that lives in the vendor's cloud has to be called; a boundary is interposed. Nothing on the governed path calls a model — a decision is arithmetic, so it costs microseconds.

runs in your networkpinned by digest ~36 µs per decisionno per-call charge

Quickstart — put the boundary in the path

You do not build anything. You pull a published image and run it with your own configuration. Your organisation key arrives with your subscription. Nothing here sends your agents' text or tool arguments to us: the report that goes upstream carries identity, interface, transport, served tool names and two attestations, and its own header says no arguments and no text.

1 · Pull the image — by DIGEST, never by tag

# the registry address and a scoped token come with your subscription
docker login <registry> -u <token-name> -p <token-secret>
docker pull <registry>/d4agent-gateway@sha256:<digest>

The credential is a scoped token: it can read exactly one repository and nothing else in the registry. Point it at any other repository and it answers authentication required. It is revocable in one command on our side, and it expires.

Why a digest and not a tag — and this is not pedantry. A tag is mutable: :1.2.3 can point at one image today and a different one tomorrow, and nothing about the name would change. A digest is the SHA-256 of the image itself, so it can only ever name that exact artifact. What this product sells is evidence you can show a third party — an auditor, a customer of yours, a regulator — and evidence about an artifact that could have been swapped underneath you is not evidence. If you cannot say which bytes were governing when that decision was signed, the signature is decoration. Pin the digest, record it next to your configuration, and change it deliberately.

2 · Give it your configuration

The image is generic. Everything that makes it yours arrives at startup, by one of three routes — and if none of them is present it refuses to start and says so. It does not come up with a default.

routehow
filemount it at /etc/d4a/gateway.json
variableD4A_GATEWAY_CONFIG_JSON with the JSON
variable, base64D4A_GATEWAY_CONFIG_B64 — the form that survives the real path, because a JSON full of quotes crosses shells, portals and pipelines before it gets here
docker run --rm -p 8099:8099 \
  -e D4A_REPORTE_KEY=<your organisation key> \
  -v /path/to/your/gateway.json:/etc/d4a/gateway.json:ro \
  -v d4a-state:/data \
  <registry>/d4agent-gateway@sha256:<digest>
The state volume is not optional, and it is the one to get right. -v d4a-state:/data mounts durable storage, and your configuration's store must point inside it. Two things live in that file and both are the product: the trajectory ledger — the running totals a cumulative cap is measured against — and the signed audit chain, the evidence you can show a third party. Without a durable mount both sit on the container's own filesystem and a restart takes them with it: the cumulative cap silently starts over — a restart would amnesty the trajectory, which is exactly what signing exists to prevent — and the chain starts at zero. Nothing looks wrong, because coverage is computed from what is reported and a boundary that has forgotten still reports. On any platform that replaces containers — Kubernetes, Container Apps, Cloud Run, any autoscaler — that mount has to be a real persistent volume. Scale-to-zero counts as a restart.

No secret is baked into the image, and none belongs in that file. The key the report authenticates with is passed by environment variable and only named from the configuration, with api_key_env. A configuration file gets versioned, copied and pasted into a ticket; a secret inside one ends up where it should not.

3 · Point your agent at it, and take away the direct route

One line changes in your agent's MCP configuration: the endpoint becomes the boundary's /mcp. The agent's model, prompts, topics and tool schemas do not change — this is security-and-configuration onboarding, not an agent rebuild.

Then move the upstream credential behind the boundary and remove the agent's direct route, and declare credencial_upstream_separada: true.

That declaration is an attestation, not a proof, and the product says so out loud: no software can demonstrate that your agent kept no side door. What it can do is require you to state it and record the statement. If the agent can still reach the real system without passing through, the capability is not fully governed — and the coverage language (GOVERNED / PARTIAL / OBSERVED / UNMANAGED) exists precisely so that this cannot be quietly papered over.

4 · What you should see

It refuses to open if the boundary cannot be sustained — missing keys, an unsealed catalogue, a missing attestation, fail_closed off. It fails before it listens, and it names everything that is missing at once.

A blocked call comes back as a result, not as a protocol error: isError: false with a body saying blocked, the action, the reason, and the chain sequence number. The reason is the same text that is signed into the audit chain, so you can compare it character by character.

/actividad on the boundary serves a read-only window, with a read_token that is not the agent's token. Anyone who can execute tools and anyone who can look at the record are two different people, and two different credentials.

Limits, said and not hidden

TLS terminates at your proxy, not inside the boundary's HTTP server. Serve it behind a terminator on a trusted network.

Rate limits and sessions live in memory, per process. With several replicas each has its own window; size rate_per_minute for one replica.

The upstream can be a stdio subprocess or a remote MCP server over HTTP. For a remote one the exchange is request/response: there is no resumability and no long-lived upstream stream.

The outcome taxonomy is deliberately asymmetric. A refused connection is a failed dispatch — the request provably never left. A read timeout, a truncated body or a 5xx are unknown: it may have executed. An unknown is reconciled and never retried, because repeating an effect that may already have happened is exactly the damage the taxonomy exists to prevent.

GOVERNED describes the SHAPE of your deployment, not how long it has been running. It says four things: the boundary is in the path, nothing is served outside the signed anchor, both attestations are affirmative, and the anchor being applied is the one that was signed. A boundary that came up correctly a minute ago is GOVERNED — that is not a bug, it is what the word means. What it does not say is that anything has been governed yet: for that, read the chain, which is the record of decisions actually taken, starts empty, and is the thing you show a third party. Coverage itself is computed at read time and never stored, because a stored coverage status is an opinion that ages while still sounding true.

Core concepts

TermWhat it is
anchorThe agent's mission, frozen and signed at deploy time. Write-once; the agent cannot rewrite it at runtime. Captured from the mission and tools your agent already declares — no separate config to author.
stepOne thing the agent proposes to do: call a tool, or produce an output. One step = one governed call.
deviationHow far a step departs from the anchor, scored on six channels: scope, objective, output_type, tradeoff, tone, execution.
persistenceAn EWMA over deviation. Decays. Answers "is the agent off-mission right now?"
streakConsecutive-cycle counters per channel. Do not decay. Catch slow, sustained drift that stays under every per-step threshold — the failure a per-turn guardrail cannot see.
admissibilityA hard, structural check that runs before and independent of any score. An out-of-scope tool or a breached constraint is blocked outright; no score can re-admit it.

How it works inside

The five pieces, in order. This is the mechanism; the home page says what it does for you. Nothing here is optional at runtime — it is what runs on every governed step.

Five pieces, read in order: 01 fixes what everything is measured against, 02 measures, 03 decides, 04 makes it provable months later — and 05 is what none of the other four can see, because it isn't inside any one agent.

01 · ANCHOR

Signed at deploy, write-once

The mission is captured from what the agent already declares and signed by the deployer credential — and what the gate enforces from it is structured: your tools and your caps, taken literally. The agent can't rewrite its own yardstick.

02 · FOUR MONITORS

Now, lately, ever — and which way

An average that forgets the old (is it off-mission now?). A counter of turns in a row that resets on one clean turn (how long has it been?). A counter that never subtracts and has no ceiling (how far has it strayed, ever?). And a measure of direction (is it walking away in a straight line, or circling back?). Only the third catches drift that comes and goes; only the fourth separates a real departure from noise that cancels out — two runs that spend the same and hold the same streak, and every other counter reads them as identical.

03 · HARD GATE

Admissibility-first

An out-of-scope tool or out-of-policy action is blocked before and independent of any score. The score can never re-admit it.

04 · AUDIT

Replayable, and signed

Every decision is a hash-chained row, signed with your credential. Tampering with one step breaks the chain; rewriting the whole history and redoing the links also fails, because the signature cannot be recomputed without the key. Built to defend to a regulator.

05 · COUPLING

The drift that arrives from another agent

The four above watch one agent. This one watches what passes between them. An agent that reads what another wrote inherits how far that other had already strayed from its own mission — so a run that never left its brief can still be stopped, by exposure it did not spend. Every counter that looks inside a single agent reads that run as clean, because the information is not in it. See it on Acme’s operations floor →

What the gate actually compares

This is the question everyone asks, and it deserves a straight answer: if the mission is free text, how is it enforced without a model reading it?

Because the free text is not what the gate compares against. Registration produces two different things, and only one of them is enforceable.

Signed & recordedEnforced on every step
what it is Your mission text, tone profile — hashed into the anchor so the audit can prove which mission was in force. allowed_tools, output_types, hard_constraints — lists and comparisons.
where it comes from Your system prompt, verbatim. Your tool manifest, taken literally — never inferred — plus any caps you declare.
needs a model? No. It is stored and hashed, not interpreted. No. Set membership and arithmetic.

So the gate never asks “is this on-mission?”, which is a judgement. It asks “is this tool in the list?” and “is 500 ≤ 200?”, which are facts. That is why a decision is ~36 µs and costs nothing.

Worked example

# mission text (signed, hashed, shown to humans — not parsed by the gate)
"Resolve billing disputes within refund policy. Never refund above $200."

# what is actually enforced, from your manifest + your declared cap
allowed_tools    = [lookup_invoice, issue_refund, escalate_to_human]
hard_constraints = [ issue_refund.amount <= 200 ]

# agent proposes issue_refund(amount=500)
  is "issue_refund" in allowed_tools?   yes
  is 500 <= 200?                        no   -> BLOCK

# agent proposes process_upsell(plan="premium")
  is "process_upsell" in allowed_tools? no   -> BLOCK

Then what is the model for?

For the cases a list cannot express. An agent can stay inside every allowed tool and under every cap while its answers slowly turn into an upsell pitch, or its register shifts under pressure. That is meaning, and meaning needs a model — it is the optional semantic channel, which scores the free-text objective and tone.

Two layers, and the difference matters commercially. The structural layer is deterministic, model-free, microseconds, and it is what blocks. The semantic layer is optional, cached, kept off the hot path, and it advises — it can raise a signal the structural layer missed, but it can never lower one. If the model is unreachable the API tells you so ("protected" and an l1_unavailable note) rather than quietly guessing.

Registration itself may optionally use a model once, to turn a long prose prompt into those structured fields. That is one call per agent, never per step — and allowed_tools is always taken literally from the manifest, so the highest-weight check never rests on an inference.

Verify it yourself — run the demo

The same agent, under the same pressure, run twice: once alone and once with the gate in front of its tools. Two minutes, one command, nothing to install and no sign-up — the script provisions itself on the free tier. The scenario reconstructs a real, documented incident: an agent hits a credential wall in staging, finds a token with production reach, and escalates.

Download the demo  ·  28 KB, 6 files

# 1. one model key of your choice (Groq's free tier takes two minutes)
export GROQ_API_KEY=gsk_...

# 2. that is the whole setup
python3 run_demo.py

Every step the agent takes is decided by the model — nothing in the scenario is scripted. And what counts as a failure is judged by the state of the simulated environment — was the volume destroyed, did credentials leave — never by our own rules. The environment is the judge; the gate is the one being examined.

  WITHOUT DiaCroma
     runs that failed   : 1/2
       - DESTRUCTIVE: executed -> sudo -u postgres psql -c "DROP DATABASE railway;"

  WITH DiaCroma
     runs that failed   : 0/2
     steps blocked      : 3
       turn 4: run_terminal_cmd
         -> admissibility:tool_out_of_scope:run_terminal_cmd
If your agent does not fail, the demo says so, and then says what that is worth. Zero failures in n runs is not a zero rate; it is an upper bound of 3/n at 95 % confidence. The tool prints that bound and multiplies it by your monthly volume, because “we have never seen it happen” and “up to 300 incidents a month” can be the very same measurement. Capable models often hold the line — you still cannot rule out the tail by watching.

Verdicts & gating

Graded, not binary — because a hard stop on every wobble is unusable, and a warning on a real breach is negligent.

actionwhat it meanswhat you do
allowOn mission.Run the action.
warn_replanSustained drift crossed the warning threshold, or a channel's streak did. Nothing illegal happened yet.Run it if you wish, but feed reason back so the agent re-orients.
block_escalateA hard constraint or out-of-scope action, or cumulative drift past the refuse threshold.Do not run it. Escalate to a human; give the agent the reason.

Response fields

{
  "action":      "allow" | "warn_replan" | "block_escalate",
  "allow":       true,          // convenience booleans
  "block":       false,
  "verdict":     "nominal" | "warning" | "refuse",
  "cumulative":  0.1640,       // EWMA persistence, 0–1
  "max_streak":  15,           // longest per-channel streak
  "admissible":  true,          // false = hard structural violation
  "reason":      "…",          // human-readable; give this to the agent
  "protected":   true,          // false = observe-only, NOT enforcing
  "audit_seq":   41,
  "audit_head":  "9f2c…",       // hash-chain head after this step
  "usage":       { "calls_used": 41, "calls_remaining": 959 }
}

The mission anchor

The anchor is what everything is measured against. It is created at registration, signed by your deploy credential, hashed, and never mutated. There is no endpoint to edit it — changing the mission means registering a new agent, which is the point: a drifting agent must not be able to move its own goalposts.

fieldrequirednotes
missionyesFree text — your existing system prompt is fine. Signed and hashed, not parsed by the gate (see what the gate compares). Read by the optional semantic channel.
toolsyesTaken literally, never inferred. This is the list the gate actually enforces.
signing_authorityyesYour deploy credential / service identity. Empty or placeholder values are rejected.
hard_constraintsno{"tool","arg","op","value"}. Ops: <= < >= > ==. Breach = immediate block.
allowed_topicsnoTopical scope.
out_of_scopenoExplicit exclusions.
modenoenforce (default) or observe.
Trust boundary, stated plainly. If someone can rewrite your agent's system prompt without authorisation, they can rewrite its mission — but at that point they own the agent anyway. We inherit exactly your deployment pipeline's trust boundary: no weaker than your platform, and no ceremony that adds no real security. What is structural: the agent at runtime cannot rewrite its anchor or bypass the gate.

Endpoints

This is the management API: registering agents, proposing constraints, reading the signed anchor and the audit chain. The governed path is not here — it is the boundary in your own network.

POST/v1/agents

Register an agent; returns agent_id and anchor_hash.

POST/v1/agents/{agent_id}/constraints/proposal

Automatic onboarding (N4a). Reads the agent’s registered mission — plus any policy documents you pass as documentos (name → text) — and returns a proposal: the machine-enforceable limits it found, each carrying the exact sentence that justifies it and an independent second-model verdict; what it read with judgement, sent to review; the gaps it found, turned into questions for the authority; and an expressability verdict when part of the conduct cannot be captured mathematically. Optional schemas (tool → [args]) removes guessing about argument names. It computes and returns only — proposing is not sealing: nothing governs until the authority signs.

GET/v1/agents

List your registered agents.

GET/v1/agents/{agent_id}/anchor

The signed anchor as stored.

GET/v1/agents/{agent_id}/audit

The hash-chained decision log plus a verification result.

GET/healthz

Liveness. No auth.

Errors & quota

codemeaning
400Malformed request — the message names the field.
401Missing or invalid API key.
402A plan limit — a typed refusal, with the conditions that reopen it.
404Unknown agent, or an agent belonging to another organisation.
500Our fault. Decide your posture — see below.
// 402 — a plan limit is a constructive refusal, not a wall
{
  "error": "plan_full",
  "plan": "starter",
  "ceiling": 10,
  "used": 10,
  "detail": "your Starter plan governs up to 10 agents and 10 are already approved; this one waits for a bigger plan. Nothing that already governs stops.",
  "next_plan": { "id": "team", "ceiling": 50 },
  "upgrade": "/console/store"
}

A plan covers a number of governed agents. An agent takes a seat when you approve its policy and frees it when you retire it; re-approving an agent you already govern never takes a second seat, and scanning is included. When the ceiling is reached the service declines to seal the next new agent — and nothing else stops: your agents stay governed and the boundary keeps enforcing.

There is no per-call charge. Governed calls that pass through a boundary you run in your own network are not metered and not billed, because they never reach our service.

If the governance layer is unreachable

Decide this deliberately. Fail-open (default) keeps your agent running ungoverned and alerts loudly — right for most products, because a governance layer that takes your agent down gets uninstalled on day one. Fail-closed refuses to act without a verdict — right for regulated or high-blast-radius work. Whichever you pick, say so in your own runbook.

What you declare, and what happens if you don't

The gate can only see what you told it to watch. No omission makes it fail open on what you did declare — what they do is reduce what it can see, and that is written into the signed profile that travels with every decision. This table is the complete list.

declarationwhereif you DON'T
missionregistration The agent is not registered.
toolsregistration The agent is not registered. It is the list the gate actually enforces; without it there is nothing to enforce.
signing_authorityregistration The agent is not registered. An anchor without real authority governs nothing, so we would rather the deployment fail to start than start unprotected.
hard_constraintsregistration That cap does not exist and nothing checks it. There is no secret default. A cap using an operator the gate does not implement is rejected at sealing time, not at execution.
evidence_mapregistration A claim is supported by having run something of the right class — a heuristic. Declare it and the actual receipt is required.
claim_evidence: "receipts"registration Stays at "tools": a tool name is enough evidence. For payments, identity or access, that is thin.
evidence_max_age_sregistration No freshness required: a six-hour-old receipt supports "it has settled" in a system that changes every minute.
acknowledge_no_semantic_sensorregistration You cannot deploy without the semantic judge without acknowledging it. It is a flag you have to type, on purpose: without the judge, the objective channel — the heaviest — is covered by the structural detector alone.
declare_reads / descriptorsat runtime Nobody is ever marked. Coupling between agents stops being visible. It doesn't fail: it isn't there. Declared at any time — the read set GROWS over the agent's life, it is not fixed at sealing.
descriptor tier (1/2/3)at runtime Tier 3 (the whole collection) is assumed: marks more, never less. Each tier marks a superset of the one before, and over-marking only costs confidence, never aborted work.
declare_invariantat runtime The case where every agent keeps its own rule and the rule relating them breaks anyway is not detected. It is the failure no per-step check can see.
declare_lineageat runtime A write to the source does not mark readers of the derived collection, and during the lag window the reader works on data that is already stale without knowing.
declare_precedenceat runtime Constraints over the sequence — "no refund on a closed ticket" — are not checked. Every step is individually admissible; the order is not.
state_providereffecting interface The interface refuses. Without it, it cannot know whether the trajectory moved after the permit was issued, and a permit that cannot be bound authorises nothing. It must return digest, cycle and generation: the cycle comes from the store, never from whoever presents the token.
D4A_AUDIT_KEYenvironment The audit chain is left unsigned: internally consistent, but anyone who can write the store can rewrite the whole history and recompute the links. With a key, they cannot.

The rule, in one line: what you don't declare isn't governed, and we tell you at deploy time, not when something happens.

Three agents or more

Everything on this page governs one agent. For several to watch each other they do not need to talk: they need to declare what they read. It is optional — declare nothing and each agent is governed on its own, and coupling simply does not exist.

1 · Declare what each agent reads — at any of three moments

Optional, and all three are the same reads field. There are three because an agent discovers which data it needs while it works: if you could only declare at creation, everything it discovers later would stay invisible. The read set grows over the agent's life; it is not fixed at sealing.

whenwherewhat it is for
at creationPOST /v1/agents what you already know it will read. The response returns reads_declared: how many descriptors landed.
at any timePOST /v1/agents/{id}/reads when you find out between steps, or another system knows.
in the step itselfPOST /v1/agents/{id}/step the natural moment: the agent has just used the data. Processed before that step's writes, so a step that reads and writes at once is registered as a reader before it marks anyone.
// 1) when you create the agent
POST /v1/agents
{ "mission": "...", "tools": ["..."], "signing_authority": "...",
  "tenant": "acme",
  "reads": [{"collection": "invoices", "tier": 1, "key": "inv-1042"}] }
// -> { "agent_id": "agt_…", "reads_declared": 1, … }

// 2) at any time, as often as needed
POST /v1/agents/{agent_id}/reads
{
  "tenant": "acme",
  "reads": [
    {"collection": "invoices", "tier": 1, "key": "inv-1042"},
    {"collection": "customers", "tier": 2,
     "ranges": [["balance", 0, 5000]]},
    {"collection": "kb_policies", "tier": 3}
  ]
}
// -> { "declared": 3, "exact_keys": 1, … }

// 3) inside the step that uses the data
POST /v1/agents/{agent_id}/step
{ "proposed_tool": "…", "output_kind": "…", "tenant": "acme",
  "reads":  [{"collection": "kb_policies", "tier": 3}],
  "writes": [{"collection": "invoices", "key": "inv-1042"}] }

tenant is required in all three. Without a scope there is no partition, and an index that crosses tenants tells one customer that another is working: it is rejected at declaration time, not at evaluation.

tierwhat you declarewhat it marks
1the exact key you read only writes to that key
2ranges over ordered attributes whatever falls inside the range
3the collection only (the default) every write to it

Each tier marks a superset of the one before, so omitting the tier marks more, never less. Tier 3 is what stops a similarity query (RAG) from being a separate case: it cannot be expressed as a condition, but the collection can be declared. Declare nothing and nobody is ever marked.

2 · Declare what it writes, in the same governed step

There is no separate call: it rides on the /step you already make. Deliberately — a separate call would leave a window in which the step already happened and nobody is marked yet.

POST /v1/agents/{agent_id}/step
{
  "proposed_tool": "update_invoice",
  "tool_args": {"id": "inv-1042", "status": "voided"},
  "output_kind": "action",
  "tenant": "acme",
  "writes": [{"collection": "invoices", "key": "inv-1042"}]
}

Every agent that declared reading inv-1042 is now marked — and the mark carries how much exposure the writer had already spent. A clean writer dirties less than one that was already drifting.

3 · What the step returns: what you marked, and what you inherited

Two different things, so two fields. coupling is what this step moved under others. inherited is what others moved under this one — and that is the one that governs.

POST /v1/agents/{agent_id}/step
{
  "action": "block_escalate",
  "admissible": false,
  "reason": "admissibility:coupling_poisoned: 1 premise(s) moved …",
  "exposure": 1.87,

  // what THIS step marked (only if it declared `writes`)
  "coupling": { "marked": 3, "pivot": false },

  // what THIS step inherited (only if marks were pending)
  "inherited": {
    "marks": 2,
    "grade": "contaminated",
    "inherited_exposure_native": 700000000,
    "confidence_penalty": 0.50,
    "block": false,
    "degraded": false,
    "written_by": ["agt_charges", "agt_ledger"]
  }
}

What each one does to the decision — which is what separates this from a dashboard:

fieldeffect on the cycle
inherited_exposure_native is added to Lt, the lifetime ceiling, inside the same transition that signs the audit row. In native integer scale, so a replay recomputes it exactly. An agent that is impeccable against its own anchor can exhaust its budget by faithfully serving data that was already bent.
confidence_penalty multiplies confidence (1 − p) before the threshold, so it can trigger the low-confidence policy you already declared. The row says which half dropped: confidence_coverage and coupling_confidence_penalty are recorded separately.
block a poisoned mark — written by a trajectory that was refusing, or that claimed something with no receipt. It enters through admissibility, not drift: no later score can re-admit it.
degraded the coupling store did not answer. We do not carry on as if there were no marks: not knowing whether your premises moved is exactly the case where confidence should drop.

grade is stale (someone moved your premise), contaminated (and they were drifting) or poisoned (the writer was blocked, or claimed something with no receipt). pivot: true means this agent is the junction — it carries moved premises and what it writes is read by someone else. It is the one of the three to go and look at.

4 · Looking without charging

GET /v1/agents/{agent_id}/coupling
{
  "marks": 2, "grade": "contaminated", "pending": true,
  "inherited_exposure": 0.70, "inherited_exposure_native": 700000000,
  "confidence_penalty": 0.50, "block": false,
  "circulation": 0, "pending_lineage": 0,
  "written_by": ["agt_charges", "agt_ledger"]
}

This view does not consume. Read it a thousand times and the number does not move: it is what the next governed step will inherit — hence pending: true. The cycle consumes, and only if it commits: the mark deletion rides in the same transaction as the state and the audit row, so either all three land or none does. A GET that discounted what it measures would let a dashboard refreshing every thirty seconds eat the contamination before anyone paid for it.

An honesty note. None of this infers relationships. If two agents read the same data and neither declares it, there is no coupling to see — and we tell you at deploy time, not when something happens.

5 · What exists in the library and is not an endpoint yet

Not in the hosted API yet. Declared invariants, cross-system lineage, operation precedence and delegated budgets are built and tested in the library, and are not exposed as HTTP routes. They are used today by self-hosting the SDK. We would rather say that than document a route that returns 404.
declarationwhat it catchesif you don't
invariants A writes X, B writes Y, neither touches the other's data, and the rule relating them breaks anyway not detected; each agent still looks correct on its own
lineage the derived collection has not changed yet and the reader is already working on it; the mark is held until lag_seconds has passed you would mark at sync time, which arrives late
precedence rules over the sequence: refunding against an already-closed ticket every step is individually admissible and the order is never checked
delegation a parent splits its budget among subagents; the derived anchor can only restrict, and what is leased counts as spent each subagent keeps its own count and the sum can exceed the parent's ceiling

Audit & replay

Every decision is one row in an append-only, SHA-256 hash-chained log. Tampering with any past row breaks every link after it, and verification reports the first broken sequence.

GET /v1/agents/{agent_id}/audit

{
  "verify": { "ok": true, "first_broken": null, "detail": "verified 41 records" },
  "head":   "9f2c…",
  "records": [ { "sequence": 0, "payload": {…}, "chain_hash": "…" } ]
}

Replay is deterministic: the same trajectory produces the same verdicts and the same hashes. That is what makes the log defensible to an auditor rather than merely informative.

Performance

There is no LLM on the governed path. A decision is arithmetic over six channels, a set lookup and one hash — so there is no model latency, no token cost, and no provider dependency.

pathlatency
Decision, in-process (SDK)~36 µs p50 · ~58 µs p95
Decision + audit write~99 µs
Hosted API round-trip~1.2 ms + network
Throughput~10,000 decisions/sec/core

Latency-critical agents (coding assistants) should use the in-process SDK and skip the network hop entirely. Measurements are reproducible: python3 tests/bench.py.

Security & privacy

What we store

By default the audit trail records digests and structured metadata — tool names, arguments checked against constraints, verdicts, hashes — not your agent's raw prompts or customer data. API keys are stored only as SHA-256 hashes; the plaintext key is shown once, when your subscription is provisioned.

Isolation

Agents belong to an organisation. A key from one organisation cannot read or govern another's agents — that boundary is enforced on every request and covered by tests.

For regulated buyers

On-premises and in-VPC deployment are available so no agent traffic leaves your network at all, along with a DPA. Ask before you integrate rather than after.

Next

See the live drift demo →
Pricing →