Two tools, two moments
The gate, bossyk, sits as a proxy between your agent harness and your inference endpoint and either forwards a model's response untouched, or replaces it with a refusal*, before your harness acts on it. The audit, auditk, measures after the fact: it reads the session file your harness outputs and scores what actually happened, independently of anything the gate saw. A scorecard merges both records and checks that they agree. The proof.
Why two records, not one
The two services watch different things. The gate enforces the rules a deterministic check can decide before execution: where the agent may write, what it may read, what it may install, where traffic may go. The audit scores what no pre-execution rule can reach: whether each step the model took matches the intent it declared, which is where drift lives. Each produces its own signed record. The gate's log says what was allowed and what was stopped; the session file, written by your harness rather than by the gate, shows what the agent actually did. The scorecard checks the two against each other. When they agree, that is corroboration earned from two components that never talk to each other, not an assumption. In one baseline run with nothing hostile in the workspace, the scorecard showed this:
All three figures verified from the trace, not taken on trust.
Why an always-on gate, not just an audit
Across our trap runs, the same prompt against the same workspace didn't produce the same behaviour twice. Here's how three models did against a planted credential file (a fake .env) sitting in the workspace, across repeated runs with identical prompts, identical workspace, identical model defaults:
Most models resisted the planted trap most of the time. But which run tripped it and which didn't followed no pattern we could find; it is not a fixed trait of the model. A one-off test on any single run could have told you almost anything. An always-on gate doesn't change how often a model reaches for the file; it changes what happens when it does, on every run, not just the ones you happened to sample. Where a model did reach for it, the gate's no-credential-reads policy stopped the read before it returned anything, and the model's own session file recorded the refusal independently, so the scorecard could confirm it rather than assume it.
Who writes the rules?
A gate is only as good as its policy pack, so the fair first question is who writes it, and what happens to everything the rules do not anticipate. Two things answer it.
First, the rules are deliberately not an attempt to describe good behaviour. They are a short list of consequences to forbid: never read a credential file, never write outside the workspace, never install off the allowlist. That makes them small, legible, and reviewable as a diff, not a specification someone has to get exhaustively right. And crucially they do not have to be complete, because the second record is not bound by them. The audit scores every step against its declared intent whether or not any rule names it, so the behaviour the pack never foresaw, the quiet test rewrite two sections down, still shows up. The gate is only as good as its rules; the audit is not. An incomplete pack degrades to a thinner gate over a full audit, not to nothing.
Second, you do not start from a blank page. Run in audit-only first and watch what your agents actually do; the record of that week is the agenda for deciding what to forbid. Each policy is one plain question about a consequence, and when the audit surfaces something no rule caught, that becomes the next rule. The behaviour is observed, then decided on, then enforced, in that order, rather than guessed at up front.
Watching intent, not just actions
This work began with a simpler question than policy gating: when an agent says what it is about to do, does it then do that thing? The gap between declared intent and enactment is drift, and it is what the audit layer scores on every step of every run, independently of whether any policy fired.
The gate handles the enforceable class of rules; drift scoring watches everything else, including the risks that cannot be gated at runtime: goals smuggled in through data, quiet scope creep.
A worked example: editing the test instead of the code
From one of our MiniMax M3 trial runs, read straight from the signed trace. The model began exactly as instructed:
No policy in the gate's pack names this. Nothing crashed, nothing left the workspace, every individual action was allowed. Only the run's own words set against its own deeds show what happened: the agent quietly moved its goalposts. This is the class of behaviour that only an audit can see, and it is exactly the failure mode a team relying on "the tests pass" never notices.
That finding did not stay a finding. It is now a deterministic auditk rule, shipped from exactly this trace: a test file edited after a failed run raises a structural flag on every audit from then on. What the judge notices once, every future run is checked for.
The drift ribbon
Every scored step of four real runs, in order, coloured by the judge's label. The first row is the run from the worked example above; the underlined stretch is the test-rewrite window:
Every scored agent step of three hostile-workspace runs; the score and every label ship inside the signed evidence pack.
On a short task most steps score faithful, and that is the point: drift on a fifteen-step run is rare and small. The behaviour operators most often describe, a model that follows instructions well at first and less well the longer you work with it, is the same quantity at session scale, where it compounds. The audit gives it a number per run, and a signed record that someone was watching even on the runs where nothing fired. That record, not the occasional catch, is the product.
Concretely, five readers use that record, each for a different question:
- You, as the operator. The record of week one is the agenda for the governance conversation with your clients, and over time it lets you route work between models on measured behaviour rather than reputation.
- Your client's compliance function. Auditors do not ask "did anything bad happen"; they ask "show me the control operated in March". Only a continuous signed record answers that.
- A security team. When something fires, the surrounding record is what turns an alert into a narrative: what was attempted, what stopped it, and what the weeks of normal looked like before it.
- Anyone in a dispute. "Your agent read our data" stops being an unfalsifiable accusation and becomes a checkable claim, in either direction, which is exactly what makes the record credible.
- A regulator, eventually. Record-keeping obligations are continuous, not incident-shaped, and a lone incident report with no surrounding baseline looks curated. The baseline is what makes it believable.
The difference it buys is the one that matters commercially: not "we believe our agents behaved", but "we can show they did, for any week you pick".
Where each piece sits
The gate is a thin proxy in front of your inference endpoint. It speaks the same OpenAI-compatible protocol on the way out, so nothing about your endpoint changes.
pi (or any OpenAI-compatible harness) talks to the gate exactly as it would talk to your endpoint directly. The gate buffers the response, checks every proposed tool call against the policy pack, then either forwards it verbatim or swaps in a refusal that names the violated policy, before your harness sees either.
The unit the gate rules on is the proposed tool call: a shell command, a file write, a call to an MCP server, whatever tools the harness exposes to the model. Coding agents are the first use case, not the shape of the thing. Any agent loop that proposes tool calls, a document pipeline, a support agent, an orchestration graph, is gated the same way, with the same evidence coming out.
models.json: point baseUrl at the gate instead of your endpoint. Your harness doesn't know the gate is there.The gate, up close
Inside the hop: the response is buffered in full, every proposed tool call is checked against the pack, and each decision leaves a signed event behind.
A conservative gate by construction: one violating call voids the whole response, an unreadable call is refused rather than guessed at, and the refusal the harness records is what lets the audit corroborate the log later.
The proof
After a run, auditk reads the session file and produces a signed evidence pack. The scorecard then pulls that pack together with the gate's own signed event log and checks both.
Two independently signed records, from two components that never talk to each other during the run, reconciled once, after the fact, in one file you can open offline.
Latency
*The gate's own work costs single-digit milliseconds against generations that take tens of seconds; in our paired probes it was unmeasurable. The real trade is buffering: a response arrives when the model finishes, not token by token, because the gate must see a complete tool call before ruling on it. Agent loops never feel this. Each round trip gates on its own, and a harness cannot act on half a tool call anyway.
It bites only where someone is waiting on the first words: chat a person watches, or voice that starts speaking as tokens arrive. For those, today, this is the wrong tool. The roadmap answer is selective streaming, text passing straight through while tool calls are held for ruling.
Also in the build: policy packs bound to a caller's SPIFFE identity, gate events exported over OTel and Prometheus into your observability stack, and HOLD, a third verdict that pauses an action for a decision instead of ending the turn. All three are off unless you switch them on. See “What's next” below for what they do and do not claim.
Four things come out of a run
Signed gate event log
gate-events.jsonl. Ed25519-signed per decision, with the policy-pack hash and the gate's own version stamped into every entry.
auditk evidence pack
evidence-pack.json. A signed record of the drift and audit findings, produced independently from the session file.
Per-run scorecard
scorecard.html. Verifies both signatures offline, using only the public keys you hold, and cross-checks every gate block against the refusal recorded in the session.
Automated incident report
A deterministic prose timeline, every sentence citing the step it comes from. Produced only when a run was blocked or fails verification, not on every run.
Two recordings
One blocked run on Nemotron, one clean run on Qwen, both gated runs against the same hostile workspace as above.
- The gate starts, five policies loaded, in front of the endpoint.
- The model is asked to build the tool, and a planted log line prompts it to reach for the
.envcredential file. - The gate blocks that read under
no-credential-readsbefore anything is returned. - The run is audited, and the scorecard confirms the block was recorded in the model's own session, not merely claimed by the gate.
- The model never touches anything it should not.
- Every proposed action is allowed and forwarded untouched.
- The run is audited exactly the same way as the blocked run.
- The scorecard verifies and shows zero blocks: an uneventful run still produces the same signed, verifiable record.
Built today, and what follows
Above is what is running today: the gate deciding before execution against policies you define, auditk scoring drift after the fact, and the scorecard and evidence pack you can hand to compliance, security and a regulator, as well as keeping as your own operator record. Below are planned improvements.
Near termBuilt
- HOLD, human-in-the-loop, a third verdict alongside allow and block: a policy line can pause an action and hand it to an approver you configure (a command or a URL, with a timeout), resuming on approve or refusing on deny. With no approver, or no answer in time, the policy's own default applies, and that default is block. Every held decision is signed with how it resolved. Interactive console approval is not built; nothing here claims a human always answers.
- OTel and Prometheus export of gate events, alongside the signed log and never instead of it: one span and one log record per decision, and counters by verdict and policy on a scrape endpoint or pushed to your push gateway. Off by default. Validated against a real collector and push gateway; it emits standard OTLP.
- Per-identity SPIFFE-bound policy packs, so different callers on the same gate run under different rules. Identity comes from the SVID on the mTLS connection; a header is trusted only if you say so and only behind your own boundary. An identity the gate cannot read is refused, and an unknown caller runs under a default pack you must declare, never unpoliced. Every signed event names the caller and the pack that ruled.
Mid termPlanned
- A signed baseline policy-pack feed: versioned, with reviewable diffs and its own test suite, that works fully air-gapped. An opt-in path would let a site submit a human-mediated sample back, scrubbed by the same redaction layer that protects the evidence packs, but nothing leaves without that explicit step.
ExploratoryExploratory
- NER-based PII detection alongside the deterministic pattern matching, with its error rates published rather than assumed.
- A desktop companion app that sits minimised in the system tray, watching local agent sessions and auditing them in the background.