wrt / design paper
design paper draft v0.7 · August 2026

How Writ connects policy to existing agents

Writ uses each agent's native user hook for the first connection. It converts every visible tool event into one action form. The same policy and record code then applies across agents and MCP.

Keep the user's agent. Add one decision point. State exactly what that point can see.

Abstract

Adoption fails when a control tool asks people to replace the agent, editor, or work pattern they already use. Writ therefore begins with writ connect claude, codex, cursor, or opencode. The user keeps the existing agent.

The native hook records visible tool calls by default. The user can then add a small tool list. A writ file can add exact authority to one selected session. The managed model loop remains available when the task needs evidence that the native hook cannot supply.

basic
Record the tool events that the agent reports. Block nothing.
normal
Allow selected tools. Stop another reported call for approval.
advanced
Apply enforceable writ terms to one existing agent session.

1. The adoption constraint

A separate agent interface adds another place to configure models, tools, context, and user habits. That cost arrives before the user sees a result.

Writ moves the first result to one command. The command installs a native user hook and writes private state under ~/.writ. It does not change MCP configuration. It does not require a writ file or model key.

userexisting agent → native hook → Writ
                                             ├── decision
                                             └── record

2. Native integrations

Each integration package contains two contracts: the hook file format and the event codec. Shared code installs Writ-owned hook commands, preserves unrelated hooks, writes a digest, reports status, and removes only the commands that Writ installed.

writ connect claude
writ connect codex
writ connect cursor
writ connect opencode

Connection is idempotent. A dry run writes nothing. A changed Writ-owned hook is not overwritten. Disconnection preserves other hook entries and existing records.

The default connection belongs to the user and applies in every project. --project selects a project connection. When both scopes exist, every tool call must pass both policies. A project policy can narrow the user policy. It cannot widen it.

writ connect codex
writ connect codex --project
writ connect status codex --project

Configuration, connections, reusable writs, organization policy state, and project records stay under ~/.writ. Writ does not create a project .writ directory. WRIT_HOME selects another state root.

3. One action contract

Agent events use different tool names and envelopes. Writ converts each event into one action request. The request contains the agent, session, source, normalized tool name, provider tool name, visible arguments, and coverage.

The source can be a built-in tool, local MCP, remote MCP, hosted tool, or an unknown boundary. The coverage says who runs the action and whether Writ observed the request, result, or effect.

provider name
Bash, exec_command, and another agent-specific identifier remain in the record.
normalized name
Those names can map to shell. A policy can use the common action class.

A named allow rule matches either identity. An organization policy can therefore allow a common action class and still restrict one provider name when needed.

4. Basic and normal decisions

The default profile is observe. Writ records the request and reported result. It returns allow for every valid event.

writ connect codex
writ review

The normal profile is enforce. --read-only allows normalized read and search actions. --allow accepts exact normalized or provider names. It does not accept pattern syntax.

writ connect codex --read-only
writ connect codex --allow read --allow mcp__github__get_issue

A denied call creates an argument-bound approval request. An approval can allow one matching retry or matching calls in the current run. An organization allow-list denial is final and cannot be changed by user or project approval.

5. Coverage is an output

The connection command and status command report four boundaries. They do not infer full control from a successful install.

Claude
Built-in, local MCP, remote MCP, WebSearch, and WebFetch events are covered by the hook contract.
Codex
Most local tools and MCP calls are visible. Hosted tools such as WebSearch are not visible.
Cursor
Built-in and MCP tools are covered in trusted workspaces. Early cloud turns can run before hooks load.
OpenCode
MCP and permissioned built-in tools are covered. Other coverage depends on permission events.

Tool results are reports from the agent. Connected Writ does not independently observe filesystem, network, or service effects.

6. The MCP boundary remains available

Writ still serves an MCP boundary for clients that need it. A local proxy starts a command over standard input and output. A remote proxy connects to a Streamable HTTP endpoint over HTTPS.

writ proxy policy.yaml -- local-server --flag
writ proxy policy.yaml --remote https://mcp.example.com/mcp

