Skip to main content
Open Source ★ Featured Open Source

Anvil: The System of Record for Agent Teams

Impact Summary

I built Anvil to give human and AI coding agents one canonical record of requirements, tasks, claims, and evidence. It coordinates parallel work through enforced leases and gates task completion behind attached proof, all in local SQLite with no hosted backend. It now ships on PyPI as anvil-state.

Role

Creator & Maintainer

Timeline

2026-Present

Scale

  • 35 CLI commands and a 24-tool MCP server
  • 8 skills, 5 agents, 4 hooks
  • 3 LLM providers (Anthropic, Bedrock, OpenAI-compatible)
  • On-disk SQLite schema v8

Links

Decision Summary

Problem
Multiple AI coding agents and the humans supervising them increasingly work the same plan at the same time, but the tools they coordinate through (GitHub Issues, markdown conventions, chat history) store free-form text, claim work by label or a chat message, and enforce nothing when two agents grab the same task. Worse, when an agent reports a task complete, nothing verifies the claim. Status outranks evidence, which is exactly backwards for autonomous or weakly-supervised runners.
Constraints
  • Must be local-first: state lives in a .anvil/ directory, no hosted backend
  • Must be runtime-neutral: any MCP client, not tied to one agent harness
  • Completion must be gated on attached proof, enforced in both CLI and MCP
  • Every mutation must be replayable from an empty database for audit
  • Concurrent claims must have exactly one winner, no races
Tradeoffs Considered
Local-first SQLite ledger with an append-only event log (chosen) Chosen
Pros
  • + Replaying events.jsonl reconstructs state.db, a real audit guarantee
  • + No server to run, no network dependency, no hosted state to trust
  • + BEGIN IMMEDIATE claim transactions give single-winner coordination
  • + Works across Claude Code, Codex, Cursor, OpenHands, Copilot, any MCP client
Cons
  • Single-machine by design: no built-in multi-host fan-out
  • Lease tuning matters: too-short leases revert tasks mid-run
GitHub Issues plus markdown conventions
Pros
  • + Zero new infrastructure, familiar to every developer
  • + Already integrated with PRs and CI
Cons
  • No enforcement: two agents can claim the same issue
  • No evidence gate: a green checkmark is taken on faith
  • Chat history is not a database and does not survive session resets
A hosted orchestration platform with managed state
Pros
  • + Multi-host coordination and dashboards out of the box
  • + Someone else operates the control plane
Cons
  • Vendor lock-in to one runtime and one billing model
  • State you cannot replay, audit offline, or fully own
Outcome
Drove overlapping-file claim collisions from 3 to 0 under concurrency benchmarks
Cut Cut typical session LLM cost about 60 percent via tier-aware model mapping
Shipped Shipped v0.3.0 across a 23.7-hour unattended run of 32 tasks and 21 PRs
Kept Kept main honest across every merge by ground-truthing the full suite

Problem

The first time I let a fleet of agents work a single plan unattended, the question that kept me up was not “will they finish?” It was “when one of them tells me a task is done, why should I believe it?”

That is the gap Anvil exists to close. Multiple AI coding agents, and the humans supervising them, increasingly work the same plan at the same time. The tools they coordinate through were never built for it. GitHub Issues and markdown-convention systems store free-form text in an issue body, claim work by a label or an “I’ll take this” message in chat, and enforce nothing when two agents grab the same task. Chat history is not a database. It does not survive session resets, model swaps, or a change of agent runtime.

The deeper problem is trust. When an agent reports a task complete, nothing checks the claim. Status outranks evidence, which is exactly backwards for autonomous or weakly-supervised runners. A claim is “it works.” Evidence is an exit code. I wanted a layer where completion is gated on attached proof, where a claim is a real lock, and where the whole history can be replayed and audited.

