Inspiration

Companies do not give agents real autonomy in production. Not because the agent cannot do the work: because a wrong write does not undo itself. The fear is always concrete — that it drops a table, overwrites a sensitive record, or applies a correction based on a number it made up.

So the industry took the safe road: agents read almost everything and write almost nothing. They stay in suggestion mode, and every bit of value they could create sits on the other side of the wall.

Gartner predicts that by 2027, 40% of enterprises will downgrade or retire their autonomous agents because of governance failures they discover after something already broke in production. And it names the cause: they treat governance as binary. Either blocked, or fully trusted.

We thought the binary was the problem, not the agent. A new hire does not get full access on day one either, and is not blocked either: they get a scope, someone reviews their work, and what they do can be undone. Nobody calls that a lack of autonomy. They call it having a job.

So we did not build a smarter agent. We built the substrate underneath it:

The agent can write to production because every write is reversible.

What it does

LufkeyW is a control plane for back-office agents. The case it runs is invoice reconciliation: the agent gets a goal and a tool catalog — no plan, no sequence — and decides for itself what to look at.

Every tool call passes through a deterministic enforcement kernel first. The kernel is not a prompt or a soft policy: it is an ABAC rule engine versioned in git, plus checkers that run in parallel. Verdicts can only travel upward — no checker can loosen what another one tightened.

  • Reads are allowed inside the session's capability contract.
  • Writes are never applied. They are classified by reversibility (R1–R4), the kernel computes the inverse operation before permitting anything, and they sit staged, waiting on a person. The verdict is STAGE, not ALLOW.
  • Egress goes through a broker that owns the only socket. Where the broker is not deployed, the external tool has no path out at all.

Everything lands in a hash-chained journal: the hypothesis it formed, the one it discarded, the evidence that killed it, the proposal and its per-field provenance. Every proposed value traces back to the step that produced it.

And the part that makes this more than theatre: the world moves between the proposal and the approval. If someone else edited that row while the human was deciding, the precondition no longer holds and the commit refuses. The agent cannot overwrite a change it never saw.

On top of that, the console streams the work live over SSE, catalogs agents by department and autonomy level, keeps sessions alive for days while they wait on a human, and generates a multimodal briefing that tells whoever comes back what happened while they were away.

How we built it

Kernel first, agent second. We wrote the rule engine and the reversibility taxonomy before wiring any model. The agent plugs into a catalog of typed, narrow tools — reads, writes, external calls — and cannot call anything that is not declared.

The verdict combinator is one line of algebra. Checkers run in parallel and their verdicts are ordered:

$$ \text{ALLOW} < \text{STAGE} < \text{ESCALATE} < \text{BLOCK} $$

The result is the strictest one, and with no applicable verdicts it fails closed:

$$ V(\text{intent}) \;=\; \begin{cases} \max{v_1, \dots, v_n} & n \geq 1 \[4pt] \text{ESCALATE} & n = 0 \end{cases} $$

Three properties follow by construction, not by discipline. Because $\max$ is monotone non-decreasing in each argument, no checker can lower the result: no LLM can grant, only tighten. A perfect prompt injection produces, at worst, denial of service. And because $\max$ is commutative and associative, the order in which checkers answer is irrelevant. We verify this with a property test, not with examples.

Reversibility is the second formal piece. For an R1 write $w$, the kernel derives its inverse $w^{-1}$ such that

$$ w^{-1} \circ w = \mathrm{id} $$

and if it cannot derive one, the operation does not proceed — the code raises rather than degrading to "probably reversible". Undoing a whole session is applying the inverses in reverse order:

$$ (w_n \circ \cdots \circ w_1)^{-1} \;=\; w_1^{-1} \circ \cdots \circ w_n^{-1} $$

The agent runs on Google ADK, with the kernel mounted as a Runner Plugin: that plugin intercepts every tool call and routes it through the kernel-guarded runtime. The tool stubs the model sees never execute anything; if one ever did, it raises on purpose. Gemini plans; the authority is not its own.

Status is derived, not stored. This was a painful correction. Session status used to be a field the lifecycle methods assigned alongside the journal — two sources of truth for one fact, and they drifted: twelve deployed sessions reported "running" while their own journals ended in concluded. The journal is hash-chained and the field was not, so the journal was right and the field was the lie. Status is now read off the journal, which makes that class of drift impossible rather than fixing this instance of it.

Infrastructure is Terraform end to end, and nothing stays warm by default: minimum instances are zero, the GPU classifier is off, and the recording window is switched on with a separate -var-file.

346 tests, and the ones that matter assert kernel behaviour, not plumbing: that a note is not evidence, that derived_from cannot cite something the agent merely thought, that a broken run stops claiming to work.

