← stitchkitDocumentation

Design decisions

Records of why stitchkit is built the way it is — each one states the context, the alternatives weighed against it and the consequences. They are immutable: when a decision changes, a new record supersedes the old one and the old one stays.

Useful if you are weighing the library against alternatives, or wondering why something is deliberately missing.

  1. ADR 0001 — Build on Bun.serve(), no HTTP framework- Status: Accepted - Date: 2025-05
  2. ADR 0002 — A generic core: the framework carries no domain model- Status: Accepted - Date: 2025-05
  3. ADR 0003 — Two context types: RuntimeContext and HandlerContext- Status: Accepted - Date: 2025-05
  4. ADR 0004 — Four lifecycle hooks instead of a middleware chain- Status: Superseded by ADR 0072 - Date: 2025-05
  5. ADR 0005 — The typed client is inferred from the contract- Status: Accepted - Date: 2025-05 (core), 2026-05 (pilot fixes)
  6. ADR 0006 — Route groups and GET/DELETE query params- Status: Accepted - Date: 2026-05-13
  7. ADR 0007 — MCP and agent tools from one shared pipeline- Status: Accepted - Date: 2026-05-13 (pipeline, extend), 2026-05-20 (createMcpHandler)
  8. ADR 0008 — Thin wrappers over the stack you already use- Status: Accepted - Date: 2026-05-20
  9. ADR 0009 — A hand-rolled WebSocket transport- Status: Superseded by 0008 - Date: 2025-05 (decided), 2026-05-20 (reverted)
  10. ADR 0010 — Grow stitchkit into a fullstack framework- Status: Rejected - Date: 2026-05-13 (explored), 2026-05-20 (rejected)
  11. ADR 0011 — Bun-only, published as one small package- Status: Accepted - Date: 2026-05-20
  12. ADR 0012 — A built-in observability module- Status: Accepted - Date: 2026-05-20
  13. ADR 0013 — Runtime-agnostic core, Bun as first-class adapter- Status: Accepted — supersedes the Bun-only clause of ADR 0011 - Date: 2026-05-20
  14. ADR 0014 — The tool surface carries the same contract guarantees as HTTP- Status: Accepted — refines ADR 0007 - Date: 2026-05-20
  15. ADR 0015 — OAuth 2.1 resource-server toolkit for MCP- Status: Superseded by ADR 0068 - Date: 2026-05-29
  16. ADR 0016 — CLI as the fourth transport- Status: Accepted — extends ADR 0007, upholds ADR 0014 - Date: 2026-05-29
  17. ADR 0017 — Typed tool-path context via createToolkit- Status: Accepted — extends ADR 0003 - Date: 2026-05-29
  18. ADR 0018 — OpenAPI generated from the contract- Status: Accepted - Date: 2026-05-29
  19. ADR 0019 — Generic native MCP tools (wait / download / upload)- Status: Accepted — extends ADR 0007, upholds ADR 0002 - Date: 2026-05-29
  20. ADR 0020 — A raw WebSocket lane composed beside Socket.IO- Status: Accepted — upholds ADR 0008, Bun-only per ADR 0013, lifecycle extended by ADR 0074 - Date: 2026-06-05
  21. ADR 0021 — Endpoint meta passthrough (opaque per-endpoint metadata)- Status: Accepted — extends ADR 0002 - Date: 2026-06-05
  22. ADR 0022 — Stable (service, action) identity on MethodDef- Status: Accepted — extends ADR 0002 and ADR 0021 - Date: 2026-06-05
  23. ADR 0023 — Range-capable file serving (serveFile)- Status: Accepted — extends ADR 0013 - Date: 2026-06-05
  24. ADR 0024 — Scope-driven mounting (scopePrefixes)- Status: Accepted — extends ADR 0002 - Date: 2026-06-05
  25. ADR 0025 — Typed scoped client (consumed keys as args)- Status: Accepted — extends ADR 0005 - Date: 2026-06-05
  26. ADR 0026 — Published stitch error-code registry- Status: Accepted — extends ADR 0002 - Date: 2026-06-05
  27. ADR 0027 — Transport-neutral contract execution (bring-your-own transport)- Status: Accepted — upholds ADR 0008 and ADR 0020; keeps the core generic per ADR 0002 - Date: 2026-06-05
  28. ADR 0028 — Revert createContractDispatcher (no adopting consumer)- Status: Accepted — supersedes the createContractDispatcher portion of ADR 0027. The rest of ADR 0027 (idempotent, createRetainedTopics, the open TransportSource union) stays Accepted. - Date:
  29. ADR 0029 — Endpoint identity and domain dimensions on the audit event- Status: Accepted — extends ADR 0012 (the observability module), ADR 0022 (stable endpoint identity), ADR 0021 (opaque passthrough) and keeps the core generic per ADR 0002. - Date: 2026-06-18
  30. ADR 0030 — Audit verb, sanitised error details, and complete error-code logging- Status: Accepted — extends ADR 0029 (audit completeness), ADR 0026 (error model), ADR 0022; keeps the core generic per ADR 0002. - Date: 2026-06-18
  31. ADR 0031 — Deep discriminated-union flattening for tool schemas- Status: Accepted — completes the flattenUnionInput feature; refines ADR 0007 / ADR 0014. - Date: 2026-06-22
  32. ADR 0032 — Brand-based AppError identification (not instanceof)- Status: Accepted — fixes the error model of ADR 0026; consequence of the multi-chunk build (ADR 0011 / ADR 0013). - Date: 2026-06-22
  33. ADR 0033 — Sound flatten: collision widening, discriminator support, probe parity- Status: Accepted — completes/repairs ADR 0031. - Date: 2026-06-22
  34. ADR 0034 — The advertised tool schema carries each object's key policy- Status: Superseded by ADR 0050 — its executable-advertised-schema premise no longer holds. Originally accepted, superseding the "advertised-only" premise of ADR 0031 and ADR 0033 (specifical
  35. ADR 0035 — Tool names are normalised across the whole character class and asserted at mount- Status: Accepted — refines the tool pipeline of ADR 0007. - Date: 2026-08-05
  36. ADR 0036 — `meta` cascades from the contract; `expose` deliberately does not- Status: Accepted — extends ADR 0021; records a rejection that amends nothing in ADR 0016. - Date: 2026-08-05
  37. ADR 0037 — The output strip stays, and becomes visible on demand- Status: Accepted — extends ADR 0014. - Date: 2026-08-05
  38. ADR 0038 — Raw-response endpoints (the handler owns the Response)- Status: Accepted — carves a documented, HTTP-only exception out of ADR 0027; upholds ADR 0002 - Date: 2026-08-05
  39. ADR 0039 — Request logging reads the request context- Status: Accepted — connects the logger of ADR 0012 to the context that ADR introduced; upholds ADR 0013 and ADR 0029 - Date: 2026-08-06
  40. ADR 0040 — The log format is chosen, not guessed- Status: Accepted — repairs the delivery of ADR 0039; upholds ADR 0013 - Date: 2026-08-06
  41. ADR 0041 — The cause of a failed tool call is observable- Status: Accepted — closes an asymmetry between the HTTP and tool paths of ADR 0007 / ADR 0014; extends the raw hooks of ADR 0012 - Date: 2026-08-06
  42. ADR 0042 — The audit row may name the cause, the caller may not- Status: Accepted — completes ADR 0041 and makes ADR 0030's "complete error logging" true on the tool path - Date: 2026-08-06
  43. ADR 0043 — The framework records the failure; the project overrides it- Status: Accepted — applies ADR 0042's rule to the HTTP path; extends ADR 0012 - Date: 2026-08-06
  44. ADR 0044 — A collided field keeps its type- Status: Accepted — narrows the collision rule of ADR 0033 while keeping its invariant; extends ADR 0031 - Date: 2026-08-06
  45. ADR 0045 — A tool call runs in its own request context- Status: Accepted — scopes the AsyncLocalStorage context of ADR 0012; makes the dimensions contract of ADR 0029 hold under concurrency - Date: 2026-08-06
  46. ADR 0046 — Tool hooks take options objects- Status: Accepted — refines the observability surface of ADR 0007, ADR 0041 and ADR 0042 - Date: 2026-08-07
  47. ADR 0047 — One MCP schema validation profile- Status: Accepted — completes the schema-parity rule of ADR 0014 and the advertised-schema guarantees of ADR 0034 - Date: 2026-08-07
  48. ADR 0048 — Framework-owned native MCP registration- Status: Superseded by ADR 0057. It extends the transport parity of ADR 0014, the stable operation identity of ADR 0022, and per-call isolation from ADR 0045 - Date: 2026-08-07
  49. ADR 0049 — Stateless MCP HTTP is the default- Status: Accepted — changes the HTTP transport default; builds on immutable surface preparation from ADR 0047 - Date: 2026-08-07
  50. The tool presentation schema is not a parserAccepted. Supersedes the executable-advertised-schema premise of ADR 0034 and the Zod-rebuilding mechanism of ADRs 0031, 0033 and 0044. Their model-facing flattening guarantees remain in force.
  51. Signed HTTP webhooks retain raw JSON text on demandHMAC webhook providers sign the original request payload, including whitespace. The contract router read that text, parsed JSON, ran the input schema and then discarded the text before lifecycle hooks
  52. ADR 0052 — Typed JSON response metadata- Status: Accepted — a controlled HTTP-only extension of ADR 0027, distinct from the raw-response exception in ADR 0038 - Date: 2026-08-07
  53. ADR 0053 — HEAD is an explicit contract operation- Status: Accepted — extends the raw-response boundary in ADR 0038 and file semantics in ADR 0023 - Date: 2026-08-07
  54. ADR 0054 — In-process tool calls use the canonical runner- Status: Accepted — extends the shared execution path in ADR 0014 and the per-call isolation rule in ADR 0045 - Date: 2026-08-07
  55. ADR 0055 — Runtime tools share one neutral operation- Status: Accepted — extends the shared runner in ADR 0014, protected native registration in ADR 0048 and per-call isolation in ADR 0045 - Date: 2026-08-07
  56. ADR 0056 — Entity cache shapes are declared- Status: Accepted — extends the optional cache bridge without turning it into a domain cache framework - Date: 2026-08-07
  57. ADR 0057 — Finite prepared MCP surfaces- Status: Accepted — extends the neutral runtime operation of ADR 0055, supersedes the protected registrar shape of ADR 0048, and preserves per-call isolation from ADR 0045 - Date: 2026-08-0
  58. ADR 0058 — Zod-first domain error definitions- Status: Accepted — extends the domain-free error boundary of ADR 0002, the published framework-code registry of ADR 0026, and cross-chunk branding from ADR 0032 - Date: 2026-08-08
  59. ADR 0059 — Unified tool-surface introspection- Status: Accepted — extends the shared runner of ADR 0014, the name guarantees of ADR 0035, the presentation document of ADR 0050, and the pathless operations of ADR 0055 - Date: 2026-08-08
  60. The official starter composes Next.js and a separate Stitchkit backendAccepted.
  61. The official starter advances independently from framework HEADAccepted. Refines ADR 0060.
  62. ADR 0062 — Explicit tool exposure is an opt-in factory policy- Status: Accepted — refines the strict-factory alternative deferred by ADR 0036; the rejection of contract-level expose inheritance still stands. - Date: 2026-08-08
  63. ADR 0063 — One HTTP completion feeds every observability projection- Status: Accepted — supersedes the HTTP wrapper portion of ADR 0012 and refines ADR 0039; tool-event semantics remain unchanged. - Date: 2026-08-08
  64. ADR 0064 — Runtime-tool factories validate context at execution- Status: Accepted — extends the neutral runtime-tool operation in ADR 0055 without adding a second runner or mount surface. - Date: 2026-08-08
  65. ADR 0065 — Flat collisions preserve every known JSON kind- Status: Accepted — extends ADR 0044, preserves the flat presentation boundary of ADR 0050, and supersedes ADR 0044's rule that genuinely different kinds become unknown. - Date: 2026-08-08
  66. The starter template is the development workspaceAccepted. Complements ADR 0060 and ADR 0061. The identity paragraph is superseded by ADR 0070; the live-workspace decision remains active.
  67. The starter connects to external PostgreSQLAccepted. Refines ADR 0060 and ADR 0066.
  68. MCP SDK v2 is one stateless hard cutThe MCP TypeScript SDK v2 split its server and client packages, made stateless HTTP the stable application boundary and introduced protocol 2026-07-28. Stitchkit's previous surface still carried a mon
  69. ADR 0069 — Realtime contracts validate without owning delivery- Status: Accepted — extends ADR 0008; the raw binary lane in ADR 0020 remains orthogonal. - Date: 2026-08-10
  70. ADR 0070 — Scaffold identity is derived from one config> Superseded in part by ADR 0104. > The single validated config this ADR introduces is still the design; the file > is now project.json, it carries the whole project declaration rather than > identity
  71. ADR 0071 — Streaming multipart uses a Fetch-clean sequential parser- Status: Accepted — preserves the runtime-neutral boundary of ADR 0013 and the typed HTTP surface of ADR 0005. - Date: 2026-08-14
  72. ADR 0072 — HTTP authorization precedes payload parsing- Status: Accepted — supersedes ADR 0004 while preserving its flat-hook design. - Date: 2026-08-14
  73. ADR 0073 — Client request options are not callback context- Status: Accepted — extends ADR 0005, ADR 0008 and ADR 0025. - Date: 2026-08-14
  74. ADR 0074 — Server-owned managed shutdown- Status: Accepted — extends ADR 0008, ADR 0013 and ADR 0020. - Date: 2026-08-14
  75. ADR 0075 — Per-scope handler context (createScopedImplement)- Status: Accepted — supersedes the deferred scope→context clause of ADR 0024 - Date: 2026-08-17
  76. ADR 0076 — Explicit process-signal binding (bindProcessSignals)- Status: Accepted — supersedes the signal clause of ADR 0074 - Date: 2026-08-17
  77. ADR 0077 — An error definition carries its default message- Status: Accepted — extends ADR 0058 - Date: 2026-08-17
  78. ADR 0078 — The scope map is derived from the auth hook- Status: Accepted — extends ADR 0075 - Date: 2026-08-17
  79. ADR 0079: Typed handshake identity gateThe realtime contract types every event but not the identity of the peer that sends them. The guide's answer was "write socket.io.use() yourself", which left socket.data untyped — a real consumer accu
  80. ADR 0080: MCP call metadata is typed contextThe modern MCP adapter already validates request metadata and projects it into the per-call context used by contract tools, runtime tools, lifecycle and hooks. Only RequestEvent.mcp documented that fa
  81. ADR 0081: Generic native operations use managed definitionsADR 0019 put generic wait, guarded-download and upload mechanics in Stitchkit, but exposed them only as direct MCP SDK registrars. A consumer using those helpers therefore had to place them under rawT
  82. ADR 0082: View-file has one managed batch operationADR 0081 moved wait, download and upload onto managed runtime-tool definitions, but deliberately left mountViewFile as a raw MCP boundary. That exception made a protected MCP/Agent surface repeat fram
  83. ADR 0083: CLI composes managed and native commandsADR 0016 made contract operations a first-class CLI transport. Later, defineRuntimeTool introduced managed pathless operations, but ADR 0059 kept them unavailable on CLI. A consumer therefore had to i
  84. ADR 0084: Stdio process signals are close-onlyADR 0076 centralised the process-signal machine for managed HTTP servers, whose shutdown(options) accepts one total grace budget and an abort signal that a later OS signal can use to force the same ch
  85. ADR 0085: Auth rules may contribute typed contextAn authorization rule often resolves membership or ownership before it can decide. Applications then duplicated the resolved fields in a handwritten scope map, so the declared handler context could dr
  86. ADR 0086: Lifecycle composition is explicit and orderedApplications repeatedly assembled auth → policy → audit identity chains. Flat hooks remain the correct execution model, but hand-written forwarding duplicated ordering, fallthrough and result-transfor
  87. ADR 0087: Surface conformance is a manifest plus explicit probesContracts and runtime tools share execution guarantees, but consumers rebuilt ad-hoc assertions for HTTP, MCP, Agent and CLI discovery, validation, errors and cancellation. A framework-owned black-box
  88. ADR 0088: Managed files bind one application-owned rootView, download and upload had different containment, size and write semantics. Passing absolute host paths through tools also leaked deployment layout and made security fixes application-specific.
  89. ADR 0089: Async operations describe transport, not jobsLong-running exports, imports and generation jobs repeat the same operation surface, but Stitchkit must not become a queue, scheduler or persistence layer.
  90. Remote implementation has a peer-free entrypointimplementRemote depends on contracts, the typed HTTP client and neutral ServiceDef types. It does not depend on MCP or Agent SDKs. Its only public owner was nevertheless stitchkit/tools, a broad barre
  91. Realtime request is a typed native acknowledgementThe realtime contract already models and validates acknowledgement callbacks. Consumers still had to build the Promise lifecycle themselves: native timeout, disconnect, exactly-once settlement and inv
  92. ADR 0092: Realtime contracts may bind an existing transportApplications may already own one Socket.IO client for authentication, reconnection and durable subscriptions. createRealtimeClient previously made adopting Stitchkit validation require a second connec
  93. ADR 0093: Surface manifests are transport projections and include realtimeA single shared tool list overclaimed what role-selected MCP, Agent and CLI surfaces actually advertised. Realtime contracts were absent entirely, so event, acknowledgement and rejection drift could n
  94. ADR 0094: Auth hook composition is owned and atomicApplications with several authentication domains manually dispatched scopes and intersected inferred context maps. That duplicated framework mechanics, could run an unrelated resolver and could expose
  95. ADR 0095: Async-operation contracts have one factory and typed adaptersThe runtime-only async-operation descriptor removed repeated transport logic, but contract-backed applications still hand-authored the same start, status, wait, cancel, result and artifacts method fam
  96. ADR 0096: Managed file boundaries own safe read semanticsADR 0088 unified containment and writes, but applications still created the root separately, read inspection was asymmetric, and expected filesystem failures could surface as generic internal errors o
  97. ADR 0097: Request cancellation is an opt-in observability outcomeADR 0063 made one framework-owned HTTP completion the source for access logging and RequestEvent. The released event model has only ok: boolean: emitting a client disconnect as ok: false makes it indi
  98. ADR 0098: Agent conversations have one optional application runtimeThe existing agent surface projects contract operations into AI SDK tools. Real applications still repeat a larger server-side layer: provider-valid message history, model resolution, prompt budgeting
  99. Starter HEAD skips require an exact-version deferred reviewAccepted. Refines ADR 0061.
  100. ADR 0100: The agent store reducer owns runtime transitionsADR 0098 put eight aggregate operations behind AgentRuntimeStore. That kept the runtime ORM-neutral, but every durable adapter still had to reproduce the reference state machine: revisions, ownership,
  101. ADR 0101: Agent runtime persistence is bounded and normalizedADR 0100 moved transition policy into Stitchkit, but its first driver contract stored every run and admission identity in one AgentStoredState. Each mutation therefore read, validated and rewrote a pa
  102. ADR 0102: Application composition is process-local and provider-neutralThe managed HTTP server and bindProcessSignals already provide a truthful transport drain and one process-signal machine. Applications still repeat the larger process boundary around them: dependency-
  103. ADR 0103: Every entrypoint declares how settled it isThe package publishes sixteen entrypoints of very different ages. The contract, HTTP server, typed client, MCP tools and CLI have not broken in months. The agent runtime appeared in 0.56.2 and within
  104. ADR 0104: The project declaration ships from the frameworkAn application repository makes statements about itself that are not code: what it is called, what roles it runs, what it needs before it starts, which environment variables it expects. Today the star
  105. ADR 0105: The error-code map is partial, and the registry is completeADR 0026 introduced STITCHERRORSTATUS and, with it, a promise: a consumer's map is Record<StitchErrorCode, AppCode>, so "a missing or renamed code becomes a TypeScript error, not a silent 500".
  106. ADR 0106: A refused realtime frame answers its senderdocs/guide/realtime.md documents a convention for distributed producer/consumer pairs: put a literal generation first in the first payload object, and Zod — which validates object fields in declaratio
  107. ADR 0107: A rollback spends a declared budget, and the budget is a boundADR 0102 lists the kernel's obligations as separate bullets: "rollback of every resource whose start was attempted" and "bounded drain, reverse-order close and optional force under two absolute proces
  108. ADR 0108: What a stopped run already said is a declared policy, not a defaultAgentInputPolicy had two values. queue finishes the run in flight first; interrupt ends it. interrupt ended the run and kept everything it had produced: the partial assistant was committed with status
  109. ADR 0109: A spend figure never claims a provenance it does not haveAgentUsage has carried provenance on every field since it existed: provider-reported | computed | estimated | unavailable. The emission did not use it honestly, and three separate figures were wrong a
  110. ADR 0110: A reconciled cost belongs to the application, not to the coreADR 0109 made the runtime's spend figure honest: a total says it was added up, an unknown says unavailable, and a run that ended before the provider reported anything says so instead of staying silent
  111. ADR 0111: The driver is the extension point, and the agent runtime is not stable yetADR 0103 requires a promotion from evolving to stable to be its own decision. stitchkit/agent-runtime has never had one, and the question has been deferred on a loop: promotion needs evidence from a r
  112. ADR 0112: A run is read without its conversation, and history stays wholeloadSnapshot(conversationId) was the only read on AgentRuntimeStore. It returns every message and every run, and the driver member behind it — history.load(tx, conversationId) — takes no cursor and no
  113. ADR 0113: An absorbed input is committed with the answer, never before itinputPolicy: 'inject' — hand a newly arrived input to the loop between tool calls and let the run continue — shipped in 0.63.0 and was withdrawn in 0.65.0. An adversarial read found four defects, and
  114. ADR 0114: The resource graph carries values, not only orderdependsOn expressed when a resource starts and nothing else. ManagedResourceStartResult carried ready and completion, both Promise<void>; ManagedResourceContext gave a resource its id, its signal, its
  115. ADR 0115: A managed server resource owns when the server existsmanagedServerResource accepted either a handle or a thunk:
  116. ADR 0116: A selected Unix transport never becomes TCPcreateHttpClient({ unix }) passed a Bun-specific option into fetch. On Bun that selected a socket file; elsewhere the option was not a portable Fetch contract and the same call could reach baseUrl ove
  117. ADR 0117: Contract streams own validation and wire terminationrawResponse, streamingRoute, parseNDJSON and parseSSE intentionally solve separate low-level jobs. Used alone they cannot prove that a producer yields the declared shape, that a client validates it, o
  118. ADR 0118: Operation capacity belongs to the underlying workApplication admission answers whether the process is ready and tracks accepted work. It does not express a global concurrency cap, a per-key cap, or bounded rate windows. Reimplementing those counters
  119. ADR 0119: Delivery policy is ordered or replaceableAn event bus invokes callbacks; it is not a queue. The application snapshot sink already has a different and useful rule: one write plus one replaceable pending revision. Applications otherwise copy c
  120. ADR 0120: Published declarations follow ESM resolutionStitchkit sources use TypeScript's bundler resolution and extensionless relative imports. The JavaScript build is bundled, so those source specifiers do not survive into published runtime entrypoints.
  121. ADR 0121: Managed server factories receive resource contextADR 0114 makes dependency values available through ManagedResourceContext.use(resource). ADR 0115 makes managedServerResource invoke its server factory during start, after declared dependencies are re
  122. ADR 0122: Optional-peer surfaces are proven from artifactsA source module can be independent of an SDK while its public barrel is not. createToolInvoker uses the canonical contract runner without MCP or AI, but stitchkit/tools also mounts those adapters and
  123. ADR 0123: Terminal output is accepted before commit and tool rounds keep causal orderA provider stop with no content was committed as success even when the application protocol required an answer. The only downstream remedy was to rewrite an already terminal durable record. Separately
  124. ADR 0124: Entity cache membership and total deltas are declared per queryReplacing an entity by id cannot maintain a filtered list when an update moves the entity across the filter. Cursor envelopes also commonly carry a total, but a create/delete event does not always pro
  125. ADR 0125: Streaming bodies bound retained bytes, not lifetime trafficA finite HTTP response needs a cumulative byte ceiling: otherwise a caller that buffers it can grow without bound. A long-lived NDJSON or SSE subscription has a different invariant. Its frame, socket
  126. ADR 0126: Schema-owned stream frames end at the terminal itemThe default contract stream envelope provides three independent wire facts: data, a safe post-header error, and explicit normal end. Some established NDJSON protocols already encode those facts in the
  127. ADR 0127: Interrupt priority is durable execution orderFIFO queue and interrupt are correct defaults, but they cannot express one common explicit action: A is running, ordinary B is waiting, urgent C interrupts A and must execute before B. Putting C at th
  128. ADR 0128: SQLite runtime storage is a leaf adapterThe public AgentRuntimeStoreDriver makes database ownership explicit, but an embedded durable runtime otherwise has to repeat the same head, run, admission, history and recovery mapping. SQLite is ava
  129. ADR 0129: Deferred Agent tools are durable direct activationAn Agent application may own hundreds of contract and runtime tools while one model step needs only a few. Sending every schema on every request consumes provider context. A generic { name, arguments
  130. ADR 0130: A headless harness composes the Agent runtimeThe Agent runtime already owns durable admission, queueing, interruption, checkpoints, recovery, tool fencing and terminal settlement. A reusable headless session also needs model resolution, instruct
  131. ADR 0131: Harness leaves preserve canonical Agent identityA useful coding harness needs progressive skill discovery, human approval, reconnectable control, bounded large output and guarded file changes. A generic command gateway would replace the real operat
  132. ADR 0132: Agent TUI is an explicit starter profileThe headless Agent harness deliberately owns no process or renderer. That boundary is correct for applications, but it leaves the first useful terminal session behind substantial integration work: pro
  133. ADR 0133: Agent TUI is an optional package over one controllerThe Agent starter proved a useful terminal session, but reusable product mechanics lived in copied application source: transcript projection, scrolling, slash commands, model discovery, approvals, con
  134. ADR 0134: Diagnostic journal is bounded local evidenceThe application entrypoint already has no-queue admission, ordered/latest bounded channels, failure-isolated event sinks and managed file operations. None owns the combined contract needed by a local
  135. ADR 0135: Contained files use native Darwin directory capabilitiesThe Agent coding tools and harness resource reader must keep an authorized root, every ancestor and the final effect attached to filesystem identity while application authorization awaits. Linux can n
  136. ADR 0136: One exact tree drives a package-aware release trainThe repository publishes independent packages, but its release mechanics treated each tag as a separate tree. Releasing two packages required two branch heads and two whole CI runs even when the secon
  137. ADR 0137: Live state opens one continuous source generationAn open realtime transport does not prove that application state is current. Reading a snapshot and attaching an event listener as unrelated operations can miss the change between them. Several applic
  138. Conversation purge reserves identity atomically- Status: Accepted - Date: 2026-08-31
  139. ADR 0139: A coding-tool refusal is something a model can act onEvery refusal the coding tools made was a plain Error. toolResultFromError scrubs anything that is not an AppError down to a bare INTERNALSERVERERROR — correct, because an internal cause must not leav
  140. ADR 0140: The runtime tells a step how full the context isA run reached a hard context overflow without changing its behaviour on a single step before it. It could not: nothing had told it a limit was approaching, so there was no moment at which compacting o
  141. ADR 0141: A provider refusal is classified, not phrasedThree independently maintained consuming applications carry the same 69-line file for turning a provider failure into something a person can read. Two copies are byte-identical but for one catch; the
  142. ADR 0142: A primitive leaves the runtime when nothing in it needs the runtimeTwo consuming applications sit on either side of one boundary. The first adopted stitchkit/agent-runtime whole — durable store, run protocol, recovery. The second did not: its model loop is written ov
  143. ADR 0143: Telegram platform primitives need no bot libraryTwo things kept being written by hand across consuming applications:
  144. Generic application primitives declare facts, not infrastructurestitchkit/primitives is a browser-safe leaf for facts an application can declare once and reuse on both sides of its transport: finite lifecycle transitions, owner scope, permission decisions, exact v
  145. A reclaimed lock is proven, never assumedcreateDiagnosticJournal accepts lock: 'refuse' | 'reclaim-stale', defaulting to refuse. reclaim-stale reclaims a present lock only when the recorded owner is provably gone. The lock file carries { pid
  146. A scanning gate asserts what it scannedA test that discovers the files it checks — a glob, a directory walk, anything whose input set is not written out in the source — asserts the size of that set before asserting the set is clean. The as
  147. Machine identity, not host name, decides whose pid this isThe diagnostic journal lock records a stable machine identity beside the pid, and reclaim-stale attributes a recorded owner by that identity rather than by os.hostname(). The identity comes from the o
  148. A refusal that never left the process is a VALIDATION_ERROR with status 0Every argument the typed client refuses while planning a request raises ApiError('VALIDATIONERROR', 0, { issues }) — one shape, from the funnel every method passes through, so both transports answer a
  149. A zombie owner is gone, and it is the safer half of gonereclaim-stale reads the owner's process state beside the signal probe, and a pid that has exited without being reaped — a zombie — is reported as liveness: 'gone'. The lock is reclaimed.
  150. An event declaration projects onto the socket we already rundefineEvents declares topics: a wire name, one payload schema, and how the topic reaches listeners in this process (emit, serial, decision).
  151. A trust fence names its lanes, because one of them bypasses hooks entirelycreateTrustFence({ trustedHosts }) compares the authority a request addressed against a declared list, and refuses with a bare 403 before anything is dispatched. It exposes two admission points, and i
  152. A keyspace is memory that nothing reaches before it is durabledefineKeyspace(name, { schema, key }) declares a record set. keyspaceResource opens it as a ManagedResource whose start publishes a handle: get and list read synchronously from memory, put and delete
  153. A watched read is one read per question, and the question is not a functionA watched read is a GET the server re-runs when something it depends on announces a change, pushing the new answer to everyone watching it.
  154. The unit of a restart is the subtree, not the resourceApplicationHandle.restart({ resourceId }) replaces the named resource and every resource that transitively depends on it, leaving the rest of the graph running and the process epoch unchanged.
  155. One decision vocabulary, and an unanswered question is an errorallow / deny (with a reason) / defer is one type, PolicyDecision, defined in internal/decision.ts and re-exported by both the entrypoints that need it: stitchkit/live for an event topic declared mode:
  156. A schema a browser cannot import is not a contractThe canonical application records ship as stitchkit/application/schemas, an entrypoint with no server runtime behind it. The same names remain reachable from stitchkit/application, which re-exports th
  157. A resource that can be restarted is a resource that can begin a generationADR 0154 defined a restart as an operation on the resource graph: close a subtree, start it again, leave the rest running. Everything it says about the graph is still true, and the tests that hold it
  158. A gate that does not run the code is measuring a proxy for the thing it cares aboutcheck-browser-executes.mjs bundles every entrypoint the build:browser lane promises, runs it, and requires it to initialise. It runs in build, alongside the static scan rather than instead of it.
  159. A replacement says so, and a teardown is announcedApplicationSnapshot carries restarting: readonly string[] — the subtree a restart is replacing at this instant, empty between restarts — and ApplicationStatusProjection carries restarting: number.
  160. One declaration of which entrypoints a browser may importpackages/core/entrypoints.mjs declares every published entrypoint once: subpath, source module, and whether the package promises a browser can import it. The build, both browser gates, the reference-c
  161. A gate that recognises one error is blind to every otherThe consumer lane's declaration check subtracts what is knowingly accepted and fails on the remainder, instead of matching the one diagnostic shape it was written for. An unresolved optional peer is s
  162. A gate that reddens from load teaches its reader to disbelieve redverify --release chooses how many heavy lanes to run at once by asking the host how much memory it has, not by asserting 2. It prints the number and why it picked it. VERIFYHEAVYCONCURRENCY still wins
  163. A revision signal broadcasts a fact, not an itemSeveral operations may need to wait until the same process-local fact changes. An event bus broadcasts callbacks but owns no await, timeout, cancellation or close lifecycle. A bounded channel retains
  164. A local gate refuses; CI only reportsThe publication-privacy scan runs on every push, beside the release metadata gate, outside the verify memo. It is not memoised, not conditional and not part of the suite the memo can skip.
  165. A safelisted body is accepted only from an allowed originAn endpoint may declare safelistedBody: true. It then accepts its JSON body under text/plain as well as application/json — same parser, same input schema, same maxJsonBodyBytes. The server honours tha
  166. Tracking mechanics live in core; storage and domain stay outsidestitchkit/tracking (browser) owns the mechanics of visitor tracking: a tab-shared outbox with reserved sequence numbers and a short flush lease, delivery with one bounded retry, the page-leave beacon
  167. A page follows the release it was built forstitchkit/release (evolving, browser + server) owns three things: a server marker of the current frontend build, built on a read the application supplies; the channels by which a browser learns it — t
  168. Path literals materialize string paramsEvery named segment and terminal wildcard in an endpoint path is parsed once by the contract layer. When params is omitted, Stitchkit materializes a Zod object whose fields are strings and exposes the
  169. Durable runtime facts use one atomic state boundarystitchkit/application exposes an async StateStore with atomic update, a bounded process-run ledger, and an at-least-once notification outbox. The Node adapter in stitchkit/server serializes cross-proc
  170. New mechanics compose existing ownersNew cross-cutting mechanics attach to the layer that already owns the fact:
  171. GeoIP swaps complete reader generationsstitchkit/geo is a server-only managed resource with three observable states: uninitialized, unavailable and ready. It accepts a peer-neutral loader; the optional MaxMind adapter opens City and option
  172. A path policy runs before discovery or readingcreateAgentCodingTools accepts an optional async or synchronous authorizePath({ path }) callback. It is operation-independent: the same decision gates direct read, write and edit, directory listing, g
  173. ADR 0173: A read snapshot does not reserve the WAL writerAgentRuntimeStoreDriver.transaction originally carried no access intent. The SQLite leaf therefore used BEGIN IMMEDIATE for every operation. That is the correct boundary for a mutation that reads stat
  174. ADR 0174: Runtime operations are durable run factsrunning begins before prompt projection, tool preparation and the provider call. A UI seeing only that state cannot truthfully distinguish local work from a model request that is waiting for its first
  175. ADR 0175: The event ledger is the agent runtime source of truthThe normalized runtime store made admission, fencing, recovery and bounded run lookups efficient, but it could not reconstruct every fact that reached a model. State injected between steps, retry deci
  176. ADR 0176: Tool slots replace and disable by name, and an unknown name refusesmountAgent composes a ToolSet from contract methods and framework-managed runtime tools. Removing or replacing a built-in was done by filtering the runtime-tool array by name in application code. That
  177. ADR 0177: An approval response is authorized separately from the approval requestTool approval already had a durable request side: a tool marked user-approval parks the run, pendingApprovals reads the parked request out of canonical messages, and respondToApproval writes a durable
  178. ADR 0178: Instructions carry a role, and user-role instructions are durable historySystem sections are policy outside history. User sections are ordinary durable user messages, prepended atomically once under prompt.user-instructions. The receipt survives compaction and history repl
  179. ADR 0179: The parent owns a child's blocking eventsThe child manager owns one live handle and settlement promise per child conversation. A late result cannot overwrite a terminal state. subagent waits for settlement and reads the same canonical row as
  180. ADR 0180: External MCP and OpenAPI connections are a separate tools subexportstitchkit/tools/connections consumes MCP and OpenAPI without adding peers to the stable tools entrypoint. Definitions mount through the existing mountAgent lifecycle, hooks and approval machinery exac
  181. ADR 0181: A sandbox backend is an optional two-phase contract with an explicit network policyThe coding tools declare themselves a root boundary, not an OS sandbox: process isolation is the host's. That is an honest boundary, but it leaves every host to invent its own way of denying egress an
  182. ADR 0182: A durability port exposes step, sleep and wait over the storecreateAgentRuntime({ durability: true }) provides optional step, sleep and waitFor functions to runtime-tool handlers mounted with mountAgent. A factory may supply a host-owned port instead; it receiv
  183. ADR 0183: A watched value may cross as a difference to a revision the subscriber holdsstitchkit.watch.value is a discriminated union on kind:
  184. ADR 0184: The audit layer ships the filter and the spool, and stays optionalstitchkit/observability gains two pieces of machinery:
  185. ADR 0185: A served schema speaks the dialect it is stamped withA consumer tried to build a CLI whose commands come from a running server — the composition the CLI surface exists to support — and mountConnections threw before returning a single definition:
  186. ADR 0186: The CLI surface owns aggregates, profiles and its own distributionA consumer built a CLI on top of its own runner rather than on createCli, and the report named exactly what was missing. Each item is the same shape: work no application should own, where the convenie
  187. ADR 0187: The framework hands over what it already knowsFive separate requests arrived from one consumer in one evening. They named different files and read as a wish list. They are one defect.
  188. 0188 — Collecting the truth is not presenting itStatus: Accepted Date: 2026-09-22
  189. 0189 — The framework reports nothing on its ownStatus: Accepted Date: 2026-09-22
  190. 0190 — A question plan is part of the state it is asked inStatus: Accepted Date: 2026-09-22
  191. 0191 — A unit of work is not a request, and must not have to pretendStatus: Accepted Date: 2026-09-22
  192. 0192 — Running a parsed call, without owning the application's namespaceStatus: Accepted Date: 2026-09-22
  193. 0193 — An unchecked signature must be visibleStatus: Accepted Date: 2026-09-22
  194. 0194 — Printing is the exclusion, not being nativeStatus: Accepted Date: 2026-09-22
  195. 0195 — A declaration the host can see belongs in the snapshotStatus: Accepted Date: 2026-09-22
  196. 0196 — A tool answer is a declared view of the HTTP answerStatus: Accepted Date: 2026-09-23
  197. 0197 — The agent runtime is a product behind a one-way boundaryStatus: Accepted Date: 2026-09-23
  198. 0198 — Stable is earned, and kept on a budgetStatus: Accepted Date: 2026-09-23
  199. 0199 — One owner per name, one way per job: the 0.94 consolidationStatus: Accepted Date: 2026-09-23
  200. ADR 0200: An effect is recorded before it runs, and settled only by its recipientADR 0182 gave durable bodies step, which records a result after its body. A body cut short by a crash runs again on replay, and 0182 says so: the host must make external effects idempotent. That is co
  201. ADR 0201: A Telegram bot is assembled from primitivesThree consuming bots were meant to be one pattern and were three: each assembled its command menu and polling by hand under its own names, only one held updates back while it was not ready, each had i
  202. ADR 0202: Schedule retries preserve occurrence identityA failed delivery must not become immediately due again: this turns a persistent refusal into CPU load and an unbounded event stream. Moving the occurrence's due time to implement backoff loses its ca
  203. 0203 — Automatic stdin probes only for the first byteStatus: Accepted Date: 2026-09-26
  204. 0204 — Release cadence is observation, not a gateStatus: Accepted Date: 2026-09-26
  205. ADR 0205 — Flat tool collisions retain structural alternatives- Status: Accepted — amends the no-nested-union collision rule of ADRs 0033 and 0065; preserves the presentation/runtime boundary of ADR 0050. - Invariants: I3, I8.
  206. Model invocations share provider evidenceA plain completion needs causal evidence without acquiring a conversation prompt, tools, or a tool loop. Recording it in an application-owned second ledger separates provider attempts from the agent's
  207. Session lifetimes fence client workA login lifetime is distinct from an authorization owner: signing into the same account creates a new lifetime. createSessionScope owns that lifetime; a captured operation owns an opaque instance ID,
  208. Change hints reconcile owned readsA notification is a volatile hint, not proof of delivery. changeSubscriptionResource connects and subscribes before reconciling a finite declared key set. Reconnect and an explicit periodic interval b
  209. Outbox actions and projections have separate receiptsA notification may opt into an immutable versioned plan captured at enqueue. Each external action returns a JSON receipt and checkpoints under the current lease ID. After all actions, an idempotent lo
  210. Lock owners are process instancesA PID identifies a process-table slot, not a lifetime. The exclusive lock records an optional process identity alongside its existing machine attribution. Both the protected lock and its reclaim guard
  211. ADR 0211: Telegram screens are declared views over grammYEvery bot with a menu writes the same layer on top of its bot library: a router from callbackdata to a handler, a packer that fits a route and its arguments into 64 bytes, code that edits the right me
  212. ADR 0212: Keyboard layers are a headless React subpathA consuming project with a keyboard-driven workspace — sections switched with ← and →, lists walked with the arrows, Escape closing one level at a time — had built that behaviour itself, and measured
  213. ADR 0213: Telegram markup is a tree, and webhook intake is durableA consuming bot kept five mechanisms of the same kind as stitchkit/telegram already holds — facts about Telegram's protocols rather than about any product — and offered them. Measured against the fami
  214. ADR 0214: A store contract ships with its check, and secrets are masked by shapeThe first consuming bot moved onto the primitives of ADR 0213 and reported where it still had to build, switch off or work around them:
  215. ADR 0215: Upload progress is counted where the bytes leaveThe client guide said it plainly: stitchkit does not expose upload progress, because Fetch has no portable upload event. A consuming project that shows a recording's upload live (on a 200 ms link, mos
  216. ADR 0216: A chunked upload ships both halvesA file larger than one request (a consuming project: 4 MiB parts, 5 MiB requests, files up to 1 GiB) goes as init → chunk × N → finalize. That project carries the protocol three times — a desktop clie
  217. ADR 0217: React Native uploads a file as the platform holds itADR 0215 counts upload bytes in one of two places: a stream the transport pulls, or XMLHttpRequest upload events in a browser. Both routes first encode the body into bytes — new Request(...).arrayBuff
  218. ADR 0218: view_file rejects a list encoded as textpaths accepts one path as a string or a real array of paths. The string '["image.png"]' passes an ordinary z.string(), but it means one wrong filename. The extension check then reports a refusal to re
  219. ADR 0219: Journal damage is data, an IO failure is an errorThe writer already rotates an unfinished active file as a whole. But a NUL tail kept in an archive breaks a consumer that calls JSON.parse on every line. The partialTails counter describes only the cu
  220. ADR 0220: Compose existing agent ownersErgonomic agent APIs compose the existing harness, tool mount, control server, realtime transport and reducers. They introduce no competing loop, store, WebSocket engine or product message projection.
  221. ADR 0221: Flat field explanations retain branch meaningThe existing flattened tool projection joins constraints and explanations independently. Structural equality can deduplicate a field schema without discarding different meanings. Distinct descriptions
  222. ADR 0222: A release candidate pays for evidence onceThe candidate is pushed to release/X.Y.Z after the metadata preflight and the fast gate. The full set of selected CI lanes checks the exact SHA before the fast-forward of master and the tag. The full
  223. ADR 0223: The candidate's unit tests are checked by the required CIFor a release commit pushed to a remote release branch, pre-push keeps metadata, privacy, frozen lockfile, lint and types. All unit tests and the selected lanes are run by the full exact-SHA push CI;
  224. ADR 0224: Shared IO primitives have one ownerFiles atomic/managed writers use a single publication owner. The default atomic file fsync is kept; directory durability and create without overwrite are chosen explicitly. A post-publication error ca
  225. Native libraries separate install and import closureInvariants: I7, I8, I13.
  226. Evidence covers the affected treeInvariant: P.
  227. CLI separates construction from registrationInvariants: I3, I7, I8, I14.
  228. Durable inbox and explicit delivery outcomesInvariants: I8, I9, I10, I13.
  229. Connection budgets follow operation phasesInvariants: I8, I9, I10, I13.
  230. CLI publication commits assets before the public pointerInvariants: I8, I9, I10, I11, I13.
  231. MCP server leaf has no AI SDK peerInvariants: I8, I9, I10, I13.
  232. HTTP remote retains error policy and caller signalInvariants: I8, I9, I10, I13.
  233. Public build-time native packagingA native capability must survive the build and the move of the application. An installer that parses the loader's private import literals depends on the internal layout, even while it checks hashes an
  234. Universal companion selects one runtime targetOne JS artifact can serve Darwin arm64, x64 and Linux. Two single-target plugins compete for one owning loader: the first onLoad replaces the file entirely. A cross-build does not prove that the nativ
  235. ADR 0235: The default native loader is invisible to bundlersstitchkit/server, stitchkit/files and stitchkit/process reach the Darwin process-identity code through the exclusive lock. The packaged loader (darwin-native.cjs) required both .node addons by literal
  236. ADR 0236: One runtime-tool declarationInvariants: I1, I3, I7, I8, I14. Builds on ADR 0227, 0231 and 0238.
  237. 0237 — The release commit holds release metadata onlyStatus: Accepted Date: 2026-10-05
  238. 0238 — Before 1.0 the shape of the API outranks compatibilityStatus: Accepted Date: 2026-10-05
  239. 0239 — A list written as text is refused for every string-or-array fieldStatus: Accepted Date: 2026-10-05
  240. 0240 — A diagnostic journal quarantines what it cannot keep, and its recovery status is a wire shapeStatus: Accepted Date: 2026-10-05
  241. Native packaging verifies published digestsAmends ADR 0233: the source SHA256 of an asset is the digest the package published, not one computed from the installed file. Invariants I8, I13, I14.
  242. 0242 — The staging name of an atomic write is a public contractStatus: Accepted Date: 2026-10-05
  243. 0243 — A native command stops by one named policy, and the host bounds only what it outlivesStatus: Accepted, amended by ADR 0248 and ADR 0255 Date: 2026-10-05
  244. 0244 — CPU is not attributed to operationsStatus: Accepted Date: 2026-10-05
  245. 0245 — CLI publication is an evolving leaf, not part of the stable CLIStatus: Accepted Date: 2026-10-05
  246. 0246 — A native command stops what its leader left in the group by defaultStatus: Accepted, amended by ADR 0248 Date: 2026-10-05
  247. ADR 0247: The candidate runs the tests that read release metadataADR 0223 left every unit test of a release candidate to the exact-SHA CI. Since ADR 0237 a release commit holds release metadata only: the changelog roll, the promoted migrations, the version bumps, r
  248. 0248 — A stopped leader settles with its exit, and a command may join the caller's groupStatus: Accepted Date: 2026-10-06
  249. 0249 — A test run leaves no process behindStatus: Accepted Date: 2026-10-06
  250. 0250 — A release that changes process behaviour is tried on the consumers before its tagStatus: Superseded by ADR 0251 Date: 2026-10-06
  251. 0251 — A release does not wait for its consumersStatus: Accepted Date: 2026-10-06
  252. ADR 0252: A bundle carries no build-machine pathInvariants: I7, I8, I13, I14. Amends ADR 0235 (decisions 1 and 3) and ADR 0233 (embedded inputs).
  253. ADR 0253: A Telegram request that never left is safe to repeatInvariants: I8, I13, I14. Builds on ADR 0141, ADR 0143.
  254. ADR 0254: A packaging plugin owns one loader and names what it findsInvariants: I8, I13, I14. Amends ADR 0252 (the artifact check).
  255. ADR 0255: An owner-loss guard belongs to the command groupInvariants: I8, I9, I10, I11, I13. Amends ADR 0243's statement that only an external supervisor can bound a command group after its caller dies.
  256. ADR 0256: A Telegram attempt is fenced by a persisted claimInvariants: I8, I9, I10, I13, I14. Amends ADR 0213's durable webhook intake.
  257. ADR 0257: CLI dotted values follow their declared schemaInvariants: I1, I2, I3, I8, I14.
  258. ADR 0258: Conversation branches have new identity and no inherited authorityInvariants: I3, I8, I12, I15.
  259. ADR 0259: Journal records and derived caches have separate transfer policiesInvariants: I3, I8, I12, I13, I15.
  260. ADR 0260: Proxy origin trust is separate from client IPInvariants: I6, I8, I9, I12, I13.
  261. ADR 0261: An ingress attaches request facts through a key the request cannot carryInvariants: I3, I6, I8, I12, I13.
  262. ADR 0262: One reader bounds a body, and the chunk loop is sharedInvariants: I8, I10, I12.
  263. ADR 0263: A fix to an entrypoint names what it touchesInvariants: I13, I14.
  264. ADR 0264: A wait on a long-lived promise is owned by the stepInvariants: I10.
  265. ADR 0265: A lock this process holds is a descriptor it holdsInvariants: I8, I13. Amends ADR 0210.
  266. ADR 0266: A discovered schema's unions are judged by its serverInvariants: I2, I13. Follows ADR 0205.
  267. ADR 0267: A flat alternative names the values that select itInvariants: I1, I2. Amends ADR 0205.