@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
@@ -31,11 +31,11 @@ because they are one problem: **what a Machine depends on that is not inside it.
31
31
  names of the secrets it names — or the `open` sentinel where a package left the url, the argv or a secret's name for
32
32
  its composer ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). `jr2 up` reads them
33
33
  without running anything, and a secret named there is what places it.
34
- - **The Sandbox Image is a `workspace()` option**, static: `workspace(body, { input, image, repos, spec })` (`repos`
35
- is ADR-0051's, and follows the same rule). `image` is a string in one of two shapes — a `file:` URL
34
+ - **The Sandbox Image is a `workspace()` option**, static: `workspace(body, { input, image, repos, workGroup })`
35
+ (`repos` is ADR-0051's, and follows the same rule). `image` is a string in one of two shapes — a `file:` URL
36
36
  (`import.meta.resolve("./image")`, a docker context the module ships) or a registry ref — or absent, which keeps
37
- ADR-0037's fallback. It moved out of the per-run spec because `jr2 up` must find it statically. It is never
38
- persisted: the provisioning state re-reads it off the Machine on restore.
37
+ ADR-0037's fallback. It is static because `jr2 up` must find it with no run. It is never persisted: the provisioning
38
+ state re-reads it off the Machine on restore.
39
39
  - **The definition rides the Turn.** The admission body carries the slot's definition; the Harness runs what it was
40
40
  handed and re-reads it per Submission as before. The Instance roster (`agents/`, then `config.agents`), its ConfigMap,
41
41
  and `JR2_AGENTS_JSON` retire — a flat roster cannot hold two `researcher`s, and a Machine edit already needs `jr2 up`
@@ -47,27 +47,27 @@ because they are one problem: **what a Machine depends on that is not inside it.
47
47
  ADR-0011 adopted for Vocabulary.
48
48
  - **Customization is `customize(machine, parts)`**, a plain function in the family of `workspace()` and `pool()`,
49
49
  returning a plain `StateMachine`. `parts` has the declaration's own shape, recursively partial:
50
- `{ agents?: { <slot>?: Partial<AgentDefinition> }, effects?: { <slot>?: { url } | { argv } | { secret } }, image?, user?, repos?: { <slot>?: Binding }, actors?: { <child>?: parts } }`
50
+ `{ agents?: { <slot>?: Partial<AgentDefinition> }, effects?: { <slot>?: { url } | { argv } | { secret } | { server } | { tool } }, image?, user?, repos?: { <slot>?: Binding }, actors?: { <child>?: parts } }`
51
51
  — the two image seats together, since ADR-0005 gives the User Container the same static option, and the Repo Slots
52
52
  beside them, since ADR-0051 makes a Repo a `workspace()` slot a composer binds. `effects` binds an Effect's Open url,
53
- argv or secret name, once — an Effect with an Open secret and an Open url or argv takes one object with both keys,
54
- `{ url, secret }` or `{ argv, secret }`
55
- ([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)): the slot's logic is built
56
- again with the bound part and substituted as an Agent override is. Internally an Agent override is xstate's
57
- `machine.provide({ actors: { researcher: agent(merged) } })`, with the override layered over the stock definition; a
58
- child override is the same call one level down, recursing — each level is exactly the one-level reach ADR-0015 found
59
- `provide` has, held by the composer who owns the child object, never host-side injection. jr2's wrappers are
60
- transparent: `customize(research, { agents })` reaches the body through the `body` slot (and `pool()`'s `worker`, at
61
- any depth), so a consumer never writes `body`. The images and the Repo bindings are the parts `provide` cannot carry
62
- (xstate copies only implementations, and every part is keyed on the config it passes through by reference), so those
63
- fields clone the wrapper's config and rebuild it with the same implementations, re-attaching what the original
64
- carried. A bound argv's load warnings are such a part too: `customize()` rebuilds the Machine the author wrote the
65
- same way, with the vocabulary, the seats and the door it carried and the warnings the walk gives for the bound argv,
66
- so no attachment is keyed on anything but the config. Only DECLARED parts can be retuned, and the compiler is what
67
- says so: the slots are read off xstate's own `TActor` parameter, so an Agent, a Repo Slot, or a child the Machine does
68
- not carry is a type error (ADR-0050), and the runtime refuses the same call naming the Machine's own slots. Rejected:
69
- a `.with()` method on the machine — it needs a jr2-owned machine type over xstate's, which ADR-0015 avoided, and it
70
- vanishes after any `.provide()`; a callable-machine hybrid — verified to work, reads as a trick.
53
+ argv, MCP server and tool, or secret name, once — an Effect with an Open secret and an Open url or argv takes one
54
+ object with both keys, `{ url, secret }` or `{ argv, secret }`, and an `mcp` Effect with an Open server and tool takes
55
+ `{ server, tool }` ([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)): the
56
+ slot's logic is built again with the bound part and substituted as an Agent override is. Internally an Agent override
57
+ is xstate's `machine.provide({ actors: { researcher: agent(merged) } })`, with the override layered over the stock
58
+ definition; a child override is the same call one level down, recursing — each level is exactly the one-level reach
59
+ ADR-0015 found `provide` has, held by the composer who owns the child object, never host-side injection. jr2's
60
+ wrappers are transparent: `customize(research, { agents })` reaches the body through the `body` slot (and `pool()`'s
61
+ `worker`, at any depth), so a consumer never writes `body`. The images and the Repo bindings are the parts `provide`
62
+ cannot carry (xstate copies only implementations, and every part is keyed on the config it passes through by
63
+ reference), so those fields clone the wrapper's config and rebuild it with the same implementations, re-attaching what
64
+ the original carried. A bound argv's load warnings are such a part too: `customize()` rebuilds the Machine the author
65
+ wrote the same way, with the vocabulary, the seats and the door it carried and the warnings the walk gives for the
66
+ bound argv, so no attachment is keyed on anything but the config. Only DECLARED parts can be retuned, and the compiler
67
+ is what says so: the slots are read off xstate's own `TActor` parameter, so an Agent, a Repo Slot, or a child the
68
+ Machine does not carry is a type error (ADR-0050), and the runtime refuses the same call naming the Machine's own
69
+ slots. Rejected: a `.with()` method on the machine — it needs a jr2-owned machine type over xstate's, which ADR-0015
70
+ avoided, and it vanishes after any `.provide()`; a callable-machine hybrid — verified to work, reads as a trick.
71
71
  - **A package exports a Machine, nothing beside it.** The door (ADR-0033), the vocabulary, the Agents, and the image all
72
72
  ride the exported object. The three uses:
73
73
 
@@ -17,7 +17,12 @@ a definition lives, and the gate that enforces it.
17
17
  an Effect names it with `secret("name")`, and the Instance declares it in the top-level `secrets` list
18
18
  ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). The name is a string, not a type: the
19
19
  walk after the typecheck refuses an Effect that names a secret the list does not hold, before anything is built.
20
- `jr2.config.ts` holds reach and credentials (`harness`, `secrets`, `git.credentials`, `registry`).
20
+ `jr2.config.ts` holds reach and credentials (`harness`, `secrets`, `git.credentials`, `registry`, and the Callers'
21
+ `callers`, `hooks` and `cors`,
22
+ [ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A Machine names no Hook: a
23
+ Gate's or a Source's handlers are keyed by the Hook's kind ([ADR-0011](0011-workflow-defined-events.md)), and only a
24
+ Hook's address names it. A Hook kind is code that the config and the Workflows import by name, so it is neither
25
+ declared nor discovered: it comes in by `import`, as a nested Machine does, and `jr2 up` bundles it into the Assembly.
21
26
  - **The slot key types a Repo.** A `workspace()` declares its Repos as slots —
22
27
  `repos: { target: open, docs: { url, ref } }` — and the keys are a phantom on the wrapper type, read through
23
28
  `pool()`'s `worker` and `workspace()`'s `body` as the image is (ADR-0049). A
@@ -1,7 +1,7 @@
1
1
  # A Repo is a slot on the `workspace()` and a cache on the node
2
2
 
3
3
  ADR-0049 moved Agents and the Sandbox Image into the Machine and left one dependency outside it: the Repo, catalogued in
4
- `jr2.config.ts` and named by string from a `workspace()` spec, with the name typed back through a `Register` interface
4
+ `jr2.config.ts` and named by string from a `workspace()`, with the name typed back through a `Register` interface
5
5
  (ADR-0050). The grill of 2026-09-13 found two faults. A packaged Machine still could not say which repository it works
6
6
  on, nor could a consumer bind one, except by the consumer's config holding the right NAME and the door threading it —
7
7
  the same encapsulation gap ADR-0049 closed for Agents. And the catalog's shape was dictated by storage: one read-only
@@ -20,28 +20,32 @@ decision: **what a Machine says about a repository, and what the cluster does wi
20
20
  target: open, // the consumer's — must be bound
21
21
  docs: { url: "https://github.com/acme/handbook.git", ref: "v3" }, // the package's own
22
22
  },
23
- spec: ({ input }) => ({ branch: input.branch }),
24
23
  });
