Skip to content

Commit 9004ac1

Browse files
OpsKernclaude
andcommitted
docs(writ): production README — Article 12 posture, quickstart, compliance table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5d367b4 commit 9004ac1

1 file changed

Lines changed: 81 additions & 62 deletions

File tree

README.md

Lines changed: 81 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# writ
2-
3-
**Codification gate and Merkle audit chain for Go agents.**
1+
# writ — cryptographic audit trail for AI calls
42

53
```
64
go get github.com/opskernel-io/writ
@@ -10,55 +8,34 @@ go get github.com/opskernel-io/writ
108

119
## The problem
1210

13-
Autonomous coding agents run with whatever permissions you gave them at setup time. There is no runtime gate that asks "is this call within policy?" before the LLM acts, and standard observability logs can be edited after the fact.
11+
EU AI Act Article 12 requires automatic, tamper-evident logging of AI system decisions over the system's lifetime. Enforcement begins August 2, 2026.
12+
13+
Most teams building on LLM APIs have neither: standard observability logs can be edited after the fact, and database rows have no chain-integrity check.
1414

1515
---
1616

1717
## What writ does
1818

19-
- **Blocks unauthorized agent calls before execution** — the codification gate intercepts every outbound LLM API call at the process level and evaluates it against your OPA policy before it leaves
20-
- **Logs every agent decision in a tamper-evident chain** — SHA-256-linked Merkle chain; altering any entry breaks verification from that point forward
21-
- **Routes tasks to the cheapest model tier that can handle them** — Ollama (free/local) → Claude Sonnet → Claude Opus, enforced by policy
22-
- **Reloads policy without restarting** — OPA hot-reload via file watch; policy changes take effect on the next call
19+
- **Drop-in Go SDK**`writ.New(cfg)` wraps your `*anthropic.Client`; one line at construction time, zero behaviour change in existing agent code
20+
- **Merkle audit chain** — every LLM call is SHA-256-linked to the previous entry; post-hoc modification is detectable by anyone who runs `writ verify`
21+
- **Optional OPA policy gate** — Rego rules run before each call; unauthorized calls never reach the API, violations are logged with chain entries
2322

2423
---
2524

26-
## How it's different
27-
28-
| | writ | Asqav | Snyk Agent Guard | Microsoft AGT |
29-
|---|---|---|---|---|
30-
| Pre-execution gate | **** ||||
31-
| Tamper-evident audit | Merkle chain | Hash chain + RFC 3161 | Mutable session logs ||
32-
| OPA policy enforcement | **** || private preview ||
33-
| Tiered dispatch | **** ||||
34-
| Language | Go | Python | N/A | TypeScript |
35-
| License | Apache 2.0 + commercial | MIT | Commercial | MIT |
25+
## Article 12 compliance status
3626

37-
**The key distinction from Asqav:** Asqav logs what happened after execution. writ controls what is allowed to happen before execution. These are different architectural positions: authorize-then-enforce vs. observe-and-record. In a regulated environment you need both; writ's gate is the piece no other tool ships.
27+
| Requirement | Status |
28+
|-------------|--------|
29+
| Logging of AI system outputs | ✅ MET |
30+
| Tamper-evident chain integrity | ✅ MET |
31+
| Third-party verification path | ✅ MET |
32+
| eIDAS-qualified timestamp | ⏳ Commercial tier |
33+
| 6-month minimum retention | ⏳ BYO (v0 is pre-commercial) |
3834

39-
**The key distinction from Microsoft AGT:** The Agent Governance Toolkit has OPA enforcement and Ed25519 signing. It has no tamper-evident audit log. writ's Merkle chain fills that specific gap.
40-
41-
---
42-
43-
## EU AI Act Article 12
44-
45-
EU AI Act Article 12 requires automatic, technical, tamper-evident logging over the lifetime of high-risk AI systems. Enforcement begins August 2, 2026.
46-
47-
**Three requirements met in full:**
48-
- Automatic logging — the gate emits chain entries without human action per call
49-
- Technical logging — machine-generated, structured, SHA-256-linked
50-
- Traceability — every gate decision links to its chain entry by AuditID
51-
52-
**Three requirements partially met (remediation paths defined, days not weeks):**
53-
- Tamper-evident — SHA-256 chain detects alteration; filesystem-level write protection (`chattr +a` + `writ.ChainProtected()`) closes the gap
54-
- Lifetime coverage — chain is append-only within a process run; cross-restart hash linking closes the gap
55-
- Granularity — input hashes logged by default; `StoreFullInputs: true` opt-in logs the full input data
56-
57-
**Two named gaps:**
58-
- **RFC 3161 timestamping** — chain timestamps are machine-generated but not externally signed. Integration roadmap: `github.com/digitorus/timestamp`. Commercial tier: hosted eIDAS-compliant timestamping.
59-
- **6-month retention management** — the open-source core writes to local storage with no managed expiry. Commercial-tier feature.
60-
61-
writ-core is suitable for teams building toward Article 12 compliance. The three partial requirements close with days of engineering. The commercial tier closes the two named gaps.
35+
Three partial requirements have defined close paths:
36+
- **Filesystem-level tamper protection**`writ.ChainProtected()` sets `chattr +a` on Linux; prevents in-place overwrites without breaking the chain
37+
- **Cross-restart chain continuity** — chain is append-only within a process run; session boundaries are flagged explicitly in `VerifyFull()` for auditor review
38+
- **Full input capture** — input hashes logged by default; `StoreFullInputs: true` writes full request/response JSON to a sidecar JSONL for replay
6239

6340
---
6441

@@ -75,7 +52,7 @@ import (
7552
client, err := writ.New(writ.Config{
7653
PolicyPath: "/etc/writ/policy.rego", // OPA Rego bundle directory
7754
AuditPath: "/var/writ/audit.chain", // Merkle chain JSONL file
78-
CallerID: "myagent-v1", // optional stable agent identifier
55+
CallerID: "myagent-v1", // stable agent identifier
7956
})
8057
if err != nil {
8158
log.Fatal(err)
@@ -84,11 +61,11 @@ if err != nil {
8461
// LLM calls go through the gate automatically.
8562
// Allowed calls are audited and dispatched; denied calls return *writ.DenialError.
8663
msg, err := client.Messages.New(ctx, anthropic.MessageNewParams{
87-
Model: anthropic.ModelClaude_Sonnet_4_6,
64+
Model: anthropic.ModelClaudeSonnet4_6,
8865
MaxTokens: 1024,
89-
Messages: anthropic.F([]anthropic.MessageParam{
90-
anthropic.NewUserMessage(anthropic.NewTextBlock("refactor the auth module")),
91-
}),
66+
Messages: []anthropic.MessageParam{
67+
anthropic.NewUserMessage(anthropic.NewTextBlock("summarise the Q3 results")),
68+
},
9269
})
9370
if err != nil {
9471
var denial *writ.DenialError
@@ -103,34 +80,58 @@ if err != nil { ... }
10380
defer stream.Close() // writes the streaming-complete audit entry
10481

10582
// For tool use and other non-LLM events:
83+
h := sha256.Sum256([]byte(filePath))
10684
client.Audit(writ.AuditEvent{
10785
ActionType: "write_file",
10886
Actor: writ.ActorAgent,
109-
InputHash: writ.HashInput([]byte(filePath)),
87+
InputHash: fmt.Sprintf("%x", h),
11088
Result: "success",
11189
})
11290

113-
// Verify the chain (run in CI or on-demand audits):
91+
// Verify chain integrity (run in CI or on-demand audits):
11492
err = writ.Verify("/var/writ/audit.chain")
11593
```
11694

