@jr2/orchestrator 0.3.0 → 0.5.1

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