25
24
  ```
26
25
 
27
26
  A slot is in one of three states, and the walk tells them apart without evaluating anything: **bound** (a url, or
28
27
  `{ url, ref? }`), **open** (the `open` sentinel — someone downstream binds it), or **per-run** (a mapper over the
29
28
  wrapper's door: `feature: ({ input }) => ({ url: input.feature.repo, ref: input.feature.baseRef })`). `ref` is the
30
- base the branch worktree is cut from; absent, the repository's own default branch. The spec no longer carries repos —
31
- it is `{ branch, workGroup?, reviewSha? }`, or `{ workGroup? }` for a Workspace with no slot (below). The body's
32
- handles are keyed by slot (`workspace.repos.target`), and so are the in-pod paths (`/work/<slot>/<branch>`): a slot
33
- key is collision-free by construction and the same in every Instance that consumes the package, so a prompt can name a
34
- path and be right everywhere.
29
+ Binding's base: `default/` stays on it, and a new branch starts from it. A branch is attached, and a tag or a commit
30
+ is detached. Absent, the base is the repository's own default branch. The body's handles are keyed by slot
31
+ (`workspace.repos.target` is `/work/target`), and so are the in-pod paths (`/work/<slot>/<name>`): a slot key is
32
+ collision-free by construction and the same in every Instance that consumes the package, so a prompt can name a path
33
+ and be right everywhere.
35
34
 
36
- - **The handles are the slot map and the branch — no slot is the `workdir`.** `workspace.repos` keeps the slots in
37
- declaration order, and jr2 reads nothing into that order. Which checkout an Agent works in is a fact about that
38
- Agent's Turn, not about the Workspace: a body with a `backend` and a `frontend` slot frames one Agent with the first
39
- path and another with the second, and either may read the other's tree. A body that names no slot may give the order a
40
- meaning of its own and document it (`task`: the first slot is the one the coder edits). The order is the kit's promise
41
- — a slot key starts with a letter, because JS reads an integer-like key first wherever it was written — and the
42
- meaning is the Machine's. Rejected: a `workdir` handle, the first declared slot's worktree — it made a singular
43
- "primary repo" a property of every Workspace, which is wrong for a body whose Agents work in different slots or in
44
- more than one, and it hid a rule in key order that the body never chose.
35
+ - **The handles are the slot map — no slot is the `workdir`.** `workspace.repos` keeps the slots in declaration order,
36
+ and jr2 reads nothing into that order. A handle is the slot's folder and not a Worktree: the Workspace only clones,
37
+ and a Machine cuts each Worktree at `/work/<slot>/<name>`
38
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). Which checkout an Agent works in is a
39
+ fact about that Agent's Turn, not about the Workspace: a body with a `backend` and a `frontend` slot frames one Agent
40
+ with a Worktree of the first and another with a Worktree of the second, and either may read the other's tree. An
41
+ Effect names its Worktree as a Turn does, in its input: a `command` and an `http` take no `slot`, and a `push` keeps
42
+ its static `slot`, which says the Repo Slot whose credential it spends. A slot that is only read needs no Worktree: an
43
+ Agent reads `/work/<slot>/default`. A body that names no slot may give the order a meaning of its own and document it
44
+ (`task`: the first slot is the one the coder edits). The order is the kit's promise — a slot key starts with a letter,
45
+ because JS reads an integer-like key first wherever it was written — and the meaning is the Machine's. Rejected: a
46
+ `workdir` handle, the first declared slot's worktree — it made a singular "primary repo" a property of every
47
+ Workspace, which is wrong for a body whose Agents work in different slots or in more than one, and it hid a rule in
48
+ key order that the body never chose.
45
49
 
46
50
  - **A Machine whose body names no slot declares the whole map Open: `repos: open`.** A body that enumerates whatever is
47
51
  attached — `@jr2/machines`'s `task` — knows nothing a slot name could say, and a `target: open` on it would claim "one
@@ -61,21 +65,18 @@ decision: **what a Machine says about a repository, and what the cluster does wi
61
65
  have a second Agent edit it, upload it. Its steps share `/work`, as the steps of one GitHub Actions job share that
62
66
  job's folder; two Workspaces are two jobs and share nothing. So `workspace(body, { input })` with no `repos` is a
63
67
  Workspace with no Repo Slot: the attach is skipped (there is nothing to clone, and the pod's `fsGroup` already makes
64
- `/work` the work group's), the body's handles are `{ repos: {} }` with no branch, typed `Workspaced<…, never>`, and a
65
- Turn that frames no `cwd` and a `command` both work in `/work`. The spec has nothing to cut a branch in: `spec` is
66
- optional, gives only `workGroup`, and a `branch` or a `reviewSha` in it is refused — at the type, and when the run
67
- enters, since a value jr2 silently ignores is the failure the refusals exist for. `repos: {}` is refused with the
68
- pointer to leaving it out, so "none" has one spelling and an empty map never reads as one somebody forgot to fill. An
69
- Open map still asks `customize` for at least one slot: Open says the composer names the slots, and a body that
70
- enumerates its checkouts (`task`) has nothing to do with none. Two Effects need a slot and are refused there, by
71
- `jr2 up` and at invoke, with one reason: a `push` publishes a slot's branch, and a `command` with an Effect secret
72
- runs against one commit of it (ADR-0067). A file-body `http` reads `/work` and stays. `/work` is an `emptyDir`, so a
73
- Lost Sandbox takes everything with it — with a repository, pushed commits outlive the pod; here only what an Effect
74
- sent out does, and the body hears `workspace.lost` as any body does. Storage that outlives the pod, the job's
68
+ `/work` the work group's), the body's handles are `{ repos: {} }`, typed `Workspaced<…, never>`, and a Turn that
69
+ frames no `cwd` and a `command` both work in `/work`. `repos: {}` is refused with the pointer to leaving it out, so
70
+ "none" has one spelling and an empty map never reads as one somebody forgot to fill. An Open map still asks
71
+ `customize` for at least one slot: Open says the composer names the slots, and a body that enumerates its checkouts
72
+ (`task`) has nothing to do with none. Three Effects need a slot and are refused there, by `jr2 up` and at invoke, for
73
+ one reason: a `worktree()` cuts in a slot's clone, a `push` publishes a slot's branch, and a `command` with an Effect
74
+ secret runs against one commit of it (ADR-0067). A file-body `http` reads `/work` and stays. `/work` is an `emptyDir`,
75
+ so a Lost Sandbox takes everything with it — with a repository, pushed commits outlive the pod; here only what an
76
+ Effect sent out does, and the body hears `workspace.lost` as any body does. Storage that outlives the pod, the job's
75
77
  "artifacts", is a decision of its own. Rejected: a placeholder repository bound to satisfy the rule — a clone nobody
76
- uses, a branch that means nothing, a Worktree that becomes every Agent's default `cwd`, and a cache entry to warm.
77
- Rejected: a new term for the repository-less Sandbox — the Glossary's Workspace never named a repository, so none was
78
- needed.
78
+ uses, a Worktree that means nothing, a `cwd` that each Turn must then state, and a cache entry to warm. Rejected: a
79
+ new term for the repository-less Sandbox — the Glossary's Workspace never named a repository, so none was needed.
79
80
 
80
81
  - **The consumer binds with `customize`**, in the same call as Agents and the image, nested through `actors` like every
81
82
  other part: `customize(codeReview, { repos: { target: "git@github.com:ourorg/app.git" } })`. Any of the three forms is
@@ -89,6 +90,14 @@ decision: **what a Machine says about a repository, and what the cluster does wi
89
90
  is the thing, not a reference into someone else's file. Two spellings of one repository (`https://`, `git@…:`,