11795
**Helm (Kubernetes):** writ-core is a library dependency of your agent process, not a sidecar. Add to your agent container's Go dependencies. See `docs/helm/` for a reference values.yaml with policy ConfigMap mounting.
11896

97+
A runnable end-to-end demo is at [`examples/demo/`](examples/demo/).
98+
11999
---
120100

121-
## Supported agents
101+
## What's in v0
122102

123-
v0 targets **Go agents that call `anthropic.Client` directly** — any agent built with `github.com/anthropics/anthropic-sdk-go`. Drop `writ.New()` in at the client construction site.
103+
- `writ.New()` SDK wrapping `*anthropic.Client`
104+
- SHA-256 Merkle chain — append-only JSONL, filesystem protection (`chattr +a`) on Linux
105+
- OPA codification gate — Rego policy evaluation before each call; hot-reload via file watch without restart
106+
- `writ verify` CLI — standalone chain verification for CI pipelines
107+
- `StoreFullInputs` mode — sidecar JSONL with full request/response JSON for audit replay
124108

125-
| Agent | v0 support | Notes |
126-
|---|---|---|
127-
| Custom Go agent (anthropic-sdk-go) | **✓ Supported** | `writ.New()` wraps at construction time — one line |
128-
| Cursor | Roadmap | Cursor proxies all LLM calls through its own servers; requires HTTP middleware path (ADR #20) |
129-
| Devin | Not planned | Cognition cloud-only; no user-controlled process to intercept |
109+
Not in v0: managed timestamp authority, 6-month retention service, multi-agent cross-chain tracing.
130110

131-
**Cursor:** writ cannot intercept at the SDK level because Cursor routes every LLM call through Cursor's backend regardless of your API key. The roadmap item (ADR #20) is a local HTTP proxy that Cursor can be configured to route through via its OpenAI-compatible base-URL override. Not yet implemented.
111+
---
132112

133-
**Devin:** Devin is a fully cloud-hosted system. There is no user-controlled process making LLM calls — Cognition's infrastructure handles all inference. writ integration is not viable without a Cognition self-hosted tier.
113+
## Known limitations
114+
115+
- **Process-level boundary only** — writ intercepts at the SDK call site in your process. A transitive dependency that constructs its own `anthropic.Client` bypasses the gate. See the threat model ADR for the scope and mitigation options.
116+
- **Single-process append assumption** — no concurrent writers to the same chain file. Multi-process setups require separate chain files or the pluggable `AuditStore` interface.
117+
- **No timestamp authority in OSS tier** — chain timestamps are machine-generated, not externally signed. eIDAS-compliant timestamping is a commercial-tier feature.
118+
119+
---
120+
121+
## How it's different
122+
123+
| | writ | Asqav | Snyk Agent Guard | Microsoft AGT |
124+
|---|---|---|---|---|
125+
| Pre-execution gate | **** ||||
126+
| Tamper-evident audit | Merkle chain | Hash chain + RFC 3161 | Mutable session logs ||
127+
| OPA policy enforcement | **** || private preview ||
128+
| Tiered dispatch | **** ||||
129+
| Language | Go | Python | N/A | TypeScript |
130+
| License | Apache 2.0 + commercial | MIT | Commercial | MIT |
131+
132+
**From Asqav:** Asqav logs what happened after execution. writ controls what is allowed to happen before execution. These are different architectural positions: authorize-then-enforce vs. observe-and-record. In a regulated environment you need both; writ's gate is the piece no other tool ships.
133+
134+
**From Microsoft AGT:** The Agent Governance Toolkit has OPA enforcement and Ed25519 signing. It has no tamper-evident audit log. writ's Merkle chain fills that specific gap.
134135

135136
---
136137

@@ -162,6 +163,22 @@ The gate sits between your agent's code and the LLM API call — not between you
162163

163164
---
164165

166+
## Supported agents
167+
168+
v0 targets **Go agents that call `anthropic.Client` directly** — any agent built with `github.com/anthropics/anthropic-sdk-go`. Drop `writ.New()` in at the client construction site.
169+
170+
| Agent | v0 support | Notes |
171+
|---|---|---|
172+
| Custom Go agent (anthropic-sdk-go) | **✓ Supported** | `writ.New()` wraps at construction time — one line |
173+
| Cursor | Roadmap | Cursor proxies all LLM calls through its own servers; requires HTTP middleware path (ADR #20) |
174+
| Devin | Not planned | Cognition cloud-only; no user-controlled process to intercept |
175+
176+
**Cursor:** writ cannot intercept at the SDK level because Cursor routes every LLM call through Cursor's backend regardless of your API key. The roadmap item (ADR #20) is a local HTTP proxy that Cursor can be configured to route through via its OpenAI-compatible base-URL override. Not yet implemented.
177+
178+
**Devin:** Devin is a fully cloud-hosted system. There is no user-controlled process making LLM calls — Cognition's infrastructure handles all inference. writ integration is not viable without a Cognition self-hosted tier.
179+
180+
---
181+
165182
## License
166183

167184
The writ-core SDK is Apache 2.0. Use it, fork it, embed it, build commercial products on top of it.
@@ -184,13 +201,15 @@ The writ-core SDK is Apache 2.0. Use it, fork it, embed it, build commercial pro
184201

185202
---
186203

204+
## Early access
205+
206+
writ is pre-launch. To request early access or discuss Article 12 compliance requirements: [writ.opskernel.io](https://writ.opskernel.io) or reach out directly via [opskern.io](https://opskern.io).
207+
208+
---
209+
187210
## Related
188211

189212
- **[hookd](https://opskernel.io)** — governs external AI traffic: MCP server verification, replay detection, per-source audit. writ governs what agents do internally; hookd governs what external servers agents call.
190213
- **[opskern-policy](https://github.com/OpsKern/opskern-policy)** — shared OPA Rego policy templates for hookd + writ.
191214
- **[Asqav](https://github.com/jagmarques/asqav-sdk)** — MIT Python SDK for agent audit trails with RFC 3161 timestamps. Complementary to writ (observe-and-record layer); writ adds the pre-execution gate that Asqav does not have.
192215
- **[EU AI Act Article 12 text](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32024R1689)** — the specific requirement writ's architecture addresses.
193-
194-
---
195-
196-
*writ is built by [OpsKern](https://opskern.io). Early access: [writ.opskernel.io](https://writ.opskernel.io)*

0 commit comments

Comments
 (0)