I also wanted it to stay local-first and runtime-neutral. The orchestration space churns constantly. Platforms keep absorbing multi-agent features, and orchestration does not even port cleanly between runtimes (a lesson I learned the hard way and wrote up in Only the Ledger Survived). So I deliberately built the substrate the work is done on, not another orchestrator. The system of record, not the conductor.

Approach

State Is the Truth, Not a Side Effect

Anvil treats agentic software work the way Terraform treats infrastructure. A canonical state file holds the truth, derived views are projected from it, and a plan-then-apply rhythm gates execution behind review. The PRD is the configuration. A SQLite database under a local .anvil/ directory is the state. Recording evidence and transitioning a task to done is the commit point.

Every mutation appends to .anvil/events.jsonl. Replaying that log from scratch against an empty database reconstructs state.db. That is the audit guarantee the whole engine is built around, and it is not decoration: it is what made a 23-hour unattended run something I could actually trust in the morning. State shape is enforced with Pydantic v2 models validated at every transition, not stored as free-form text.

A Claim Is a Lock, Not a Label

Coordination runs through atomic SQLite transactions. A claim is a row with a lease and a heartbeat, opened inside a BEGIN IMMEDIATE transaction so exactly one agent wins. Stale leases are detected and reaped on every CLI or MCP call. This is the part that looks trivial and is not: the problems of multi-agent coordination are the same problems of multi-human coordination. File ownership is code ownership. Two agents editing the same function is the same hazard as two engineers editing the same Terraform module.

The numbers bear it out. Under a concurrency benchmark, Anvil’s lease plus BEGIN IMMEDIATE claim transaction drove overlapping-file collisions from 3 to 0 across runs. Not a clever heuristic. A database doing what databases are for.

Evidence Over Claims, Enforced at Both Surfaces

When an agent claims a task, the claim is an enforced database row. Completion is gated on attached proof. Anvil does not record completed work without it. That gate lives in both the CLI and the MCP server, so an agent cannot route around it by switching surfaces.

The surface is intentionally split: a CLI (anvil) for pure state operations, and a FastMCP stdio server (anvil-mcp) exposing 24 tools to any MCP-compatible client. As of v0.3.0 that is 35 CLI commands, 24 MCP tools, 8 skills, 5 agents, and 4 hooks, sitting on an on-disk SQLite schema at version 8. It works with Claude Code, Codex, Cursor, OpenHands, Copilot, and any other MCP client. Runtime-neutral is not a slogan here. It is the bet.

Routing Is a Cost Decision

Anvil scores each task on six dimensions: complexity, parallelizability, context_load, blast_radius, review_risk, and agent_suitability. Those scores feed tier-aware model defaults (opus, sonnet, haiku). Routing every task to the top tier is the lazy default and an expensive one. Tier-aware mapping drops typical session cost about 60 percent versus top-tier-everything, while an explicit override always wins. The provider layer resolves across three backends: the Anthropic API as default, Amazon Bedrock, and any OpenAI-compatible custom endpoint.

Installing It Should Be One Line

Anvil ships on PyPI as anvil-state (the bare name anvil was already taken). Installing it puts both anvil and anvil-mcp on your PATH:

# Install as an isolated tool (puts `anvil` and `anvil-mcp` on PATH)
uv tool install anvil-state   # or: pipx install anvil-state

# Initialize state in your repo, parse a PRD into tasks
anvil init
anvil prd parse .anvil/prd.md

# See what is ready, then claim it (one winner via BEGIN IMMEDIATE)
anvil next
anvil claim T012

# Render the work packet, do the work, then attach proof and apply
anvil packet T012
anvil submit T012 \
  --commands "pytest tests/test_migration.py" \
  --files-changed "src/migrations/v8.py, tests/test_migration.py"
anvil apply T012 --approve

# Wire the MCP server into any MCP client
anvil-mcp   # FastMCP stdio server, 24 tools