90
91
  `ssh://`, with or without `.git`) resolve to one identity — host plus path, scheme and user dropped — and therefore
91
92
  one cache. A door that offers a menu of repositories is the Instance's own `z.enum` of urls.
93
+ - **The cache key names one identity.** The key is the Repo CR's name, the hostPath folder, the volume name `repo-<key>`
94
+ and the mount `/repos/<key>`: a slug of the identity's last segment, at most 16 characters, a dash, and the first 32
95
+ hex digits (128 bits) of the identity's sha256, at most 49 characters in all. No one can find two identities with one
96
+ key, and a per-run url is the Agent's text, so a shorter hash is a way into another Repo's cache: with 32 bits, a
97
+ search found a url of an attacker's repository with the key of a private one in seconds. Each Repo CR states its
98
+ identity (`jr2.dev/identity`). The Orchestrator refuses to write a CR whose stated identity is not the one that it
99
+ attaches or binds, and names both urls: no url, no `secretRef` and no clock of one repository is written on a CR of
100
+ another.
92
101
  - **The cluster keeps a bare cache per node, not a volume.** Each repository is a `Repo` custom resource the operator
93
102
  reconciles (spec: url, refresh interval; status: per node — synced, generation, last error). A cache agent runs as a
94
103
  DaemonSet per Instance over its Sandbox nodes (ADR-0052) with a hostPath directory: it clones a repository onto its
