Inspiration

A schema change ships on Tuesday. In March, a regulator asks who approved it, on what evidence, and whether anyone checked it against the retention register.

The commit is there. The reasoning is not. It was in a review comment, in someone's head, or in a chat thread, and none of those survive contact with an audit.

We did not want a chatbot that reviews pull requests. We wanted a fleet of institutional agents that does the reasoning, records every step of it in a place nobody can quietly edit, and still cannot write a single byte without a human approving that exact byte.

What it does

Mitos wakes on a real pull request, decides among five specialists who is actually concerned by the change, reads the repository to find out, and proposes exactly one governed write which no agent is permitted to perform.

Features and functionality

  • Nobody opens it. A GitHub App webhook wakes the fleet. There is no scheduler and no queue: the agents hold open Firestore query subscriptions, so writing one deferral hands the fleet every open one, and expired deferrals escalate with nobody watching.
  • A router decides who wakes. Five agents are catalogued by department with the signals they care about. The router reads that catalogue. On a change with no personal data, compliance is skipped and the thread records that it was skipped and why.
  • The model can only tighten. Every specialist runs its deterministic rules first. The model may add findings and may block, and there is no branch anywhere that lets it remove a finding, clear a flag or turn a failure into a pass. That is a property of the code, not an instruction in a prompt, and it is asserted by tests that mutate the invariant and check the tests notice.
  • A deterministic gate, in its own process. Drafts are judged by a third Cloud Run service under its own identity, reached with an OIDC token audience bound to it. It checks for leaked credentials, prompt injection planted in the diff, attempts to skip the gate, and citations of files that were never opened. If it cannot be reached, the run stops. There is no local fallback, because a reader that judges its own draft when the gate is down is a reader with no gate.
  • Two model families, and the second one cannot approve either. Gemini 3.7 Flash routes, reads and drafts. Gemma 4 26B then reviews the draft independently and may only add advisories a human reads.
  • Content addressed approval. The plan is hashed. A human sees the exact bytes, the digest, and what will happen. The writer service refuses any other hash and runs under the only identity in the project holding a write credential.
  • A thread you can walk backwards. Every trigger, dispatch, signal, recall, finding, verdict, plan, approval and write is appended to Firestore, and any entry can be walked back to the diff that caused it.

Technologies used

Google Agent Development Kit, Gemini 3.7 Flash on Vertex AI, Gemma 4 26B A4B IT through Google Cloud managed open models, Cloud Run, Firestore, Secret Manager, Artifact Registry, Cloud Build, Workload Identity Federation, Terraform, FastAPI, React with TypeScript and Vite, and a GitHub App.

Other data sources used

The GitHub REST API, for pull request diffs and for posting check runs under an installation token scoped to the one repository being written to. A small synthetic corpus seeds the demo with weeks of history so recall has something to recall, and it is labelled as synthetic on screen and in the interface. No third party datasets, no scraped data, and no personal data of any kind.

How we built it

Three Cloud Run services, three service accounts, one image. The reader takes the webhook and runs the fleet. The evaluator judges drafts and refuses anonymous callers. The writer holds the write credential and refuses any plan whose hash it was not given. All three carry every route, and each one refuses to serve the routes that are not its job, so a misrouted request fails twice rather than once.

The interceptor sits in the Agent Development Kit's before_tool_callback. It is not a system prompt asking the model to behave. It is a function that returns a refusal, and it is tested from both directions.

Everything is Terraform. Continuous integration runs the unit suite, an integration suite against the Firestore adapter, a static analysis pass, a secret scan, a live call to Gemini and a live call to Gemma, and a separate step that fails the build if either live suite silently skipped, because a live test that skips is a claim nobody is checking.

Challenges we ran into

The hardest ones were not bugs. They were checks that passed over the thing they were named after.

A test asserting the interceptor refused a write failed to run at all, and a probe that cannot run reads exactly like a gate that held. A guard on the README matched wording rather than the claim, so rephrasing the claim silently disarmed it. A test that walked the provenance kinds used a list of prefixes, so a kind added under a new prefix was invisible to the test written to find exactly that. A frame comparison in the video build averaged over the whole frame and passed a mutation that replaced the closing line.

We found about fifteen of these. Each one was a green check standing in front of nothing.

Accomplishments that we are proud of

It is deployed and a stranger can check it. The GitHub App is installed on the repository, so every pull request gets a real check run posted by the fleet. Two pull requests carry two different verdicts, one because findings needed review and one because the router found nothing to govern and said which specialists it skipped.

867 tests, a coverage floor enforced in CI, and every claim in the README carries the command that produced it.

What we learned

Findings and learnings

  • A model that can only add is safe to add anywhere. Once the union property is real in code, a second opinion from a different model family costs nothing in risk and buys a genuinely independent read.
  • A refusal has to name something a person can act on. Our first gate counted model opinions towards the pass, and because the repair step is a regular expression it could never satisfy a sentence of judgement. Twelve of thirteen items parked with the reason "the gate could not be satisfied". Judgements belong in front of the human, not in the pass or fail.
  • Configured is not the same as ran. Every surface here that reports the second model reads the provenance thread, never the environment variable, because an environment variable proves a deployment intends to call a model and proves nothing about whether one answered.
  • Measure before you argue. Recording the demo against the real Firestore ledger looked obviously better and produced a 486 second video against a 240 second limit, because every append is a network round trip. The measurement is in the architecture decision record, next to the decision it reversed.
  • Never widen a gate to make it pass. Fix reality, or write down the limitation.

What is next

Private repositories, durable execution so a run survives a cold start mid flight, and moving the remaining writes behind the writer service so the reader holds no credential at all.

Built With

Share this project:

Updates

Submission history