@jr2/orchestrator 0.4.1 → 0.6.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 (361) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +478 -0
  10. package/docs/concepts/architecture.md +243 -0
  11. package/docs/concepts/charts.md +146 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +257 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +658 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +298 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +306 -0
  20. package/docs/concepts/the-menu.md +381 -0
  21. package/docs/concepts/workspaces.md +756 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +383 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +993 -0
  27. package/docs/get-started/install.md +130 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +308 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +224 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/cut-a-worktree.md +560 -0
  34. package/docs/guides/fix-a-failed-converge.md +361 -0
  35. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  36. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  37. package/docs/guides/let-an-agent-write-a-chart.md +230 -0
  38. package/docs/guides/look-inside-a-sandbox.md +177 -0
  39. package/docs/guides/mirror-the-kit-images.md +176 -0
  40. package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
  41. package/docs/guides/open-the-console.md +91 -0
  42. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  43. package/docs/guides/push-and-sign-a-branch.md +532 -0
  44. package/docs/guides/run-a-chart-by-name.md +270 -0
  45. package/docs/guides/run-a-command-or-call-a-url.md +587 -0
  46. package/docs/guides/run-one-worker-per-item.md +322 -0
  47. package/docs/guides/set-an-agents-access-and-dials.md +420 -0
  48. package/docs/guides/size-a-workspace.md +217 -0
  49. package/docs/guides/start-a-run-from-a-run.md +237 -0
  50. package/docs/guides/start-a-run.md +207 -0
  51. package/docs/guides/upgrade-the-kit-version.md +240 -0
  52. package/docs/guides/use-a-hosted-provider.md +214 -0
  53. package/docs/guides/use-a-local-model.md +313 -0
  54. package/docs/guides/watch-a-run.md +349 -0
  55. package/docs/guides/write-a-workflow.md +318 -0
  56. package/docs/index.md +44 -0
  57. package/docs/llms.txt +237 -0
  58. package/docs/reference/authoring-surface/agent.md +151 -0
  59. package/docs/reference/authoring-surface/answer.md +156 -0
  60. package/docs/reference/authoring-surface/arg.md +82 -0
  61. package/docs/reference/authoring-surface/basic.md +62 -0
  62. package/docs/reference/authoring-surface/bearer.md +53 -0
  63. package/docs/reference/authoring-surface/branchname.md +15 -0
  64. package/docs/reference/authoring-surface/chartschema.md +23 -0
  65. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  66. package/docs/reference/authoring-surface/command.md +206 -0
  67. package/docs/reference/authoring-surface/customize.md +64 -0
  68. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  69. package/docs/reference/authoring-surface/defineevent.md +114 -0
  70. package/docs/reference/authoring-surface/dispatch.md +126 -0
  71. package/docs/reference/authoring-surface/fail.md +96 -0
  72. package/docs/reference/authoring-surface/file.md +142 -0
  73. package/docs/reference/authoring-surface/frompick.md +277 -0
  74. package/docs/reference/authoring-surface/http.md +259 -0
  75. package/docs/reference/authoring-surface/isfailure.md +58 -0
  76. package/docs/reference/authoring-surface/isready.md +14 -0
  77. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  78. package/docs/reference/authoring-surface/open.md +105 -0
  79. package/docs/reference/authoring-surface/openbody.md +86 -0
  80. package/docs/reference/authoring-surface/paramsof.md +143 -0
  81. package/docs/reference/authoring-surface/pool.md +186 -0
  82. package/docs/reference/authoring-surface/push.md +130 -0
  83. package/docs/reference/authoring-surface/readyof.md +21 -0
  84. package/docs/reference/authoring-surface/secret.md +139 -0
  85. package/docs/reference/authoring-surface/sentby.md +83 -0
  86. package/docs/reference/authoring-surface/source.md +51 -0
  87. package/docs/reference/authoring-surface/text.md +62 -0
  88. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  89. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  90. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  91. package/docs/reference/authoring-surface/type-agentslots.md +15 -0
  92. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  93. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  94. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  95. package/docs/reference/authoring-surface/type-arg.md +19 -0
  96. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  97. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  98. package/docs/reference/authoring-surface/type-binding.md +61 -0
  99. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  100. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  101. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  102. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  103. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  104. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  105. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  106. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  107. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  108. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  109. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  110. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  111. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  112. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  113. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  114. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  115. package/docs/reference/authoring-surface/type-childat.md +12 -0
  116. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  117. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  118. package/docs/reference/authoring-surface/type-commandinput.md +20 -0
  119. package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
  120. package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
  121. package/docs/reference/authoring-surface/type-customize.md +111 -0
  122. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  123. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  124. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  125. package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
  126. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  127. package/docs/reference/authoring-surface/type-duration.md +15 -0
  128. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  129. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  130. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  131. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  132. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  133. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  134. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  135. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  136. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  137. package/docs/reference/authoring-surface/type-failure.md +19 -0
  138. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  139. package/docs/reference/authoring-surface/type-filebody.md +37 -0
  140. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  141. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  142. package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
  143. package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
  144. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  145. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  146. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  147. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
  149. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  150. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  151. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  152. package/docs/reference/authoring-surface/type-lends.md +38 -0
  153. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  154. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  155. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  156. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  157. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  158. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
  159. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  160. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  161. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  162. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  163. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  164. package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
  165. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  166. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  167. package/docs/reference/authoring-surface/type-openof.md +12 -0
  168. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  169. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  170. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
  171. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
  172. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  173. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  174. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  175. package/docs/reference/authoring-surface/type-pushinput.md +34 -0
  176. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  177. package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
  178. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  179. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  180. package/docs/reference/authoring-surface/type-reached.md +12 -0
  181. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  182. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  183. package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
  184. package/docs/reference/authoring-surface/type-secret.md +25 -0
  185. package/docs/reference/authoring-surface/type-sender.md +19 -0
  186. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  187. package/docs/reference/authoring-surface/type-size.md +85 -0
  188. package/docs/reference/authoring-surface/type-slothandles.md +24 -0
  189. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  190. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  191. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  192. package/docs/reference/authoring-surface/type-text.md +19 -0
  193. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  194. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  195. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  196. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  197. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  198. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  199. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  200. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  201. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  202. package/docs/reference/authoring-surface/type-workspaced.md +115 -0
  203. package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
  204. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  205. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  206. package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
  207. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  208. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  209. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  210. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  211. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  212. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  213. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  214. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  215. package/docs/reference/authoring-surface/workspace.md +287 -0
  216. package/docs/reference/authoring-surface/worktree.md +71 -0
  217. package/docs/reference/authoring-surface.md +217 -0
  218. package/docs/reference/cli.md +433 -0
  219. package/docs/reference/config.md +765 -0
  220. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
  221. package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
  222. package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
  223. package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
  224. package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
  225. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  226. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  227. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  228. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  229. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  230. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  231. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
  232. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  233. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
  234. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
  235. package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
  236. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  237. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
  238. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  239. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
  240. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  241. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
  242. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
  243. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  244. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  245. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
  246. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
  247. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  248. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  249. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
  250. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  251. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
  252. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  253. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  254. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  255. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  256. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  257. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  258. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  259. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  260. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  261. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  262. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  263. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  264. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  265. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  266. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  267. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  268. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
  269. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
  270. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
  271. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  272. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  273. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  274. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  275. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
  276. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
  277. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  278. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  279. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
  280. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
  281. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  282. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  283. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
  284. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
  285. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
  286. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  287. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
  288. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  289. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  290. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  291. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  292. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  293. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  294. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  295. package/docs/reference/decisions.md +86 -0
  296. package/docs/reference/glossary.md +687 -0
  297. package/docs/reference/http-api.md +1386 -0
  298. package/docs/reference/machines.md +189 -0
  299. package/docs/reference/mechanism-events.md +72 -0
  300. package/docs/why.md +207 -0
  301. package/package.json +5 -3
  302. package/src/actor.ts +694 -106
  303. package/src/agent.ts +114 -38
  304. package/src/ambient.ts +3 -4
  305. package/src/assembly-checks.ts +189 -7
  306. package/src/cause.ts +234 -0
  307. package/src/chart.ts +345 -46
  308. package/src/charts.ts +43 -8
  309. package/src/config.ts +440 -178
  310. package/src/custodian.lua +17 -5
  311. package/src/custodian.ts +325 -99
  312. package/src/customize.ts +199 -49
  313. package/src/dispatch-effect.ts +945 -0
  314. package/src/door-compat.ts +217 -0
  315. package/src/durability.ts +11 -4
  316. package/src/effect-checks.ts +50 -3
  317. package/src/effect.ts +641 -128
  318. package/src/effector-wire.ts +165 -13
  319. package/src/fail.ts +222 -0
  320. package/src/harness-client.ts +587 -50
  321. package/src/harness-env.ts +33 -0
  322. package/src/held-secrets.ts +125 -16
  323. package/src/holds.ts +3 -0
  324. package/src/http-effect.ts +556 -58
  325. package/src/http.ts +480 -30
  326. package/src/index.ts +74 -82
  327. package/src/instance.ts +68 -8
  328. package/src/internal.ts +85 -0
  329. package/src/kube-client.ts +6 -3
  330. package/src/machine-doc.ts +24 -7
  331. package/src/mechanism-routes.ts +168 -0
  332. package/src/model-keys.generated.ts +53 -0
  333. package/src/model-keys.ts +42 -58
  334. package/src/names.ts +19 -6
  335. package/src/notices.ts +483 -41
  336. package/src/open-body.ts +242 -40
  337. package/src/open-effect.ts +81 -8
  338. package/src/open.ts +90 -6
  339. package/src/parts.ts +213 -30
  340. package/src/party.ts +101 -2
  341. package/src/pick.ts +497 -11
  342. package/src/pool.ts +282 -50
  343. package/src/registration.ts +133 -12
  344. package/src/repo-identity.ts +7 -3
  345. package/src/repos.ts +22 -3
  346. package/src/run-host.ts +665 -45
  347. package/src/run-output.ts +38 -0
  348. package/src/sandbox-effect.ts +457 -71
  349. package/src/sandbox-kube.ts +66 -17
  350. package/src/secret-placement.ts +59 -17
  351. package/src/server.ts +3 -0
  352. package/src/setup.ts +325 -62
  353. package/src/size.ts +121 -9
  354. package/src/snapshot-store.ts +54 -10
  355. package/src/stub-harness.ts +44 -16
  356. package/src/timers.ts +14 -1
  357. package/src/tokens.ts +107 -32
  358. package/src/vocabulary.ts +88 -14
  359. package/src/wire.ts +100 -26
  360. package/src/workspace.ts +771 -238
  361. package/src/worktree.ts +249 -0