@@ -100,9 +109,8 @@ decision: **what a Machine says about a repository, and what the cluster does wi
100
109
  on-demand fetch; every cache speaks git protocol v2, so a fetch advertises only the refs it asks for; and the refresh
101
110
  interval carries ±20% jitter, like the Lease, so R Repos on N nodes do not hit a remote on one beat (scaling review
102
111
  R13, 2026-09-30). The agent also consolidates a cache's packs without deleting an object (ADR-0004). The
103
- Orchestrator's attach then runs unchanged: `git clone --shared` off the mount, worktree beside it. A Sandbox that
104
- lands on a cold node pays one clone there, once — the same economics as an image pull, and the reason a Sandbox can
105
- run on any node.
112
+ Orchestrator's attach then runs unchanged: `git clone --shared` off the mount. A Sandbox that lands on a cold node
113
+ pays one clone there, once — the same economics as an image pull, and the reason a Sandbox can run on any node.
106
114
  - **The Orchestrator creates `Repo` CRs; it does not sync them.** At boot it walks its registered Machines and creates
107
115
  one CR per bound identity, so a statically known repository is KNOWN before a run can ask: the cache agent on every
108
116
  node probes it (`git ls-remote`) as soon as the CR exists, so a wrong url or a credential that does not reach shows in
@@ -123,10 +131,13 @@ decision: **what a Machine says about a repository, and what the cluster does wi
123
131
  whose clone Secret `git-clone.json` does not list gets none, and the clone is anonymous; the cache agent reads only
124
132
  that Secret, and the operator matches nothing. The Orchestrator writes and reads no git credential (ADR-0068). The
125
133
  Secret uses Flux's key names (`username`/`password`; `identity`, `identity.pub`, `known_hosts`), so a Flux or Argo
126
- user reuses the Secret they have. **The list is also the fence.** A per-run url — a run input, a ticket field — is
127
- otherwise a way to spend the cluster's credential against any host: one that matches no entry is refused at attach,
128
- naming the list. A static binding is code the Instance typechecked and deployed, so it is admitted without a match and
129
- cloned anonymously; a private one then fails at the cache with git's error. `jr2 init` scaffolds one wildcard entry —
134
+ user reuses the Secret they have. With a token, the cache agent's helper answers for the Repo's own origin alone
135
+ (scheme, host and port), and its clone, probe and fetch follow no redirect, so the token never reaches an origin
136
+ outside the entry. A repository that moved, and that its old url redirects to, is cloned by its new url. **The list is
137
+ also the fence.** A per-run url — a run input, a ticket field — is otherwise a way to spend the cluster's credential
138
+ against any host: one that matches no entry is refused at attach, naming the list. A static binding is code the
139
+ Instance typechecked and deployed, so it is admitted without a match and cloned anonymously; a private one then fails
140
+ at the cache with git's error. `jr2 init` scaffolds one wildcard entry —
130
141
  `{ match: "*", token: "JR2_GIT_TOKEN", sshKey: "jr2-git-ssh" }`, today's two implicit defaults made visible, with a
131
142
  comment saying to narrow it before anything untrusted can start a run, and that it also pushes: a `push()` whose Repo
132
143
  Slot url it matches, a per-run url included, spends the same token or key — so the open fence is in the user's file
@@ -198,15 +209,15 @@ decision: **what a Machine says about a repository, and what the cluster does wi
198
209
  the kit's first cluster requirement a storage class most small clusters lack.
199
210
  - **A Flux-style `GitRepository` serving an artifact over HTTP.** Rejected: an artifact download is a clone per Sandbox
200
211
  — the cost the cache exists to avoid.
201
- - **Urls kept in the spec, not the slot** (the shape before this decision, with a url in place of a name). Rejected: the
202
- walk cannot see inside the spec, so a bound repository would be invisible to `jr2 up` and to prewarm, and `customize`
203
- would have nothing to bind.
212
+ - **Urls from a per-run function of the `workspace()`, not the slot** (the shape before this decision, with a url in
213
+ place of a name). Rejected: the walk cannot see inside a function, so a bound repository would be invisible to
214
+ `jr2 up` and to prewarm, and `customize` would have nothing to bind.
204
215
 
205
216
  ## Consequences
206
217
 
207
218
  - The operator becomes git-aware, which ADR-0001 kept it from being: the `Repo` CRD and the cache agent are its. The
