@jr2/orchestrator 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -1,4 +1,4 @@
1
- # Observation is open; run state and control need the Instance token
1
+ # Observation is open; run state and control need a Caller's scope
2
2
 
3
3
  [ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md) minted two tokens and scoped them
4
4
  for **delivery** — a Sandbox token may deliver to its own agent registrations, an Instance token may deliver to gates.
@@ -16,20 +16,44 @@ Building `jr2 visualize` (the Machine renderer and its live run highlighting) fo
16
16
 
17
17
  ```
18
18
  open /workflows, /workflows/:name/machine, /viz/* structure
19
+ /console/config (the login's issuers, ADR-0032) public values
19
20
  /workflows/:name/runs, …/runs/:runId/events OBSERVATION (new)
20
21
  authenticated /agents/:iid/* the Agent's surface [Sandbox token]
21
- instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + control [Instance token]
22
- POST /workflows/:name/runs starting a run (ADR-0058)
23
- /runs/:id/gates/:gate/events gates (ADR-0011/0013)
22
+ guarded each route below, opened by one scope of a Caller run state + control [a Caller, ADR-0078]
24
23
  ```
25
24
 
26
- - **Run state and run control are the Instance token's alone.** `authenticated` accepts _any_ token we minted, so a
27
- Sandbox token — the one credential that sits in a pod an Agent shares a network namespace with — could read **every**
28
- run's context (other features' branches, ticket bodies, review verdicts, Harness endpoints) and **CANCEL any run**.
29
- Neither is on the Agent's surface, no more than a gate is: ADR-0013 scopes that token to delivering into registrations
30
- recorded against its _own_ Sandbox. This closes the same class of hole ADR-0013 closed for gates, on the routes it did
31
- not name. The band is named for the credential it requires, not for a role — "operator" in this repo is the Kubernetes
32
- operator (ADR-0001).
25
+ - **Run state and run control are a Caller's, and each route needs one scope.** `authenticated` accepts _any_ token we
26
+ minted, so a Sandbox token — the one credential that sits in a pod an Agent shares a network namespace with — could
27
+ read **every** run's context (other features' branches, ticket bodies, review verdicts, Harness endpoints) and
28
+ **CANCEL any run**. Neither is on the Agent's surface, no more than a gate is: ADR-0013 scopes that token to
29
+ delivering into registrations recorded against its _own_ Sandbox. This closes the same class of hole ADR-0013 closed
30
+ for gates, on the routes it did not name. The guarded band takes only a Caller
31
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)): a Sandbox token is never
32
+ one. Each route of the band needs one scope, checked against the Workflow of the run or the door that it addresses:
33
+
34
+ ```
35
+ start POST /workflows/:name/runs, POST /workflows/:wrapper/charts/:name/runs
36
+ read GET /runs (summaries of the runs of its Workflows, live and ended), GET /runs/:id (the whole status),
37
+ GET /runs/:id/events, GET /runs/:id/conversations[/:iid[/events]] (the kept conversations, ADR-0023)
38
+ answer POST /runs/:id/gates/:gate/events, GET /gates (the open Gates it may answer), and the `gates` of GET /runs/:id
39
+ cancel POST /runs/:id/events (CANCEL)
40
+ steer POST /runs/:id/steering, GET /runs/:id/agents (the slot list { agent, open, steerable }, ADR-0002)
41
+ ```
42
+
43
+ `answer` alone opens a run's `gates` and their routing, and never its `context`: a person who answers a Gate reads the
44
+ Gate's `meta`, which the author wrote for that person. So `GET /runs/:id` gives `read` the status and `answer` the
45
+ `gates`; with `answer` alone the answer is the run's observation (below) and its `gates`. `GET /runs` leaves out the
46
+ runs of the Workflows that the Caller may not read; it refuses no run, and refuses only a Caller with no `read` scope
47
+ at all (ADR-0078). `answer` can name single Gates of a Workflow (ADR-0078). Then the gate POST refuses another Gate of
48
+ that Workflow, and `GET /gates` (its entries are ADR-0011's) and the `gates` of `GET /runs/:id` list only the Gates
49
+ that the Caller may answer. Neither gives the run's `context`. `GET /runs/resolve` answers ids to a Caller with any
50
+ scope, for the Workflows of its scopes. `GET /whoami` answers any Caller. A route that no scope names stays the
51
+ Instance token's: the runtime Chart routes (`PUT` and `DELETE /workflows/:wrapper/charts/:name`), `GET /repos`, a
52
+ Hook's log (`GET /hooks/:name`, which can hold a payload's fields and a handler's words), and the Instance-token door
53
+ on `/agents/:iid/*` (ADR-0013) except the conversation route, which takes only a control token
54
+ ([ADR-0023](0023-the-harness-prints-the-conversation.md)). `POST /hooks/:name` is outside the bands: the Hook's own
55
+ verifier authenticates it ([ADR-0011](0011-workflow-defined-events.md)). The band is named for what it checks, not for
56
+ a role — "operator" in this repo is the Kubernetes operator (ADR-0001).
33
57
 
34
58
  - **The observer gets a PROJECTION, not a credential.** `RunObservation` is
35
59
  `{ runId, workflow, status, value, children }` — identity, and where in the Machine the run is. What is absent is the
@@ -48,16 +72,15 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
48
72
 
49
73
  ## Considered options
50
74
 
51
- - **Give the page a token.** It would have to be the **Instance** token — gates, cancel, every run's context — handed to
52
- whatever can load a URL. Putting it in `jr2.config.ts` (checked in) is worse still, and the token is minted per boot,
53
- so the page would not have it anyway. Rejected: the visualizer needs to _see_ a run, and seeing is not a subset of the
54
- credential that steers it. That mismatch is the whole reason a projection exists.
75
+ - **Give the page a token.** It would be a credential — gates, cancel, every run's context — handed to whatever can load
76
+ a URL. Putting it in `jr2.config.ts` (checked in) is worse still, and the token is minted per boot, so the page would
77
+ not have it anyway. Rejected: the visualizer needs to _see_ a run, and seeing is not a subset of the credential that
78
+ steers it. That mismatch is the whole reason a projection exists.
55
79
  - **Keep `/runs*` on `authenticated` and filter in the handler.** Rejected: the guard would then depend on every future
56
80
  handler remembering to redact. The band is a middleware precisely so the default is refusal.
57
81
  - **A fourth "observer" token.** Rejected: it buys nothing today (the observation surface carries no secret) and costs a
58
82
  minting, distribution, and revocation story. If observation ever needs to be _private_ — a deployed jr2 Application on
59
- a shared network — that is the ADR to write, and it is the same open question ADR-0013 left about authn for non-kube
60
- callers.
83
+ a shared network — the guarded band and ADR-0078's scopes are already where it goes.
61
84
 
62
85
  ## Consequences
63
86
 
@@ -66,11 +89,11 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
66
89
  now **403**. The test pinned the hole.
67
90
  - **The API is a cluster Service** (ADR-0019), so "open" means open to whoever can reach it — in-cluster peers, a
68
91
  port-forward, or an ingress — deliberately, since what is open is structure plus lit states. Anything that would
69
- embarrass a user if seen belongs behind `instanceOnly`, and that is where it is.
70
- - **The CLI is unaffected.** It holds the Instance token from the in-cluster Secret (ADR-0019) and reads the guarded
71
- routes, as before.
72
- - **Open, inherited from ADR-0013:** what a deployed jr2 Application uses for human callers. Whatever it is, the band
73
- split survives it — only the credential in the `instanceOnly` band changes.
92
+ embarrass a user if seen belongs in the guarded band, and that is where it is.
93
+ - **The CLI is unaffected.** It holds the Instance token from the in-cluster Secret (ADR-0019), which has every scope,
94
+ and reads the guarded routes, as before.
95
+ - **A Caller without kube credentials is ADR-0078's:** a named token, a JWT, or a browser login. The band split holds
96
+ for every Caller; what the guarded band checks is one scope for each route.
74
97
 
75
98
  - **One scoped exception, and it does not live on this surface at all (ADR-0023).** The Harness prints each Agent's
76
99
  conversation — prompts, assistant text, thinking, tool calls, and the answer jr2 gave each Menu pick — to container
@@ -87,9 +110,12 @@ instanceOnly /runs, /runs/:id, /runs/:id/events (GET + POST) run state + c
87
110
  form; then it prints there as the model read it. The answer also carries an Effect's output as the model read it: a
88
111
  `command` Effect's output prints there up to the bound, and a Held value an upstream echoed into that output prints
89
112
  with it (ADR-0023, ADR-0059). What remains, deliberately, is that on a shared cluster the population who can read an
90
- Agent's reasoning, and what its picks answered, is whoever can read pod logs. If that becomes unacceptable, the fix is
91
- the read-through ADR-0023 declined to build — `instanceOnly`, on this surface, where the band already works.
113
+ Agent's reasoning, and what its picks answered, is whoever can read pod logs. The Orchestrator's kept copy of the same
114
+ conversation is not part of the exception: it is on this surface, behind `read`, and `jr2 logs` and the Console read
115
+ it there (ADR-0023). Thus `read` opens the same residual of a pick's answer for as long as the run is kept
116
+ (`runs.retain`, ADR-0065). If the pod-log audience becomes unacceptable, the print is what to turn off, and the record
117
+ stays.
92
118
 
93
- Evidence: `packages/orchestrator/src/http.ts` (the three middlewares), `run-host.ts` (`observe` / `RunObservation` /
94
- `RunChild`), `test/auth.test.ts` (the boundary), `test/http-viz.test.ts` (a planted secret never reaches the feed, at
95
- any depth of the child tree).
119
+ Evidence: `packages/orchestrator/src/http.ts` (the middlewares), `run-host.ts` (`observe` / `RunObservation` /
120
+ `RunChild`), `test/auth.test.ts` (the boundary), `test/callers.test.ts` (each scope on its routes),
121
+ `test/http-viz.test.ts` (a planted secret never reaches the feed, at any depth of the child tree).
@@ -15,11 +15,13 @@ else.
15
15
 
16
16
  - **The rule.** A name is on the surface when an author writes it in a workflow module or in `jr2.config.ts`. No other
17
17
  name is. A name that an author only gets by inference stays off the surface: the event union that `jr2Setup` derives,
18
- a Gate's input, an Agent's Frame. The `gate` actor is off the surface too. `jr2Setup` registers it, and a state
19
- invokes it as `src: "gate"`, so no author writes the value. A type is on the surface when an author must write its
20
- name, for example `Workspaced`, `HostInjectedInput`, `AgentDefinition`, and the types of the parts that `customize()`
21
- takes. `JR2Config` is on the surface too: the `jr2.config.ts` Reference documents its fields, and an author names it
22
- to type a config that is built outside `defineConfig()`.
18
+ a Gate's input, an Agent's Frame. The plain `gate` actor is off the surface too. `jr2Setup` registers it, and a state
19
+ invokes it as `src: "gate"`, so no author writes the value. The `gate()` factory of a Gate slot that hears a Hook is
20
+ on the surface, because an author writes it in `actors` ([ADR-0011](0011-workflow-defined-events.md)); the walk knows
21
+ such a slot by identity, not only by `src === "gate"`. A type is on the surface when an author must write its name,
22
+ for example `Workspaced`, `HostInjectedInput`, `AgentDefinition`, and the types of the parts that `customize()` takes.
23
+ `JR2Config` is on the surface too: the `jr2.config.ts` Reference documents its fields, and an author names it to type
24
+ a config that is built outside `defineConfig()`.
23
25
  - **Named exports only.** `src/index.ts` names each export (`export { a } from …`, `export type { T } from …`), in
24
26
  groups by topic, and has no `export *`. Thus an Assistant that reads the file sees the full surface, and each change
25
27
  to the surface is a line in a diff.
@@ -73,14 +75,14 @@ another Agent slot (a nested Turn: its picks are its own) or a `gate` (its trans
73
75
  leaves out everything below that child; a child that runs anything else stays in. A state that invokes `gate` gets its
74
76
  accepted set from its own and its ancestors' transitions and those written on its plain children, by the same descent:
75
77
  position gives a Gate the writings below it that no other party answers for, as it gives an Agent its modes (the Pool's
76
- wake Gate, on its root, takes the writing on `parked`). The consumer names neither; MCP appears nowhere in workflow
77
- code. Mechanics: the **vocabulary** derivation is static, in `jr2Setup.createMachine` — a config walk wraps each
78
- invoke's `input` to append the derived names, so names still ride serializable input and the ADR-0007 restore path and
79
- invoke-time validation are unchanged. The same walk unions the derived sets of every state that invokes an Agent of one
80
- name into that Agent's **Menu** — its tools, fixed for a conversation so the provider's prompt cache survives a change
81
- of state. The walk reads transition _keys_ and so cannot see guards, which is why what a Turn may pick is not static on
82
- top of it: `agentSurface` asks the invoking machine's guards, and the Turn is told its **Allowed picks** — the derived
83
- set, less every event that the live snapshot, in its active mode, would not take
78
+ `source` Gate, on its root, takes the writing on `parked`). The consumer names neither, and the Menu's `mcp__jr2__` tool
79
+ names appear nowhere in workflow code. Mechanics: the **vocabulary** derivation is static, in `jr2Setup.createMachine` —
80
+ a config walk wraps each invoke's `input` to append the derived names, so names still ride serializable input and the
81
+ ADR-0007 restore path and invoke-time validation are unchanged. The same walk unions the derived sets of every state
82
+ that invokes an Agent of one name into that Agent's **Menu** — its tools, fixed for a conversation so the provider's
83
+ prompt cache survives a change of state. The walk reads transition _keys_ and so cannot see guards, which is why what a
84
+ Turn may pick is not static on top of it: `agentSurface` asks the invoking machine's guards, and the Turn is told its
85
+ **Allowed picks** — the derived set, less every event that the live snapshot, in its active mode, would not take
84
86
  ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md) owns the rationale).