The bridge relays the complete MCP message stream, including cancellation and server requests. It checks tools/call before forwarding. Remote credentials come from environment-variable references. Redirects cannot change origin.

7. Advanced use keeps the existing agent

A writ file is not required for connection. In advanced use, the user selects one observed session and applies every clause that its capability plan can enforce. Other sessions keep their normal connection policy.

authority and budget
Grants limit actions. Bounds limit iterations, cost, time, files, and changed lines.
state and completion
Invariants protect workspace state. Satisfaction clauses state the checks that must pass.
writ check bug-fixer.yaml --explain
writ plan bug-fixer.yaml --agent claude
writ sessions --agent claude
writ start bug-fixer.yaml --agent claude --session <session-id>
writ finish --agent claude
writ review bug-fixer

writ start does not launch or replace the agent. It refuses the activation when the hook cannot enforce an authored clause. writ finish records human satisfaction. writ finish --cancel does not attest satisfaction.

Use writ issue when the task needs the managed model loop. It can charge model usage, check worktree changes, apply remedies, and run satisfaction checks. An omitted bound is authored-unbounded. Managed issue adds operational defaults of 25 iterations, $1.00, and 15 minutes.

Do not put secrets in a writ document or placeholder value. Attached activation state stores the bound document.

8. Organization policy is an outer layer

An organization publishes a JSON bundle with an Ed25519 signature, sequence, issue time, expiry time, allowed tool patterns, approval patterns, redaction pointers, and bound ceilings. Writ stores the source URL and a separately supplied public trust key in the project's state under ~/.writ.

organization policy ─┐
user policy ─────────┼── all allow → action can run
project policy ──────┘

The resolver rejects a bad signature, expiry, rollback to a lower sequence, or different content at the same sequence. A verified cache can cover a source outage only until signed expiry. The effective policy hash and source state enter the run record.

9. Records remove secrets before they become evidence

Writ redacts before storage. Built-in rules replace common secret fields, bearer tokens, secret assignments, and private-key blocks. Organization JSON Pointers add exact fields. The replacement enters the record as [REDACTED].

A normal native tool-call entry stores normalized arguments and a digest of exact provider arguments. Exact provider arguments stay transient. They can enter the record only in an explicit approval request, after redaction.

Each entry contains the previous entry hash. Each append creates an Ed25519-signed checkpoint for the current root. The checkpoint also binds the run and effective policy metadata.

entry 1 hash → entry 2 hash → entry 3 hash
                             └── signed root

The entry tables and checkpoint table are append-only. Redaction reduces disclosure risk. It does not replace review before a record is published.

10. Verification is separate from the database

writ record export produces a portable JSON bundle. writ record verify checks every link, the complete root, the checkpoint signature, and the trusted key ID without opening the source database.

An independent witness receives only the run ID, sequence, root hash, record signer key ID, and a fresh nonce. It returns a receipt signed with a separate key. The nonce prevents an old receipt from satisfying a new request.

Record verification proves integrity and signer identity. It does not prove that the recorded tool result or external effect is true.

11. Failure and process boundaries

Enforcement denies a call when Writ cannot verify an active organization policy or cannot write the decision record. Observe mode can allow a hook failure only when no organization layer requires enforcement.

A local process under the same operating-system account can still replace binaries, change configuration, or reach resources outside Writ. Use an account, container, or host boundary when that threat is in scope.

12. Implementation status

Implemented: native hooks for Claude, Codex, Cursor, and OpenCode; normalized action policy; visible built-in and MCP checks; local and remote MCP proxying; argument-bound approvals; pre-storage redaction; append-only hash chains; signed checkpoints; portable verification; signed organization policy inheritance; valid offline policy cache; and independent witness receipts.

Known limits: agent hook coverage differs; connected results are agent reports; hosted tools are not visible in every agent; local processes need operating-system isolation against a same-account attacker; and record integrity is not proof of an external effect.

The product test is now direct: connect an existing agent, make one tool call, read one record, and add one limit without leaving the agent.

DESIGN QUESTION
Which reported tool boundary is still unclear?
Report the agent, native hook event, command, and observed result.