208
- Sandbox CRD stays free of git semantics beyond a list of identities; clone and worktree remain the Orchestrator's
209
- post-`Ready` step (ADR-0004).
219
+ Sandbox CRD stays free of git semantics beyond a list of identities; the clone remains the Orchestrator's post-`Ready`
220
+ step, and a Worktree is a Machine's cut (ADR-0004).
210
221
  - `workspace()`'s `repos` names at least one slot, or is `open` whole — in which case the `customize` that names the map
211
222
  is held to the same rule — or is left out, for a Workspace with no Repo Slot; a Machine that composes no Sandbox has
212
223
  no repos to customize, and the data-plane switch ("does this Instance need Sandboxes") is "a registered Machine
@@ -214,12 +225,12 @@ decision: **what a Machine says about a repository, and what the cluster does wi
214
225
  Sandboxes, the cache agent and the Sandbox Images; a deployed Orchestrator wires its kube client whatever its current
215
226
  Assembly composes, because a publish can bring the first `workspace()` without a roll
216
227
  ([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)).
217
- - Every slot gets a branch worktree cut at the run's branch, a reference checkout included. A slot has no read-only
218
- mode; that is a decision of its own if it is ever wanted.
228
+ - A slot has no read-only mode, and needs none: a slot in which a Machine cuts nothing holds only `default/`, on the
229
+ Binding's base.
219
230
  - The Console's start form loses the catalog dropdown: a per-run repository is a url field unless the Machine's door
220
231
  enumerates its own.
221
- - The volume paths change: the in-pod source mount is `/repos/<key>` where `<key>` is derived from the identity, and the
222
- pod-local layout is `/work/<slot>/{default,<branch>}`. Existing checkouts on a pre-0051 PVC are not migrated.
232
+ - The volume paths change: the in-pod source mount is `/repos/<key>` where `<key>` is the identity's cache key, and the
233
+ pod-local layout is `/work/<slot>/{default,<name>}`. Existing checkouts on a pre-0051 PVC are not migrated.
223
234
  - ADR-0047's deploy-key prompt reads ssh urls off the walk. A per-run ssh url first seen at attach was never prompted
224
235
  for; its first attach fails with the key hint, as any unregistered key does.
225
236
  - A `customize` of a per-run slot with a static url is legal and makes it bound; a Machine that wants a slot to stay the
@@ -27,6 +27,11 @@ use. **Which nodes are an Instance's Sandbox nodes, and who follows that set** i
27
27
  Sandbox node, so a cache is only ever present where a Sandbox can land, and the affinity it feeds only ever names a
28
28
  usable node. The build set (ADR-0045) is the union of the nodes an ordinary pod lands on — the Orchestrator
29
29
  Deployment's own placement — and the Sandbox nodes, intersected with the supported set as before.
30
+ - **The cache agent also reports Landlock for its node.** It probes Landlock when it starts, and on a node without it
31
+ the agent is NotReady, with the reason. `jr2 up` reads the DaemonSet's pods and refuses to converge while a Sandbox
32
+ node lacks Landlock, naming each one ([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)). This is the
33
+ one refusal on the node set: Landlock is a fact that each Sandbox node must have, not a set that can be empty. The
34
+ admin keeps a bad node out with `sandbox.nodeSelector` or a taint.
30
35
  - **The Sandbox CR carries the placement.** `Sandbox.spec` gains `nodeSelector` and `tolerations` beside `resources`,
31
36
  `initContainers`, and `volumes`; the Orchestrator writes them from its baked config at provision and the operator
32
37
  copies them onto the pod, merging nothing. The operator stays config-blind (ADR-0001), and every Instance's placement
@@ -29,8 +29,8 @@ a fetch inside a pod reaches the remote, and who may ask** is one decision.
29
29
  Binding's own spelling, the caller's own credential, never the Agent's (ADR-0005).
30
30
 
31
31
  - **The ask rides the Sandbox CR and reaches the agent through the pod.** The program asks the Custodian on `localhost`