85
87
  Authoring is untouched by the split: a workflow names no tools in either leg. The same walk feeds `jr2 visualize` ("this
86
88
  state's agent can call X, Y"). Dotted names (`agent.*`, `workspace.lost`, `xstate.*`, `after`) are mechanically
@@ -89,7 +91,9 @@ excluded.
89
91
  **A transition belongs to the party that its state, or the nearest state above it, invokes.** The party is the Agent
90
92
  slot or the `gate` that state invokes, or both when one state invokes both; root transitions, with no such state above,
91
93
  belong to both. That is who may send an event whose def leaves `audience` unset
92
- (`defineEvent({ audience?: "agent" | "external" | "any" })`, no default):
94
+ (`defineEvent({ audience?: "agent" | "external" | "any" })`, no default). One case reads as `external`: an event in a
95
+ Gate slot's `delivers` whose def leaves the audience unset, so a Hook's event is never on the Menu of an Agent below
96
+ that Gate ([ADR-0011](0011-workflow-defined-events.md)):
93
97
 
94
98
  | Transition written on | Unset audience: who may send |
95
99
  | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
@@ -143,8 +147,14 @@ sends there, and every case with the Agent's state at Rest.
143
147
  **Load checks on modes and audience.** `createMachine` refuses a registered event that no state handles: it is in no
144
148
  derived set, so it is on no Menu and no Gate. It refuses a parallel region below a state that invokes an Agent — the
145
149
  Agent's state itself being parallel included, since its regions are below it: the shape is reserved for picks the Agent
146
- does not wait for, which jr2 does not build, and background work goes above the Agent's state. It warns — the Machine
147
- stays legal; the doc carries the warning, `jr2 up` prints it and the Console shows it with the seats — about a mode that
150
+ does not wait for, which jr2 does not build, and background work goes above the Agent's state. It refuses one Agent slot
151
+ invoked by two states that can be active together — two parallel regions, one state inside the other, or one state
152
+ twice: a slot is one Agent of the Machine, so its two Turns would have one sender, which `sentBy` cannot tell apart, and
153
+ one conversation, which `continue` cannot give to both. Two Agents at once are two slots (one definition can fill both),
154
+ a child Machine in each region, or a Pool ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
155
+ jr2 refuses this shape and does not support it as a special case, because jr2 can remove a refusal later without
156
+ breaking a Machine, but a refusal that it adds later breaks each Machine that loads now. It warns — the Machine stays
157
+ legal; the doc carries the warning, `jr2 up` prints it and the Console shows it with the seats — about a mode that
148
158
  nothing enters (not initial, and no state the Machine can enter has a transition into it or a state inside it — so
149
159
  neither the mode's own `initial` nor its transition into itself counts: its picks are on the Menu and never allowed; a
150
160
  transition into a history state enters that history state's default target, shallow or deep, or its parent's initial
@@ -21,7 +21,7 @@ restore re-attaches to a Submission whose model is already fixed server-side and
21
21
  `agentRun` walks `self._parent` to the nearest registered ancestor. Invoked-actor registration (not an entry action) is
22
22
  what makes this restore-safe: invoked actors restart on snapshot restore, entry actions do not. Not under a workspace →
23
23
  fail loudly, unless input carries an explicit `endpoint` (the dev-stub, workspace-less path). Body-facing workspace
24
- handles are `{ repos, branch }` ([ADR-0012](0012-workspace-wrapper-machine.md)).
24
+ handles are `{ repos }`: each is a Repo Slot's folder ([ADR-0012](0012-workspace-wrapper-machine.md)).
25
25
 
26
26
  **[ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)'s token scoping survives and
27
27
  strengthens**: the registration records the Sandbox resolved from the enclosing wrapper — the same deterministic
@@ -108,12 +108,14 @@ modes — `continue` governs only what a _new invocation_ means. The author name
108
108
  conversation's identity is already on the page. (This corrects the jr-parity exercise's "same iid = jr's resume
109
109
  machinery, free" — an inversion of jr's actual semantics.)
110
110
 
111
- Two live invocations of one continued iid — parallel states — are refused at the registration table: one live surface
112
- per address. A Submission that arrives behind a live one on the Harness queues: prompts for one instance enter one
113
- per-instance queue in admission order, and a Submission is promoted only when it is the first unsettled one for its
114
- conversation (ADR-0027 states this as jr2's own semantics). A queued prompt waits behind work that may never finish,
115
- which is why [ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md) both ends the turn and orders the
116
- abort ahead of the next `send`.
111
+ Two live invocations of one continued iid are refused: one live surface per address. The load walk refuses the shape
112
+ that makes them, one Agent slot in two states that can be active together
113
+ ([ADR-0015](0015-authoring-surface-absorbs-the-mechanism.md)), and the registration table refuses a second registration
114
+ of one address as a second guard. A Submission that arrives behind a live one on the Harness queues: prompts for one
115
+ instance enter one per-instance queue in admission order, and a Submission is promoted only when it is the first
116
+ unsettled one for its conversation (ADR-0027 states this as jr2's own semantics). A queued prompt waits behind work that
117
+ may never finish, which is why [ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md) both ends the
118
+ turn and orders the abort ahead of the next `send`.
117
119
 
118
120
  The minting doctrine — addresses are computed by jr2, never by the workflow — covers gate ids too: a gate with no
119
121
  authored id derives one from its actor path below the run root, through the same walk `mintIid` uses (`actorPath` in
@@ -9,33 +9,219 @@ done — was ~90 lines of hand-rolled bookkeeping in the jr-parity exercise (`ac
9
9
  `spawnChild` top-level or visualize goes blind" footgun). No xstate-ecosystem primitive exists for this (verified: the
10
10
  canonical v5 pattern _is_ the hand-rolled loop), so jr2 ships one.
11
11
 
12
+ The webhook round of 2026-11-03 added the other half: work that starts from the outside. An alert service sends one
13
+ alert, and each alert needs one run of a triage Workflow. A forge labels an issue, and each labeled issue needs one run
14
+ of a ticket Workflow. A Hook ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md))
15
+ never starts a run itself. It reaches two things that exist: a Gate, which answers a run
16
+ ([ADR-0011](0011-workflow-defined-events.md)), and a Source, which brings items. So this ADR gives the Source a push
17
+ half, lets a Pool's worker be a run of another Workflow, and lets such a Pool stand.
18
+
12
19
  ## `pool(worker, spec)` — a machine factory, like `workspace()`
13
20
 
14
- `pool(worker, { source, itemId, cap, itemInput, onDrained, failures })` returns a plain machine that owns
21
+ `pool(worker, { source, itemId, cap, itemInput, onDrained, failures, standing })` returns a plain machine that owns
15
22
  spawn-under-cap, stable child identity, typed completion collection (`DoneActorEvent<Output, string>` + the
16
- `xstate.done.actor.*` wildcard — zero casts), `stopChild` bookkeeping, the wake gate, and the re-query timer. The one
17
- top-level `spawnChild` lives in jr2's pool code, once — the visualize footgun is dissolved by construction, and
23
+ `xstate.done.actor.*` wildcard — zero casts), `stopChild` bookkeeping, the Source's Gate, and the re-query timer. The
24
+ one top-level `spawnChild` lives in jr2's pool code, once — the visualize footgun is dissolved by construction, and
18
25
  everything durable is a registered string src (xstate cannot persist inline-src children, which independently forces
19
26
  this shape). The pool composes: it returns a plain machine and can itself be nested as a child. The typed idioms
20
27
  (wildcard done events, `assertEvent`, outcome-in-context root output) ship as documented patterns regardless, so
21
28
  outgrowing the pool means hand-rolling without penalty, not fighting a framework.
22
29
 
30
+ **The worker is a child Machine or a `dispatch(...)` slot, never a bare Workflow name.** A child Machine runs inside the
31
+ Pool's run, as it always did. A `dispatch` slot ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)) makes
32
+ each item a run of its own: `pool(dispatch("triage", { input: triageDoor, wait: true }), { … })`. The two worker kinds
33
+ differ in five places — a worker's defect, a cancel of the Pool, what `customize` reaches, the life of an item's key,
34
+ and where the Gates are — and each difference is one that ADR-0071 states between a child and a dispatched run. So they
35
+ belong to the worker kind, and the word `dispatch` at the call site shows them. `pool("triage", …)` would hide all five
36
+ behind a string, so jr2 does not offer it. Inside, one claim loop serves both kinds with two internal policies.
37
+
23
38
  ## The source port is generalized; Work Source is one adapter
24
39
 
25
- The pool draws from a **Source** port — `next(active) → item | null`, plus an optional `wake` event def (webhook /
26
- `jr2 send` push seam) and `pollEvery` (the set mutates underneath us) — a queue, a generator, or a re-queried set. tk's
27
- claim actor is one adapter; GLOSSARY.md's **Work Source** reframes as _the Source adapter for tickets_, and the
28
- Ready-set doctrine (re-query, never materialize) survives as that adapter's semantics, not the primitive's.
40
+ The pool draws from a **Source** port in one of three forms:
41
+
42
+ - **Pull**: `next({ active, settled, input }) → item | null`, plus an optional `wake` event def (for `jr2 send`) and
43
+ `pollEvery` (the set changes under the Pool). `input` is the Pool's input and `settled` holds the ids of the finished
44
+ and failed items, so a Source can fan out over a plan with no state of its own.
45
+ - **Read**: `read`, an `http` Effect marked `idempotent: true` or a promise (`pool()` refuses another Effect,
46
+ [ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md), as a Gate's look does),
47
+ and `items`, which turns its answer into the items in order (by default the answer itself, when it is a list). The
48
+ read's input is the Pool's input. It is the re-queried set, read through an Effect, so the read can spend a secret
49
+ that the Instance Effector holds ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), which
50
+ a `next` in the Orchestrator cannot. A promise read holds no secret, and the default tier reads with one. A failed
51
+ read gives no items.
52
+ - **Push**: `item`, a zod schema, and `hooks` whose handlers carry each item. Nothing can be read again: each item comes
53
+ in a delivery, and the Pool holds it (below).
54
+
55
+ A pull or a read Source can hear a Hook too. Its `hooks` handlers give `reread()`: read the set again now. **When a
56
+ Source can read, the read is the truth**: a delivery only says "read again", and an item that lost its label while it
57
+ waited is not in the next read. A handler of a Source has the shape of a Gate's (by Hook kind, then event key) and reads
58
+ `{ payload, caller, delivery, input }`. It gives `reread()`, `carry(item)` or `skip(reason)`, and never a bare
59
+ `undefined`: a delivery that brings nothing has a reason that a person can read in the Hook's answer, the hook log and
60
+ `jr2 hooks`. A throw is a skip with the error as its reason. `skip` is for a Source's handlers only; a Gate's handler
61
+ gives an event or `undefined`. A verdict that the Source's form cannot take (`carry` from a pull or a read Source,
62
+ `reread` from a push Source) and an item that the `item` schema refuses are each a skip, with the reason. **An item
63
+ whose `itemId` throws or gives no id gets no worker, in each form of Source.** In a delivery to a push Source, it is a
64
+ skip with the reason. In a read, the read skips that item, with a `pool` mark `skipped` on the feed that says which item
65
+ and why, and it claims the rest. A read at each `pollEvery` finds the item again, so jr2 marks each such item one time
66
+ in a run's life. Else one item with no id would make each read give no items, with no mark, and no item of that Source
67
+ would start. A `next` gives one item at a time, so its item with no id ends the run `error`, and the streak below pauses
68
+ the Pool when that does not stop. Mechanically the push half is the Pool's `source` Gate, which heard only `wake`
69
+ before, so the Hook's fan-in reaches it as it reaches any open Gate (ADR-0011). A Source may also give
70
+ `reply(verdict, { caller })`: pure, from its own verdict to plain text or `undefined`, which the person who acted on a
71
+ chat service reads, and nobody else; a `skipped` text shows only when no other target gave one (ADR-0011). The verdict
72
+ is the line of `sources`, and for `held`, `existing` and `hold-full` also the parsed `item`. A paused Pool has no run,
73
+ so no `reply` runs for `paused`. The reply after the work is a plain `http` Effect of the Machine.
74
+
75
+ The claim loop has three paths: `next`, the read, and the held items. A `reread`, a `wake` or a settled worker that
76
+ comes during a read (or during a call of `next`) sets a latch, and the loop reads once more after that read, so no
77
+ change is lost. Reads coalesce: one read claims every free slot, and the loop does not read after each claim and each
78
+ settled item one by one. The Pool drops a known id before it claims, so a read that finds an active or settled item
79
+ starts nothing. For a Pool of runs, an item that started a run has one record of "settled": the store's key check
80
+ (below). An item that ended with no run (`start-refused`, `dispatch-error`, `unreachable`) has no row in the store, so
81
+ the Pool counts its key as known for the rest of its run's life: else the read that its own settle starts would claim it
82
+ again at once, with no pause and no end, and one item that the callee's door refuses would hold the Orchestrator's event
83
+ loop. The next run of the Pool (a hand-over or a resume) tries such an item again, so a publish that fixes the door or
84
+ the `itemInput` takes it. The Pool keeps no settled keys past its own run's life: it keeps the run and the status of
85
+ each item that ended in that life, which the bound of a standing run limits, and which a `next` reads as `settled`. A
86
+ `next` that gives an item whose run ended before gets that run (`existing`), whose wait ends at once, so the item
87
+ settles and is not asked for again.
29
88
 
30
- A Work Source — `tk`, GitHub issues, Jira, a task queue — is a behavior port, not a canonical data schema: verbs like
89
+ tk's claim actor is one adapter; GLOSSARY.md's **Work Source** reframes as _the Source adapter for tickets_, and the
90
+ Ready-set doctrine (re-query, never materialize) survives as that adapter's semantics, not the primitive's. A Work
91
+ Source — `tk`, GitHub issues, Jira, a task queue — is a behavior port, not a canonical data schema: verbs like
31
92
  `claimNext`, `updateStatus` and `comment`, each gated by a capability the adapter advertises. It owns the dependency
32
93
  graph and hands out the Ready-set, the currently-unblocked items in dependency order, so the Machine never encodes a
33
94
  dependency edge. The Ready-set mutates during execution — items can be reopened or created mid-run — so it is not a
34
- static FIFO queue.
95
+ static FIFO queue. An adapter can give both halves of a Source: its read and its Hook's handlers, so the mapping from
96
+ the service's data to an item is written once.
97
+
98
+ ## A pushed item is held until the Pool claims it
99
+
100
+ - **The Pool holds each carried item in its run's blob**, in the order of arrival, until it claims it. An item that
101
+ leaves for a worker is no longer held: the worker, by its key, is the record. The Hook's answer leaves after the save
102
+ that records the item, so a `held` answer is durable.
103
+ - **The key check comes before the hold.** An item whose key a held item, an active worker, or a run of the worker's
104
+ Workflow holds is `existing`, and the answer names the run when one holds it.
105
+ - **`hold` bounds the held items**, 1000 by default. Above it, the Hook's answer is 429 (for a chat command, 200 with a
106
+ text that says why, ADR-0011) and the hook log keeps the delivery. The 1000 is not measured.
107
+ - **What a push loses**: a delivery while the Orchestrator is down, while no Assembly is current (503), while the Pool
108
+ is paused (`paused`), and above `hold`. A read Source loses nothing, because the next read finds the item.
109
+ - **A 429 does not claim the delivery's id.** The hook log keeps it as `hold-full`, and a repeat is found only by a
110
+ delivery that the door claimed (ADR-0011), so the service's send again is heard: a Gate that took it passes it over by
111
+ the run's answers, and a Pool that holds its item answers `existing`.
112
+ - **A Source's verdict on a delivery** is one of `held`, `existing`, `hold-full`, `reread`, `skipped` and `paused`, and
113
+ it goes in the delivery's outcome as a line of `sources` (ADR-0011).
114
+
115
+ ## A Pool of runs, and a Pool that stands
116
+
117
+ A Pool whose worker is a `dispatch` slot keys each item's run by `itemId`, scoped to the callee Workflow
118
+ ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)): every Pool, every hand-over and every `jr2 run` that
119
+ gives the same key for that Workflow gets the same run while its row lives, live and for `runs.retain` after it ends
120
+ ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)). `cap` counts the live runs that
121
+ this Pool dispatched, so its slot must say `wait: true`: with `started` alone, the Pool cannot know when a run ends. A
122
+ run that ends in any way frees its slot. A run that ends `failed`, `error`, `cancelled`, `lost` or `drifted`, or a start
123
+ that the door refuses, is a failure of its item, and never the Pool's `error`: the run is not the Pool's child. A cancel
124
+ of the Pool never cancels its runs (ADR-0071), and `customize(pool, { agents })` reaches nothing in them
125
+ ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)). The Pool spawns its slot with the item's id as
126
+ the spawn id, and the Effect sends that id as the key, not its ledger row id. The run's `dispatchedBy` gains `key`, and,
127
+ for a carried item, `hook: { name, delivery, onBehalfOf? }`, so `jr2 status` prints which Pool, key and delivery started
128
+ it. Neither is on the open band (ADR-0014): a key is the workflow's data. A run that a read claimed names no delivery:
129
+ the Pool's feed says why an item did or did not start, with a `pool` mark for each start (`started`), each end
130
+ (`settled`, with the run's status, `start-refused` or `dispatch-error`), each held item that a hand-over drops
131
+ (`carry-refused`) and each item of a read that has no id (`skipped`). A `wait` that the slot's timeout ends while the
132
+ run is still live (`status: "live"`) is sent again under its key, so the wait follows the run and `cap` still counts it.
133
+
134
+ **`standing: true` makes a Pool of runs stand.** It is two things:
135
+
136
+ - **Parking.** A standing Pool never ends by itself. It has no `drained` and no `deadlocked` end, no output and no
137
+ `failures` mode. When it has no work it waits for a delivery, a `reread` or its `pollEvery`. An item that ends is a
138
+ `settled` mark on the feed, with the run's status.
139
+ - **The standing run.** jr2 keeps one live run of the Workflow under the current Assembly
140
+ ([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)). Nobody starts it: jr2
141
+ starts it at boot and at the publish that adds it, and `jr2 run` of it is refused, except to resume it (below). A
142
+ dispatch to a standing Workflow is refused 409. jr2 **hands it over** to a new run at every publish, at drift, after
143
+ `error` (with back-off), and when its blob passes its bound. At every publish, because the Assembly is one hash and a
144
+ handler has no stamp of its own. The bound is 500 items taken or one day: each item taken is a dispatch, which keeps a
145
+ row in the Effect ledger for the run's life (ADR-0067), about 217 bytes, so 500 is about 110 KB a save. The bound
146
+ counts the items that the run took, not the keys that a hand-over carried to it: each carried key is sent again, so a
147
+ count of the ledger's rows would hand a run with a `cap` at the bound over again at once. **A run at the bound in
148
+ which no item that it took itself started a run is handed over as a run that ended `error` is**: after the back-off,
149
+ and it counts in the streak below. An item that ends with no run (`start-refused`, `dispatch-error`, `unreachable`,
150
+ `unknown`) is known only for its run's life (above), so a backlog of as many such items as the bound fills the bound
151
+ again in each new run. A hand-over at once would then loop with no pause and no end, and would hold the event loop.
152
+ jr2 ends that run `done`, with no `handedOverTo`, and starts the next run after the back-off. A run in which one item
153
+ that it took started a run (or found its run under its key) is handed over at once, and the count starts again. A
154
+ carried key does not count: the hand-over sends it again and it finds a run that an earlier run started, so a callee
155
+ run that lives for hours would hide the loop for as long as it lives. While each item that the run took is still in
156
+ flight, jr2 does not yet know, so it waits for the save of the first answer. The back-off before the hand-over after
157
+ the `n`-th counted run in a row is 1 s, 4 s, then 16 s. The numbers are not measured. The hand-over carries the held
158
+ items, parsed by the new Assembly's `item` schema (an item that does not parse is dropped, with a `carry-refused`
159
+ mark), and the active keys: the new run sends each one again with `wait: true`, the door answers `existing`, and the
160
+ wait follows that run, so `cap` still counts it. A held item is mapped by the new `itemInput`. A hand-over ends the
161
+ old run `done`, and its record carries `handedOverTo: <new run id>`: a standing Pool has no output, so its stint is
162
+ done. The start of the new run and the end of the old one are one synchronous step. A delivery that a Source hears
163
+ across that step (its key lookup awaits the store) goes on to the new run's Source, so the door never claims a
164
+ delivery that no run holds. jr2 keeps the **head** of each standing Workflow in a table of the store: its newest run,
165
+ when that run started, how many counted runs in a row ended, and why it is paused. jr2 writes the head in the same
166
+ step as the start. The sweep of rows older than `runs.retain`
167
+ ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)) never deletes the run that a head
168
+ names, so a paused Pool keeps its held items for as long as it is paused. A Workflow that a publish removes, or that
169
+ no longer stands, gets no hand-over: its live run holds its Assembly and runs on, as any run does (ADR-0069), until a
170
+ person cancels it. A standing run has no Caller: its record's `startedBy` is `{ kind: "jr2", why }`, with `why` one of
171
+ `boot`, `publish`, `hand-over` and `resume`
172
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)).
173
+ - **A cancel is a pause.** `jr2 send <run> --event CANCEL` of a standing run ends that run `cancelled`
174
+ ([ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)) and pauses its Workflow: jr2 starts it again at no boot and no
175
+ publish until `jr2 run <workflow>` resumes it. `jr2 up` and `jr2 status` print the Pool as paused, with the number of
176
+ items it holds. They read it from `GET /assembly` and from the answer of the publish, whose `standing` list gives each
177
+ standing Workflow's state (`live`, `paused` or `waiting`), its run and its count of held items. It holds no key and no
178
+ item, so it is on the open band with the held Assemblies ([ADR-0014](0014-observation-is-open-run-state-is-not.md)). A
179
+ paused Pool has no live run: its held items and active keys stay in the blob of the cancelled run. A resume is a
180
+ hand-over from that run, so the new run takes them and claims under `cap`, and a read Source reads again. The new run
181
+ records the Caller of the `jr2 run` that resumed it as `resumedBy`. On the wire the resume is
182
+ `POST /workflows/<workflow>/runs`, which answers `201 { runId, instanceId, resumed: true }`, and 409 while the
183
+ Workflow is live or waits out a back-off. `jr2 run <workflow> --drop-held` (`?drop-held=true`) resumes with no held
184
+ item, so a person can remove a poison item or a queue that they paused on purpose; a read Source then finds again what
185
+ is still true. A delivery to a paused Pool gets `paused`, and nothing is held, so a push Source loses it and a read
186
+ Source finds it at the resume. A delivery to a standing Pool in its back-off (no live run, and not paused) makes the
187
+ door answer 503 (ADR-0011), so the service sends it again. **One rule counts a run in the streak**, for each end that
188
+ hands over after the back-off: `error`, `failed` (a `fail()` of the Machine,
189
+ [ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md), which jr2 treats as
190
+ `error`) and the bound with no run started. A run in which an item that it took itself, not a carried key, started a
191
+ run starts the count again, and is handed over after 1 s. Any other run counts. So a run that took only items that the
192
+ callee's door refused, and then ended `error`, counts. After 3 hand-overs from counted runs in a row, jr2 pauses the
193
+ Pool when the next counted run ends, so a poison item, or a backlog that the callee's door refuses, cannot loop
194
+ without end. The pause says why: `errors` when the last run ended `error` or `failed`, and `bound` when it took its
195
+ bound; `jr2 status` and the Orchestrator's log line name that end. The 3 is not measured. The Pool's context keeps the
196
+ keys that the hand-over carried, so jr2 tells them from the items that the run took.
197
+
198
+ **What the load walk refuses**, with its fix in the message:
199
+
200
+ - `standing: true` on a Pool of child Machines: a child cannot be handed over.
201
+ - `standing: true` on a Pool that is not the top Machine of its Workflow: jr2 keeps one run for each standing Workflow,
202
+ so the Workflow is the Pool. The fix is to move the Pool to its own file in `workflows/`.
203
+ - A Pool of runs that does not stand. A batch of runs (a Pool of runs that a Caller starts and that ends) is not offered
204
+ now: its `itemId` is a key that lives for `runs.retain` past the run, so a second batch in that time does no work. To
205
+ add it later does not break anything.
206
+ - A Pool of runs whose `dispatch` slot has no `wait: true`, or has a `target`: a foreign worker is not designed.
207
+ - A Pool of runs with no `cap`. The author must choose it; a Pool of child Machines keeps its default of 1.
208
+ - A push Source on a Pool that ends when it drains, which would end `drained` at once. The types refuse it too.
209
+ - A worker that is a bare Workflow name.
210
+
211
+ **What `jr2 up` prints and warns.** For each standing Pool, it prints the chain from each Hook that reaches it to the
212
+ callee: `sentry → alerts → triage`. It warns when two standing Pools dispatch to one Workflow: `cap` is per Pool, so one
213
+ issue with two labels can take a slot in each, and the caps add.
214
+
215
+ **The callee must end the level.** A read Source re-reads a level, such as "the open issues with the label `jr2`". When
216
+ the callee's run ends and the label stays, the key keeps the item from a second run while the run's row lives.
217
+ `runs.retain` after the run ended (30 days by default), the row is swept, and the next read starts the item again. So
218
+ the callee (or the person) removes the label. A drifted callee holds its key for `runs.retain`, and nothing starts that
219
+ item again without a person.
35
220
 
36
221
  ## Terminal triage is three-valued, surfaced as status
37
222
 
38
- Forced by parking-is-retention (ADR-0012) plus workflows whose children park on gates (escalation, human review):
223
+ Forced by parking-is-retention (ADR-0012) plus workflows whose children park on gates (escalation, human review). It
224
+ holds for a Pool that does not stand:
39
225
 
40
226
  - **drained** — source empty and all children settled → final, or the run ends `failed` when items failed (below);
41
227
  - **deadlocked** — open-but-never-ready items with nothing progressing → surfaced distinctly (jr exit 2), not an
@@ -67,3 +253,33 @@ is a correct answer. The default is `"fail"` because `done` means that the work
67
253
  not exit 0 while every item failed. The deadlock is the usual end of a dependency backlog with a failure: a Source over
68
254
  a tracker keeps the failed item open, so its dependents never become ready. The `status` in the detail tells the two
69
255
  ends apart, and the two details have one shape.
256
+
257
+ ## Tests
258
+
259
+ The default e2e tier (`features/standing-pool.feature`) and `pool-sources.test.ts` prove the standing Pool of runs: a
260
+ push Source, the held items and `hold`, `cap`, `reread`, the latch and merged reads, a restart and a hand-over with no
261
+ item lost or started twice, a hand-over across a changed callee door, and the key per Workflow. They read with a
262
+ promise, because the default tier has no Instance Effector. The `@kind` tier proves a read through an `http` Effect.
263
+
264
+ ## Considered options
265
+
266
+ - **An Intake**: a kit-shipped `intake({ routes })`, each `route({ start, input, hooks, look, cap })`, that jr2 keeps as
267
+ a **Standing Workflow**. Rejected: it needs three concepts (Intake, route, Start) and two GLOSSARY terms, and reuses
268
+ nothing that an author knows about a Pool, though it is a Pool inside. It writes the mapping from a delivery to a
269
+ start twice, once in the handler and once in the look, and its look starts an issue that lost its label while it
270
+ waited. One intake run serves every route, so its fault stops every route; a standing Pool for each Source keeps a
271
+ fault in one Source.
272
+ - **A direct door**: a Hook that starts a run itself (`door()`, or a Listener's `startOn(machine, kind, …)`). Rejected:
273
+ it is the smallest form for one alert, but it cannot grow. A cap, a read of a level, a queue and an answer after a
274
+ start are each state over time, which a door does not have, and each needs a standing Machine. So an Instance gets two
275
+ ways to start a run. It also makes a Hook more than a door that checks and decides nothing (ADR-0078).
276
+ - **A router that the author writes**: a hand-written standing Machine with `dispatch({ key })`, regions and a table of
277
+ runs in flight. Rejected: about 90 lines of mechanism for 25 lines of intent. When a judge wrote one alert's router
278
+ from it, the result had no hand-over and no routes for a child: its table went stale at each publish, and the first
279
+ refused start faulted it. A run holds its Assembly, so a router keeps its old handlers after a publish; a router that
280
+ ends `error` has no open Gate, so each later delivery reaches nothing; and a Kit bump drifts it and loses its backlog.
281
+ - **Two factories over one Source port**: `pool()` for children and batches, and a second factory for standing Pools of
282
+ runs. Rejected: the split removes three rows from the table of differences, and costs a GLOSSARY term for the second
283
+ factory, which is the Intake again.
284
+ - **A batch Pool of runs now.** Rejected for now, as above: no scenario needs it, and its key makes a second batch
285
+ within `runs.retain` do nothing. It adds later without a break.
@@ -7,9 +7,9 @@ hands: the [ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-
7
7
  Submission — forget it and the Agent is silently mute, the same footgun class ADR-0016 removed from workflows), the
8
8
  ADR-0004/0012 sandbox geography (working tools execute in the Harness container against `/work`), the dependency pin
9
9
  that must match the Orchestrator's wire, and the image contracts the operator imposes (numeric uid, `:8080` = Ready, git
10
- present). The only content a user genuinely owns is the persona: model + instructions (+ someday tools/skills).
11
- GLOSSARY.md already promised the split — an Agent is a definition jr2 maps into the Harness, and the Instance folder
12
- holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of the things it can).
10
+ present). The only content a user genuinely owns is the Agent's definition: model + instructions, and the MCP servers it
11
+ uses. GLOSSARY.md already promised the split — an Agent is a definition jr2 maps into the Harness, and the Instance
12
+ folder holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of the things it can).
13
13
 
14
14
  ## Decision
15
15
 
@@ -53,6 +53,12 @@ holds only what a Machine cannot carry (ADR-0049/0050: the definition is one of
53
53
  Stand-in. `harness.provider.apiKey` is one implicitly: the held secret `JR2_PROVIDER_API_KEY`, bound to `baseUrl`'s
54
54
  host, so it leaves the `jr2-harness-env` Secret and `baseUrl` must be `https://` when a key is set. A known model key
55
55
  in `harness.env`, or in an `envFrom` Secret, is refused.
56
+ - **MCP is the one extension contract for tools.** Beyond its Working tools and its Menu, an Agent gets tools only from
57
+ the MCP servers that its definition names (`mcp`), and jr2 has no tool API of its own
58
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
59
+ definition names a server by the name that the `mcp` block of `jr2.config.ts` gives it, never by its url or its
60
+ secret: as `harness` declares reach for models, the `mcp` block declares reach for tools, and the definition makes the
61
+ choice.
56
62
  - **A catalog provider can be moved to a gateway, and stays a catalog provider.** `harness.catalog.<id>.baseUrl`
57
63
  replaces the base URL of every model of one pi catalog provider — a LiteLLM gateway in front of Bedrock, for
58
64
  `anthropic/…`. The catalog keeps its limits, cost and thinking levels; only the endpoint moves. It is reach, not a
@@ -99,11 +105,13 @@ Definitions keep converge-time safety; overrides pay the later check for the fle
99
105
 
100
106
  ## Consequences
101
107
 
102
- - The definition contract is data-only: what an Agent may do to the Workspace is part of it (`access`, ADR-0028); custom
103
- tools/skills/subagents have no seat yet. When someone needs them, the contract grows deliberately — there is no eject
104
- hatch to a foreign harness project (ADR-0027 deleted it).
108
+ - The definition contract is data-only: what an Agent may do to the Workspace is part of it (`access`, ADR-0028), and so
109
+ are the MCP servers it uses (ADR-0079); an author's own tool is an MCP server. Skills and subagents have no seat yet.
110
+ When someone needs them, the contract grows deliberately — there is no eject hatch to a foreign harness project
111
+ (ADR-0027 deleted it).
105
112
  - Because definitions are plain data, stock ones compose with no API: a Machine carries `agent(coder)`, and a consumer
106
- retunes it with `customize(machine, { agents: { coder: { model: "…" } } })` (ADR-0049, ADR-0009).
113
+ retunes it with `customize(machine, { agents: { coder: { model: "…" } } })` (ADR-0049, ADR-0009), or rebinds its
114
+ servers with `{ mcp: [...] }` in the same place.
107
115
  - An invalid definition fails at admission, loudly, naming the slot: a model no registry resolves, a malformed field. A
108
116
  Machine with no Agent slots is valid — a `workspace()` body parking a Sandbox (ADR-0012) invokes none. Readiness is
109
117
  binding `:8080`, with no boot build ahead of it (ADR-0027).