The phrase that stuck was “forkable from the event log.” I was reading Yohei Nakajima’s two ActiveGraph papers back to back: the runtime, The Log is the Agent, and the improvement loop built on it, Regimes. It landed on a gap I had already built for myself. Brigade wrote a receipt for every promotion, but status.json still acted like the source of truth. GraphTrail mapped a repo into a queryable graph, but could not compare two versions of it. Each tool held state and trusted a materialized blob a little too much.
Brigade is the hub the other two feed. It is the reviewed catalog of MCP servers, tools, and memory an agent shares, and it keeps the receipts this whole story runs on.

The runtime paper is precise about why that helps. An append-only event log is the source of truth. The working graph is a deterministic projection of that log. Behaviors react to graph changes and emit new events, and a content-addressed cache records every model and tool response, so a replay makes no new model calls. Three properties fall out of that one decision: deterministic replay, cheap forking, and lineage from a goal down to the call that produced each artifact.
[ TWO PAPERS, ONE SUBSTRATE ]
An append-only event log is the source of truth, the working graph is a deterministic projection of that log, and behaviors react to graph changes and emit new events. Replay, fork, and lineage are architecture, not a logging layer bolted on.
Built on the runtime. It diagnoses a dominant failure regime, routes it to a typed action seam, has a model author a repair, and promotes only after static checks, a sandbox, an in-sample eval, and a held-out gate.
I did not vendor ActiveGraph. GraphTrail is Rust and the Python library was never importable, and Brigade already owns its own orchestration. What I took was the operating habit. Write the state changes down. Rebuild from them. Fork before you trust the change. Diff the fork against the current branch. Promote only after a check passes.
[ IN THEIR WORDS ]
The changes are mine, the ideas are theirs. Each quote below is verbatim from one of the two papers, next to what it moved.
”The append-only event log is the source of truth; the working graph is a deterministic projection of that log; and behaviors … react to changes in the graph and emit new events.”The Log is the Agent, arXiv:2605.21997The habit I took: state is a projection of a log, not a blob you mutate and hope stays right.
”end-to-end lineage from a high-level goal down to the individual model call that produced each artifact”The Log is the Agent, arXiv:2605.21997Why a Brigade promotion can now name the receipt that caused it instead of a commit range.
”a recorded run replays exactly from its log, candidate patches scope to typed pipeline seams, gates are auditable, and every promotion or discard is itself an event”Regimes, arXiv:2606.10241Brigade’s drift oracle and treating a promotion as an event, not a silent write.
Status.json stopped being the source of truth
Brigade already wrote a receipt for every promotion, so this fix was small. brigade outcome rebuild-status --check folds those receipts back into a fresh status and fails if the materialized status.json disagrees (#145). The file stays for speed, but it is a projection now, not the truth. The same command’s fork and diff replay the receipts under a different reconciliation config, so you can ask what a threshold change would have promoted without touching live state.
One wrinkle worth sending back to the papers: a single event log was not enough here. Promotion is a stateful ratchet with a cooldown, so rebuilding it needs the decision receipts as their own transition log next to the signal log. A single-log model would have to re-derive the transitions from signals plus a clock, which is exactly the coupling the separate receipts avoid.
GraphTrail learned to compare two versions of a graph
GraphTrail already mapped a repo into a SQLite graph of symbols, imports, and call edges over a read-only MCP surface. The morning this work started, the brigade graph held 312 files, 5,096 symbols, and 10,860 call edges in one database. What it lacked was the compare.
the new surface
# GraphTrail: diff two indexed snapshots of the same repo
graphtrail diff --before before.db --after after.db --json
# The counts block is named "summary", the changed symbols carry
# their qualified names, edges split into added_edges / removed_edges.diff takes two indexed snapshots and reports added, removed, and changed symbols plus added and removed call edges, over a CLI and an MCP tool (#10, #12). Schema v3 (#15) made it exact: each symbol carries a body hash of its precise indexed span, so a one-line body change with the same signature reports as one changed node instead of hiding. Edge counts come in two flavors on purpose, a raw count where edge identity includes the call line, and a line-insensitive count, so a pure line shift reads as churn in one and zero in the other instead of crying wolf.
The fake binary that lied
The graph-delta work passed all its own tests and its reviewer. Then the first real-binary dogfood failed with graphtrail sync failed (2). Three separate inventions, all masked by a permissive fake test binary that accepted any flags I threw at it.
| 1 | - | # what the permissive fake test binary happily accepted | |
| 2 | - | graphtrail sync --json # invented flag | |
| 3 | - | graphtrail diff --from-db <snapshot> # invented flag | |
| 4 | - | diff_payload["counts"] # invented JSON key | |
| 5 | - | diff_payload["changed_symbols"] # invented JSON key | |
| 6 | - | diff_payload["edges"]["added"] # invented JSON key | |
| 7 | 1 | \ No newline at end of file | |
| 2 | + | # what the real clap CLI actually exposes | |
| 3 | + | graphtrail sync # --json here exits 2 | |
| 4 | + | graphtrail diff --before <db> --after <db> --json | |
| 5 | + | diff_payload["summary"] # the counts live here | |
| 6 | + | diff_payload["changed_nodes"][].qualified_name | |
| 7 | + | diff_payload["added_edges"] # and removed_edges | |
| 8 | 8 | \ No newline at end of file |
The third bug never threw. GraphTrail names its counts block summary, so reading diff_payload["counts"] would have returned 0 for every diff, forever, without raising. The fix that made it stick: rewrite the fake to strictly mirror the real CLI, reject unknown flags, require --before / --after / --json, emit the golden-fixture JSON shape, then add a real-binary dogfood step. The diff golden fixture had merged at 05:50 that morning (#14) and locked summary as the counts key, which turned the 07:15 bug into a 30-second grep instead of an argument. One more subtle one: the “after” hash had been computed over the live WAL-mode database file, which does not attest what the diff actually read, because WAL pages live outside the main file. Snapshots now go through SQLite’s backup() from a read-only URI.
The receipt became the join point
A Brigade verify or run now snapshots the target’s GraphTrail graph before and after, diffs them, and writes a compact code_graph_delta into the receipt (#158): the counts, up to 20 changed symbol names, and a graph-delta.json sidecar for the full diff. The two ~11 MB snapshots are deleted after the diff; only their sha256 digests stay in the receipt, so it is content-addressed attestation with no retention cost. Read-only and dry-run brigade runs skip capture entirely, because a run that promised not to write must not trigger a sync.
That delta is where the receipt turns into lineage. The receipt records the git head, branch, and dirty files inside its own content, and the Miseledger export emits the GitHub commit URL as a link (#162). So a promotion points at the exact commit and the exact code-graph change that produced it, not a commit range. That is the runtime paper’s “lineage from a goal down to the individual call that produced each artifact,” made concrete for a repo.
Miseledger closed the loop with zero new code
Miseledger stores content-addressed evidence: its rows are immutable and keyed on a content hash, so state is reproducible by construction with no log to fold. That is why a Brigade receipt exports into it (#159) without a line of dedupe code. The adapter contract is miseledger.adapter.v1 JSONL, and the receipt’s receipt_sha256 becomes the row’s content hash, so re-importing the same receipt reports inserted: 0, already_known: true. The first import of a repo’s history wrote 91 receipts; the second run wrote 0.
Miseledger picked up its own fork and diff in the same stretch (#14): fork branches an archive into a standalone SQLite copy via VACUUM INTO, and diff compares added, changed, and removed evidence between archive states as a content-hash set difference. Event sourcing is one way to make state reproducible. Content-addressing is another, and where it fits it removes the need to fold a log on every read.
Outcome records copy the compact delta onto the outcome ledger, and outcome rank gained a graph: N changing / M no-op counter per subject (#163). The promotion ratchet’s decision rules were left alone on purpose. The counter surfaces the delta; it does not gate on it.
The return path
Export into an evidence archive is only half a loop. The other half is reading it back. brigade run can now attach an evidence brief (#171): recent verified results for the target repo, pulled back out of Miseledger, capped at 2,000 bytes, always framed as untrusted evidence rather than instructions, and recorded in run.json next to the other briefs. A run starts knowing what earlier runs on that repo verifiably changed. brigade work import context --from-miseledger '<query>' does the same fetch on demand.
And because the receipt holds both the pre-run brief and the post-run delta, a run with a code-graph brief also gets a context eval (#167): did the brief name the files the run actually touched. hits, missed, and a brief_hit_rate, computed from artifacts the run already writes, no model in the loop and no eval at all when nothing changed. Code Search finds concepts, GraphTrail finds relationships, and the receipt records what the agent did with that context.
All of this shipped in Brigade 0.21.0, 1,709 tests green at the cut. There is no 0.20.0: it was a version-bump commit reverted the same afternoon, so the number just goes unused.
The loop that now exists

The proof lives in the repos, not this post. Each tool carries the code that does the work plus a passing verify receipt:
One verify receipt now carries the exit code, the code-graph delta (body-hash exact since schema v3), and the git head that produced it, and it flows into a 13 GB content-addressed evidence archive on a 30-minute timer where importing it twice is a no-op. Then the next run reads it back. Every piece of that sentence was a separate pull request with its own Brigade receipt. The run that built the export pipeline produced a receipt whose own code_graph_delta read 20 changed symbols and 59 edges of churn. The pipeline measured its own construction.
What we adapted from Yohei, and how we took it forward
The adaptation, compressed: the log is the source of truth, the graph is a projection of it, and anything that matters gets an event you can replay, fork, diff, and trace. Brigade got the drift oracle and delta receipts, GraphTrail got the compare, Miseledger got the export, and the evidence brief closed the circle.
Then Yohei recommended a paper making the rounds: Auto: The AGI Compiler, which measures how much of a frontier agent’s recorded behavior is secretly deterministic (87.1% of their 560 spans) and compiles those parts into verified artifacts, with guards that deopt back to the live agent when inputs drift. Brigade already had the raw material for a shell-scale version of that idea sitting on disk: months of run artifacts and verify receipts, each with per-command argv. A 6-scout swarm run mapped the internals in one pass (run 20260708-020552-f005da08, a 112-line cited evidence pack), and two features fell out.
The workflow scanner (#155) is the measurement half. It mines run artifacts for command sequences operators keep repeating and proposes runbooks from them. Proposals go through review like everything else in Brigade; nothing auto-installs. The pins (#156) are the verification half: a runbook step pins its binary by sha256, runbook plan shows pin status without executing anything, and runbook run refuses on a mismatch unless you pass --allow-pin-mismatch, which is recorded in the receipt. brigade runbook pin adopts the binaries you have right now. A crystallized workflow with unverified binaries would just be a faster way to run the wrong thing.
GraphTrail kept moving too
The diff only means something if the graph under it is honest, so the follow-on GraphTrail work went after freshness. Sync respects .gitignore now (#16): one real-world graph dropped from 2,955 files, 2,803 of them site-packages noise, to 152, and a file that becomes ignored reads as deleted on the next sync, so every polluted database cleans itself. Schema v4 (#17) records an extractor fingerprint per file, so upgrading one language’s extractor re-extracts exactly those files instead of forcing a full reindex, and the MCP query tools grew an opt-in refresh. And graphtrail doctor (#18) answers the question every consumer of the graph should ask first: FRESH, STALE, or NEEDS-MIGRATION, with exit codes 0, 1, and 2 so a script can gate on it. The brigade graph this post opened with is already on v4.