Challenges we ran into

The agent reasoned, but could not record what it ruled out. The journal already had kinds for hypothesis, hypothesis_discarded and replanned; the frontend struck them through, the briefing animated them, the metrics counted them. Every consumer was built — and on the ADK path the producer did not exist. The agent investigated the decoy invoice, dismissed it, and moved on without a trace: the investigation read as if the right answer had been obvious from the first step.

We closed it with a fourth tool kind: note. It reads and writes no data, so the kernel has nothing to rule on. And it produces no result_id, which is the part that matters: derived_from cannot cite a note, so no proposed value can trace back to something the agent only thought. A note is not evidence.

With that in place, the agent still discarded nothing — zero discards across five runs. Reading what it wrote showed why: it stated hypotheses broad enough that no record could kill them. "The amount differs, possibly due to currency conversion or quantities." Nothing refutes that. We sharpened the instruction to require that every candidate cause be stated narrowly enough that a record could contradict it. It went to two discards on the first run.

The model invented a number, and that was the most important bug we fixed. In an early report the agent multiplied an exchange rate by an amount and proposed the result. Nobody could audit that figure: it came from no record. We wrote a grounding rule — a proposed value must come from a record it actually read, and it must say which one. Watching it work live was the best moment of the project: the agent did the multiplication, saw that the result contradicted both the invoice and the receipt, and discarded its own conclusion instead of proposing it.

Dependency floors that only bite inside the container. google-genai names aiohttp.ClientConnectorDNSError in its retry handler, and that class only exists from aiohttp 3.11 — nobody declared that floor, so a retryable Vertex 503 died as an AttributeError instead of retrying. Then the same thing with Pillow: the module that draws the trail over the video imports it, no dependency declared it, and it worked only where someone already had it installed for another reason. The same lesson twice: a development environment lies by being generous.

And the deploy that said SUCCESS without deploying. The cloudbuild.yaml built and pushed all four images, and stopped there. Cloud Run does not create a revision on its own when a tag is rewritten, so the new image sat in Artifact Registry while production kept serving the old container — for hours, with the build green.

Accomplishments that we're proud of

The agent discards its own hypothesis, live, and it is on the record. It is not scripted: in one run it opens suspecting a duplicate, finds that the similar invoice belongs to a different purchase order, discards it naming the record that killed it, and changes approach. That is what separates an agent from a workflow that would have carried on regardless.

Reversibility is a computed property, not a checkbox. The kernel computes the inverse operation before permitting the write, and if it cannot compute one, it does not proceed. The R1–R4 classification is not a label someone attached: it is the result of that computation.

Deriving instead of storing. Moving session status to something read off the hash-chained journal did not fix twelve lying sessions: it made the failure impossible to repeat, and self-healed the ones already persisted wrong.

The briefing invents no figures. Veo provides the motion, Lyria the music, Gemini the narration — but everything readable on screen is drawn from the journal, not generated. A briefing that invents a figure is worse than no briefing, and in an audit artifact that is exactly where it matters.

A refusal that explains itself. When the kernel blocks, it says which rule fired and why. The agent treats that as information about the world, not as a wall.

What we learned

Binary governance is not a conservative stance: it is the cause of the failure. An agent allowed only to suggest is not safer — it is useless, and it gets switched off. What enables real autonomy is not trusting more; it is making the damage bounded and reversible by construction.

A record that shows only the steps that worked is worse than one that shows what was ruled out. It hides the fact that the agent checked. A reviewer reading the case months later needs to see the theory that was tested and abandoned, or they have no way to tell whether the right answer was reasoned or lucky.

Two sources of truth for one fact always drift. It is not a discipline problem. If a fact can be derived, derive it.

Arithmetic the model performs is a figure nobody can audit. In this domain the receipts, the purchase order and the contract terms already hold the answers. Proposing a wrong figure is far worse than proposing nothing.

What's next for LufkeyW Agent Control Plane

  • The Gemma classifier switched on. It is deployed and acts as one more kernel checker, but it is off for GPU cost. The kernel treats its absence as one veto fewer, never as a permission.
  • Runs distributed across instances. Today the run registry lives in process memory; the append/follow seam already has the shape Pub/Sub fanout needs.
  • Briefings in durable storage. They are written to the container's disk today, which is ephemeral.
  • Terraform-governed deploys. The image tag is latest, so Terraform cannot see code changes and revisions get created outside its state.
  • More agents in the registry. The catalog, the capability contract and the reversibility taxonomy are generic; the reconciler is the first, not the only.

Built With

  • armor
  • cloud
Share this project:

Updates

Submission history