@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
@@ -10,8 +10,9 @@ Run a program in the [Workspace](../reference/glossary.md#workspace), or call a
10
10
 
11
11
  Put the Effect under `actors` in [`jr2Setup`](../reference/authoring-surface/jr2setup.md), beside the
12
12
  [Agents](../reference/glossary.md#agent). Then invoke it by its key from a state, and route on its output in `onDone`.
13
- In this [Workflow](../reference/glossary.md#workflow), a coder fixes the tests. It picks `run_test` to run one test file
14
- with a [`command`](../reference/authoring-surface/command.md) Effect. When it picks `finished`, an
13
+ In this [Workflow](../reference/glossary.md#workflow), the Machine cuts a [Worktree](../reference/glossary.md#worktree),
14
+ and a coder fixes the tests there. It picks `run_test` to run one test file with a
15
+ [`command`](../reference/authoring-surface/command.md) Effect. When it picks `finished`, an
15
16
  [`http`](../reference/authoring-surface/http.md) Effect posts its summary to a URL. Save the file as
16
17
  `workflows/fix-and-report.ts` in your [Instance](../reference/glossary.md#instance):
17
18
 
@@ -29,9 +30,11 @@ import {
29
30
  fail,
30
31
  fromPick,
31
32
  http,
33
+ isReady,
32
34
  jr2Setup,
33
35
  secret,
34
36
  workspace,
37
+ worktree,
35
38
  type CommandOutput,
36
39
  type Workspaced,
37
40
  } from "@jr2/orchestrator";
@@ -50,6 +53,9 @@ const finished = defineEvent({
50
53
  const runInput = z.object({ prompt: z.string(), branch: z.string() });
51
54
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
52
55
 
56
+ // The Worktree that the coder and the command name.
57
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
58
+
53
59
  const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
54
60
 
55
61
  const body = jr2Setup({
@@ -60,6 +66,8 @@ const body = jr2Setup({
60
66
  model: "anthropic/claude-haiku-5-5",
61
67
  instructions: "You fix failing tests. You change no test. You commit the fix.",
62
68
  }),
69
+ // Cut the Worktree: an Effect too.
70
+ cut: worktree(),
63
71
  // One program, with no shell. arg("file") is one argument, from the input of the Effect.
64
72
  test: command(["node", "--test", arg("file")], { timeout: "5m" }),
65
73
  // A call to a fixed URL. The Effector puts the value of the secret in the header.
@@ -77,12 +85,20 @@ const body = jr2Setup({
77
85
  id: "fix-and-report",
78
86
  context: ({ input }) => input,
79
87
  on: { "workspace.lost": ".lost" },
80
- initial: "coding",
88
+ initial: "cutting",
81
89
  states: {
90
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
91
+ cutting: {
92
+ invoke: {
93
+ src: "cut",
94
+ input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
95
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
96
+ },
97
+ },
82
98
  coding: {
83
99
  invoke: {
84
100
  src: "coder",
85
- input: ({ context }) => ({ prompt: context.prompt, cwd: context.workspace.repos.app }),
101
+ input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
86
102
  },
87
103
  on: {
88
104
  finished: "reporting",
@@ -95,8 +111,8 @@ const body = jr2Setup({
95
111
  testing: {
96
112
  invoke: {
97
113
  src: "test",
98
- // The `file` of the pick goes to the arg("file") of the command.
99
- input: fromPick(runTest),
114
+ // The `file` of the pick goes to the arg("file") of the command. The Machine gives `worktree`.
115
+ input: fromPick(runTest, ({ context }) => ({ worktree: fixTree(context) })),
100
116
  onDone: {
101
117
  target: "ready",
102
118
  // The Agent reads this text as the answer to its run_test pick.
@@ -114,7 +130,7 @@ const body = jr2Setup({
114
130
  invoke: {
115
131
  src: "report",
116
132
  // The Agent gives `summary`. The Machine gives `branch`.
117
- input: fromPick(finished, ({ context }) => ({ branch: context.workspace.branch })),
133
+ input: fromPick(finished, ({ context }) => ({ branch: context.branch })),
118
134
  onDone: [
119
135
  { guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
120
136
  { target: "notReported" },
@@ -123,6 +139,7 @@ const body = jr2Setup({
123
139
  },
124
140
  done: { type: "final" },
125
141
  // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
142
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
126
143
  notReported: { invoke: { src: "fail", input: { reason: "the report did not go out" } } },
127
144
  failed: { invoke: { src: "fail", input: { reason: "the coder could not go on" } } },
128
145
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
@@ -132,7 +149,6 @@ const body = jr2Setup({
132
149
  export const machine = workspace(body, {
133
150
  input: runInput,
134
151
  repos: { app: "https://github.com/<you>/app.git" },
135
- spec: ({ input }) => ({ branch: input.branch }),
136
152
  });
137
153
  ```
138
154
 
@@ -150,9 +166,11 @@ import {
150
166
  fail,
151
167
  fromPick,
152
168
  http,
169
+ isReady,
153
170
  jr2Setup,
154
171
  secret,
155
172
  workspace,
173
+ worktree,
156
174
  type CommandOutput,
157
175
  type Workspaced,
158
176
  } from "@jr2/orchestrator";
@@ -171,6 +189,9 @@ const finished = defineEvent({
171
189
  const runInput = z.object({ prompt: z.string(), branch: z.string() });
172
190
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
173
191
 
192
+ // The Worktree that the coder and the command name.
193
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
194
+
174
195
  const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
175
196
 
176
197
  const body = jr2Setup({
@@ -181,6 +202,8 @@ const body = jr2Setup({
181
202
  model: "local/qwen3.6-35b-a3b",
182
203
  instructions: "You fix failing tests. You change no test. You commit the fix.",
183
204
  }),
205
+ // Cut the Worktree: an Effect too.
206
+ cut: worktree(),
184
207
  // One program, with no shell. arg("file") is one argument, from the input of the Effect.
185
208
  test: command(["node", "--test", arg("file")], { timeout: "5m" }),
186
209
  // A call to a fixed URL. The Effector puts the value of the secret in the header.
@@ -198,12 +221,20 @@ const body = jr2Setup({
198
221
  id: "fix-and-report",
199
222
  context: ({ input }) => input,
200
223
  on: { "workspace.lost": ".lost" },
201
- initial: "coding",
224
+ initial: "cutting",
202
225
  states: {
226
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
227
+ cutting: {
228
+ invoke: {
229
+ src: "cut",
230
+ input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
231
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
232
+ },
233
+ },
203
234
  coding: {
204
235
  invoke: {
205
236
  src: "coder",
206
- input: ({ context }) => ({ prompt: context.prompt, cwd: context.workspace.repos.app }),
237
+ input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
207
238
  },
208
239
  on: {
209
240
  finished: "reporting",
@@ -216,8 +247,8 @@ const body = jr2Setup({
216
247
  testing: {
217
248
  invoke: {
218
249
  src: "test",
219
- // The `file` of the pick goes to the arg("file") of the command.
220
- input: fromPick(runTest),
250
+ // The `file` of the pick goes to the arg("file") of the command. The Machine gives `worktree`.
251
+ input: fromPick(runTest, ({ context }) => ({ worktree: fixTree(context) })),
221
252
  onDone: {
222
253
  target: "ready",
223
254
  // The Agent reads this text as the answer to its run_test pick.
@@ -235,7 +266,7 @@ const body = jr2Setup({
235
266
  invoke: {
236
267
  src: "report",
237
268
  // The Agent gives `summary`. The Machine gives `branch`.
238
- input: fromPick(finished, ({ context }) => ({ branch: context.workspace.branch })),
269
+ input: fromPick(finished, ({ context }) => ({ branch: context.branch })),
239
270
  onDone: [
240
271
  { guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
241
272
  { target: "notReported" },
@@ -244,6 +275,7 @@ const body = jr2Setup({
244
275
  },
245
276
  done: { type: "final" },
246
277
  // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
278
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
247
279
  notReported: { invoke: { src: "fail", input: { reason: "the report did not go out" } } },
248
280
  failed: { invoke: { src: "fail", input: { reason: "the coder could not go on" } } },
249
281
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
@@ -253,7 +285,6 @@ const body = jr2Setup({
253
285
  export const machine = workspace(body, {
254
286
  input: runInput,
255
287
  repos: { app: "https://github.com/<you>/app.git" },
256
- spec: ({ input }) => ({ branch: input.branch }),
257
288
  });
258
289
  ```
259
290
 
@@ -261,7 +292,7 @@ The `coding` state routes [`agent.fault`](../reference/mechanism-events.md#agent
261
292
  Turn of the coder fails with no pick. Each state that invokes an Agent has a route for it, on the state or on a state
262
293
  above it: `jr2 check` refuses a Machine without one. When no transition takes the event, the run ends `error`.
263
294
 
264
- `failed`, `notReported` and `lost` are not final states. Each one invokes
295
+ `notCut`, `failed`, `notReported` and `lost` are not final states. Each one invokes
265
296
  [`fail`](../reference/authoring-surface/fail.md), and the run ends `failed` with its reason: a final state means `done`.
266
297
  Do not throw from a promise to fail, because a throw is a defect, and the run ends `error`.
267
298
 
@@ -301,25 +332,24 @@ The options:
301
332
  - `timeout`: how long the program can run, such as `"90s"`, `"10m"` or `"1h"`. The default is `"10m"`. After it, jr2
302
333
  kills the process group, and the outcome is `timeout`.
303
334
  - `tail`: how many characters of the end of the output the Machine gets. The default is 4096.
304
- - `slot`: the [Repo Slot](../reference/glossary.md#repo-slot) whose [Worktree](../reference/glossary.md#worktree) the
305
- program runs in. Write it only when the Workspace has more than one Repo Slot.
306
335
 
307
- The program starts in the root of the Worktree. In a Workspace with no Repo Slot, it starts in `/work`, the folder that
308
- the steps of the Workspace share (see [Workspaces](../concepts/workspaces.md#a-workspace-with-no-repo)). To run it in a
309
- subfolder, give the path as an argument, such as `["node", "--test", "<dir>/test/*.test.js"]`, or write
310
- `["sh", "-c", "cd <dir> && <program>"]`.
336
+ The input of the Effect says where the program runs. Under [Repo Slots](../reference/glossary.md#repo-slot), `worktree`
337
+ is required: the path of a Worktree that a cut made, or `/work/<slot>/default`. The program starts in the root of that
338
+ folder. In a Workspace with no Repo Slot, it starts in `/work`, the folder that the steps of the Workspace share (see
339
+ [Workspaces](../concepts/workspaces.md#a-workspace-with-no-repo)). To run it in a subfolder, give the path as an
340
+ argument, such as `["node", "--test", "<dir>/test/*.test.js"]`, or write `["sh", "-c", "cd <dir> && <program>"]`.
311
341
 
312
342
  A `command` with no Effect secret runs in the [Harness](../reference/glossary.md#harness) container of the
313
- [Sandbox](../reference/glossary.md#sandbox), in the Worktree, beside the
314
- [Working tools](../reference/glossary.md#working-tools) of the Agent. It uses the tools of the
315
- [Sandbox Image](../reference/glossary.md#sandbox-image). Thus invoke it in the body of a
343
+ [Sandbox](../reference/glossary.md#sandbox), beside the [Working tools](../reference/glossary.md#working-tools) of the
344
+ Agent. It uses the tools of the [Sandbox Image](../reference/glossary.md#sandbox-image). Thus invoke it in the body of a
316
345
  [`workspace()`](../reference/authoring-surface/workspace.md).
317
346
 
318
347
  Each outcome is output for `onDone`, also a failure. Route on `outcome`
319
348
  ([`CommandOutput`](../reference/authoring-surface/type-commandoutput.md)):
320
349
 
321
350
  - `exited`, with `code` and `tail`. A test that fails is `exited` with a `code` that is not 0. A program that the
322
- container does not have is `exited` with code 127.
351
+ container does not have is `exited` with code 127. A bad `worktree` is `exited` with code 127 too, and `tail` gives
352
+ the reason. A bad path does not add an outcome.
323
353
  - `timeout`, with `tail`.
324
354
  - `killed`, with `signal` and `tail`.
325
355
  - `unknown`, with `reason`: jr2 cannot tell what the program did. jr2 does not run it again.
@@ -332,13 +362,14 @@ the value. `secret("<name>").path` gives the path of a file that holds the value
332
362
  list of `jr2.config.ts`, as for `hook-token`.
333
363
 
334
364
  A `command` with an Effect secret in `env` does not run beside the Agent. It runs in the
335
- [Sandbox Effector](../reference/glossary.md#sandbox-effector), a container that the Agent cannot reach. It runs against
336
- one commit of the branch, not against the live Worktree:
365
+ [Sandbox Effector](../reference/glossary.md#sandbox-effector), a container that the Agent cannot reach. Its `worktree`
366
+ must be a branch Worktree that a cut made in this Sandbox, because a path alone does not name the target of a
367
+ credential. It runs against one commit of that branch, not against the live Worktree:
337
368
 
338
369
  - By default, the commit is the tip of the branch when the Effect starts. When the Worktree holds changes that the
339
370
  program did not see, its output has `dirty: true`.
340
- - With `pin: true`, the commit is the `sha` of the input of the Effect: `{ sha: "<full commit id>" }`. Use it for a
341
- commit that a [Gate](../reference/glossary.md#gate) approved.
371
+ - With `pin: true`, the commit is the `sha` of the input of the Effect: `{ worktree, sha: "<full commit id>" }`. Use it
372
+ for a commit that a [Gate](../reference/glossary.md#gate) approved.
342
373
 
343
374
  The Sandbox Effector runs in the Sandbox Image, or in the image of the `effector` option of `workspace()`. Thus that
344
375
  image must hold the program.
@@ -362,8 +393,10 @@ image must hold the program.
362
393
  - `maxBody`: how many bytes of a text body the output keeps. The default is 65536.
363
394
  - `idempotent`: `true` when a second call is safe. The default is `false`. See [Effects](../concepts/effects.md).
364
395
 
365
- To send a file of the Worktree as the body, use [`file(path)`](../reference/authoring-surface/file.md) as `body`. In a
366
- Workspace with no Repo Slot, the path is relative to `/work`. jr2 sends a file body only with `PUT`, `POST` or `PATCH`.
396
+ To send a file of a Worktree as the body, use [`file(path)`](../reference/authoring-surface/file.md) as `body`. The
397
+ input of the Effect names the Worktree, `{ worktree }`, and a cut in this Sandbox must have made it. The path is
398
+ relative to that Worktree. In a Workspace with no Repo Slot, the input has no `worktree`, and the path is relative to
399
+ `/work`. jr2 sends a file body only with `PUT`, `POST` or `PATCH`.
367
400
 
368
401
  An `http` Effect runs in the [Instance Effector](../reference/glossary.md#instance-effector). The
369
402
  [Effector](../reference/glossary.md#effector) puts the value of each Effect secret into the call. The Agent never reads
@@ -414,9 +447,12 @@ jr2 up
414
447
  ```text
415
448
  …
416
449
  effects: workflow "fix-and-report" — what it runs outside itself (ADR-0067)
450
+ cut: worktree — 600s, in the Harness
417
451
  test: command ["node","--test","<file>"] — 300s, in the Harness
418
452
  report: http POST https://hooks.example.com — 30s, in the Instance Effector, secrets "hook-token"
453
+ …
419
454
  failures: workflow "fix-and-report" — the states that fail the run (ADR-0073)
455
+ fix-and-report.notCut: invokes fail
420
456
  fix-and-report.notReported: invokes fail
421
457
  fix-and-report.failed: invokes fail
422
458
  fix-and-report.lost: invokes fail
@@ -440,20 +476,19 @@ When you call `command()`, it refuses:
440
476
  - `pin: true` with no Effect secret in `env`:
441
477
  ``command(argv, { pin: true }) names no secret in its env: `pin` chooses the commit a credentialed command runs against in the Sandbox Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``
442
478
 
479
+ `workspace()` refuses a body with an invoke that does not say where it works, when it can read the input:
480
+ ``workspace(): state "<state>": "<slot>" names no `<key>`, and a Workspace with Repo Slots says where each Turn and Effect works: a Worktree, or `cwd: "/work"` for a Turn (ADR-0057)``
481
+
443
482
  When a `command` starts, it refuses:
444
483
 
445
484
  - a state outside a `workspace()`. With no Effect secret in `env`:
446
485
  `command "<slot>" runs in the Harness container of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
447
486
  With one:
448
487
  `command "<slot>" runs in the Sandbox Effector of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
449
- - no `slot` under a Workspace with more than one Repo Slot:
450
- `command "<slot>" runs under a Workspace carrying more than one Repo Slot (<slots>) and names none — say which Worktree it runs in: command(argv, { slot: "<slot>" }) (ADR-0051)`.
451
- With an Effect secret in `env`, the text is
452
- `command "<slot>" runs under a Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" } (ADR-0051)`
488
+ - no `worktree` under Repo Slots, with no Effect secret in `env`:
489
+ ``command "<slot>" runs under a Workspace with Repo Slots (<slots>) and its input names no `worktree`: say which, a Worktree that a cut gave or `<slot>/default` ``
453
490
  - with an Effect secret in `env`, a Workspace with no Repo Slot:
454
491
  `command "<slot>" runs under a Workspace with no Repo Slot: a command with an Effect secret runs against one commit of a Repo Slot's branch, and there is none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent (ADR-0051)`
455
- - a `slot` that the Workspace does not have:
456
- `command "<slot>" names Repo Slot "<name>", which its Workspace does not carry (<slots>)`
457
492
  - an `arg()` with no string in the input. The angle brackets around the name are in the text, and `<type>` is `nothing`
458
493
  when the input has no such key:
459
494
  `command "<slot>": argv slot <<name>> has no string value in its input (got <type>) — map it in the invoke's input`
@@ -477,7 +512,7 @@ When you call `http()`, it refuses:
477
512
  - a file body with a method other than `PUT`, `POST` or `PATCH`:
478
513
  `http({ method: "<method>", body: file(...) }): a file body is sent with PUT, POST, PATCH (ADR-0068)`
479
514
  - a `body` that is not `file()`:
480
- ``http({ body }): a body is file(path, { slot?, segments?, contentType? }) — json and text come from `request` ``
515
+ ``http({ body }): a body is file(path, { segments?, contentType? }) — json and text come from `request` ``
481
516
  - a `content-type` header beside the `contentType` of `file()`:
482
517
  `http({ headers, body: file(path, { contentType }) }): "content-type" is set by file()'s contentType — write one of them (ADR-0067)`
483
518
  - a `request` with a file body:
@@ -489,9 +524,16 @@ When an `http` Effect starts, it refuses an input that its `input` schema does n
489
524
  `http "<slot>": its input does not match the definition's input schema — <path>: <message>`. With a file body, the text
490
525
  ends after `input schema`.
491
526
 
492
- An `http` Effect with a file body runs in the Sandbox Effector. When it starts, it refuses a state outside a
493
- `workspace()`:
494
- `http "<slot>" runs in the Sandbox Effector of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
527
+ An `http` Effect with a file body runs in the Sandbox Effector. When it starts, it refuses:
528
+
529
+ - a state outside a `workspace()`:
530
+ `http "<slot>" runs in the Sandbox Effector of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
531
+ - no `worktree` under Repo Slots:
532
+ ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no `worktree` ``
533
+
534
+ A bad `worktree` is output, because a word of a run can cause it. An `http` Effect with a file body settles `refused`. A
535
+ `command` settles `exited` with code 127, with the reason in `tail`. A credentialed `command` and a file body need a
536
+ cut: `no cut made <path> in this Sandbox: cut it with worktree()`
495
537
 
496
538
  When you call `secret()`, `bearer()` or `text`, they refuse:
497
539
 
@@ -534,6 +576,7 @@ When you call `secret()`, `bearer()` or `text`, they refuse:
534
576
  - [Secrets and trust](../concepts/secrets-and-trust.md): who holds an Effect secret.
535
577
  - [The Menu](../concepts/the-menu.md): how a pick waits for its answer.
536
578
  - [Push and sign a branch](push-and-sign-a-branch.md): the `push` Effect.
579
+ - [Cut a Worktree](cut-a-worktree.md): the Worktree that a `command` runs in, and the removal of a Worktree.
537
580
  - Reference: [`command`](../reference/authoring-surface/command.md),
538
581
  [`CommandOptions`](../reference/authoring-surface/type-commandoptions.md),
539
582
  [`http`](../reference/authoring-surface/http.md), [`secret`](../reference/authoring-surface/secret.md),
@@ -31,7 +31,7 @@ The imports:
31
31
  ```ts
32
32
  import { fromPromise } from "xstate";
33
33
  import { z } from "zod";
34
- import { customize, defineEvent, pool, source } from "@jr2/orchestrator";
34
+ import { customize, defineEvent, pool, source, type PoolNextInput } from "@jr2/orchestrator";
35
35
  import { task } from "@jr2/machines";
36
36
  ```
37
37
 
@@ -59,13 +59,9 @@ type Issue = { number: number; title: string; body: string };
59
59
  // because the request has no token.
60
60
  const ISSUES = "https://api.github.com/repos/you/app/issues?labels=jr2&state=open&per_page=100";
61
61
 
62
- // The issues that this Source gave to a worker. A worker has each one now, or it finished it.
63
- // The set is in the memory of the Orchestrator, and a restart empties it.
64
- const claimed = new Set<number>();
65
-
66
62
  // The Source: give the next open issue that no worker has had.
67
63
  const issues = source<Issue>({
68
- next: fromPromise<Issue | null, { active: string[] }>(async ({ input }) => {
64
+ next: fromPromise<Issue | null, PoolNextInput>(async ({ input }) => {
69
65
  const res = await fetch(ISSUES, { headers: { accept: "application/vnd.github+json" } }).catch(() => undefined);
70
66
  if (!res?.ok) {
71
67
  // While workers run, look again later. With no worker, stop the run with the error.
@@ -78,12 +74,11 @@ const issues = source<Issue>({
78
74
  body: string | null;
79
75
  pull_request?: unknown;
80
76
  }>;
81
- // The issues API also lists pull requests. Skip them, and skip each issue that a worker has now.
82
- const issue = open.find(
83
- (i) => i.pull_request === undefined && !claimed.has(i.number) && !input.active.includes(`issue-${i.number}`),
84
- );
77
+ // The issues API also lists pull requests. Skip them, and skip each issue that a worker has now
78
+ // or that a worker finished.
79
+ const taken = [...input.active, ...input.settled];
80
+ const issue = open.find((i) => i.pull_request === undefined && !taken.includes(`issue-${i.number}`));
85
81
  if (!issue) return null;
86
- claimed.add(issue.number);
87
82
  return { number: issue.number, title: issue.title, body: issue.body ?? "" };
88
83
  }),
89
84
  // Send this event to the `source` Gate when you add the label to an issue.
@@ -129,10 +124,10 @@ The file has five parts:
129
124
  When no issue is ready, it gives `null`. The request has no token, so the Repo must be public. Without a token, GitHub
130
125
  allows 60 requests in each hour. The example reads only the first page of issues: at most 100. When the request fails
131
126
  while workers run, `next` gives `null` in place of the error, so that the failure does not stop the run.
132
- - **The record of the Source.** `next` keeps each issue that it gives in the `Set` `claimed`, and skips the issues in
133
- `active`. The `Set` is in the memory of the [Orchestrator](../reference/glossary.md#orchestrator), and each run of the
134
- Workflow uses the same `Set`. A restart of the Orchestrator empties it. Then `next` can give a finished issue again,
135
- but never an issue that a worker of this run has now.
127
+ - **The record of the Source.** The Source keeps no record of its own. `next` skips each issue in `active` and in
128
+ `settled`. The Pool holds both, so they stay across a restart of the
129
+ [Orchestrator](../reference/glossary.md#orchestrator). Each run of the Workflow has its own lists, so a new run can
130
+ give an issue that is still open.
136
131
  - **The worker.** `fixer` is `task` with its two [Open](../reference/glossary.md#open) parts bound: the Repo and the
137
132
  model of the coder. [Bind a shipped Machine's Open parts](bind-open-parts.md) tells more.
138
133
  - **The run input.** `door` is the run input of the Pool. `cap` reads `workers` from it. A worker never gets it.
@@ -149,15 +144,21 @@ jr2 up
149
144
  …
150
145
  jr2 up — instance "my-instance" → context kind-jr2 / namespace my-instance
151
146
  typecheck: tsc --noEmit (the instance's own compiler)
147
+ …
148
+ effects: workflow "issues" — what it runs outside itself (ADR-0067)
149
+ cut: worktree — 600s, in the Harness
150
+ …
152
151
  failures: workflow "issues" — the states that fail the run (ADR-0073)
153
152
  issues.failing: invokes fail
153
+ task.noBranch: invokes fail
154
+ task.notCut: invokes fail
154
155
  task.lost: invokes fail
155
156
  …
156
157
  seats: workflow "issues" — who may send what, per state (ADR-0015)
157
158
  task.working agent:coder: [finish]
158
159
  task.review gate: [approve, request_changes]
159
160
  …
160
- agents: coder, triager (carried by 4 workflow machine(s))
161
+ agents: coder, triager (carried by 5 workflow machine(s))
161
162
  …
162
163
  sandbox nodes: jr2-control-plane
163
164
  …
@@ -180,7 +181,7 @@ jr2 run issues --input '{"workers":2}'
180
181
  ```text
181
182
  …
182
183
  → context kind-jr2 / namespace my-instance
183
- run e1effd33-9bf6-4137-ae70-3aa504910784 (issues) — attached
184
+ run 7c248a11-445a-4123-8ce2-834cb57706d7 (issues) — attached
184
185
  → active "discovering"
185
186
  → active "discovering"
186
187
  → active "discovering"
@@ -205,7 +206,8 @@ jr2 status <run id>
205
206
  ```text
206
207
  …
207
208
  → context kind-jr2 / namespace my-instance
208
- {"runId":"e1effd33-9bf6-4137-ae70-3aa504910784","workflow":"issues",…,"gates":[{"gate":"source",…},{"gate":"issue-3.body.review",…},{"gate":"issue-2.body.review",…}]}
209
+ {"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues",…,"gates":[{"gate":"source",…},{"gate":"issue-3.body.review",…},{"gate":"issue-2.body.review",…}]}
210
+ …
209
211
  ```
210
212
 
211
213
  In the JSON that it prints, `gates` holds each open Gate of the run:
@@ -233,7 +235,7 @@ jr2 send <run id> --gate issue-2.body.review --event approve
233
235
  ```text
234
236
  …
235
237
  → context kind-jr2 / namespace my-instance
236
- delivered approve to gate "issue-2.body.review" on e1effd33-9bf6-4137-ae70-3aa504910784
238
+ delivered approve to gate "issue-2.body.review" on 7c248a11-445a-4123-8ce2-834cb57706d7
237
239
  ```
238
240
 
239
241
  The Pool then calls `next` again, and starts a worker for the next issue.
@@ -254,12 +256,18 @@ jr2 send <run id> --gate source --event issues_changed
254
256
  ```text
255
257
  …
256
258
  → context kind-jr2 / namespace my-instance
257
- delivered issues_changed to gate "source" on e1effd33-9bf6-4137-ae70-3aa504910784
259
+ delivered issues_changed to gate "source" on 7c248a11-445a-4123-8ce2-834cb57706d7
258
260
  ```
259
261
 
260
- Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A webhook can send the same
262
+ Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A script can send the same
261
263
  event with [`POST /runs/:runId/gates/:gate/events`](../reference/http-api.md#post-runs-runid-gates-gate-events).
262
264
 
265
+ A [Hook](../reference/glossary.md#hook) can tell the Pool too. Give the Source a `hooks` handler for the forge's event,
266
+ for example `github` and `issues.labeled`, that gives [`reread()`](../reference/authoring-surface/reread.md), or
267
+ [`skip(reason)`](../reference/authoring-surface/skip.md) for a label that is not `jr2`. Then each delivery of the Hook
268
+ makes the Pool call `next` at once. [Pools and Sources](../concepts/pools-and-sources.md#a-hook-reaches-a-source) tells
269
+ how.
270
+
263
271
  ## The end of the run
264
272
 
265
273
  When the last worker finishes and no issue is ready, the run ends. In the first terminal, `jr2 run` prints the last
@@ -269,7 +277,7 @@ state of the Pool, then the status of the run as JSON:
269
277
  …
270
278
  → active "discovering"
271
279
  → done "drained"
272
- {"runId":"e1effd33-9bf6-4137-ae70-3aa504910784","workflow":"issues","instanceId":"db9712c8-7c19-4d18-8fb1-8c01d326808f","status":"done","value":"drained","context":{"runInput":{"workers":2,"instanceId":"db9712c8-7c19-4d18-8fb1-8c01d326808f"},"cap":2,"active":[],"items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{},"status":"drained"},"children":[],"waiting":[],"held":[],"assembly":"ca3b0e760c85","output":{"status":"drained","items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{}}}
280
+ {"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues","instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4","startedAt":"2026-11-06T19:20:02.886Z","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"status":"done","value":"drained","context":{"runInput":{"workers":2,"instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4"},"cap":2,"active":[],"items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{},"held":[],"ready":[],"claimed":{},"again":[],"latch":false,"taken":3,"carried":[],"refused":[],"status":"drained"},"children":[],"waiting":[],"held":[],"answers":[{"gate":"issue-2.body.review","type":"approve","at":"2026-11-06T19:21:49.638Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"source","type":"issues_changed","at":"2026-11-06T19:21:57.842Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-3.body.review","type":"approve","at":"2026-11-06T19:22:03.493Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-1.body.review","type":"approve","at":"2026-11-06T19:22:33.752Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}],"assembly":"f5bb024883b4","output":{"status":"drained","items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{}},"worktrees":[{"slot":"app","path":"/work/app/task","branch":"jr2/issue-3"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-2"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-1"}]}
273
281
  ```
274
282
 
275
283
  In the JSON, `context.items` holds the output of each worker that finished, by item id. `context.failures` holds the
@@ -286,8 +294,8 @@ In the JSON, `context.items` holds the output of each worker that finished, by i
286
294
  workers register the same Gate or [Agent](../reference/glossary.md#agent), the run stops with the status `error`, and
287
295
  its `fault` is `registration address "<address>" is already live (two concurrent registrations share an id)`. For
288
296
  `task`, `<address>` is `gate/<run id>/<item id>.body.review` or `agent/<run id>/<item id>.body/coder`.
289
- - `next` gets only `active`: the ids of the items that workers have now. To not give a finished item again, the Source
290
- must keep the items that it gave.
297
+ - `next` gets `active`, the ids of the items that workers have now, and `settled`, the ids of the finished and failed
298
+ items. It also gets `input`, the input of the Pool. To not give a finished item again, skip each id in `settled`.
291
299
  - When a worker fails, for example when the Sandbox of a `task` is lost, the Pool keeps the Failure in `failures` and
292
300
  goes on. Only the Sandbox of that worker is deleted. The other workers, and the work in their Sandboxes, stay. A
293
301
  defect in a worker is different: the run stops with the status `error`.
@@ -305,9 +313,8 @@ In the JSON, `context.items` holds the output of each worker that finished, by i
305
313
  starts with ``task: no `branch` on the door and no Instance ID to derive one from``.
306
314
  - Without `cap`, one worker runs at a time. Each `task` worker has its own
307
315
  [Workspace](../reference/glossary.md#workspace). Thus the run has `cap` Sandboxes or fewer at one time.
308
- - The Pool takes the `wake` event only while it waits: workers run, and no item is ready. At other times,
309
- [`jr2 send`](../reference/cli.md#send) refuses it:
310
- `error: gate "source" is open, and the run does not take "<event>" now: no writing of it would move the run with this payload — not delivered (takes now: nothing)`.
316
+ - The Pool takes the `wake` event at any time while the run is open. While it waits, it calls `next` at once. While
317
+ `next` runs, it calls `next` once more after that call, so that the change is not lost.
311
318
  - `onDrained` takes only `"final"`: the run ends.
312
319
 
313
320
  ## Read next