writ / blog.writ.build
agents July 2026 · 8 min read

You already wrote this loop. Stop writing the rest.

Every agent you've shipped is a loop wrapped in policy code: retries, allowlists, budget caps, approval gates. Writ keeps the loop and turns the policy into a contract.

what you write today
model
tools
+ retries + caps + guards + approvals, in code
what runs under Writ
model
registrar
checks the writ
tools
edit src/…
edit tests/…
grant ✓ bounds ✓ invariant ✗
every action is checked before it runs
fig. 1 — the loop stays. It moves into a runtime that enforces your contract.

The loop you know

A standard agent works like this: your code sends messages and tool schemas to the model. The model returns a tool call. Your code runs the tool, appends the result, and repeats until the model stops or a limit trips.

You've written this loop before. You've also written everything around it, which is the part that actually takes the time: retry policies, iteration caps, cost meters, tool allowlists, "never touch this path" guards, approval interrupts, verification steps. Frameworks like LangGraph give that logic a shape, with nodes, edges, and routing functions. You still author all of it, and every model release makes more of it redundant, because the model already knows how to sequence its own work.

Strip the sequencing away and what remains isn't a workflow. It's a goal, a set of permissions, a budget, a list of things that must never happen, and a definition of proof. Those are the terms of a contract, not the nodes of a graph.

One file instead of a hundred lines

Take the classic task: fix a failing test. In a graph orchestrator that runs well over a hundred lines: a state schema, a handful of nodes, two routing functions, and the wiring between them. In Writ you write one file, a writ, and a runtime called the registrar runs the loop for you:

writ: bug-fixer
mandate: "Failing test {failing_test} passes without breaking others."
grants:
  - read:    { paths: ["**/*"] }
  - shell:   { commands: ["pytest *"] }
  - edit:    { paths: ["src/**", "tests/**"] }
  - open_pr: { requires: assent }
bounds:
  iterations: 40
  cost: $2.50
invariants:
  - { layer: letter, untouched: "{failing_test_file}" }
satisfaction:
  - { layer: letter,   run: "pytest -x -q", exit: 0 }
  - { layer: judgment, clause: "Fixes the cause, not the symptom." }
remedies:
  on_bound_breach: { do: halt, inform: issuer }
← the goal, as a condition — not a procedure
← your tool allowlist, now data
← human approval, one line — was interrupt_before
← your retry policy — was two routing functions
← blocks the "delete the test" cheat
← your verification node
← what happens on breach

Nothing was deleted here; the logic just moved. The reproduce-investigate-patch sequence isn't in the file because the model plans it. The retry edges turned into iterations: 40 with a remedy attached. The vague "make a minimal fix" can turn into a bound a machine checks. You write policy as policy instead of burying it in code paths.

Who checks what

Every clause names its check layer, so you always know how far to trust it:

letter
A machine check — path match, exit code, arithmetic. Runs on every action. Trust it fully.
judgment
A separate judge model reads the clause and the actions. Trust it as you trust a model.
assent
A human decision. The run stops until a person approves.

The judge is never the agent's own model, so the agent never grades its own work. Notice the authoring move in the example above: the judgment-layer worry "don't cheat the test" became the letter-layer invariant untouched: {failing_test_file}. Whenever a fuzzy worry has a machine-checkable form, use it.

Delegation: agents that hire agents

Mid-task, the bug-fixer can spin up an investigator by issuing a sub-writ. One rule governs all of it: a writ can only issue writs weaker than itself. The child's grants must be a subset of the parent's. Its budget is subtracted from the parent's remaining budget and refunded if unspent. It inherits every invariant. Delegation can narrow authority; it can never create it.

bug-fixer$2.50
grants: read · shell · edit · open_pr
issues ↓  $0.60 subtracted from parent
investigate-root-cause$0.60
grants: read · shell  ✗ no edit, no open_pr
fig. 2 — the chain of authority: grants attenuate, budgets are conserved, invariants inherit.

If this sounds familiar, it is capability attenuation from object-capability security, a discipline built on capability systems studied formally since the 1960s. Writ didn't invent this security model. It adopted one that already has proofs.

The record: your trace, upgraded

The registrar writes every model turn, tool call, check, and violation to a record. You read it like a trace, except each line also shows which clauses applied and what they decided. When the registrar blocks an action, it tells the agent which clause failed and why, so the agent can plan around the constraint rather than repeat the failure.

#actionchecks
07shell  pytest test_sync.py -x -q✓ grant ✓ bounds 33/40
08edit   src/sync/retry.py✓ grant ✓ invariants
09edit   tests/test_sync.py✗ denied — invariant: untouched
10shell  pytest -x -q  → exit 0✓ satisfaction (letter)
11judge  "fixes cause, not symptom"✓ satisfaction (judgment)
12open_pr  ⏸ awaiting assentassent: issuer

Why this bet, why now

The graph orchestrators were built on a bet that held in 2023: models can't sequence their own work, so humans must encode the sequence. The best models are now the strongest planners in most systems that contain them. Hand-authored control flow loses value with every release, while governance gains it. A more capable agent needs fewer instructions and a stronger contract.

This shift has happened before. Kubernetes replaced deploy scripts with declared state and a controller. Terraform replaced runbooks with declared resources and a plan/apply engine. In both, the steps didn't vanish; they moved into a runtime that enforces a declaration. Writ is the same move for agents.

Writ is not for everything. If you can write the whole procedure down before it starts, like invoice approval or any process with enumerable paths, use a workflow tool. There the intelligence lives in the graph, and that's fine. A simple test: if you'd hand a person a procedure, draw a workflow; if you'd hand them a goal and trust their judgment within limits, issue a writ.

Try it without changing your agent

The registrar can run as an MCP proxy. Point your existing agent's tool configuration at it, and your grants, bounds, and letter-layer invariants apply at the tool boundary with no code changes. Or start a new agent with three fields:

writ: my-agent
mandate: "State the goal here."
grants: [ read, shell: { commands: ["pytest *"] } ]

Defaults cover the rest. Run it, read the record, and add a clause the first time the record shows a behavior you want to stop. Writs grow from what you observe, not from upfront design.

Read the full design paper
The argument in full, including the one open problem we haven't solved.
read the design paper →