32
- (`POST /fetch`, with the Sandbox token's Stand-in — a constant, because the User Container gets no env); the Custodian
33
- puts the token it holds in place of the Stand-in and asks the Orchestrator for THIS pod's Sandbox
32
+ (`POST /fetch`, with the fetch token's Stand-in — a constant, because the User Container gets no env); the Custodian
33
+ puts the fetch token in place of the Stand-in, on this route alone and asks the Orchestrator for THIS pod's Sandbox
34
34
  (`POST /sandboxes/<name>/fetch` — the program cannot name another,
35
35
  [ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)); the Orchestrator patches one
36
36
  annotation on the Sandbox CR, per Repo key, timestamp value — the Lease's shape, an annotation the Orchestrator
@@ -45,16 +45,28 @@ a fetch inside a pod reaches the remote, and who may ask** is one decision.
45
45
  sticky, once per pod life. The Orchestrator's route waits on that entry — a Sandbox watch event, never a poll
46
46
  ([ADR-0063](0063-the-orchestrator-watches-the-cluster.md)) — then answers the Custodian, which answers the program.
47
47
  One wait, and it is the wait an attach already makes.
48
- - **The scope is the Sandbox token's scope, and no seat gains a credential.** A Sandbox may ask for the caches it mounts
49
- and nothing else; the remote is reached by the cache agent with the Repo CR's `secretRef`, the credential that cloned
50
- the cache. The pod holds none before and none after. The Menu is untouched — a fetch is not something the Agent
51
- _says_, it is something it _does_ — and no Machine declares anything: the ability is ambient in every Workspace pod,
52
- the way Working tools are, and absent on the Instance Harness, which mounts no Repo.
48
+ - **The scope is the fetch token's scope, and no seat gains a credential.** The fetch token is valid on the fetch route
49
+ alone, and the control token is not valid there
50
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). A Sandbox may ask for the
51
+ caches it mounts and nothing else; the remote is reached by the cache agent with the Repo CR's `secretRef`, the
52
+ credential that cloned the cache. The pod holds none before and none after. The Menu is untouched — a fetch is not
53
+ something the Agent _says_, it is something it _does_ — and no Machine declares anything: the ability is ambient in
54
+ every Workspace pod, the way Working tools are, and absent on the Instance Harness, which mounts no Repo.
53
55
  - **Freshness degrades, absence does not, and the caller is told.** A remote fetch that fails or outruns the on-demand
54
56
  budget lets the program fall through to the objects the cache holds — ADR-0051's stance for attach — and writes one
55
57
  line to stderr, which git passes through verbatim:
56
58
  `warning: jr2: remote fetch failed (<git's error>); serving the cache as of <lastFetched>`. The budget is the cache
57
- agent's one on-demand number; the program's wait is that plus watch slack, and nothing else owns a timeout.
59
+ agent's one on-demand number; the program's wait is that plus watch slack, and nothing else owns a timeout. A cut
60
+ reads that line too: its output carries the line in `stale`, and the cut uses what the clone holds. A branch name that
61
+ the clone does not hold is then `refused`, because the remote cannot confirm that it is new
62
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
63
+ - **One fetch at a time in a clone.** All Worktrees of a clone share `refs/remotes/origin/*`, and git updates a ref only
64
+ if it still holds the value that the fetch read. So two fetches that overlap on a moved remote can fail with
65
+ `cannot lock ref`. The failure does no harm, but it costs a Turn. So the program takes a per-clone lock after the ask,
66
+ so a dead remote does not make coders wait in line. `git upload-pack` can end before the fetch writes its refs: that
67
+ happens when a pack moves. In a shared clone, git often keeps the connection open to the end. The lock must last to
68
+ the end of the fetch in both cases, so a small child process that the program leaves watches the fetch and holds the
69
+ lock. ADR-0075 holds the measurements.
58
70
  - **Every seat that holds the checkouts holds the program.** The fetch url lives in the shared `default/.git/config`, so
59
71
  a seat without the program has checkouts whose `git fetch` dies. That is ADR-0005's own argument for `/repos` — half
60
72
  of one exception, not a second — applied once more: the pod's `/opt/jr2` volume is mounted read-only into the User
@@ -31,48 +31,63 @@ the one part ADR-0049/0051 left a package unable to leave unsaid: **the model.**
31
31
  who never reads the file spends money on a model they did not choose, silently, which is the failure class `jr2 up`
32
32
  exists to make loud. Rejected: a Machine factory `task({ model })` — ADR-0049 settled that a package exports a Machine
33
33
  and retunes with `customize`, and a factory reopens that.
34
- - **Open widens to an Effect's url, argv and secret, bound once.** A package cannot know the host its `http` Effect
35
- calls, the program its `command` runs
34
+ - **Open widens to an Effect's url, argv, MCP server and tool, and secret, bound once.** A package cannot know the host
35
+ its `http` Effect calls, the program its `command` runs, the MCP server and tool its `mcp` Effect calls
36
36
  ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)), or the secret its
37
37
  composer spends, so it may leave each Open: `http({ method, url: open, auth, input, request })`,
