Skip to content

Design decisions

The architecture decision records: one page per decision that shaped OpenSwitchboard, with the context it was made in, what was decided, what that costs, the alternatives that were rejected, and the named pattern the decision instantiates so a newcomer can map the code to a concept they already know in one lookup.

A record is written once and never edited. When a decision stops holding, a new record supersedes it and the old one keeps its reasoning with status: superseded and a pointer forward. That is checked, not hoped for: npm run decisions:check (part of verify) fails the build when a record lacks a status, a superseded record names nothing, or an accepted record's body differs from the copy on main. The table below is generated from the records' own frontmatter by npm run docs:gen.

Statuses: proposed (written, not yet agreed), accepted (agreed, being built), implemented (in the code the record describes), superseded (replaced by the record it names).

#DecisionPatternStatusDate
0001Every boundary is an interface with at least two implementationsPorts & Adaptersimplemented2026-09-08
0002The dispatcher is the only place an agent run startsRegistry with orchestration outside itimplemented2026-09-08
0003Slack is reached over Socket Mode, outbound only, and the gaps it leaves are recovered on reconnectDurable record outside the processimplemented2026-09-08
0004Every identity is a platform-namespaced idNamespaced principal identifiersimplemented2026-09-08
0005Model and effort resolve through the same configuration layersLayered configuration resolutionimplemented2026-09-08
0006A run has two lives, a live registry and then a history record, behind one read serviceTwo stores, one facadeimplemented2026-09-08
0007Authorization is one policy table over a closed condition vocabulary, asked once per requestRules tableimplemented2026-09-08
0008A command is defined once and every surface is derived from itRegistryimplemented2026-09-08
0009Residents hold their own GitHub credential; the bot never holds a repo-write tokenTrust boundary per planeimplemented2026-09-08
0010Every model output is a typed contract, normalized once at the answer boundaryStrategy per output typeimplemented2026-09-08
0011One live run per thread; a follow-up steers the run or is refused, never queued as a second runAdmission controlimplemented2026-09-08
0012Slack itself is the record of what was handled; every reconnect replays what it never sawExternal system as durable recordimplemented2026-09-08
0013A live run page is opened by an unguessable per-run token; finished runs are read by the policy tableCapability-based securityimplemented2026-09-08
0014The dashboard runs under a Content Security Policy that executes no inline scriptDefense in depthimplemented2026-09-08
0015Releases deploy from CI in one fixed Worker order, and a deploy is done only when the new code is liveDerived, not declaredimplemented2026-09-08
0016One long-lived bot process plus Durable Objects for what must survive it, not a serverless runtimeState outside the processimplemented2026-09-08
0017Memory is off by default, byte-identical to absent when off, advisory when on, and gated on the way inNull Objectimplemented2026-09-08
0018Capabilities are computed once at startup and every off-state is a Null ObjectFeature toggles resolved once + Null Objectimplemented2026-09-08
0019A run outlives the container through a leased, fenced ledger with a write-ahead step recordLease with a fencing tokenimplemented2026-09-08
0020A span is the one measurement primitive; every duration a user sees falls out of itExecute Around Methodimplemented2026-09-08
0021Records are immutable and specs are checked; documentation drift is prevented by CI, not disciplineTwo kinds of documentimplemented2026-09-08
0022The public tree carries no imprint of the company that grew it, and a ratchet holds that lineRatchetaccepted2026-09-08
0023Cloudflare is the one supported production target; docker compose is the local loopOne way to do itaccepted2026-09-09
0024The dispatcher is a pipeline of stages, split one file move at a timePipeline (Pipes and Filters) with Interface Segregation, by Tidy Firstsuperseded → 0025-dispatch-pipeline-as-built.md2026-09-09
0025The dispatch pipeline as built, and the 800-line cap that keeps it that shapePipeline (Pipes and Filters) with Interface Segregation — the as-built recordaccepted2026-09-09
0026A run is a capability profile over three axes, named agents become presets, and a routing stage picks the profileStrategy as data + a routing filter in the pipeline; the policy table keeps one question, the config layers gain an intersecting setting, the authorize stage gains a gateaccepted2026-09-09
0027Published images are copied into each installation's Cloudflare registry once per version, never pulled at runtimeArtifact promotionaccepted2026-09-09
0028What a container image installs is a manifest per image class, rendered into the committed Dockerfile and extended by an operator overlay that deploy images buildsGenerated artifact (gen + check) over a declarative manifest; artifact promotion for the operator overlayproposed2026-09-10
0029Durable Objects are the store and never the scheduler, Cloudflare Workflows schedules the multi-step lifecycles, and the agent loop stays in a containerDurable execution for the lifecycles around the loop, never for the loop; one order for four converging tracksaccepted2026-09-10
0030deploy all copies the images it needs into the account registry itself, over HTTPS, and the operator's whole deploy is one commandArtifact promotion, folded into the one deploy commandaccepted2026-09-10

The dated implementation plans under docs/plans/ are records of the same kind — proposals that were reviewed, built, and then frozen with their final status — and are held to the same check. They are working documents for the repository and are not published on this site.