Nothing runs
without a writ.

Your agent asks. Your policy decides. The ledger remembers.

$cargo install --path crates/writ-cli && writ run -- claude
A writ-governed session: two calls allowed, one redacted, one held for a human and denied, one egress call refused with its rule and reason
A session under writ: every call decided before it runs, every decision recorded after.
How it works

Three steps, one decision point

Writ wraps agents; it never asks you to adopt a runtime. The same decision point and the same record apply in every interception mode.

01

INTERCEPT

MCP proxy, process wrap, SDK hook — the agent itself is unchanged.

02

DECIDE

One writ.yaml checked before every call: allow, deny, ask, redact. First match wins; unmatched calls fail closed.

03

RECORD

Every call — including denials — lands in a hash-chained ledger, with an OTel GenAI span alongside.

Policy

writ.yaml is the whole surface

Four verdicts, first match wins. The file lives in your repo, so the policy travels with the code and reviews like code. A denial carries the rule id, the human reason and the line that produced it — the agent can correct itself instead of retrying blind.

allow deny ask — a human gates it redact — masked before it re-enters the model
# writ.yaml
version: 1
default: ask                          # fail closed
rules:
  - id: block-destructive-shell
    when: tool == "bash" and command matches "rm -rf|mkfs|dd if="
    verdict: deny
    reason: "Destructive system command. Narrow the path and retry."

  - id: protect-production-db
    when: tool startswith "postgres" and query matches "(?i)(DROP|TRUNCATE)"
    verdict: ask
    irreversible: true                # excluded from automated replay

  - id: egress-allowlist
    when: tool == "http" and not url.host in hosts.allowed
    verdict: deny

  - id: mask-pii
    when: tool startswith "postgres"
    verdict: redact
    patterns: ["[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"]
Evidence

The part that survives the session

Logs are what an application chose to write. A ledger is evidence: every call, its verdict, the rule that decided it, who approved it, and the hash of the record before it. Edit one line and writ verify names the record where the chain broke.

writ verify reports a chain intact, then reports a broken chain at record 12 after a ledger line is edited
writ verify — tamper-evident by construction. Nothing is captured beyond metadata and hashes unless you turn content capture on, and there is no telemetry to opt out of.
Surface

Commands

writ run -- wrap an agent process under policy
writ proxy --mcp --server -- govern every call to an MCP server
writ log · writ show what did my agent actually do last night
writ verifyis this ledger still the one that was written
writ replay --candidate what would this policy have done to last week's run
writ policy testunit-test rules against recorded fixtures
writ doctorwhat is governed, and what is blind
writ reportone self-contained HTML file to hand to someone else
Limits

What writ does not do

Writ governs actions, not reasoning. The threat model is public: docs/THREAT_MODEL.md.

  • It does not stop prompt injection. It shrinks the blast radius: least privilege, egress allow-lists, a human gate on irreversible calls.
  • The ledger is tamper-evident, not tamper-proof. Editing a record breaks the chain and is caught; transparency-log anchoring, on the roadmap, is what closes the deletion gap.
  • MCP-proxy-only mode is partial coverage. The agent's own shell, file writes and direct HTTP go around it — writ doctor says this out loud.
  • It does not reverse side effects. A denied call never ran; an approved one is yours.