@@ -0,0 +1,756 @@
1
+ ---
2
+ title: Workspaces
3
+ description: How a Workflow gets a Sandbox with a git Worktree or a shared folder, and how long it keeps it.
4
+ ---
5
+
6
+ # Workspaces
7
+
8
+ A [Workspace](../reference/glossary.md#workspace) is a [Machine](../reference/glossary.md#machine) that runs a body
9
+ Machine in a [Sandbox](../reference/glossary.md#sandbox). The Sandbox is a pod of its own, with a folder, `/work`, that
10
+ the Agents and Effects of the body share. When the Workspace names [Repos](../reference/glossary.md#repo), `/work` holds
11
+ a clone of each Repo. The body cuts each git [Worktree](../reference/glossary.md#worktree) that it needs. A Workspace
12
+ that names no Repo has only the folder (see [A Workspace with no Repo](#a-workspace-with-no-repo)). You make a Workspace
13
+ with [`workspace()`](../reference/authoring-surface/workspace.md). The Workspace makes the Sandbox when the run enters
14
+ it, and deletes the Sandbox when the body reaches a final state.
15
+
16
+ ## Why Workspaces exist
17
+
18
+ An [Agent](../reference/glossary.md#agent) that works on code runs programs, writes files and opens ports. Two Agents on
19
+ one host would share the filesystem, the ports and the processes. A Sandbox gives each unit of work its own pod, with
20
+ its own filesystem, network and processes.
21
+
22
+ The Workspace holds the life of that pod, so that your Machine does not. Your body Machine does the work: it codes,
23
+ reviews and waits for a person. The Workspace makes the Sandbox before the body starts, and removes it after the body
24
+ ends. A coder and a reviewer in one body share one Workspace: the isolation is for each unit of work, not for each
25
+ Agent.
26
+
27
+ ## A Workspace and its body
28
+
29
+ This example is a [Workflow](../reference/glossary.md#workflow). A coder changes the `app` Repo, and reads the
30
+ `handbook` Repo beside it. The Machine first cuts the coder's Worktree. Then a person approves the change at a
31
+ [Gate](../reference/glossary.md#gate), and the Machine pushes the branch:
32
+
33
+ **Anthropic**
34
+
35
+ ```ts
36
+ import { z } from "zod";
37
+ import {
38
+ agent,
39
+ defineEvent,
40
+ fail,
41
+ isReady,
42
+ jr2Setup,
43
+ push,
44
+ workspace,
45
+ worktree,
46
+ type Workspaced,
47
+ } from "@jr2/orchestrator";
48
+
49
+ const finished = defineEvent({
50
+ name: "finished",
51
+ description: "Call this when your change is committed.",
52
+ input: z.object({}),
53
+ });
54
+ const approve = defineEvent({
55
+ name: "approve",
56
+ description: "The change is good. Push it, and end the run.",
57
+ input: z.object({}),
58
+ });
59
+
60
+ // The run input: what `jr2 run` gives with --input.
61
+ const runInput = z.object({ ticket: z.string(), prompt: z.string() });
62
+ type BodyInput = Workspaced<z.infer<typeof runInput>, "app" | "handbook">;
63
+
64
+ // The folder of the Worktree, and its branch. The Machine knows both before the cut.
65
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
66
+ const branchOf = (context: BodyInput) => `docs/${context.ticket}`;
67
+
68
+ const body = jr2Setup({
69
+ types: {} as { context: BodyInput; input: BodyInput },
70
+ events: [finished, approve],
71
+ actors: {
72
+ coder: agent({
73
+ model: "anthropic/claude-haiku-5-5",
74
+ instructions: "You are a careful engineer. You commit your work on the branch.",
75
+ }),
76
+ cut: worktree(),
77
+ // Two Repo Slots, so the push names the slot that it pushes.
78
+ publish: push({ slot: "app" }),
79
+ fail: fail(),
80
+ },
81
+ }).createMachine({
82
+ id: "docs-fix",
83
+ context: ({ input }) => input,
84
+ initial: "cutting",
85
+ // The Sandbox is gone, and its unpushed commits with it. This body fails the run.
86
+ on: { "agent.fault": ".failed", "workspace.lost": ".lost" },
87
+ states: {
88
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
89
+ cutting: {
90
+ invoke: {
91
+ src: "cut",
92
+ input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
93
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
94
+ },
95
+ },
96
+ working: {
97
+ invoke: {
98
+ src: "coder",
99
+ input: ({ context }) => ({
100
+ // A slot that is only read needs no cut: its `default` folder is on the Binding's base.
101
+ prompt: `${context.prompt}\nRead the style rules in ${context.workspace.repos.handbook}/default.`,
102
+ cwd: fixTree(context),
103
+ }),
104
+ },
105
+ on: { finished: "review" },
106
+ },
107
+ // The run waits here, and the Sandbox stays, until a person sends approve.
108
+ review: {
109
+ invoke: {
110
+ src: "gate",
111
+ input: ({ context }) => ({
112
+ meta: { branch: branchOf(context), worktree: fixTree(context) },
113
+ }),
114
+ },
115
+ on: { approve: "publishing" },
116
+ },
117
+ // Push before the body ends: jr2 deletes the Sandbox, and its commits, at a final state.
118
+ // A push that did not publish goes back to the Gate, so the Sandbox stays.
119
+ publishing: {
120
+ invoke: {
121
+ src: "publish",
122
+ // The Worktree names what to push: jr2 reads its branch from the cut.
123
+ input: ({ context }) => ({ worktree: fixTree(context) }),
124
+ onDone: [{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" }, { target: "review" }],
125
+ },
126
+ },
127
+ done: { type: "final" },
128
+ // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
129
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
130
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
131
+ failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
132
+ },
133
+ });
134
+
135
+ export const machine = workspace(body, {
136
+ input: runInput,
137
+ // The Size of the whole Sandbox. jr2 reserves all of it on the node.
138
+ resources: { limits: { memory: "4Gi", cpu: "2" } },
139
+ repos: {
140
+ // Two Repo Slots. Each one becomes a folder under /work.
141
+ app: { url: "https://github.com/you/app.git", ref: "main" },
142
+ handbook: "https://github.com/you/handbook.git",
143
+ },
144
+ });
145
+ ```
146
+
147
+ **Local**
148
+
149
+ ```ts
150
+ import { z } from "zod";
151
+ import {
152
+ agent,
153
+ defineEvent,
154
+ fail,
155
+ isReady,
156
+ jr2Setup,
157
+ push,
158
+ workspace,
159
+ worktree,
160
+ type Workspaced,
161
+ } from "@jr2/orchestrator";
162
+
163
+ const finished = defineEvent({
164
+ name: "finished",
165
+ description: "Call this when your change is committed.",
166
+ input: z.object({}),
167
+ });
168
+ const approve = defineEvent({
169
+ name: "approve",
170
+ description: "The change is good. Push it, and end the run.",
171
+ input: z.object({}),
172
+ });
173
+
174
+ // The run input: what `jr2 run` gives with --input.
175
+ const runInput = z.object({ ticket: z.string(), prompt: z.string() });
176
+ type BodyInput = Workspaced<z.infer<typeof runInput>, "app" | "handbook">;
177
+
178
+ // The folder of the Worktree, and its branch. The Machine knows both before the cut.
179
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
180
+ const branchOf = (context: BodyInput) => `docs/${context.ticket}`;
181
+
182
+ const body = jr2Setup({
183
+ types: {} as { context: BodyInput; input: BodyInput },
184
+ events: [finished, approve],
185
+ actors: {
186
+ coder: agent({
187
+ model: "local/qwen3.6-35b-a3b",
188
+ instructions: "You are a careful engineer. You commit your work on the branch.",
189
+ }),
190
+ cut: worktree(),
191
+ // Two Repo Slots, so the push names the slot that it pushes.
192
+ publish: push({ slot: "app" }),
193
+ fail: fail(),
194
+ },
195
+ }).createMachine({
196
+ id: "docs-fix",
197
+ context: ({ input }) => input,
198
+ initial: "cutting",
199
+ // The Sandbox is gone, and its unpushed commits with it. This body fails the run.
200
+ on: { "agent.fault": ".failed", "workspace.lost": ".lost" },
201
+ states: {
202
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
203
+ cutting: {
204
+ invoke: {
205
+ src: "cut",
206
+ input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
207
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
208
+ },
209
+ },
210
+ working: {
211
+ invoke: {
212
+ src: "coder",
213
+ input: ({ context }) => ({
214
+ // A slot that is only read needs no cut: its `default` folder is on the Binding's base.
215
+ prompt: `${context.prompt}\nRead the style rules in ${context.workspace.repos.handbook}/default.`,
216
+ cwd: fixTree(context),
217
+ }),
218
+ },
219
+ on: { finished: "review" },
220
+ },
221
+ // The run waits here, and the Sandbox stays, until a person sends approve.
222
+ review: {
223
+ invoke: {
224
+ src: "gate",
225
+ input: ({ context }) => ({
226
+ meta: { branch: branchOf(context), worktree: fixTree(context) },
227
+ }),
228
+ },
229
+ on: { approve: "publishing" },
230
+ },
231
+ // Push before the body ends: jr2 deletes the Sandbox, and its commits, at a final state.
232
+ // A push that did not publish goes back to the Gate, so the Sandbox stays.
233
+ publishing: {
234
+ invoke: {
235
+ src: "publish",
236
+ // The Worktree names what to push: jr2 reads its branch from the cut.
237
+ input: ({ context }) => ({ worktree: fixTree(context) }),
238
+ onDone: [{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" }, { target: "review" }],
239
+ },
240
+ },
241
+ done: { type: "final" },
242
+ // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
243
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
244
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
245
+ failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
246
+ },
247
+ });
248
+
249
+ export const machine = workspace(body, {
250
+ input: runInput,
251
+ // The Size of the whole Sandbox. jr2 reserves all of it on the node.
252
+ resources: { limits: { memory: "4Gi", cpu: "2" } },
253
+ repos: {
254
+ // Two Repo Slots. Each one becomes a folder under /work.
255
+ app: { url: "https://github.com/you/app.git", ref: "main" },
256
+ handbook: "https://github.com/you/handbook.git",
257
+ },
258
+ });
259
+ ```
260
+
261
+ [Agents and Turns](agents-and-turns.md) tells how to define an Agent.
262
+
263
+ The options of `workspace()` say what the Sandbox is. [`jr2 up`](../reference/cli.md#up) reads them before a run starts.
264
+ Only a per-run [Repo Slot](../reference/glossary.md#repo-slot) reads the run input:
265
+
266
+ - `input` is the schema of the run input: what [`jr2 run`](../reference/cli.md#run) gives with `--input`. Put it on the
267
+ `workspace()`, not on the body.
268
+ - `repos` gives the Repo Slots that the Sandbox clones. Leave it out for a Workspace with no Repo.
269
+ - `resources` is the [Size](../reference/glossary.md#size). `image`, `user` and `effector` name the images of the pod's
270
+ containers.
271
+ - `secrets` names the [Held secrets](../reference/glossary.md#held-secret) that the Sandbox can use (see
272
+ [Secrets and trust](secrets-and-trust.md)).
273
+ - `workGroup` is the group id that owns the files in `/work`. The default is 2000.
274
+
275
+ The body gets the run input, plus `workspace`: the handles of the Workspace (see
276
+ [`WorkspaceHandles`](../reference/authoring-surface/type-workspacehandles.md)). `workspace.repos` gives the folder of
277
+ each Repo Slot, `/work/<slot>`. This folder is not a Worktree. Give the path of a Worktree to an Agent's
278
+ [Turn](../reference/glossary.md#turn) as its `cwd`, as the example does. Type the body's input with
279
+ [`Workspaced`](../reference/authoring-surface/type-workspaced.md), also when the run input is `z.object({})`.
280
+
281
+ The Agents with [Working tools](../reference/glossary.md#working-tools) in the body run in the Sandbox. An Agent with
282
+ `workspace: "none"` runs on the [Instance Harness](../reference/glossary.md#instance-harness), also when its state is in
283
+ a body. A `command` and a `push` [Effect](../reference/glossary.md#effect) in the body also run in the Sandbox (see
284
+ [Effects](effects.md)).
285
+
286
+ ## Repos and Repo Slots
287
+
288
+ A Repo is a git repository. Its url names it: the host and the path. The scheme, the user and `.git` do not change which
289
+ Repo it is. `jr2.config.ts` has no list of Repos. A Machine names each Repo that it uses in a Repo Slot.
290
+
291
+ A Repo Slot is a key of the `repos` option. The key is your Machine's name for the Repo, and it is the folder of the
292
+ Repo under `/work`. Each slot is one of three kinds:
293
+
294
+ - **Bound**: the Machine gives the url, or a [Binding](../reference/glossary.md#binding) `{ url, ref }`. `ref` is the
295
+ branch, the tag or the commit that the clone stays on, and a new branch starts from it. Without `ref`, it is the
296
+ Repo's default branch.
297
+ - **[Open](../reference/glossary.md#open)**: the Machine gives [`open`](../reference/authoring-surface/open.md). The
298
+ [Instance](../reference/glossary.md#instance) that registers the Machine binds the slot with
299
+ [`customize()`](../reference/authoring-surface/customize.md) (see [Composition](composition.md)).
300
+ - **Per-run**: the Machine gives a function of the run input, `({ input }) => url`. Each run chooses its Repo.
301
+
302
+ A Machine that names no slot writes `repos: open` for the whole map. Then the composer names each slot.
303
+ `workspace.repos` keeps the slots in the order that `repos` declares them. jr2 gives no slot a special meaning. Your
304
+ Machine says where each Agent works.
305
+
306
+ The cluster keeps one read-only cache of each Repo on each node. A Sandbox clones from that cache, and a fetch in the
307
+ pod goes through it. [Architecture](architecture.md) tells about the Repo cache. Run
308
+ [`jr2 status`](../reference/cli.md#status) with no run id to see the Repos of the Instance, for each node.
309
+
310
+ ## The folders in the Sandbox
311
+
312
+ Each Repo Slot gets a folder under `/work`. The clone of the Repo is `/work/<slot>/default/`. It stays on the base of
313
+ the Binding: a branch is attached, and a tag or a commit is detached. No work happens in `default/`. A slot that an
314
+ Agent only reads needs nothing more: the Agent reads `/work/<slot>/default`. This is the
315
+ [Project layout](../reference/glossary.md#project-layout). `/work/.jr2/` is reserved for jr2.
316
+
317
+ The Workspace only clones. Your Machine cuts each Worktree with the
318
+ [`worktree()`](../reference/authoring-surface/worktree.md) Effect, in a state of the body. You give the path, and the
319
+ branch or the commit:
320
+
321
+ - `{ path: "/work/app/fix", branch: "docs/123" }` cuts a branch Worktree. The cut finds a local branch, then
322
+ `origin/<branch>`, tracked, after a fetch. With neither, it makes a new branch from `from` or the base of the Binding.
323
+ - `{ path: "/work/app/review", detach: sha }` cuts a detached Worktree for a reviewer. Each cut forces it to the commit
324
+ and cleans it. Ignored files stay, so that the reviewer can run the tests.
325
+
326
+ The path is exactly `/work/<slot>/<name>`. The name is one segment that is not `default` and starts with neither `.` nor
327
+ `-`. So your Machine knows the path before the cut, and the folder name does not change with the branch name.
328
+
329
+ The output of a cut is `ready`, `refused` or `unknown`. A later outcome can come, so route the last arm of `onDone` with
330
+ no guard. A bad path or a bad branch name is `refused`, and nothing changes. A cut is idempotent: the same cut again
331
+ reads the same Worktree, and its `head` is the tip of the branch.
332
+
333
+ `worktree({ expect: "new" })` refuses a branch that exists. `worktree({ expect: "existing" })` refuses a branch that
334
+ does not exist, and makes nothing.
335
+
336
+ The author chooses how many Agents work in a Workspace. Agents in one Worktree share git, as people in one checkout do.
337
+ Each coder that needs its own branch gets its own Worktree of the one clone.
338
+
339
+ Your Machine chooses the branch name. Give each run a different branch, so that two runs do not push to one branch. Make
340
+ it from a field of the run input, or from `instanceId` when the run input has no field that is different for each run.
341
+ Only a root `workspace()` gets `instanceId` from jr2. A `workspace()` that a different Machine invokes gets it only when
342
+ its parent gives it.
343
+
344
+ An Agent, a `command`, a `push`, and an `http` Effect with a file body each name their Worktree by its path: `cwd` for a
345
+ Turn, `worktree` for an Effect. A `push` has no branch in its input. jr2 reads the branch from the cut of that path. So
346
+ an Agent that moves `HEAD` does not change what is pushed.
347
+
348
+ jr2 has no Effect that removes a Worktree. To remove one, run `git worktree remove` in a `command`.
349
+ [Cut a Worktree](../guides/cut-a-worktree.md) gives the procedure.
350
+
351
+ ## A Workspace with no Repo
352
+
353
+ Some work has no Repo: get data, write a report from it, edit the report, and send it. For this work, leave `repos` out.
354
+ The Sandbox then has only `/work`, and the steps of the body share it. A step is an Agent's Turn or an Effect of the
355
+ body. This is like one job of a GitHub Actions workflow, whose steps share one folder. Two Workspaces are two jobs, and
356
+ they share nothing.
357
+
358
+ **Anthropic**
359
+
360
+ ```ts
361
+ import { z } from "zod";
362
+ import {
363
+ agent,
364
+ arg,
365
+ bearer,
366
+ command,
367
+ defineEvent,
368
+ fail,
369
+ file,
370
+ http,
371
+ jr2Setup,
372
+ secret,
373
+ workspace,
374
+ type CommandOutput,
375
+ type Workspaced,
376
+ } from "@jr2/orchestrator";
377
+
378
+ const drafted = defineEvent({
379
+ name: "drafted",
380
+ description: "Call this when report.md is written.",
381
+ input: z.object({}),
382
+ });
383
+ const edited = defineEvent({
384
+ name: "edited",
385
+ description: "Call this when report.md is ready to send.",
386
+ input: z.object({}),
387
+ });
388
+
389
+ const runInput = z.object({ week: z.string() });
390
+ // `never`: the Workspace has no Repo Slot, so the handles hold no path and no branch.
391
+ type BodyInput = Workspaced<z.infer<typeof runInput>, never>;
392
+
393
+ const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
394
+
395
+ const body = jr2Setup({
396
+ types: {} as { context: BodyInput; input: BodyInput },
397
+ events: [drafted, edited],
398
+ actors: {
399
+ // A command with no Effect secret runs in /work, beside the Agents.
400
+ fetch: command(["curl", "-fsSLo", "sales.csv", arg("url")]),
401
+ writer: agent({
402
+ model: "anthropic/claude-haiku-5-5",
403
+ instructions: "You write short, clear reports from data.",
404
+ }),
405
+ editor: agent({
406
+ model: "anthropic/claude-haiku-5-5",
407
+ instructions: "You edit reports. You make them shorter and correct each number.",
408
+ }),
409
+ // The Sandbox Effector reads the file from /work and sends it. The Agents never read the secret.
410
+ upload: http({
411
+ method: "PUT",
412
+ url: "https://files.example.com/reports",
413
+ auth: bearer(secret("files")),
414
+ input: runInput,
415
+ body: file<typeof runInput>("report.md", {
416
+ segments: ({ week }) => [`${week}.md`],
417
+ contentType: "text/markdown",
418
+ }),
419
+ }),
420
+ fail: fail(),
421
+ },
422
+ }).createMachine({
423
+ id: "weekly-report",
424
+ context: ({ input }) => input,
425
+ // /work goes with the Sandbox. Nothing is sent yet, so this body fails the run.
426
+ on: { "agent.fault": ".failed", "workspace.lost": ".lost" },
427
+ initial: "fetching",
428
+ states: {
429
+ fetching: {
430
+ invoke: {
431
+ src: "fetch",
432
+ input: ({ context }) => ({ url: `https://data.example.com/sales/${context.week}.csv` }),
433
+ onDone: [{ guard: ({ event }) => passed(event.output), target: "writing" }, { target: "failed" }],
434
+ },
435
+ },
436
+ // Each Agent's Turn frames no `cwd`, so it works in /work, where sales.csv is.
437
+ writing: {
438
+ invoke: { src: "writer", input: { prompt: "Write report.md: the sales of the week in sales.csv." } },
439
+ on: { drafted: "editing" },
440
+ },
441
+ editing: {
442
+ invoke: { src: "editor", input: { prompt: "Edit report.md. Check each number against sales.csv." } },
443
+ on: { edited: "uploading" },
444
+ },
445
+ // Send the report before the body ends: jr2 deletes /work with the Sandbox.
446
+ uploading: {
447
+ invoke: {
448
+ src: "upload",
449
+ input: ({ context }) => ({ week: context.week }),
450
+ onDone: [
451
+ { guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
452
+ { target: "failed" },
453
+ ],
454
+ },
455
+ },
456
+ done: { type: "final" },
457
+ // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
458
+ failed: { invoke: { src: "fail", input: { reason: "a step of the report did not succeed" } } },
459
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
460
+ },
461
+ });
462
+
463
+ // No `repos`: the Workspace clones nothing, and its steps share /work.
464
+ export const machine = workspace(body, { input: runInput });
465
+ ```
466
+
467
+ **Local**
468
+
469
+ ```ts
470
+ import { z } from "zod";
471
+ import {
472
+ agent,
473
+ arg,
474
+ bearer,
475
+ command,
476
+ defineEvent,
477
+ fail,
478
+ file,
479
+ http,
480
+ jr2Setup,
481
+ secret,
482
+ workspace,
483
+ type CommandOutput,
484
+ type Workspaced,
485
+ } from "@jr2/orchestrator";
486
+
487
+ const drafted = defineEvent({
488
+ name: "drafted",
489
+ description: "Call this when report.md is written.",
490
+ input: z.object({}),
491
+ });
492
+ const edited = defineEvent({
493
+ name: "edited",
494
+ description: "Call this when report.md is ready to send.",
495
+ input: z.object({}),
496
+ });
497
+
498
+ const runInput = z.object({ week: z.string() });
499
+ // `never`: the Workspace has no Repo Slot, so the handles hold no path and no branch.
500
+ type BodyInput = Workspaced<z.infer<typeof runInput>, never>;
501
+
502
+ const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
503
+
504
+ const body = jr2Setup({
505
+ types: {} as { context: BodyInput; input: BodyInput },
506
+ events: [drafted, edited],
507
+ actors: {
508
+ // A command with no Effect secret runs in /work, beside the Agents.
509
+ fetch: command(["curl", "-fsSLo", "sales.csv", arg("url")]),
510
+ writer: agent({
511
+ model: "local/qwen3.6-35b-a3b",
512
+ instructions: "You write short, clear reports from data.",
513
+ }),
514
+ editor: agent({
515
+ model: "local/qwen3.6-35b-a3b",
516
+ instructions: "You edit reports. You make them shorter and correct each number.",
517
+ }),
518
+ // The Sandbox Effector reads the file from /work and sends it. The Agents never read the secret.
519
+ upload: http({
520
+ method: "PUT",
521
+ url: "https://files.example.com/reports",
522
+ auth: bearer(secret("files")),
523
+ input: runInput,
524
+ body: file<typeof runInput>("report.md", {
525
+ segments: ({ week }) => [`${week}.md`],
526
+ contentType: "text/markdown",
527
+ }),
528
+ }),
529
+ fail: fail(),
530
+ },
531
+ }).createMachine({
532
+ id: "weekly-report",
533
+ context: ({ input }) => input,
534
+ // /work goes with the Sandbox. Nothing is sent yet, so this body fails the run.
535
+ on: { "agent.fault": ".failed", "workspace.lost": ".lost" },
536
+ initial: "fetching",
537
+ states: {
538
+ fetching: {
539
+ invoke: {
540
+ src: "fetch",
541
+ input: ({ context }) => ({ url: `https://data.example.com/sales/${context.week}.csv` }),
542
+ onDone: [{ guard: ({ event }) => passed(event.output), target: "writing" }, { target: "failed" }],
543
+ },
544
+ },
545
+ // Each Agent's Turn frames no `cwd`, so it works in /work, where sales.csv is.
546
+ writing: {
547
+ invoke: { src: "writer", input: { prompt: "Write report.md: the sales of the week in sales.csv." } },
548
+ on: { drafted: "editing" },
549
+ },
550
+ editing: {
551
+ invoke: { src: "editor", input: { prompt: "Edit report.md. Check each number against sales.csv." } },
552
+ on: { edited: "uploading" },
553
+ },
554
+ // Send the report before the body ends: jr2 deletes /work with the Sandbox.
555
+ uploading: {
556
+ invoke: {
557
+ src: "upload",
558
+ input: ({ context }) => ({ week: context.week }),
559
+ onDone: [
560
+ { guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
561
+ { target: "failed" },
562
+ ],
563
+ },
564
+ },
565
+ done: { type: "final" },
566
+ // Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
567
+ failed: { invoke: { src: "fail", input: { reason: "a step of the report did not succeed" } } },
568
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
569
+ },
570
+ });
571
+
572
+ // No `repos`: the Workspace clones nothing, and its steps share /work.
573
+ export const machine = workspace(body, { input: runInput });
574
+ ```
575
+
576
+ In a Workspace with no Repo:
577
+
578
+ - Type the body's input as `Workspaced<RunInput, never>`. The handles are `{ repos: {} }`.
579
+ - An Agent's Turn that gives no `cwd` works in `/work`. The Agent's `workspace` access is the same as in each Workspace:
580
+ `"write"` gives it `write`, `edit` and `bash`, and `"read"` gives it `bash` only.
581
+ - A `command` with no Effect secret runs in `/work`.
582
+ - An `http` Effect sends a file of `/work` with `file(path)`. The path is relative to `/work`.
583
+ - A `worktree()` needs a Repo Slot, a `push` needs a branch, and a `command` with an Effect secret runs on a commit.
584
+ Thus `jr2 up` refuses all three.
585
+
586
+ jr2 deletes `/work` with the Sandbox. Send what must stay before the body ends, as the example does with `upload`. A
587
+ Lost Sandbox has lost all of its work, because no commit was pushed.
588
+
589
+ ## The pod
590
+
591
+ A Sandbox pod has two containers, and two more when the Machine needs them:
592
+
593
+ | Container | What runs in it |
594
+ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
595
+ | The [Harness](../reference/glossary.md#harness) container | The Harness, the Agent's Working tools, and a `command` that names no [Effect secret](../reference/glossary.md#effect-secret) |
596
+ | The [Custodian](../reference/glossary.md#custodian) | jr2's own container. It holds the pod's Held secrets, and the Harness's calls out of the pod go through it (see [Architecture](architecture.md)) |
597
+ | The [User Container](../reference/glossary.md#user-container) | Your own image, with its own entrypoint. Only when `user` names one. |
598
+ | The [Sandbox Effector](../reference/glossary.md#sandbox-effector) | A `push`, an `http` Effect with a Worktree file, and a `command` that names an Effect secret. Only when an Effect needs it. |
599
+
600
+ ### The Sandbox Image
601
+
602
+ The [Sandbox Image](../reference/glossary.md#sandbox-image) is the image of the Harness container. It holds the tools
603
+ that an Agent's Working tools can use, and the shell that you get when you run `kubectl exec` into the pod. The image is
604
+ yours. Its floor is glibc, git and a writable `HOME`. [Add tools to the Sandbox](../guides/add-tools-to-the-sandbox.md)
605
+ gives the full list. jr2 adds nothing to the image. It mounts its own runtime into the pod at `/opt/jr2`, and starts the
606
+ Harness from there. Thus the `ENTRYPOINT` and the `CMD` of the image do not run.
607
+
608
+ `image` names it in one of two forms:
609
+
610
+ - A `file:` URL to a docker context that your module ships, such as `import.meta.resolve("./image")`. `jr2 up` builds
611
+ it.
612
+ - A registry ref, for an image that you build and push yourself. jr2 does not build it.
613
+
614
+ Without `image`, the Sandbox uses the Instance's `images/default`, which `jr2 init` writes. When the Instance has no
615
+ `images/default`, it uses jr2's Harness image. [Converge](converge.md) tells how `jr2 up` builds and loads the image.
616
+
617
+ ### The User Container
618
+
619
+ The User Container is an optional third container, for a program that must run without an Agent, such as an sshd. It
620
+ runs its own entrypoint, with `/work` mounted. jr2 never builds into it, probes it or sends it commands. `user` names
621
+ its image, in the same two forms as `image`.
622
+
623
+ The pod has one network. Thus the User Container cannot use the ports of jr2's containers: `8080`, `8082` and `15021`,
624
+ and `8081` and `15001` on loopback.
625
+
626
+ ## Size
627
+
628
+ The Size is the most memory and cpu that the whole Sandbox can use. jr2 reserves all of it on the node. Thus a Sandbox
629
+ never gets less than its Size, and never uses more. jr2 also enforces the cpu limit.
630
+
631
+ The Size is a fact of the Machine, as its image is. It is never set for one run. jr2 finds it in this way:
632
+
633
+ 1. The Machine's Size is the `resources` of its `workspace()`. Each field that `customize(machine, { resources })` gives
634
+ replaces the same field. For example, `customize()` with memory `6Gi`, over a `workspace()` with memory `3Gi` and cpu
635
+ `2`, gives memory `6Gi` and cpu `2`.
636
+ 2. When the Machine states a Size, jr2 uses it. A field that it does not state comes from the kit default: memory `2Gi`,
637
+ cpu `1`.
638
+ 3. When the Machine states no Size, jr2 uses [`sandbox.resources`](../reference/config.md#sandbox-resources) in
639
+ `jr2.config.ts`. A field that it does not state comes from the kit default.
640
+
641
+ A Size is `{ limits: { memory, cpu } }`, and nothing else (see [`Size`](../reference/authoring-surface/type-size.md)).
642
+ Inside the pod, the Custodian gets a small fixed part. A User Container gets the part that `user` gives it, or shares
643
+ the rest. The Harness container gets the rest. `jr2 up` refuses a Size that leaves the Harness container less than 256Mi
644
+ of memory.
645
+
646
+ ## Placing
647
+
648
+ A Sandbox waits until a node has room for its Size. It can also wait for room under a quota of the cluster. This wait is
649
+ the Workspace's [Placing](../reference/glossary.md#placing) state. jr2 sets no time limit on it, because jr2 cannot tell
650
+ "full now" from "never fits". When your Machine needs a limit, put an `after` on the state that holds the Workspace.
651
+
652
+ The wait shows in the [Console](../reference/glossary.md#console) and in the CLI. [`jr2 runs`](../reference/cli.md#runs)
653
+ marks a run that has a Workspace in `placing`. `jr2 status <run id>` gives the reason, in the words of the scheduler or
654
+ the quota. `<run id>` is the id that `jr2 run` printed, or a prefix of it. The body gets no event.
655
+
656
+ A Sandbox lands where an ordinary pod lands. To limit the nodes, set
657
+ [`sandbox.nodeSelector`](../reference/config.md#sandbox-nodeselector) and
658
+ [`sandbox.tolerations`](../reference/config.md#sandbox-tolerations) in `jr2.config.ts`. After Placing, the pod has 120
659
+ seconds to become ready. A Sandbox that waits for the Repo cache to clone has 25 minutes.
660
+
661
+ ## The life of a Workspace
662
+
663
+ A Workspace goes through these states:
664
+
665
+ 1. `placing`: jr2 writes the Sandbox, and waits for a node.
666
+ 2. `provisioning`: the pod starts.
667
+ 3. `attaching`: the Harness clones each Repo Slot. With no Repo Slot, this state has nothing to do.
668
+ 4. `running`: the body runs.
669
+ 5. `teardown`: the body is in a final state, and jr2 deletes the Sandbox.
670
+ 6. `failing`: the body invoked [`fail`](../reference/authoring-surface/fail.md), and jr2 deletes the Sandbox. Then the
671
+ Workspace sends the same [Failure](../reference/glossary.md#failure) up to its parent.
672
+
673
+ The output of the Workspace is the output of the body.
674
+
675
+ A final state of the body deletes the Sandbox, with each commit in it. Push the work before the body ends: with a
676
+ [`push`](../reference/authoring-surface/push.md) Effect, or by hand while the run waits. A body that must keep its
677
+ Sandbox waits in a state that is not final, such as at a Gate. The Sandbox stays while the run waits.
678
+
679
+ A Failure also deletes the Sandbox, with each commit in it. To keep the work for a person to read, wait at a Gate before
680
+ the state that fails.
681
+
682
+ A parent that invokes the same Machine again, with the same invoke id, gives the new Workspace the same Sandbox. That
683
+ happens on a retry after it catches a Failure, or when it enters the state again. jr2 does not delete that Sandbox. The
684
+ new Workspace takes it over, with the Worktree and each commit of the attempt before it, as a restore does.
685
+
686
+ When a run faults after its Sandbox was placed, the Sandbox stays, so that you can look inside it. jr2's operator
687
+ removes it after an idle timeout of 30 minutes. jr2 immediately deletes a Sandbox that it never placed. `CANCEL` deletes
688
+ the Workspaces of the run, unless you send it with `--keep` (see [`jr2 send`](../reference/cli.md#send)).
689
+
690
+ ## Lease and Continuity
691
+
692
+ Nothing in the cluster stands for a run. Thus the [Orchestrator](../reference/glossary.md#orchestrator) must say that it
693
+ still wants each Sandbox. The [Lease](../reference/glossary.md#lease) does this: about every 5 minutes, the Workspace
694
+ writes an annotation on its Sandbox. It does this from the write of the Sandbox to its teardown, `placing` included.
695
+ When the Lease stops for 30 minutes, the operator removes the Sandbox. Thus a Sandbox that nobody wants does not stay.
696
+
697
+ [Continuity](../reference/glossary.md#continuity) asks a different question: is the Sandbox still the one that the body
698
+ attached to? A Sandbox has one pod for its life. The work in `/work` is only in that pod. When a person or the cluster
699
+ deletes or evicts the pod, or its node goes, the Sandbox is Lost. jr2 does not make the pod again, because a new pod has
700
+ none of the work.
701
+
702
+ The Orchestrator watches its Sandboxes. While the body runs, when it sees a Sandbox that is Lost or gone, it sends
703
+ [`workspace.lost`](../reference/mechanism-events.md#workspace-lost) to the body, with the reason. The body decides what
704
+ to do. In the example, the body goes to its `lost` state, which invokes `fail`, and the run ends `failed`. A restart of
705
+ the Harness container does not lose the Workspace: the pod and `/work` stay.
706
+
707
+ ## Rules
708
+
709
+ - `workspace()` takes the run input in `input`. It refuses a body that declares its own `input`.
710
+ - `repos` names at least one Repo Slot, or is `open`, or is not there. jr2 refuses `repos: {}`. A slot key starts with a
711
+ letter, because it is a folder name.
712
+ - `jr2 up` refuses a Workflow with an Open Repo Slot that nobody bound.
713
+ - A per-run url must match an entry of [`git.credentials`](../reference/config.md#git-credentials). jr2 refuses the url
714
+ before it makes the Sandbox when no entry matches it. Thus a run cannot send the cluster's credentials to a host that
715
+ it chooses.
716
+ - `jr2 up` refuses a Repo Slot name that starts with `.`, because `/work/.jr2/` is reserved for jr2.
717
+ - A cut of the branch that `default/` holds is `refused`, and the reason names `/work/<slot>/default`.
718
+ - With no Repo Slot, `jr2 up` refuses a `worktree()`, a `push`, and a `command` that names an Effect secret.
719
+ - A Size has only `limits.memory` and `limits.cpu`. jr2 refuses `requests` and each other key.
720
+ - `jr2 up` refuses a Size that leaves the Harness container less than 256Mi of memory, or no cpu.
721
+ - Under a Workspace with Repo Slots, a Turn states its `cwd`, and a `command`, a `push` and an `http` Effect with a file
722
+ body state their `worktree`. A Turn that wants `/work` states `cwd: "/work"`. An Agent with `workspace: "none"` needs
723
+ no `cwd`.
724
+ - A `push`, a `command` that names an Effect secret, and an `http` Effect with a file body need a Worktree that a cut of
725
+ this run made.
726
+ - Under a Workspace with more than one Repo Slot, a `push` names its `slot`.
727
+
728
+ The guides and the Reference give the refusal text of each rule.
729
+
730
+ ## Read next
731
+
732
+ - [Cut a Worktree](../guides/cut-a-worktree.md)
733
+ - [Look inside a Sandbox](../guides/look-inside-a-sandbox.md)
734
+ - [Add tools to the Sandbox](../guides/add-tools-to-the-sandbox.md)
735
+ - [Size a Workspace](../guides/size-a-workspace.md)
736
+ - [Give the cluster git credentials](../guides/give-the-cluster-git-credentials.md)
737
+ - [Effects](effects.md), [Composition](composition.md), [Architecture](architecture.md)
738
+ - Reference: [`workspace`](../reference/authoring-surface/workspace.md),
739
+ [`worktree`](../reference/authoring-surface/worktree.md),
740
+ [`RepoSlot`](../reference/authoring-surface/type-reposlot.md), [`Size`](../reference/authoring-surface/type-size.md),
741
+ [`UserContainer`](../reference/authoring-surface/type-usercontainer.md),
742
+ [`workspace.lost`](../reference/mechanism-events.md#workspace-lost), [`sandbox`](../reference/config.md#sandbox)
743
+ - [ADR-0012](../reference/decisions/0012-workspace-wrapper-machine.md): `workspace()` wraps a body Machine and owns only
744
+ Sandbox lifecycle
745
+ - [ADR-0075](../reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md): a Machine cuts a
746
+ Worktree, and the Workspace only clones
747
+ - [ADR-0051](../reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md): a Repo is a slot
748
+ on the `workspace()` and a cache on the node
749
+ - [ADR-0060](../reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md): a Workspace states
750
+ its Size, and jr2 reserves all of it
751
+ - [ADR-0064](../reference/decisions/0064-a-workspace-waits-for-capacity.md): a Workspace waits for capacity
752
+ - [ADR-0021](../reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md): Workspace Continuity is
753
+ watched, and the Lease asserts
754
+ - [ADR-0005](../reference/decisions/0005-sandbox-pod-composition.md) and
755
+ [ADR-0037](../reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md): the pod, and
756
+ the Sandbox Image