Policy engine
One deterministic verdict on every governed request: allow, block, redact, or park it for a human.
A rule inside the agent’s own code is not a control.
Whether an agent may put a card number in a prompt, or act on a ticket saying “ignore your previous instructions”, is usually decided inside the agent: a regular expression in a try block, a threshold written by whoever built it. Each is enforced by the thing being governed, changes whenever somebody redeploys, and is invisible to whoever answers for it in an audit.
Injection in a user’s message is the demonstration. Injection in a tool result (a ticket body, a scraped page, a row somebody outside your organisation can write to) is the channel that hijacks agents, because the model reads data as instructions.
How it works
Step 6 of the request path. The order is encoded in the evaluator, and where each step sits is the reason the guarantee holds.
- 01
Sanitise first, then scan each fragment under its own source
Model-visible text is normalised before any detector reads it: the Unicode tag block, which encodes a complete invisible ASCII alphabet, plus zero-width characters and bidi overrides. Then eleven PII detectors and nine injection heuristics run over the system prompt, message blocks, tool results, arguments, tool definitions and vendor passthrough fields. - 02
Decide, first hard failure wins
A kill switch beats everything, then lifecycle status, then deny-by-default permissions including the delegation chain, then budget and rate ceilings. Only then are policies evaluated, in ascending priority. A shadow match is recorded and skipped; among enforcing matches the first block wins. - 03
Enact, or refuse rather than rewrite
On allow, the plan is applied and a redaction event records kinds and counts, never values. Data-class rules run again on the response leg: after a buffered response, before the first byte of a streamed one. Two cases are refused rather than rewritten: sensitive data in a JSON key, and in an image. - 04
Stage it before it bites
A new rule defaults to shadow: evaluated exactly as an enforcing one, then recorded and skipped. Where the deployment turns the promotion gate on, no rule enforces until a backtest of that exact rule is acknowledged by a named person.
Every check lives in one function, so you can read your rules and know what they do.
POST /v1/chat/completions
The key resolves to one registered agent with a named owner, and the trace is opened before anything can refuse the request.
Kill switches, then lifecycle, then deny-by-default permissions across every delegation hop, then rate limits, then your policies by priority.
returns: allow · block · require_approval — plus a redaction plan
A spend ceiling is the deliberate exception: it is deferred until routing has fixed which provider and model will serve the call, so it is tested against the price you will pay.
Routed to the provider you configured, on your key. Metering runs last and the trace closes with a terminal status.
The plan is applied to the outbound payload and a redaction event records kinds and counts, never values. A redaction that cannot be applied faithfully becomes a refusal instead.
An approval bound to a hash of this exact payload, single-use and expiring. The caller receives the approval id and the header to resume with.
429 ACP_BUDGET_EXCEEDED or ACP_RATE_LIMITED where a ceiling decided it. The trace closes as blocked and is kept as thoroughly as one that went through.
Verdict names and status codes are the product’s own. Every rule can run in shadow mode first, where the decision is recorded and the request is not changed. That is how you learn your false-positive rate before you refuse real work.
Seven triggers, and no eighth.
There is no free-text trigger: the payload conditions available are the ones the detectors produce. Scope is the other half: agent ids, teams and tags each select, and an empty scope is global. Teams compare case-insensitively; tags compare case-sensitively, and the backtest report names that asymmetry rather than showing a clean zero.
- tool_call and model_request
- A tool or model name pattern, plus argument conditions on a dot path with one of eight operators, or a maximum estimated input size. Patterns are wildcards, not regular expressions.
- data_class
- One or more of eleven detected kinds, with a direction from your point of view: outbound is a prompt leaving, inbound a completion or tool result arriving. Getting it backwards silently disables the rule, so the frame is fixed in one place.
- injection
- A minimum confidence between 0 and 1, optionally filtered to a user message, a tool result or the system prompt. Scoping to tool results polices indirect injection without blocking your own support console.
- spend, rate and time_window
- Accumulated spend against a USD threshold; requests, tool calls and tokens per minute; or an allowed hour range in UTC. Spend and rate read the same windows the budget check reads.
name Refunds over £200 require human approval
priority 10 scope {} // empty scope: global
trigger tool_call
toolPattern '*issue_refund'
argMatchers [{ path: 'amount', op: 'gt', value: 200 }]
action require_approval, approvalTtlMinutes 60
mode enforceFor traffic routed through the gateway, configured policies can allow, redact, hold or block a request. Detection is heuristic and can miss sensitive data or misclassify a payload.
The request passes the configured checks and proceeds to its provider. Its trace records the policy decision.
A configured rule matches a value and masks or tokenises it before forwarding the payload to the provider. Unmatched values can still pass through.
A policy requires human approval. The approval binds to this payload, expires and can be used once; the caller receives a typed refusal with the approval identifier.
The gateway denies the routed request and records the blocking policy and outcome in its trace.
Shadow mode records policy decisions without changing the request. Review false positives and missed detections before enforcing the policy on live traffic.
Nine patterns, weighted higher inside a tool result.
Nine weighted patterns run over sanitised text, summed, multiplied by 1.25 when the source is a tool result, capped at 1. The multiplier exists because the author of a tool result is data, not a principal. A directive scores 0.4 typed by a person and 0.5 arriving from a tool, so a rule set at 0.5 fires on the ticket body and not on your support agent.
Redaction has two modes. Masking is irreversible; tokenising gives the same value the same placeholder everywhere in one call, so the model can treat a customer and a card as distinct entities without receiving either. Credentials are always masked, even in tokenise mode.
in turn 1 card 4111 1111 1111 1111, key sk-ant-api03-XXXX…
turn 2 the card 4111 1111 1111 1111 again
out turn 1 card <CREDIT_CARD_1>, key [REDACTED:API_KEY]
turn 2 the card <CREDIT_CARD_1> again
// one call, one token map. Not carried between calls, and a
// credential is never given a reversible placeholder at all.Each line closes off an assumption a reader would otherwise carry into a proof of concept.
What this does not doWhat this leans on
Agent permissions
Deny by default, explicit deny wins, and delegation intersects, so an agent cannot borrow authority it was never granted.
Human approvals
One human decision, bound to one exact payload, spendable once.
Flight recorder
Every governed request in a timeline a compliance officer can read, and a search box that never writes SQL.
Where this comes up
Jobs that draw on it
What can a Token Observe policy match on?
Seven kinds: a tool call, by name pattern and conditions on its argument values; a model request, by pattern and estimated input size; accumulated spend against a USD threshold; requests or tokens per minute; sensitive-data classes with a direction; the injection score, optionally by source; and the hour in UTC.
Does Token Observe use a model to detect prompt injection?
No. Scoring is nine weighted regular expressions run over sanitised text, summed, multiplied by 1.25 on a tool result, capped at 1. No machine-learning dependency and no semantic understanding, which is why it runs inline on every request, and why it misses a phrasing nobody wrote a pattern for.
What does a policy backtest prove?
That the candidate rule, run against your own recorded traffic, would have newly blocked or gated a stated number of requests, or that the recording cannot answer. Requests that never reached policy evaluation are excluded from the denominator rather than counted as safe, and where a trigger depends on something the feature vector does not carry, the counters come back null. Null is a question; zero is a false answer that reads as safe to promote.
Prefer to ask a person? Write to James
Bring us the agent you are least comfortable with.
Create a cloud account and workspace. No card or server installation. Model calls and Google BigQuery scans can incur separate provider charges.