@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
@@ -111,8 +111,14 @@ can differ when your Assistant used other names. Other Workflows of the Instance
111
111
  …
112
112
  typecheck: tsc --noEmit (the instance's own compiler)
113
113
  effects: workflow "fix" — what it runs outside itself (ADR-0067)
114
+ cut: worktree — 600s, in the Harness
114
115
  tests: command ["node","--test","docs/site/tutorial/greet/test/*.test.js"] — 300s, in the Harness
115
116
  publish: push — 300s, in the Sandbox Effector
117
+ …
118
+ failures: workflow "fix" — the states that fail the run (ADR-0073)
119
+ fix.notCut: invokes fail
120
+ fix.lost: invokes fail
121
+ …
116
122
  seats: workflow "fix" — who may send what, per state (ADR-0015)
117
123
  fix.working agent:coder: [finish]
118
124
  fix.review gate: [approve, request_changes]
@@ -126,8 +132,9 @@ git.credentials "*": push Secret jr2-git-push-684888c0 — pushes over https
126
132
 
127
133
  Read these lines as a first check:
128
134
 
129
- - `effects` shows two Effects: a command and a push. A Workflow that pushes with no
135
+ - `effects` shows three Effects: the cut, a command and a push. A Workflow that pushes with no
130
136
  [Gate](../reference/glossary.md#gate) before the push shows the same lines. The graph shows where the push is.
137
+ - `failures` shows the two states that end the run as `failed`.
131
138
  - `seats` shows that the coder can send only `finish`. Only the Gate in `review` can send `approve` and
132
139
  `request_changes`.
133
140
  - The `warning` line is about git over ssh. The `jr2-git-ssh` Secret is for an ssh key, and it does not exist. Your fork
@@ -199,7 +206,7 @@ jr2 run fix --input '{"prompt":"In docs/site/tutorial/greet, one test fails. Fix
199
206
  ```text
200
207
  …
201
208
  → context kind-jr2 / namespace my-instance
202
- run b22cef63-1523-45f3-9c51-36c54b79ce36 (fix) — attached
209
+ run 790ad78a-db4e-4b09-a248-ab62da088cde (fix) — attached
203
210
  → active "placing"
204
211
  → active "provisioning"
205
212
  → active "attaching"
@@ -221,12 +228,14 @@ jr2 status <run id>
221
228
 
222
229
  [`jr2 status`](../reference/cli.md#status) prints the status of the run as one line of JSON. Its `gates` list holds the
223
230
  Gate of `review`. Its `gate` is `body.review`. Give that value to `--gate`. The `meta` of the Gate gives the coder's
224
- `summary`, the `branch` and the `worktree`:
231
+ `summary`, the `branch` and the `worktree`. After the JSON, `jr2 status` prints one line for each Worktree that the run
232
+ cut:
225
233
 
226
234
  ```text
227
235
  …
228
236
  → context kind-jr2 / namespace my-instance
229
- {…,"gates":[{"gate":"body.review",…,"meta":{"summary":"Fixed the \"trims the name\" test failure in docs/site/tutorial/greet/src/greet.js by adding .trim() to the name parsing. The name \" ada \" was not being trimmed, causing the greeting to include extra whitespace. Changed `args.find((a) => !a.startsWith(\"-\")) ?? \"world\"` to `(args.find((a) => !a.startsWith(\"-\")) ?? \"world\").trim()`.","branch":"jr2/fix-336ae477-2a46-49c2-b142-b81e705bb3f8","worktree":"/work/target/jr2-fix-336ae477-2a46-49c2-b142-b81e705bb3f8"}}]}
237
+ {…,"gates":[{"gate":"body.review",…,"meta":{"summary":"Fixed the \"trims the name\" test failure in docs/site/tutorial/greet. The bug was that the `greet` function didn't trim whitespace from the name argument. Added `.trim()` to the name extraction so that `greet([\" ada \"])` correctly returns `\"hello, ada\"` instead of `\"hello, ada \"`.","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395","worktree":"/work/target/fix"}}]}
238
+ worktree: /work/target/fix (target), branch jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395
230
239
  ```
231
240
 
232
241
  The `summary` is in the words of the model, so your summary is different.
@@ -242,7 +251,7 @@ jr2 send <run id> --gate body.review --event approve
242
251
  ```text
243
252
  …
244
253
  → context kind-jr2 / namespace my-instance
245
- delivered approve to gate "body.review" on b22cef63-1523-45f3-9c51-36c54b79ce36
254
+ delivered approve to gate "body.review" on 790ad78a-db4e-4b09-a248-ab62da088cde
246
255
  ```
247
256
 
248
257
  The run goes to `pushing`, and the push Effect pushes the branch. Then the run ends, and jr2 removes the
@@ -253,7 +262,7 @@ status as one line of JSON:
253
262
  …
254
263
  → active "teardown"
255
264
  → done "done"
256
- {"runId":"b22cef63-1523-45f3-9c51-36c54b79ce36","workflow":"fix",…,"output":{"outcome":"pushed","branch":"jr2/fix-336ae477-2a46-49c2-b142-b81e705bb3f8","commit":"03f8800ec105c98f612bd2c55687bb92db80357a"}}
265
+ {"runId":"790ad78a-db4e-4b09-a248-ab62da088cde","workflow":"fix",…,"output":{"outcome":"pushed","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395","commit":"ee7f5d8084db32c3610efb5c9b889a710ef30f70"},"worktrees":[{"slot":"target","path":"/work/target/fix","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395"}]}
257
266
  ```
258
267
 
259
268
  In that JSON, `context.output` and `output` are the output of `fix`. In this result, it is
@@ -289,9 +298,11 @@ import {
289
298
  command,
290
299
  defineEvent,
291
300
  fail,
301
+ isReady,
292
302
  jr2Setup,
293
303
  push,
294
304
  workspace,
305
+ worktree,
295
306
  type CommandOutput,
296
307
  type HostInjectedInput,
297
308
  type PushOutput,
@@ -339,6 +350,15 @@ type Context = BodyInput & {
339
350
  // The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
340
351
  type Output = { outcome: "pushed"; branch: string; commit: string };
341
352
 
353
+ // The Worktree of the coder. The Machine names the folder, and each Effect that works there names it.
354
+ function fixTree(context: BodyInput) {
355
+ return `${context.workspace.repos.target}/fix` as const;
356
+ }
357
+ // One branch for each run, from its seed Instance ID.
358
+ function branchOf(context: BodyInput): string {
359
+ return `jr2/fix-${context.instanceId}`;
360
+ }
361
+
342
362
  // Two guards. A guard with a name shows that name on its arrow in the Console's graph.
343
363
  function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
344
364
  return event.output.outcome === "exited" && event.output.code === 0;
@@ -362,7 +382,9 @@ const body = jr2Setup({
362
382
  - Make the smallest change that fixes the bug.
363
383
  - End your turn with exactly one call to \`finish\`.`,
364
384
  }),
365
- // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
385
+ // Cut a Worktree of `target`: the Workspace only clones.
386
+ cut: worktree(),
387
+ // A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
366
388
  tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
367
389
  // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
368
390
  publish: push(),
@@ -372,16 +394,24 @@ const body = jr2Setup({
372
394
  }).createMachine({
373
395
  id: "fix",
374
396
  context: ({ input }) => ({ ...input, fresh: true }),
375
- initial: "working",
397
+ initial: "cutting",
376
398
  // The Sandbox is gone, and every commit that was not pushed went with it.
377
399
  on: { "workspace.lost": { target: ".lost" } },
378
400
  states: {
401
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
402
+ cutting: {
403
+ invoke: {
404
+ src: "cut",
405
+ input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
406
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
407
+ },
408
+ },
379
409
  working: {
380
410
  invoke: {
381
411
  src: "coder",
382
412
  input: ({ context }) => ({
383
413
  prompt: promptOf(context),
384
- cwd: context.workspace.repos.target,
414
+ cwd: fixTree(context),
385
415
  // Each Turn continues the coder's one conversation, so the coder remembers its work.
386
416
  continue: true,
387
417
  }),
@@ -406,6 +436,7 @@ const body = jr2Setup({
406
436
  testing: {
407
437
  invoke: {
408
438
  src: "tests",
439
+ input: ({ context }) => ({ worktree: fixTree(context) }),
409
440
  onDone: [
410
441
  { guard: testsPassed, target: "review" },
411
442
  {
@@ -423,8 +454,8 @@ const body = jr2Setup({
423
454
  meta: {
424
455
  ...(context.summary !== undefined ? { summary: context.summary } : {}),
425
456
  ...(context.reason !== undefined ? { reason: context.reason } : {}),
426
- branch: context.workspace.branch,
427
- worktree: context.workspace.repos.target,
457
+ branch: branchOf(context),
458
+ worktree: fixTree(context),
428
459
  },
429
460
  }),
430
461
  },
@@ -442,6 +473,7 @@ const body = jr2Setup({
442
473
  pushing: {
443
474
  invoke: {
444
475
  src: "publish",
476
+ input: ({ context }) => ({ worktree: fixTree(context) }),
445
477
  onDone: [
446
478
  {
447
479
  guard: pushed,
@@ -457,11 +489,12 @@ const body = jr2Setup({
457
489
  type: "final",
458
490
  output: ({ context }): Output => ({
459
491
  outcome: "pushed",
460
- branch: context.workspace.branch,
492
+ branch: branchOf(context),
461
493
  commit: context.commit ?? "",
462
494
  }),
463
495
  },
464
- // Not a final state: the run ends `failed`, and jr2 deletes the Sandbox.
496
+ // Not final states: the run ends `failed`, and jr2 deletes the Sandbox.
497
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
465
498
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
466
499
  },
467
500
  // The output of the run. xstate reads only this root `output` as the output of the Machine. It
@@ -476,14 +509,14 @@ function promptOf(context: Context): string {
476
509
  const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
477
510
  return (
478
511
  `${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
479
- `Work in ${context.workspace.repos.target}, on the branch ${context.workspace.branch}. ` +
512
+ `Work in ${fixTree(context)}, on the branch ${branchOf(context)}. ` +
480
513
  "Commit your fix there, then call finish."
481
514
  );
482
515
  }
483
516
 
484
517
  // What the coder reads when the tests fail: the end of the output of `node --test`.
485
518
  function testFeedback(output: CommandOutput): string {
486
- const text = output.outcome === "unknown" ? output.reason : output.tail;
519
+ const text = "tail" in output ? output.tail : output.reason;
487
520
  return (
488
521
  `The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
489
522
  "Fix the code, commit, then call finish."
@@ -503,8 +536,6 @@ function pushFailure(output: PushOutput): string {
503
536
  export const machine = workspace(body, {
504
537
  input: door,
505
538
  repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
506
- // One branch for each run, from its seed Instance ID: `input.instanceId`.
507
- spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
508
539
  });
509
540
  ```
510
541
 
@@ -518,9 +549,11 @@ import {
518
549
  command,
519
550
  defineEvent,
520
551
  fail,
552
+ isReady,
521
553
  jr2Setup,
522
554
  push,
523
555
  workspace,
556
+ worktree,
524
557
  type CommandOutput,
525
558
  type HostInjectedInput,
526
559
  type PushOutput,
@@ -568,6 +601,15 @@ type Context = BodyInput & {
568
601
  // The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
569
602
  type Output = { outcome: "pushed"; branch: string; commit: string };
570
603
 
604
+ // The Worktree of the coder. The Machine names the folder, and each Effect that works there names it.
605
+ function fixTree(context: BodyInput) {
606
+ return `${context.workspace.repos.target}/fix` as const;
607
+ }
608
+ // One branch for each run, from its seed Instance ID.
609
+ function branchOf(context: BodyInput): string {
610
+ return `jr2/fix-${context.instanceId}`;
611
+ }
612
+
571
613
  // Two guards. A guard with a name shows that name on its arrow in the Console's graph.
572
614
  function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
573
615
  return event.output.outcome === "exited" && event.output.code === 0;
@@ -591,7 +633,9 @@ const body = jr2Setup({
591
633
  - Make the smallest change that fixes the bug.
592
634
  - End your turn with exactly one call to \`finish\`.`,
593
635
  }),
594
- // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
636
+ // Cut a Worktree of `target`: the Workspace only clones.
637
+ cut: worktree(),
638
+ // A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
595
639
  tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
596
640
  // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
597
641
  publish: push(),
@@ -601,16 +645,24 @@ const body = jr2Setup({
601
645
  }).createMachine({
602
646
  id: "fix",
603
647
  context: ({ input }) => ({ ...input, fresh: true }),
604
- initial: "working",
648
+ initial: "cutting",
605
649
  // The Sandbox is gone, and every commit that was not pushed went with it.
606
650
  on: { "workspace.lost": { target: ".lost" } },
607
651
  states: {
652
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
653
+ cutting: {
654
+ invoke: {
655
+ src: "cut",
656
+ input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
657
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
658
+ },
659
+ },
608
660
  working: {
609
661
  invoke: {
610
662
  src: "coder",
611
663
  input: ({ context }) => ({
612
664
  prompt: promptOf(context),
613
- cwd: context.workspace.repos.target,
665
+ cwd: fixTree(context),
614
666
  // Each Turn continues the coder's one conversation, so the coder remembers its work.
615
667
  continue: true,
616
668
  }),
@@ -635,6 +687,7 @@ const body = jr2Setup({
635
687
  testing: {
636
688
  invoke: {
637
689
  src: "tests",
690
+ input: ({ context }) => ({ worktree: fixTree(context) }),
638
691
  onDone: [
639
692
  { guard: testsPassed, target: "review" },
640
693
  {
@@ -652,8 +705,8 @@ const body = jr2Setup({
652
705
  meta: {
653
706
  ...(context.summary !== undefined ? { summary: context.summary } : {}),
654
707
  ...(context.reason !== undefined ? { reason: context.reason } : {}),
655
- branch: context.workspace.branch,
656
- worktree: context.workspace.repos.target,
708
+ branch: branchOf(context),
709
+ worktree: fixTree(context),
657
710
  },
658
711
  }),
659
712
  },
@@ -671,6 +724,7 @@ const body = jr2Setup({
671
724
  pushing: {
672
725
  invoke: {
673
726
  src: "publish",
727
+ input: ({ context }) => ({ worktree: fixTree(context) }),
674
728
  onDone: [
675
729
  {
676
730
  guard: pushed,
@@ -686,11 +740,12 @@ const body = jr2Setup({
686
740
  type: "final",
687
741
  output: ({ context }): Output => ({
688
742
  outcome: "pushed",
689
- branch: context.workspace.branch,
743
+ branch: branchOf(context),
690
744
  commit: context.commit ?? "",
691
745
  }),
692
746
  },
693
- // Not a final state: the run ends `failed`, and jr2 deletes the Sandbox.
747
+ // Not final states: the run ends `failed`, and jr2 deletes the Sandbox.
748
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
694
749
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
695
750
  },
696
751
  // The output of the run. xstate reads only this root `output` as the output of the Machine. It
@@ -705,14 +760,14 @@ function promptOf(context: Context): string {
705
760
  const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
706
761
  return (
707
762
  `${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
708
- `Work in ${context.workspace.repos.target}, on the branch ${context.workspace.branch}. ` +
763
+ `Work in ${fixTree(context)}, on the branch ${branchOf(context)}. ` +
709
764
  "Commit your fix there, then call finish."
710
765
  );
711
766
  }
712
767
 
713
768
  // What the coder reads when the tests fail: the end of the output of `node --test`.
714
769
  function testFeedback(output: CommandOutput): string {
715
- const text = output.outcome === "unknown" ? output.reason : output.tail;
770
+ const text = "tail" in output ? output.tail : output.reason;
716
771
  return (
717
772
  `The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
718
773
  "Fix the code, commit, then call finish."
@@ -732,8 +787,6 @@ function pushFailure(output: PushOutput): string {
732
787
  export const machine = workspace(body, {
733
788
  input: door,
734
789
  repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
735
- // One branch for each run, from its seed Instance ID: `input.instanceId`.
736
- spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
737
790
  });
738
791
  ```
739
792
 
@@ -765,16 +818,19 @@ const requestChanges = defineEvent({
765
818
  });
766
819
  ```
767
820
 
768
- **The Effects.** `tests` and `publish` are actor slots, as the coder is. A state runs one when it invokes it.
769
- [`command()`](../reference/authoring-surface/command.md) takes a list of arguments, not a line of shell text. It runs in
770
- the [Worktree](../reference/glossary.md#worktree) of the one [Repo Slot](../reference/glossary.md#repo-slot). A command
771
- runs in the root of the Worktree, so `tests` names the test files by their path from that root.
772
- [`push()`](../reference/authoring-surface/push.md) pushes the tip of the coder's branch.
821
+ **The Effects.** `cut`, `tests` and `publish` are actor slots, as the coder is. A state runs one when it invokes it.
822
+ [`worktree()`](../reference/authoring-surface/worktree.md) cuts a [Worktree](../reference/glossary.md#worktree) of the
823
+ [Repo Slot](../reference/glossary.md#repo-slot). [`command()`](../reference/authoring-surface/command.md) takes a list
824
+ of arguments, not a line of shell text. Its input names the Worktree. A command runs in the root of the Worktree, so
825
+ `tests` names the test files by their path from that root. [`push()`](../reference/authoring-surface/push.md) pushes the
826
+ tip of the coder's branch. Its input names the Worktree too, and jr2 reads the branch from the cut.
773
827
  [`fail()`](../reference/authoring-surface/fail.md) is a Part, not an Effect. The `lost` state invokes it to end the run
774
828
  `failed`, because a final state means `done`.
775
829
 
776
830
  ```ts
777
- // A program in the Harness container of the Sandbox, in the Worktree of `target`. Node expands the glob itself.
831
+ // Cut a Worktree of `target`: the Workspace only clones.
832
+ cut: worktree(),
833
+ // A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
778
834
  tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
779
835
  // Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
780
836
  publish: push(),
@@ -782,6 +838,21 @@ publish: push(),
782
838
  fail: fail(),
783
839
  ```
784
840
 
841
+ **The cut.** The Workspace only clones. `cutting` is the first state, and it makes the coder's Worktree. The path is
842
+ `/work/target/fix`, and the branch has the Instance ID of the run in it. See
843
+ [Cut a Worktree](../guides/cut-a-worktree.md).
844
+
845
+ ```ts
846
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
847
+ cutting: {
848
+ invoke: {
849
+ src: "cut",
850
+ input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
851
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
852
+ },
853
+ },
854
+ ```
855
+
785
856
  **The coder's state.** `working` invokes the coder. Each [Turn](../reference/glossary.md#turn) gets a prompt and the
786
857
  folder to work in. `continue: true` keeps one conversation, so the coder remembers its last Turn. `finish` is the
787
858
  coder's pick: the run goes to `testing`. [`agent.fault`](../reference/mechanism-events.md#agent-fault) is an event from
@@ -793,7 +864,7 @@ working: {
793
864
  src: "coder",
794
865
  input: ({ context }) => ({
795
866
  prompt: promptOf(context),
796
- cwd: context.workspace.repos.target,
867
+ cwd: fixTree(context),
797
868
  // Each Turn continues the coder's one conversation, so the coder remembers its work.
798
869
  continue: true,
799
870
  }),
@@ -825,6 +896,7 @@ catches every other outcome. See [`CommandOutput`](../reference/authoring-surfac
825
896
  testing: {
826
897
  invoke: {
827
898
  src: "tests",
899
+ input: ({ context }) => ({ worktree: fixTree(context) }),
828
900
  onDone: [
829
901
  { guard: testsPassed, target: "review" },
830
902
  {
@@ -849,8 +921,8 @@ review: {
849
921
  meta: {
850
922
  ...(context.summary !== undefined ? { summary: context.summary } : {}),
851
923
  ...(context.reason !== undefined ? { reason: context.reason } : {}),
852
- branch: context.workspace.branch,
853
- worktree: context.workspace.repos.target,
924
+ branch: branchOf(context),
925
+ worktree: fixTree(context),
854
926
  },
855
927
  }),
856
928
  },
@@ -875,6 +947,7 @@ commits in it. See [`PushOutput`](../reference/authoring-surface/type-pushoutput
875
947
  pushing: {
876
948
  invoke: {
877
949
  src: "publish",
950
+ input: ({ context }) => ({ worktree: fixTree(context) }),
878
951
  onDone: [
879
952
  {
880
953
  guard: pushed,
@@ -890,15 +963,13 @@ pushing: {
890
963
 
891
964
  **The Workspace.** [`workspace()`](../reference/authoring-surface/workspace.md) runs the body in a Sandbox. `input` is
892
965
  the schema of the run input that `jr2 run --input` sends. `repos` binds the Repo Slot `target` to your fork, at the
893
- release tag. `spec` gives the branch. The input of `spec` also holds `instanceId`, an id that jr2 makes for each run, so
966
+ release tag. A new branch starts from that tag. The body also gets `instanceId`, an id that jr2 makes for each run, so
894
967
  each run gets a branch of its own.
895
968
 
896
969
  ```ts
897
970
  export const machine = workspace(body, {
898
971
  input: door,
899
972
  repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
900
- // One branch for each run, from its seed Instance ID: `input.instanceId`.
901
- spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
902
973
  });
903
974
  ```
904
975
 
@@ -20,6 +20,10 @@ This is the first page of the tutorial. You need a computer with these tools:
20
20
  kind makes the cluster from Docker containers. `jr2 up` builds an image with Docker and loads it into the kind cluster.
21
21
  kubectl is the tool that `jr2 up`, `jr2 run` and the other commands use to speak to the cluster.
22
22
 
23
+ The kind cluster uses the Linux kernel of Docker: the kernel of your computer, or on macOS and Windows, the kernel of
24
+ the virtual machine of Docker. jr2 needs Landlock in that kernel: Linux 6.2 or later, with `landlock` in its list of
25
+ security modules. `jr2 up` refuses to converge without Landlock.
26
+
23
27
  ## Check Node.js
24
28
 
25
29
  Run:
@@ -12,16 +12,17 @@ Put the programs that your [Agents](../reference/glossary.md#agent) and
12
12
  A [Workspace](../reference/glossary.md#workspace) runs its body in a [Sandbox](../reference/glossary.md#sandbox): a pod
13
13
  of its own. The Sandbox Image is the image of the pod's `harness` container. The Agent's
14
14
  [Working tools](../reference/glossary.md#working-tools) run in that container, and so do the `command` Effects and the
15
- shell that you get with `kubectl exec`. Thus a program that is in the image is a program that they can run.
15
+ shell that you get with `jr2 workspace shell`. Thus a program that is in the image is a program that they can run.
16
16
 
17
17
  Use this table to choose:
18
18
 
19
- | You want | Do this |
20
- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
21
- | The same tools for each Workspace | Change `images/default/Dockerfile`. |
22
- | Different tools for the Workspaces of one file | Put a docker context beside the workflow module, and name it in the `image` option. |
23
- | An image that you build and push yourself | Write its registry ref in the `image` option. |
24
- | A service that runs beside the Agent | Name its image in the `user` option. It runs in the [User Container](../reference/glossary.md#user-container). |
19
+ | You want | Do this |
20
+ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | The same tools for each Workspace | Change `images/default/Dockerfile`. |
22
+ | Different tools for the Workspaces of one file | Put a docker context beside the workflow module, and name it in the `image` option. |
23
+ | An image that you build and push yourself | Write its registry ref in the `image` option. |
24
+ | A service that runs beside the Agent | Name its image in the `user` option. It runs in the [User Container](../reference/glossary.md#user-container). |
25
+ | A tool that the model calls by name | Put an MCP server in the image, and declare it in `jr2.config.ts`. See [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md). |
25
26
 
26
27
  ## Add a tool to the default image
27
28
 
@@ -137,26 +138,39 @@ RUN pip install --no-cache-dir pytest ruff
137
138
 
138
139
  ```ts
139
140
  import { z } from "zod";
140
- import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
141
+ import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
141
142
 
142
143
  const runInput = z.object({ branch: z.string() });
143
144
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
144
145
 
146
+ // The Worktree that the tests run in.
147
+ const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
148
+
145
149
  const body = jr2Setup({
146
- types: {} as { input: BodyInput },
150
+ types: {} as { input: BodyInput; context: BodyInput },
147
151
  actors: {
152
+ cut: worktree(),
148
153
  // python and pytest come from the Sandbox Image in ./python.
149
154
  tests: command(["python", "-m", "pytest"], { timeout: "15m" }),
150
155
  fail: fail(),
151
156
  },
152
157
  }).createMachine({
153
158
  id: "pytest",
159
+ context: ({ input }) => input,
154
160
  on: { "workspace.lost": ".lost" },
155
- initial: "testing",
161
+ initial: "cutting",
156
162
  states: {
163
+ cutting: {
164
+ invoke: {
165
+ src: "cut",
166
+ input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
167
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
168
+ },
169
+ },
157
170
  testing: {
158
171
  invoke: {
159
172
  src: "tests",
173
+ input: ({ context }) => ({ worktree: testTree(context) }),
160
174
  onDone: [
161
175
  {
162
176
  guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
@@ -169,6 +183,7 @@ const body = jr2Setup({
169
183
  passed: { type: "final" },
170
184
  // Not final states: each one fails the run, and the run ends `failed`.
171
185
  failed: { invoke: { src: "fail", input: { reason: "pytest did not pass" } } },
186
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
172
187
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
173
188
  },
174
189
  });
@@ -178,7 +193,6 @@ export const machine = workspace(body, {
178
193
  // The docker context in the folder beside this file. jr2 up builds it.
179
194
  image: import.meta.resolve("./python"),
180
195
  repos: { app: "https://github.com/you/app.git" },
181
- spec: ({ input }) => ({ branch: input.branch }),
182
196
  });
183
197
  ```
184
198
 
@@ -226,17 +240,23 @@ RUN apk add --no-cache git
226
240
  # The Worktrees under /work belong to a different user. Tell git to trust them.
227
241
  RUN git config --system --add safe.directory '*'
228
242
 
229
- # jr2 does not replace this command. It must keep running: when it ends, the pod is not ready.
243
+ # jr2 does not replace this command. It must keep running: when it ends, Kubernetes starts the container again.
230
244
  CMD ["sh", "-c", "while true; do sleep 3600; done"]
231
245
  ```
232
246
 
233
247
  Save it as `workflows/shell/Dockerfile`. Name it in the `workspace()` options with
234
248
  `user: import.meta.resolve("./shell")`. `user` takes the same two forms as `image`, and `jr2 up` builds a `file:`
235
- context in the same way. Open the shell with `kubectl -n <namespace> exec -it <sandbox> -c user -- sh`.
249
+ context in the same way. Open the shell with [`jr2 workspace shell`](../reference/cli.md#workspace-shell) and `--user`.
250
+ [Look inside a Sandbox](look-inside-a-sandbox.md#find-the-sandbox) tells how to find `<workspace>`:
251
+
252
+ ```sh
253
+ jr2 workspace shell <workspace> --user
254
+ ```
236
255
 
237
256
  The User Container is not a second Sandbox Image:
238
257
 
239
- - Its own `ENTRYPOINT` and `CMD` run. Its process must keep running: when it ends, the pod is not ready.
258
+ - Its own `ENTRYPOINT` and `CMD` run. Its process must keep running. When it ends, Kubernetes starts the container
259
+ again, so a `CMD` that ends at once restarts again and again.
240
260
  - jr2 gives it `/work`, which it can write, and `/repos` and `/opt/jr2`, which it can only read. jr2 gives it no other
241
261
  mount, no environment variable and no command.
242
262
  - It has no floor: an Alpine base is correct here. It runs as the `USER` of its image, and root is permitted.
@@ -253,6 +273,42 @@ container, the [Sandbox Effector](../reference/glossary.md#sandbox-effector). By
253
273
  [Run a command or call a URL with an Effect](run-a-command-or-call-a-url.md) and
254
274
  [Push and sign a branch](push-and-sign-a-branch.md).
255
275
 
276
+ ### Reach a service in the User Container
277
+
278
+ No Service of the cluster goes to the User Container. To reach a port in the Sandbox pod, run
279
+ [`jr2 workspace forward`](../reference/cli.md#workspace-forward). It forwards a port on your computer to the port in the
280
+ pod, through the API server of the cluster, until you push Ctrl-C:
281
+
282
+ ```sh
283
+ jr2 workspace forward <workspace> 3000
284
+ ```
285
+
286
+ ```text
287
+ → context <context> / namespace <namespace>
288
+ 127.0.0.1:36779
289
+ forwarding 127.0.0.1:36779 → 7a0e8b50/x:0:3000 — Ctrl-C ends it
290
+ ```
291
+
292
+ The address on stdout is the local address. The port changes on each run.
293
+
294
+ When the User Container runs an sshd on port `22`, `ssh` can go through `jr2 workspace forward --stdio`. Add this entry
295
+ to `~/.ssh/config`. Replace `<namespace>` with the namespace of your Instance:
296
+
297
+ ```text
298
+ Host *.jr2
299
+ ProxyCommand jr2 workspace forward %h 22 --stdio -n <namespace>
300
+ ```
301
+
302
+ Then connect with the name of the Sandbox, which `jr2 workspace ls` prints, and `.jr2`:
303
+
304
+ ```sh
305
+ ssh <sandbox>.jr2
306
+ ```
307
+
308
+ With `-n`, `jr2 workspace forward` needs no Instance folder, so `ssh` can run it from each folder. The sshd and its keys
309
+ are yours: jr2 gives the User Container no key. With `ssh -A`, your `ssh-agent` goes only to the User Container, and the
310
+ Agent runs nothing there.
311
+
256
312
  ## Rules
257
313
 
258
314
  - `workspace()` refuses an `image` that is empty or not a string:
@@ -290,6 +346,7 @@ container, the [Sandbox Effector](../reference/glossary.md#sandbox-effector). By
290
346
  [`UserContainer`](../reference/authoring-surface/type-usercontainer.md), [`jr2 up`](../reference/cli.md#up)
291
347
  - [ADR-0037](../reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md): the Instance
292
348
  builds its Sandbox Images, and jr2 injects the Harness.
293
- - [ADR-0005](../reference/decisions/0005-sandbox-pod-composition.md): the containers of the Sandbox pod.
349
+ - [ADR-0005](../reference/decisions/0005-sandbox-pod-composition.md): the containers of the Sandbox pod, and how you
350
+ reach the User Container.
294
351
  - [ADR-0049](../reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md): a Machine carries its
295
352
  images.