38
- `http({ method: "PUT", url: open, body: file(path) })`, `command(open, { env, pin })`, and `secret(open)` in place of
39
- a secret's name. The url, the argv and the secret's name are the Open parts of an Effect that exist; everything else
40
- the definition says is the package's as written — the method, the headers, the auth and the secrets they name, the
41
- input, `request`, the env, `pin` — and is checked when the Machine is built. `secret(open)` is taken by `bearer`,
42
- `basic` (the user or the password), a header's value, and a command's env, as its value or its `.path`; in a url it is
43
- refused when the Machine is built ("leave the whole url Open"), since a url that carries a secret is left Open whole
44
- (below). An Effect takes one Open secret: a second `secret(open)` in one definition is refused when the Machine is
45
- built ("an Effect takes one Open secret"); the one `secret(open)` may stand in several places, a value and its
46
- `.path`. An Effect that names an Open secret is placed as one that names a secret is — a credentialed command in the
47
- Sandbox Effector, an `http` where its body places it — before the name is known. The composer binds the parts with
48
- `customize(machine, { effects: { <slot>: { url } } })`, `{ argv }` or `{ secret }`, and an Effect with an Open url or
49
- argv beside an Open secret takes one object with both keys (`{ url, secret }`, `{ argv, secret }`): a url is a string
50
- or a `text` with a secret in its path, an argv is the usual list, and a secret is a name that obeys `secret()`'s own
51
- rule — it is the secret's file in the Effector's mount (ADR-0068). The type of `effects` is the intersection of the
52
- Open parts' bindings, so a binding that leaves one of them unbound is a compile error, as an unbound url is; at run
53
- time, a binding that leaves one unbound gives an Effect still Open in it, which a later `customize()` may bind. Each
54
- is bound ONCE, to a value: never a mapper over the door, so a host is fixed in the definition or bound before any run,
55
- and no run chooses one, nor a secret. Binding builds the Effect again with its constructor, with the bound name in
56
- every place `secret(open)` stood, so a bound part meets every rule the constructor checks — a literal host, no secret
57
- in a query, a non-empty argv, a secret's name — and the bound Effect is the one the same definition written with that
58
- name gives; `jr2 up` reads it as written: the placement, the secrets it names, and the Effector's fence
59
- ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)) are the bound ones, and the bound secret
60
- is placed only in that Effect's Effector, as a named secret is. Only an Open part is offered: a part the author fixed
61
- is no key of `effects` and is refused at the call, and a part bound once is fixed from then on, so a second composer
62
- cannot move it. `effects` reaches through `workspace()`'s body, `pool()`'s worker and a composed Machine's `actors`,
63
- as `agents` does, typed off the slot's logic. An Open argv's slots are the composer's, so the load walk counts every
64
- pick param as read by it when the Machine is built, and `customize()` runs the walk's pick-param check again once the
65
- argv is bound — a param the bound argv does not read is refused there, and a warning it gives for the bound argv is
66
- the bound Machine's, as it is the same Machine's written with that argv: the bound Machine gets a config of its own,
67
- which carries those warnings ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so a copy
68
- `provide()` makes of it — a run's — keeps them, and the Open Machine keeps its own. The compiler's check does not
69
- reach a bound argv: an Open argv's input is one string per key, so nothing at build or at `customize()` checks that
70
- the invoke supplies each slot the bound argv names, and a slot it does not supply faults when the Effect starts — its
71
- state's `onError`, with nothing run. The `jr2 up` walk reports an Open Effect beside Open Repo Slots and Agents and
72
- refuses the converge naming the `effects` line — `{ effects: { <slot>: { secret: "<name>" } } }` for an Open secret,
73
- one object with both keys for an Effect with two Open parts; the Effect refuses to run while Open, as the second fence
74
- — its state's `onError`, with nothing sent and no ledger row. The Open part is the whole url, the whole argv, or the
75
- secret's name.
38
+ `http({ method: "PUT", url: open, body: file(path) })`, `command(open, { env, pin })`,
39
+ `mcp.call(open, open, { input })`, and `secret(open)` in place of a secret's name. The url, the argv, an `mcp`
40
+ Effect's server and tool, and the secret's name are the Open parts of an Effect that exist; everything else the
41
+ definition says is the package's as written — the method, the headers, the auth and the secrets they name, the input,
42
+ `request`, the env, `pin` — and is checked when the Machine is built. `secret(open)` is taken by `bearer`, `basic`
43
+ (the user or the password), a header's value, and a command's env, as its value or its `.path`; in a url it is refused
44
+ when the Machine is built ("leave the whole url Open"), since a url that carries a secret is left Open whole (below).
45
+ An Effect takes one Open secret: a second `secret(open)` in one definition is refused when the Machine is built ("an
46
+ Effect takes one Open secret"); the one `secret(open)` may stand in several places, a value and its `.path`. An Effect
47
+ that names an Open secret is placed as one that names a secret is — a credentialed command in the Sandbox Effector, an
48
+ `http` where its body places it — before the name is known. The composer binds the parts with
49
+ `customize(machine, { effects: { <slot>: { url } } })`, `{ argv }`, `{ secret }`, `{ server }` or `{ tool }`, and an
50
+ Effect with two Open parts takes one object with both keys (`{ url, secret }`, `{ argv, secret }`,
51
+ `{ server, tool }`): a url is a string or a `text` with a secret in its path, an argv is the usual list, and a secret
52
+ is a name that obeys `secret()`'s own rule — it is the secret's file in the Effector's mount (ADR-0068) — and a server
53
+ is a name of the `mcp` block of `jr2.config.ts`, whose secret is the server's own, so an `mcp` Effect takes no
54
+ `secret(open)`
55
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
56
+ type of `effects` is the intersection of the Open parts' bindings, so a binding that leaves one of them unbound is a
57
+ compile error, as an unbound url is; at run time, a binding that leaves one unbound gives an Effect still Open in it,
58
+ which a later `customize()` may bind. Each is bound ONCE, to a value: never a mapper over the door, so a host is fixed
59
+ in the definition or bound before any run, and no run chooses one, nor a secret. Binding builds the Effect again with
60
+ its constructor, with the bound name in every place `secret(open)` stood, so a bound part meets every rule the
61
+ constructor checks — a literal host, no secret in a query, a non-empty argv, a secret's name — and the bound Effect is
62
+ the one the same definition written with that name gives; `jr2 up` reads it as written: the placement, the secrets it
63
+ names, and the Effector's fence ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)) are the
64
+ bound ones, and the bound secret is placed only in that Effect's Effector, as a named secret is. Only an Open part is
65
+ offered: a part the author fixed is no key of `effects` and is refused at the call, and a part bound once is fixed
66
+ from then on, so a second composer cannot move it. `effects` reaches through `workspace()`'s body, `pool()`'s worker
67
+ and a composed Machine's `actors`, as `agents` does, typed off the slot's logic. An Open argv's slots are the
68
+ composer's, so the load walk counts every pick param as read by it when the Machine is built, and `customize()` runs
69
+ the walk's pick-param check again once the argv is bound — a param the bound argv does not read is refused there, and
70
+ a warning it gives for the bound argv is the bound Machine's, as it is the same Machine's written with that argv: the
71
+ bound Machine gets a config of its own, which carries those warnings
72
+ ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so a copy `provide()` makes of it — a run's
73
+ — keeps them, and the Open Machine keeps its own. The compiler's check does not reach a bound argv: an Open argv's
74
+ input is one string per key, so nothing at build or at `customize()` checks that the invoke supplies each slot the
75
+ bound argv names, and a slot it does not supply faults when the Effect starts — its state's `onError`, with nothing
76
+ run. The `jr2 up` walk reports an Open Effect beside Open Repo Slots and Agents and refuses the converge naming the
77
+ `effects` line — `{ effects: { <slot>: { secret: "<name>" } } }` for an Open secret, one object with both keys for an
78
+ Effect with two Open parts, `{ effects: { <slot>: { server: "<name>" } } }`, `{ tool: "<tool>" }` or both for an `mcp`
79
+ Effect, with only its Open keys; the Effect refuses to run while Open, as the second fence — its state's `onError`,
80
+ with nothing sent and no ledger row. The Open part is the whole url, the whole argv, an MCP server's name or a tool's
81
+ name, or the secret's name.
82
+ - **Open widens to a hooked Gate's look, which only warns.** A package writes the Hook handlers of its Gate slots for
83
+ each forge that it supports, because a forge's payload is public, so a handler is never Open. It imports the Hook kind
84
+ of each forge that it hears, for the payload types, and it has `@jr2/orchestrator`, which holds the kinds, as a peer
85
+ dependency ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). Which Hooks of
86
+ those kinds exist is the Instance's config. A look needs a url and a token, so a package leaves `look: open`, and the
87
+ composer binds it once, to a look or `"none"`, with `customize(machine, { gates: { <slot>: { look } } })`, which can
88
+ also add, replace or remove a handler by kind and event key ([ADR-0011](0011-workflow-defined-events.md)). An Open
89
+ look that nobody binds is the one Open part that `jr2 up` does not refuse: it warns, as it warns about a hooked Gate
90
+ with no look, because the Gate still works when a person answers it with `jr2 send`.
76
91
  - **The per-run Dial is the escape hatch, not the default.** A shipped Machine's door may carry `model` and
