Skip to content

Architecture decision records

An ADR records a decision that was hard to make and would be expensive to reverse, along with the context that made it the right call and the consequences accepted in taking it.

The point is not to document what the code does — the rest of docs/ does that, and it stays current as the code changes. The point is to answer “why is it like this?” a year later, so a future change is made knowingly rather than by accident.

#DecisionStatus
0001Record architecture decisions in ADRsAccepted
0002A workspace with a dependency-free core crateAccepted
0003Policy is an ordered, short-circuiting chain carrying evidenceAccepted
0004Default-deny lives in config, not in codeAccepted
0005rustls, and a deliberately split HTTP stackAccepted
0006CEL for the rules layer, not a general scripting languageAccepted
0007Bodies stream by default; buffering must be declaredAccepted
0008TLS interception is mandatory, not a fallbackAccepted
0009Identity is derived from the connection, never assertedAccepted
0010Resolve once, check every address, connect to the checked oneAccepted
0011Secrets are injected at the boundary, not held by the agentAccepted; placeholder model superseded by 0027
0012The judge sees a reduced request as data, never as instructionPartially superseded by 0042
0013MCP denials are protocol errors, and tools/list is filteredAccepted
0014Network-namespace isolation without CAP_NET_ADMINAccepted
0015The cgroup naming convention is the identity registrationAccepted (amended by 0037)
0016Config splits by fixed directory convention, not include globsAccepted
0017The fallback profile is embedded, unnamed, and unreferenceableAccepted
0018Profiles do not inheritAccepted
0019Log detail, sink and format are three independent axesAccepted
0020Reload builds everything before swapping; a failure changes nothingAccepted
0021“Identity”, not “session”Accepted
0022Remove transparent (nftables REDIRECT) captureAccepted
0023Multi-port explicit listeners, to restore listener_port identityAccepted
0024netns isolation binds an explicit allowlist, not the whole host rootAccepted
0025Secret injection understands Basic auth, with no new configSuperseded by 0027
0026Blind credential injection, for a client that presents nothingAccepted
0027Secret injection is unconditional injection onlyAccepted
0028SigV4 injection buffers the body, capped, as a declared exceptionAccepted
0029The redaction set is learned at runtime, not sealed at startupAccepted
0030OAuth2 is a secret source, not an injection kindAccepted
0031A responder may answer a request instead of forwarding itAccepted
0032Marshal owns the PKCE verifier and terminates the authorization flowAccepted
0033The env file is an overlay, not the environmentAccepted
0034Bootstrap capture reads the token exchange, and trusts the sessionAccepted
0035Release versioning uses cocogitto, not release-plzAccepted
0036Bind groups are named and shared, the same way bundles areAccepted
0037Identity and profile are independent axes of the scope nameAccepted
0038A second source can read another swap’s ID tokenAccepted
0039OAuth2 can exchange a token before caching itAccepted
0040Header filtering is allow or deny, never both — and never touches wire framingAccepted
0041The LLM router rewrites destination after allow; intercept may connect to a mapped originAccepted

| 0042 | Native decisions may replace judge tool calls; native decision routing stays within its family | Accepted |

Copy template.md to NNNN-a-short-imperative-title.md, taking the next free number. Add a row to the index above.

Write one when: a choice constrains future work, closes off an obvious alternative, trades one desirable property for another, or is likely to look wrong to someone who wasn’t there.

Don’t write one for: anything the code or the rest of the docs already states plainly, a choice with an obvious default and no trade-off, or an implementation detail that could change next week without anyone noticing.

ADRs are immutable once accepted. Do not rewrite the reasoning of a decision to match what you now believe — that erases exactly the history the record exists to preserve.

When a decision changes:

  1. Write a new ADR describing the new decision, with a Supersedes: ADR-NNNN line.
  2. Edit the old one’s Status to Superseded by ADR-MMMM, linking to it. That status line is the only part of an accepted ADR that may change.
  3. Update the index table.

Correcting a typo or a broken link is fine. Changing the Context, Decision or Consequences of an accepted ADR is not.

If a decision is under discussion but not settled, write it with Status: Proposed and say what would decide it. If it is abandoned before implementation, mark it Rejected and keep it — knowing an option was considered and refused is worth as much as knowing one was taken.

See AGENTS.md for when a change needs an ADR alongside its code.