The release arc was deliberate. v0.1.0 was the initial public beta, the Agent Fleet capacity-coordination MVP. v0.1.1 made it installable as a standard package, dropping the git-checkout installer in favor of uv tool install anvil-state. v0.1.2 made the Claude Agent SDK the default LLM provider, so Anvil rides a flat-rate Claude subscription instead of a per-token API key. That choice matters because Anvil is capacity-bound, not per-token-cost bound: the constraint is how many agents you can run at once, not the marginal price of a token. v0.3.0 added multi-PRD revisable partitioned state (schema v7, then v8 for a per-PRD revision counter). It is still labeled beta, pre-1.0. The core loop works today. Surfaces may change before 1.0.

What I Learned

The ledger is what makes an unattended run auditable. I produced v0.3.0 in a single 23.7-hour overnight run, under one instruction: keep going until done, report in the morning. About two dozen human messages drove 1,537 assistant turns, 32 tasks, and 21 merged PRs while the model churned roughly 28 million generated tokens. The only reason that stayed coherent is that Anvil kept score the whole time. Every claim was a row, every completion carried evidence, every mutation was replayable. Ground-truthing evidence over claims is exactly the discipline I had been preaching for agents, applied to my own build. The ledger kept the run honest while I slept.

Ground-truthing the merge boundary is the single habit that saved me. PR #93 merged “clean” by GitHub’s textual check, then broke main. It and another PR both edited the same function, serialize_state, on independent bases. The textual merge was clean, but a test stub the other PR added did not implement methods that #93’s new code now called. It needed a hotfix, PR #97. The thing I want to be honest about: we were lucky the conflict was loud, not that we caught it. The break surfaced as a stub missing a method, a noisy failure. Had the same edits produced subtly-wrong-but-still-passing output, the suite would have gone green and shipped the bug. CI tested #93 against its stale base. No textual conflict check can see a semantic conflict. The only thing that caught it was re-running the full test suite against the actual merged main, on every single merge, and refusing to trust the green checkmark. That habit is now non-negotiable.

The actually-hard part was lease tuning, not the lock. The single-winner claim was the design I was proud of, but the operational pain came from the other end of the lease. During long workflows, over fifteen minutes, claim leases expired mid-run and silently reverted tasks to “ready.” Work that was genuinely in progress looked abandoned. The fix was a longer lease, but the lesson is sharper: a lease is a bet on how long work takes, and getting that bet wrong fails quietly, which is the worst way to fail. There is still an open red-team gap here, that renewing a lease should require proof of progress rather than a bare heartbeat. I have it filed. I have not closed it.

Delegation is what kept a 23-hour run from losing the plot. About 90 percent of those 28 million generated tokens ran inside background workflows. The orchestrator stayed small, mostly git, CI, and full-suite verification. That is not a performance footnote. It is why the run stayed coherent: the thing holding the thread never had to hold the whole context. A reusable “implement, verify, adversarially review, fix one task” loop ran eighteen times. Holding the riskiest phase back for a dedicated adversarial review found two real bugs the per-task reviews missed, and the release review caught a CHANGELOG that never mentioned its own headline feature.

I will not pretend skill caught everything. Some of what held was luck wearing the costume of process. The #93 break was loud, not silent. API overload errors killed review subagents mid-run, but never struck a merge or a release, the irreversible steps. A blunt version-bump sed from 0.1.2 to 0.3.0 happened to hit exactly one occurrence per file, and an unrelated dependency that also contained the string 0.1.2 dodged it by pure coincidence. Pretending that was all engineering would be dishonest. The honest version is that durable, evidence-gated state turned luck into something I could check. It did not remove the luck. It removed the part where I would never have known.

That is the principle I keep coming back to. Orchestration is sand: free, churning, and not even portable between runtimes. The durable thing worth owning is the record of what happened and the proof that it did. Build the anvil, not the conductor. The conductor changes with every season. The anvil is where the work gets verified.

Co-authored with AI, based on the author's working sessions, dictations, and notes.

Explore the source

fakoli/anvil

Star it, fork it, or open an issue — contributions and feedback welcome.

Related Projects