77
92
  `thinkingLevel` (ADR-0018's two Dials) and fold them into each invoke's input; the bound definition is what `jr2 up`
78
93
  preflights, the door value overrides it for one run's Turns. Nothing in jr2 changes for this — it is the Machine's own
@@ -16,10 +16,10 @@ version, images, gate, trigger.
16
16
  an npmjs credential is the release job, and it holds no token: it publishes with npm trusted publishing (OIDC from
17
17
  GitHub Actions), so nothing long-lived exists to leak. A `pnpm -r publish` on a dev box reaches npmjs and fails
18
18
  `ENEEDAUTH`, which is the guard — an absence that cannot be edited past, where the manifest line was a presence that
19
- had to be. `publishConfig.access: public` stays on all four, because a scoped package's first publish is 402 without
20
- it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that **no** public
21
- manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a release to
22
- localhost.
19
+ had to be. `publishConfig.access: public` stays on every public package, because a scoped package's first publish is
20
+ 402 without it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that
21
+ **no** public manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a
22
+ release to localhost.
23
23
  - **Lockstep, and `main` carries the next version.** One number across `packages/*` and `templates/default`, because
24
24
  [ADR-0019](0019-one-converging-command-against-the-current-context.md)'s one release train already reads a single
25
25
  `KIT_VERSION`, ADR-0043's scaffold pins two packages at one exact number, and `workspace:*` packs to an exact
@@ -50,11 +50,11 @@ version, images, gate, trigger.
50
50
  ([ADR-0072](0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md)). It does not wait for the
51
51
  npm approval, so the site can be ahead of the live packages until the maintainer approves them.
52
52
  - **The job stages; the maintainer approves.** Each trusted publisher is configured for `npm stage publish` only, so the
53
- tag produces four staged tarballs and one 2FA approval per package (`npm stage approve`, dependencies first) makes
54
- them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is bound to the
55
- repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise publish under
56
- the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be remembered in
57
- order, the staged tarballs wait.
53
+ tag produces one staged tarball per public package and one 2FA approval per package (`npm stage approve`, dependencies
54
+ first) makes them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is
55
+ bound to the repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise
56
+ publish under the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be
57
+ remembered in order, the staged tarballs wait.
58
58
  - **Two executors of one train.** `scripts/publish.sh` is the irreversible half — images, then packages, dependencies
59
59
  first, live versions skipped — and the job runs it with `--stage`. A maintainer runs it as `just publish` from the
60
60
  pushed tag when a 0.x release cannot wait the job's hour: `npm publish` with a 2FA prompt per package in place of an
@@ -94,14 +94,15 @@ version, images, gate, trigger.
94
94
  configuration" no longer holds; the rest of that list does.
95
95
  - **Bootstrap, once:** create the `jr2` org on npmjs (a scope is a user or an org), publish the first version by hand
96
96
  with a token — npm attaches a trusted publisher only to a package that already exists — then configure the trusted
97
- publisher on all four packages and revoke the token. After that no token that publishes on its own exists anywhere:
98
- the job's identity stages, a maintainer's login needs its second factor. Likewise once: make the three GHCR packages
99
- public after the first push, because GHCR creates them private.
97
+ publisher on each public package and revoke the token. Each new public package needs the same step once: publish its
98
+ first version by hand before the first tag that holds it, or that tag's `npm stage publish` of it fails. After that no
99
+ token that publishes on its own exists anywhere: the job's identity stages, a maintainer's login needs its second
100
+ factor. Likewise once: make the three GHCR packages public after the first push, because GHCR creates them private.
100
101
  - `publish.test.ts` asserts no registry, `access: public`, and `engines`; `dist-publish.sh` drops its manifest read-back
101
102
  and passes `--registry`; the `@dist` fixture takes its registry from `JR2_DIST_PORT`'s default rather than the
102
103
  manifest; `kit-push.sh`'s cli-vs-orchestrator cross-check widens to every manifest; `just release` is new;
103
104
  `.github/workflows/` gains `ci.yml` and `release.yml`.
104
105
  - A release costs a runner kind cluster and roughly ten minutes of docker. Accepted: it is the only time the installed
105
106
  path must be proven on the exact tree that ships.
106
- - The four tarballs gain the npm-page fields (`description`, `repository` with `directory`, `homepage`, `bugs`,
107
+ - The public tarballs gain the npm-page fields (`description`, `repository` with `directory`, `homepage`, `bugs`,
107
108
  `keywords`) and a README each; the root README's "only scaffolding exists" line goes.