@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,420 @@
1
+ ---
2
+ title: "Set an Agent's access and Dials"
3
+ description: Choose what an Agent can do to the Workspace, and set the Dials and continue of one Turn.
4
+ ---
5
+
6
+ # Set an Agent's access and Dials
7
+
8
+ Set what an [Agent](../reference/glossary.md#agent) can do to the [Workspace](../reference/glossary.md#workspace), and
9
+ set the [Dials](../reference/glossary.md#dials) and `continue` of one [Turn](../reference/glossary.md#turn).
10
+
11
+ The definition of the Agent sets its access, in the `workspace` field of
12
+ [`agent()`](../reference/authoring-surface/agent.md). The state that invokes the Agent sets the Dials and `continue`, in
13
+ the `input` of the invoke. In this [Workflow](../reference/glossary.md#workflow), a reviewer reads one commit and asks
14
+ for changes. A coder makes the changes on the branch. Then a Menu-only checker decides if the changes are complete. Save
15
+ the file as `workflows/review.ts` in your [Instance](../reference/glossary.md#instance):
16
+
17
+ **Anthropic**
18
+
19
+ ```ts
20
+ import { z } from "zod";
21
+ import { assign } from "xstate";
22
+ import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
23
+
24
+ const approve = defineEvent({
25
+ name: "approve",
26
+ description: "Call this when the change is complete and correct.",
27
+ input: z.object({}),
28
+ });
29
+ const requestChanges = defineEvent({
30
+ name: "request_changes",
31
+ description: "Call this when the change is not complete or not correct. Say what to change.",
32
+ input: z.object({ notes: z.string() }),
33
+ });
34
+ const finished = defineEvent({
35
+ name: "finished",
36
+ description: "Call this when you committed the changes. Say what you changed.",
37
+ input: z.object({ summary: z.string() }),
38
+ });
39
+
40
+ // The run input: the branch, the commit to review, and an optional model for the coder's Turns.
41
+ const runInput = z.object({ branch: z.string(), sha: z.string(), model: z.string().optional() });
42
+ type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
43
+ type Context = BodyInput & { notes: string; summary: string };
44
+
45
+ // Two Worktrees of one clone: the coder's branch, and the commit that the reviewer reads.
46
+ const coderTree = (context: BodyInput) => `${context.workspace.repos.app}/work` as const;
47
+ const reviewTree = (context: BodyInput) => `${context.workspace.repos.app}/review` as const;
48
+
49
+ const body = jr2Setup({
50
+ types: {} as { input: BodyInput; context: Context },
51
+ events: [approve, requestChanges, finished],
52
+ actors: {
53
+ // "read": no write tool and no edit tool. It keeps bash.
54
+ reviewer: agent({
55
+ model: "anthropic/claude-haiku-5-5",
56
+ instructions: "You review one commit. You do not change files.",
57
+ workspace: "read",
58
+ }),
59
+ // No `workspace` field: "write", all the Working tools.
60
+ coder: agent({
61
+ model: "anthropic/claude-haiku-5-5",
62
+ instructions: "You change code as the review asks, and you commit each change.",
63
+ }),
64
+ // "none": a Menu-only Agent. It has no Working tools, and it runs on the Instance Harness.
65
+ checker: agent({
66
+ model: "anthropic/claude-haiku-5-5",
67
+ instructions: "You read review notes and the summary of a change, and you decide if the change is complete.",
68
+ workspace: "none",
69
+ thinkingLevel: "low",
70
+ }),
71
+ cut: worktree(),
72
+ fail: fail(),
73
+ },
74
+ }).createMachine({
75
+ id: "review",
76
+ context: ({ input }) => ({ ...input, notes: "", summary: "" }),
77
+ on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
78
+ initial: "cutting",
79
+ states: {
80
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
81
+ cutting: {
82
+ invoke: {
83
+ src: "cut",
84
+ input: ({ context }) => ({ path: coderTree(context), branch: context.branch }),
85
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "detaching" }, { target: "notCut" }],
86
+ },
87
+ },
88
+ // The second cut has no branch: the Worktree holds the commit, detached.
89
+ detaching: {
90
+ invoke: {
91
+ src: "cut",
92
+ input: ({ context }) => ({ path: reviewTree(context), detach: context.sha }),
93
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "reviewing" }, { target: "notCut" }],
94
+ },
95
+ },
96
+ reviewing: {
97
+ invoke: {
98
+ src: "reviewer",
99
+ input: ({ context }) => ({
100
+ prompt: `Review commit ${context.sha}.`,
101
+ // The review Worktree holds the commit, detached from the branch.
102
+ cwd: reviewTree(context),
103
+ // A Dial: this Turn thinks harder than the default of the model.
104
+ thinkingLevel: "high",
105
+ }),
106
+ },
107
+ on: {
108
+ approve: "done",
109
+ request_changes: { target: "fixing", actions: assign({ notes: ({ event }) => event.notes }) },
110
+ },
111
+ },
112
+ fixing: {
113
+ invoke: {
114
+ src: "coder",
115
+ input: ({ context }) => ({
116
+ prompt: `Change the code on the branch as the review asks:\n\n${context.notes}`,
117
+ cwd: coderTree(context),
118
+ // Each Turn of the coder continues the same conversation.
119
+ continue: true,
120
+ // A Dial: the model of this Turn, from the run input. Absent, the model of the definition.
121
+ model: context.model,
122
+ }),
123
+ },
124
+ on: { finished: { target: "checking", actions: assign({ summary: ({ event }) => event.summary }) } },
125
+ },
126
+ checking: {
127
+ invoke: {
128
+ src: "checker",
129
+ input: ({ context }) => ({
130
+ prompt: `The review asked for this:\n\n${context.notes}\n\nThe coder did this:\n\n${context.summary}`,
131
+ }),
132
+ },
133
+ on: {
134
+ approve: "done",
135
+ request_changes: { target: "fixing", actions: assign({ notes: ({ event }) => event.notes }) },
136
+ },
137
+ },
138
+ done: { type: "final" },
139
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
140
+ failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
141
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
142
+ },
143
+ });
144
+
145
+ export const machine = workspace(body, {
146
+ input: runInput,
147
+ repos: { app: "https://github.com/<you>/app.git" },
148
+ });
149
+ ```
150
+
151
+ **Local**
152
+
153
+ ```ts
154
+ import { z } from "zod";
155
+ import { assign } from "xstate";
156
+ import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
157
+
158
+ const approve = defineEvent({
159
+ name: "approve",
160
+ description: "Call this when the change is complete and correct.",
161
+ input: z.object({}),
162
+ });
163
+ const requestChanges = defineEvent({
164
+ name: "request_changes",
165
+ description: "Call this when the change is not complete or not correct. Say what to change.",
166
+ input: z.object({ notes: z.string() }),
167
+ });
168
+ const finished = defineEvent({
169
+ name: "finished",
170
+ description: "Call this when you committed the changes. Say what you changed.",
171
+ input: z.object({ summary: z.string() }),
172
+ });
173
+
174
+ // The run input: the branch, the commit to review, and an optional model for the coder's Turns.
175
+ const runInput = z.object({ branch: z.string(), sha: z.string(), model: z.string().optional() });
176
+ type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
177
+ type Context = BodyInput & { notes: string; summary: string };
178
+
179
+ // Two Worktrees of one clone: the coder's branch, and the commit that the reviewer reads.
180
+ const coderTree = (context: BodyInput) => `${context.workspace.repos.app}/work` as const;
181
+ const reviewTree = (context: BodyInput) => `${context.workspace.repos.app}/review` as const;
182
+
183
+ const body = jr2Setup({
184
+ types: {} as { input: BodyInput; context: Context },
185
+ events: [approve, requestChanges, finished],
186
+ actors: {
187
+ // "read": no write tool and no edit tool. It keeps bash.
188
+ reviewer: agent({
189
+ model: "local/qwen3.6-35b-a3b",
190
+ instructions: "You review one commit. You do not change files.",
191
+ workspace: "read",
192
+ }),
193
+ // No `workspace` field: "write", all the Working tools.
194
+ coder: agent({
195
+ model: "local/qwen3.6-35b-a3b",
196
+ instructions: "You change code as the review asks, and you commit each change.",
197
+ }),
198
+ // "none": a Menu-only Agent. It has no Working tools, and it runs on the Instance Harness.
199
+ checker: agent({
200
+ model: "local/qwen3.6-35b-a3b",
201
+ instructions: "You read review notes and the summary of a change, and you decide if the change is complete.",
202
+ workspace: "none",
203
+ thinkingLevel: "low",
204
+ }),
205
+ cut: worktree(),
206
+ fail: fail(),
207
+ },
208
+ }).createMachine({
209
+ id: "review",
210
+ context: ({ input }) => ({ ...input, notes: "", summary: "" }),
211
+ on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
212
+ initial: "cutting",
213
+ states: {
214
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
215
+ cutting: {
216
+ invoke: {
217
+ src: "cut",
218
+ input: ({ context }) => ({ path: coderTree(context), branch: context.branch }),
219
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "detaching" }, { target: "notCut" }],
220
+ },
221
+ },
222
+ // The second cut has no branch: the Worktree holds the commit, detached.
223
+ detaching: {
224
+ invoke: {
225
+ src: "cut",
226
+ input: ({ context }) => ({ path: reviewTree(context), detach: context.sha }),
227
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "reviewing" }, { target: "notCut" }],
228
+ },
229
+ },
230
+ reviewing: {
231
+ invoke: {
232
+ src: "reviewer",
233
+ input: ({ context }) => ({
234
+ prompt: `Review commit ${context.sha}.`,
235
+ // The review Worktree holds the commit, detached from the branch.
236
+ cwd: reviewTree(context),
237
+ // A Dial: this Turn thinks harder than the default of the model.
238
+ thinkingLevel: "high",
239
+ }),
240
+ },
241
+ on: {
242
+ approve: "done",
243
+ request_changes: { target: "fixing", actions: assign({ notes: ({ event }) => event.notes }) },
244
+ },
245
+ },
246
+ fixing: {
247
+ invoke: {
248
+ src: "coder",
249
+ input: ({ context }) => ({
250
+ prompt: `Change the code on the branch as the review asks:\n\n${context.notes}`,
251
+ cwd: coderTree(context),
252
+ // Each Turn of the coder continues the same conversation.
253
+ continue: true,
254
+ // A Dial: the model of this Turn, from the run input. Absent, the model of the definition.
255
+ model: context.model,
256
+ }),
257
+ },
258
+ on: { finished: { target: "checking", actions: assign({ summary: ({ event }) => event.summary }) } },
259
+ },
260
+ checking: {
261
+ invoke: {
262
+ src: "checker",
263
+ input: ({ context }) => ({
264
+ prompt: `The review asked for this:\n\n${context.notes}\n\nThe coder did this:\n\n${context.summary}`,
265
+ }),
266
+ },
267
+ on: {
268
+ approve: "done",
269
+ request_changes: { target: "fixing", actions: assign({ notes: ({ event }) => event.notes }) },
270
+ },
271
+ },
272
+ done: { type: "final" },
273
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
274
+ failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
275
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
276
+ },
277
+ });
278
+
279
+ export const machine = workspace(body, {
280
+ input: runInput,
281
+ repos: { app: "https://github.com/<you>/app.git" },
282
+ });
283
+ ```
284
+
285
+ Write your GitHub user name in place of `<you>`. Each Agent uses the model of the tab.
286
+
287
+ ## Set the access
288
+
289
+ The `workspace` field selects the [Working tools](../reference/glossary.md#working-tools) of the Agent. It also selects
290
+ where its Turns run. The default is `"write"`.
291
+
292
+ | `workspace` | The Working tools | Where the Turn runs |
293
+ | ------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------- |
294
+ | `"write"` (default) | read, write, edit, bash, grep and glob | The [Sandbox](../reference/glossary.md#sandbox) of the Workspace |
295
+ | `"read"` | read, bash, grep and glob: no write, no edit | The Sandbox of the Workspace |
296
+ | `"none"` | none | The [Instance Harness](../reference/glossary.md#instance-harness), with no Sandbox |
297
+
298
+ An Agent with `"write"` or `"read"` works in a Sandbox. Thus invoke it in the body of a
299
+ [`workspace()`](../reference/authoring-surface/workspace.md). An Agent with `"none"` is a Menu-only Agent. It reads its
300
+ prompt and picks from its [Menu](../reference/glossary.md#menu). You can invoke it in the body of a `workspace()`, as
301
+ the example does, or in a [Machine](../reference/glossary.md#machine) with no Workspace.
302
+
303
+ A `"read"` Agent keeps bash, so a command can still change a file or the branch. To keep a reviewer away from the
304
+ branch, give its Turn a detached [Worktree](../reference/glossary.md#worktree):
305
+
306
+ 1. Cut the Worktree with [`worktree()`](../reference/authoring-surface/worktree.md), with `{ path, detach }`.
307
+ 2. In the Turn, set `cwd` to that path.
308
+
309
+ The Worktree holds the commit, with no branch. In the example, the path is `/work/app/review`, and the commit comes from
310
+ the run input. [Cut a Worktree](cut-a-worktree.md) tells more.
311
+
312
+ The access is a part of the identity of the Agent. Only the definition sets it. A Turn cannot change it.
313
+
314
+ ## Set the Dials of a Turn
315
+
316
+ The Dials are `model` and `thinkingLevel`. Write them in the `input` of the invoke, beside the
317
+ [Frame](../reference/glossary.md#frame) (`prompt` and `cwd`). A Dial changes only that Turn. When the Turn sets no Dial,
318
+ it uses the value of the definition.
319
+
320
+ - `thinkingLevel` is one of `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"` and `"xhigh"`. In the example, the
321
+ definition of `checker` sets `"low"`, and the `reviewing` state sets `"high"` for the reviewer's Turn. A definition
322
+ with no `thinkingLevel` uses `"off"`. A model can lack a level. Then the [Harness](../reference/glossary.md#harness)
323
+ runs the Turn at a different level, and the printed conversation names it in a `[thinking level]` line. See
324
+ [Watch a run](watch-a-run.md).
325
+ - `model` is `<provider>/<model id>`, as in the definition. In the example, the `fixing` state gives the coder the
326
+ `model` of the run input. When the run input has no `model`, the value is `undefined`, and the Turn uses the model of
327
+ the definition.
328
+
329
+ Run [`jr2 up`](../reference/cli.md#up) to converge the new Workflow:
330
+
331
+ ```sh
332
+ jr2 up
333
+ ```
334
+
335
+ The `effects:` lines show the cut. The `seats:` lines show the picks of the three Agents.
336
+
337
+ To give a model to the coder's Turns, add `model` to the run input:
338
+
339
+ ```sh
340
+ jr2 run review --input '{"branch":"<branch>","sha":"<commit>","model":"<provider>/<model id>"}' --detach
341
+ ```
342
+
343
+ ```text
344
+ → context kind-jr2 / namespace my-instance
345
+ {"runId":"48401e39-f8b2-4246-a24e-6ee2665ee892"}
346
+ ```
347
+
348
+ The first line gives your kubectl context and the namespace of the Instance. `runId` is the id of the new run. Your run
349
+ id is different.
350
+
351
+ Write a new branch name for the coder in place of `<branch>`. The branch starts from the default branch of the
352
+ [Repo](../reference/glossary.md#repo). Thus write the full id of a commit on the default branch in place of `<commit>`,
353
+ such as its tip. [Start a run](start-a-run.md) tells how to follow the run.
354
+
355
+ [`jr2 up`](../reference/cli.md#up) reads the models of the definitions. It cannot read a model that a Turn sets as a
356
+ Dial. Thus:
357
+
358
+ - The Harness checks a Dial model only when it admits the Turn. A model that it cannot find gives
359
+ [`agent.fault`](../reference/mechanism-events.md#agent-fault).
360
+ - `jr2 up` gives each pod the model key of the providers that the definitions of its Agents name. For an Agent in a
361
+ Sandbox, use a Dial model of a provider that a definition in that Sandbox names. For a Menu-only Agent, use a provider
362
+ that a Menu-only definition of the Instance names.
363
+
364
+ ## Continue a conversation
365
+
366
+ Without `continue`, each Turn starts a new conversation. With `continue: true`, the Turn continues the conversation of
367
+ this Agent in this Machine instance. In the example, the first `fixing` Turn starts the coder's conversation. When the
368
+ checker picks `request_changes`, the run goes back to `fixing`, and the coder continues with what it did before.
369
+
370
+ Set `continue: true` on each Turn that must join the conversation. A Turn without it starts a new conversation and does
371
+ not join.
372
+
373
+ After `agent.fault` on a Turn with `continue: true`, the next Turn with `continue: true` starts a new conversation. A
374
+ Harness restart that cut only the Turn is the exception: the conversation stays. Thus write a prompt that gives the full
375
+ task, also on a Turn that continues. In the example, each `fixing` prompt gives the notes of the review.
376
+
377
+ ## Rules
378
+
379
+ - jr2 refuses a Turn whose `input` has a key that a Turn does not have, such as `instructions` or `workspace`. Only the
380
+ definition sets those two. `<keys>` is each key that is not correct, in backticks, with a comma between them:
381
+ ``agent "<slot>": the Turn's input carries <keys>, which a Turn has no use for — its input is its Frame (`prompt`, `cwd`), its Dials (`model`, `thinkingLevel`), and `continue` (ADR-0057)``
382
+ - Under a Workspace with [Repo Slots](../reference/glossary.md#repo-slot), jr2 refuses a Turn of an Agent with Working
383
+ tools that sets no `cwd`:
384
+ ``turn <id>: agent "<slot>" works under a Workspace with Repo Slots (<slots>) and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above them (ADR-0057)``
385
+ - `workspace()` refuses a body with an invoke that does not say where it works, when it can read the input:
386
+ ``workspace(): state "<state>": "<slot>" names no `<key>`, and a Workspace with Repo Slots says where each Turn and Effect works: a Worktree, or `cwd: "/work"` for a Turn (ADR-0057)``
387
+ - The Harness refuses a `cwd` that is not an absolute path. The run gets `agent.fault`, with this reason:
388
+ ``harness admission failed (400): agent "<slot>": the Turn's Frame carries a `cwd` that is not an absolute path — the Frame says WHERE this Turn works (ADR-0057), and a Turn rooted anywhere else would work in the wrong directory silently``
389
+ - The Harness refuses a model that is not in its catalog and not a model of the custom provider. The run gets
390
+ `agent.fault`, with this reason:
391
+ `harness admission failed (400): agent "<slot>": model "<provider>/<model id>" resolves to nothing — not in pi's catalog, and no custom provider "<provider>" is configured (ADR-0018)`
392
+ - The Harness refuses a model that is not in the form `<provider>/<modelId>`. The run gets `agent.fault`, with this
393
+ reason:
394
+ `harness admission failed (400): agent "<slot>": model "<model>" is not a <provider>/<modelId> specifier (ADR-0018)`
395
+ - jr2 refuses a Turn of a `"write"` or `"read"` Agent outside a `workspace()`:
396
+ ``turn <id>: agent "<slot>" has workspace: "<access>" — invoke it inside a workspace() (ambient resolution), or pass an explicit `endpoint` (workspace-less stub path)``
397
+ - `model` is required in each definition. A Machine from a package can leave it [Open](../reference/glossary.md#open).
398
+ `jr2 up` refuses an Open model that nobody bound:
399
+ `refusing: workflow "<workflow>" leaves Agent "<slot>" open — bind it where the Machine is registered: export const machine = customize(<import>, { agents: { <slot>: { model: "<provider>/<model>" } } }) (ADR-0054)`
400
+ - When a Turn starts with an Open model, jr2 refuses it:
401
+ ``agent "<slot>" has an Open model — nobody bound it; bind it where the Machine is registered (`customize(<machine>, { agents: { <slot>: { model: "<provider>/<model>" } } })`), because a packaged Machine cannot pick a model on your behalf (ADR-0054)``
402
+
403
+ ## Read next
404
+
405
+ - [Agents and Turns](../concepts/agents-and-turns.md): the definition, the Frame, the Dials and `continue`.
406
+ - [Workspaces](../concepts/workspaces.md): the Worktrees.
407
+ - [Cut a Worktree](cut-a-worktree.md): a detached Worktree for a reviewer.
408
+ - [The Menu](../concepts/the-menu.md): the picks of an Agent.
409
+ - [Bind a shipped Machine's Open parts](bind-open-parts.md): bind an Open model.
410
+ - Reference: [`agent`](../reference/authoring-surface/agent.md),
411
+ [`AgentDefinition`](../reference/authoring-surface/type-agentdefinition.md),
412
+ [`ThinkingLevel`](../reference/authoring-surface/type-thinkinglevel.md),
413
+ [`WorkspaceAccess`](../reference/authoring-surface/type-workspaceaccess.md) and
414
+ [`agent.fault`](../reference/mechanism-events.md#agent-fault).
415
+ - [ADR-0028](../reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md): what an
416
+ Agent can do to the Workspace is a part of its definition.
417
+ - [ADR-0057](../reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md): a Turn is its
418
+ Frame, its Dials, and `continue`.
419
+ - [ADR-0018](../reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md): an Agent is a
420
+ definition.
@@ -0,0 +1,217 @@
1
+ ---
2
+ title: Size a Workspace
3
+ description: Give a Workspace the memory and cpu that its Sandbox needs, and see how jr2 divides them in the pod.
4
+ ---
5
+
6
+ # Size a Workspace
7
+
8
+ Give a [Workspace](../reference/glossary.md#workspace) the memory and cpu that its
9
+ [Sandbox](../reference/glossary.md#sandbox) needs.
10
+
11
+ The [Size](../reference/glossary.md#size) is the most memory and cpu that the whole Sandbox can use. jr2 reserves all of
12
+ it on a node, so the Sandbox always has it. Write it in the `resources` option of `workspace()`. In this example, the
13
+ [Workflow](../reference/glossary.md#workflow) `workflows/e2e.ts` runs Playwright with 4 GiB of memory and 2 cpus:
14
+
15
+ ```ts
16
+ import { z } from "zod";
17
+ import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
18
+
19
+ const runInput = z.object({ branch: z.string() });
20
+ type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
21
+
22
+ // The Worktree that the tests run in.
23
+ const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
24
+
25
+ const body = jr2Setup({
26
+ types: {} as { input: BodyInput; context: BodyInput },
27
+ actors: {
28
+ cut: worktree(),
29
+ // JR2_CPUS holds the cpu limit of the container. Playwright does not read that limit.
30
+ e2e: command(["sh", "-c", "npx playwright test --workers=$JR2_CPUS"], { timeout: "30m" }),
31
+ fail: fail(),
32
+ },
33
+ }).createMachine({
34
+ id: "e2e",
35
+ context: ({ input }) => input,
36
+ on: { "workspace.lost": ".lost" },
37
+ initial: "cutting",
38
+ states: {
39
+ cutting: {
40
+ invoke: {
41
+ src: "cut",
42
+ input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
43
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
44
+ },
45
+ },
46
+ testing: {
47
+ invoke: {
48
+ src: "e2e",
49
+ input: ({ context }) => ({ worktree: testTree(context) }),
50
+ onDone: [
51
+ {
52
+ guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
53
+ target: "passed",
54
+ },
55
+ { target: "failed" },
56
+ ],
57
+ },
58
+ },
59
+ passed: { type: "final" },
60
+ // Not final states: each one fails the run, and the run ends `failed`.
61
+ failed: { invoke: { src: "fail", input: { reason: "the Playwright tests did not pass" } } },
62
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
63
+ lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
64
+ },
65
+ });
66
+
67
+ export const machine = workspace(body, {
68
+ input: runInput,
69
+ // The Size of the whole Sandbox: 4 GiB of memory and 2 cpus. jr2 reserves all of it on a node.
70
+ resources: { limits: { memory: "4Gi", cpu: "2" } },
71
+ repos: { app: "https://github.com/you/app.git" },
72
+ });
73
+ ```
74
+
75
+ The [Sandbox Image](../reference/glossary.md#sandbox-image) must have Playwright and its browsers.
76
+ [Add tools to the Sandbox](add-tools-to-the-sandbox.md) tells how.
77
+
78
+ ## Choose the numbers
79
+
80
+ A Size is `{ limits: { memory, cpu } }`, and nothing else. Each value is a Kubernetes quantity: `"4Gi"` or `"512Mi"` for
81
+ memory, `"2"` or `"1500m"` for cpu.
82
+
83
+ The kit default is memory `2Gi` and cpu `1`. It is enough for `tsc` and vitest, for `cargo build`, and for Playwright
84
+ with 2 workers. State a larger Size for Playwright with more workers, or for a large monorepo.
85
+
86
+ A larger Size is not free. The Sandbox holds all of its Size while the run waits, also at a
87
+ [Gate](../reference/glossary.md#gate). When no node has room for the Size, the Workspace waits in the
88
+ [Placing](../reference/glossary.md#placing) state, with no time limit. [Workspaces](../concepts/workspaces.md#placing)
89
+ tells how to see the wait.
90
+
91
+ `jr2 up` warns when no node can hold a Size now. It does not stop, because the nodes can change. For example, a kind
92
+ node with 16 cpus cannot hold a Size of memory `64Gi` and cpu `32`:
93
+
94
+ ```text
95
+ …
96
+ sandbox nodes: jr2-control-plane
97
+ warning: no Sandbox node right now has the allocatable for a Size of memory 64Gi, cpu 32 (workflow "e2e") — its Sandbox stays Pending until one does
98
+ …
99
+ ```
100
+
101
+ ## Where the Size comes from
102
+
103
+ The Size is a fact of the [Machine](../reference/glossary.md#machine). A run cannot change it. jr2 uses the first of
104
+ these that states a Size:
105
+
106
+ 1. [`customize(machine, { resources })`](../reference/authoring-surface/customize.md), in the
107
+ [Instance](../reference/glossary.md#instance) that registers the Machine.
108
+ 2. `resources` on the `workspace()`.
109
+ 3. [`sandbox.resources`](../reference/config.md#sandbox-resources) in `jr2.config.ts`, such as
110
+ `sandbox: { resources: { limits: { memory: "3Gi" } } }`.
111
+ 4. The kit default.
112
+
113
+ These rules apply to the order:
114
+
115
+ - `customize()` writes over the Size of the `workspace()` one field at a time. Thus
116
+ `customize(machine, { resources: { limits: { memory: "6Gi" } } })` keeps the cpu of the Machine.
117
+ - `sandbox.resources` applies only to a Workspace that states no Size. It never changes a Size that a Machine states.
118
+ - A field that the chosen Size does not state comes from the kit default. For example, under
119
+ `resources: { limits: { memory: "4Gi" } }`, the cpu is `1`, also when `sandbox.resources` states a cpu.
120
+
121
+ Use `customize()` to change the Size of a Machine from a package, such as `task` from
122
+ [`@jr2/machines`](../reference/machines.md). See [Bind a shipped Machine's Open parts](bind-open-parts.md).
123
+
124
+ ## How jr2 divides the Size in the pod
125
+
126
+ The pod has the Size as one limit. Inside the pod, jr2 divides it among the containers:
127
+
128
+ | Container | Its part |
129
+ | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
130
+ | The [Custodian](../reference/glossary.md#custodian) | A fixed part: memory `64Mi`, cpu `50m`. |
131
+ | The [User Container](../reference/glossary.md#user-container) | The part that `user.resources` gives. With no part, it shares the rest. |
132
+ | The [Sandbox Effector](../reference/glossary.md#sandbox-effector) | No part of its own. It shares the rest. |
133
+ | The `harness` container | The rest, as its own limit. The [Agent](../reference/glossary.md#agent)'s processes and the `command`s run here. |
134
+
135
+ `jr2 up` refuses a Size that leaves the `harness` container less than `256Mi` of memory, or no cpu.
136
+
137
+ To give the User Container a part, write `user` as an object:
138
+ `user: { image: import.meta.resolve("./shell"), resources: { limits: { memory: "256Mi", cpu: "250m" } } }`. The part
139
+ comes out of the part of the `harness` container. It is not added to the Size.
140
+
141
+ The Size holds only memory and cpu. jr2 also sets these, and you cannot change them:
142
+
143
+ - `/dev/shm` in the `harness` container is 25% of the container's memory, and not less than `64Mi`. It counts in that
144
+ memory. To get more, state a larger Size.
145
+ - `/work` has a disk request of `10Gi`, with no limit. The disk is not part of the Size.
146
+
147
+ ## Use the cpus
148
+
149
+ jr2 enforces the cpu limit. Many tools read the limit, such as Node's `availableParallelism()`, Go, cargo and the JVM.
150
+ For tools that do not read it, these environment variables in the `harness` container hold its cpu limit, rounded up to
151
+ a whole number: `JR2_CPUS`, `OMP_NUM_THREADS`, `PYTHON_CPU_COUNT` and `GOMAXPROCS`.
152
+
153
+ Node's `os.cpus()` gives the cpus of the node, not the limit. Playwright uses it to choose its number of workers. Thus
154
+ the example gives `--workers=$JR2_CPUS`. A `command` sees these variables.
155
+
156
+ ## When the memory is not enough
157
+
158
+ The [Harness](../reference/glossary.md#harness) watches the memory of its container. Near the limit, it stops all of the
159
+ Agent's processes. Then the Agent's `bash` call returns this text, and the Agent can try again with less:
160
+
161
+ ```text
162
+ killed: memory limit (peak <peak> of <limit>); use fewer workers or a larger Size
163
+ ```
164
+
165
+ The Harness also watches when no `bash` call runs. A process that a Turn left running, such as a dev server, can pass
166
+ the limit while the workflow waits at a [Gate](../reference/glossary.md#gate). Then no `bash` call returns the text. jr2
167
+ tells the next Agent that works in the Workspace: the next Turn, or an open Turn that jr2 asks again.
168
+
169
+ The Harness can miss a fast increase. Then the kernel stops the whole container, and the
170
+ [Turn](../reference/glossary.md#turn) faults. The reason starts with `memory limit`:
171
+
172
+ ```text
173
+ memory limit (OOMKilled, limit <memory>): the Workspace's processes passed the Harness container's memory limit and the kernel killed the container, the conversation with it. Use fewer workers, or give the Workspace a larger Size (ADR-0060, ADR-0061).
174
+ ```
175
+
176
+ Each conversation that was in the Workspace at the kill gets a notice of it one time, and so does the next Turn of the
177
+ Agent whose Turn the kill cut. When you see either text again and again, state a larger Size.
178
+
179
+ ## Rules
180
+
181
+ - `workspace()`, `customize()` and the config refuse a key other than `limits`, where you write it. `<where>` is
182
+ `workspace(): resources`, `customize(): resources`, `<file> at sandbox.resources`, `workspace() user.resources` or
183
+ `customize() user.resources`:
184
+ `<where>.requests is not accepted — jr2 reserves the whole Size, so every request equals its limit — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template size (ADR-0060)`
185
+ and
186
+ `<where>.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template size (ADR-0060)`.
187
+ - They refuse a key in `limits` other than `memory` and `cpu`:
188
+ `<where>.limits.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template size (ADR-0060)`.
189
+ - They refuse a value that is not a quantity more than zero:
190
+ `<where>.limits.memory: <value> is not a positive quantity (e.g. "2Gi", "512Mi")` and
191
+ `<where>.limits.cpu: <value> is not a positive quantity (e.g. "2", "1500m")`.
192
+ - They refuse a Size, or a `limits`, that is not an object:
193
+ `<where>: expected an object (got <value>) — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template size (ADR-0060)`
194
+ and the same text for `<where>.limits`.
195
+ - `customize()` refuses `resources` for a Machine that has no `workspace()`:
196
+ ``customize(): `resources` say what a SANDBOX is made of, how big it is and which Repos it attaches, and machine "<id>" composes none — only a workspace() wrapper carries those seats, that Size and those slots (ADR-0049, ADR-0037, ADR-0051, ADR-0060).``
197
+ - `jr2 up` refuses a Size that leaves the `harness` container less than `256Mi` of memory. The text names each number
198
+ and where it came from. The part `− the User Container's <memory> (its user.resources.limits.memory)` is there only
199
+ when `user.resources` gives a memory:
200
+ `refusing: workflow "<workflow>": the Size leaves the Harness container <memory> memory, below its 256Mi floor: memory <size> (<source>) − the Custodian's 64Mi − the User Container's <memory> (its user.resources.limits.memory) — raise the Size's limits.memory, or shrink the User Container's split (ADR-0060)`.
201
+ The text can also name the Machine that states the Size, and it can end with the `customize()` line that changes the
202
+ Size. For cpu, the text holds `the Size leaves the Harness container no cpu:`.
203
+ - `jr2 up` refuses a cluster that drops pod-level resources:
204
+ `refusing: <context> (Kubernetes <version>) drops pod-level resources — the PodLevelResources feature gate is off, and a Workspace's Size cannot hold (ADR-0060). Enable the gate on the API server and kubelets. The namespace was created; nothing else was applied.`
205
+
206
+ ## Read next
207
+
208
+ - [Workspaces](../concepts/workspaces.md#size): the Size, and Placing.
209
+ - [Add tools to the Sandbox](add-tools-to-the-sandbox.md): the User Container.
210
+ - Reference: [`Size`](../reference/authoring-surface/type-size.md),
211
+ [`UserContainer`](../reference/authoring-surface/type-usercontainer.md),
212
+ [`sandbox.resources`](../reference/config.md#sandbox-resources), [`jr2 up`](../reference/cli.md#up)
213
+ - [ADR-0060](../reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md): a Workspace states
214
+ its Size, and jr2 reserves all of it.
215
+ - [ADR-0061](../reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md): a memory kill ends
216
+ the Agent's processes, not the Harness.
217
+ - [ADR-0064](../reference/decisions/0064-a-workspace-waits-for-capacity.md): a Workspace waits for capacity.