@jr2/orchestrator 0.4.1 → 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 (327) 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 +442 -0
  10. package/docs/concepts/architecture.md +230 -0
  11. package/docs/concepts/charts.md +143 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +254 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +603 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +289 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +222 -0
  20. package/docs/concepts/the-menu.md +378 -0
  21. package/docs/concepts/workspaces.md +678 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +381 -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 +922 -0
  27. package/docs/get-started/install.md +126 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +223 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/fix-a-failed-converge.md +361 -0
  34. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  35. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  36. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  37. package/docs/guides/look-inside-a-sandbox.md +174 -0
  38. package/docs/guides/mirror-the-kit-images.md +176 -0
  39. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  40. package/docs/guides/open-the-console.md +91 -0
  41. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  42. package/docs/guides/push-and-sign-a-branch.md +486 -0
  43. package/docs/guides/run-a-chart-by-name.md +270 -0
  44. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  45. package/docs/guides/run-one-worker-per-item.md +321 -0
  46. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  47. package/docs/guides/size-a-workspace.md +204 -0
  48. package/docs/guides/start-a-run-from-a-run.md +237 -0
  49. package/docs/guides/start-a-run.md +207 -0
  50. package/docs/guides/upgrade-the-kit-version.md +240 -0
  51. package/docs/guides/use-a-hosted-provider.md +208 -0
  52. package/docs/guides/use-a-local-model.md +313 -0
  53. package/docs/guides/watch-a-run.md +326 -0
  54. package/docs/guides/write-a-workflow.md +318 -0
  55. package/docs/index.md +44 -0
  56. package/docs/llms.txt +216 -0
  57. package/docs/reference/authoring-surface/agent.md +128 -0
  58. package/docs/reference/authoring-surface/answer.md +140 -0
  59. package/docs/reference/authoring-surface/arg.md +60 -0
  60. package/docs/reference/authoring-surface/basic.md +62 -0
  61. package/docs/reference/authoring-surface/bearer.md +53 -0
  62. package/docs/reference/authoring-surface/chartschema.md +23 -0
  63. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  64. package/docs/reference/authoring-surface/command.md +191 -0
  65. package/docs/reference/authoring-surface/customize.md +64 -0
  66. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  67. package/docs/reference/authoring-surface/defineevent.md +114 -0
  68. package/docs/reference/authoring-surface/dispatch.md +126 -0
  69. package/docs/reference/authoring-surface/fail.md +83 -0
  70. package/docs/reference/authoring-surface/file.md +122 -0
  71. package/docs/reference/authoring-surface/frompick.md +277 -0
  72. package/docs/reference/authoring-surface/http.md +159 -0
  73. package/docs/reference/authoring-surface/isfailure.md +58 -0
  74. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  75. package/docs/reference/authoring-surface/open.md +74 -0
  76. package/docs/reference/authoring-surface/openbody.md +86 -0
  77. package/docs/reference/authoring-surface/paramsof.md +125 -0
  78. package/docs/reference/authoring-surface/pool.md +186 -0
  79. package/docs/reference/authoring-surface/push.md +112 -0
  80. package/docs/reference/authoring-surface/secret.md +118 -0
  81. package/docs/reference/authoring-surface/sentby.md +83 -0
  82. package/docs/reference/authoring-surface/source.md +51 -0
  83. package/docs/reference/authoring-surface/text.md +62 -0
  84. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  85. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  86. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  87. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  88. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  89. package/docs/reference/authoring-surface/type-arg.md +19 -0
  90. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  91. package/docs/reference/authoring-surface/type-binding.md +62 -0
  92. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  93. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  94. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  95. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  96. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  97. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  98. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  99. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  100. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  101. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  102. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  103. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  104. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  105. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  106. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  107. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  108. package/docs/reference/authoring-surface/type-childat.md +12 -0
  109. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  110. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  111. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  112. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  113. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  114. package/docs/reference/authoring-surface/type-customize.md +109 -0
  115. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  116. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  117. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  118. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  119. package/docs/reference/authoring-surface/type-duration.md +15 -0
  120. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  121. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  122. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  124. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  125. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  126. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  127. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  128. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  129. package/docs/reference/authoring-surface/type-failure.md +19 -0
  130. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  131. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  132. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  133. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  134. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  135. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  136. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  137. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  138. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  139. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  140. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  141. package/docs/reference/authoring-surface/type-lends.md +38 -0
  142. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  143. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  144. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  145. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  146. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  147. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  148. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  149. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  150. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  151. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  152. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  153. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  154. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  155. package/docs/reference/authoring-surface/type-openof.md +12 -0
  156. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  157. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  158. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  159. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  160. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  161. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  162. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  163. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  164. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  165. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  166. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  167. package/docs/reference/authoring-surface/type-reached.md +12 -0
  168. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  169. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  170. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  171. package/docs/reference/authoring-surface/type-secret.md +25 -0
  172. package/docs/reference/authoring-surface/type-sender.md +19 -0
  173. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  174. package/docs/reference/authoring-surface/type-size.md +85 -0
  175. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  176. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  177. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  178. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  179. package/docs/reference/authoring-surface/type-text.md +19 -0
  180. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  181. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  182. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  183. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  184. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  185. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  186. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  187. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  188. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  189. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  190. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  191. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  192. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  193. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  194. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  195. package/docs/reference/authoring-surface/workspace.md +259 -0
  196. package/docs/reference/authoring-surface.md +197 -0
  197. package/docs/reference/cli.md +433 -0
  198. package/docs/reference/config.md +756 -0
  199. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  200. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  201. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  202. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  203. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  204. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  205. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  206. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  207. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  208. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  209. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  210. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  211. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  212. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  213. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  214. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  215. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  216. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  217. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  218. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  219. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  220. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  221. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  222. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  223. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  224. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  225. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  226. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  227. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  228. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  229. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  230. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  231. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  232. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  233. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  234. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  235. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  236. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  237. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  238. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  239. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  240. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  241. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  242. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  243. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  244. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  245. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  246. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  247. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  248. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  249. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  250. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  251. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  252. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  253. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  254. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  255. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  256. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  257. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  258. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  259. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  260. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  261. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  262. 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
  263. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  264. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  265. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  266. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  267. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  268. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  269. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  270. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  271. package/docs/reference/decisions.md +83 -0
  272. package/docs/reference/glossary.md +648 -0
  273. package/docs/reference/http-api.md +1348 -0
  274. package/docs/reference/machines.md +190 -0
  275. package/docs/reference/mechanism-events.md +72 -0
  276. package/docs/why.md +206 -0
  277. package/package.json +5 -3
  278. package/src/actor.ts +541 -79
  279. package/src/agent.ts +114 -38
  280. package/src/ambient.ts +2 -1
  281. package/src/assembly-checks.ts +189 -7
  282. package/src/cause.ts +234 -0
  283. package/src/chart.ts +169 -37
  284. package/src/charts.ts +34 -6
  285. package/src/config.ts +430 -178
  286. package/src/custodian.ts +74 -22
  287. package/src/customize.ts +188 -46
  288. package/src/dispatch-effect.ts +945 -0
  289. package/src/door-compat.ts +217 -0
  290. package/src/durability.ts +11 -4
  291. package/src/effect.ts +341 -44
  292. package/src/effector-wire.ts +160 -11
  293. package/src/fail.ts +209 -0
  294. package/src/harness-client.ts +541 -47
  295. package/src/http-effect.ts +495 -53
  296. package/src/http.ts +427 -16
  297. package/src/index.ts +60 -82
  298. package/src/instance.ts +68 -8
  299. package/src/internal.ts +84 -0
  300. package/src/machine-doc.ts +21 -3
  301. package/src/mechanism-routes.ts +168 -0
  302. package/src/model-keys.generated.ts +53 -0
  303. package/src/model-keys.ts +18 -58
  304. package/src/notices.ts +66 -13
  305. package/src/open-body.ts +159 -23
  306. package/src/open-effect.ts +81 -8
  307. package/src/open.ts +59 -6
  308. package/src/parts.ts +184 -13
  309. package/src/party.ts +68 -2
  310. package/src/pick.ts +463 -11
  311. package/src/pool.ts +247 -36
  312. package/src/registration.ts +83 -4
  313. package/src/repos.ts +2 -1
  314. package/src/run-host.ts +565 -31
  315. package/src/run-output.ts +38 -0
  316. package/src/sandbox-effect.ts +273 -38
  317. package/src/sandbox-kube.ts +3 -1
  318. package/src/secret-placement.ts +52 -17
  319. package/src/server.ts +3 -0
  320. package/src/setup.ts +239 -56
  321. package/src/size.ts +121 -9
  322. package/src/snapshot-store.ts +54 -10
  323. package/src/stub-harness.ts +44 -16
  324. package/src/timers.ts +14 -1
  325. package/src/vocabulary.ts +69 -14
  326. package/src/wire.ts +90 -16
  327. package/src/workspace.ts +697 -175
package/src/setup.ts CHANGED
@@ -51,33 +51,59 @@ import { gate } from "./gate.ts";
51
51
  import { attachLent, isOpenBody, openBodyDeclarationOf, type LentParts } from "./open-body.ts";
52
52
  import { refuseInlineParts } from "./parts.ts";
53
53
  import { partyWarnings, refuseUnknownSentBy, SENT_BY, sentByGuard } from "./party.ts";
54
+ import { refuseLostOutput } from "./run-output.ts";
55
+ import { refuseUnroutedAgentFault } from "./mechanism-routes.ts";
54
56
  import { boundEpoch, boundRunId, continuedIid } from "./registration.ts";
55
57
  import { attachInputSchema, attachLoadWarnings, attachSeats, attachVocabulary } from "./vocabulary.ts";
56
58
 
57
59
  /**
58
- * The events jr2's own mechanism delivers into any workflow machine, injected into every jr2Setup
59
- * union. Dotted names by construction (`NAME_RE` forbids dots in workflow event names), so they
60
- * can never collide with a def.
60
+ * The marker that `compileChart` puts on the config of a Chart's Machine (ADR-0070). In a Chart the
61
+ * `agent.fault` route is optional: the Open body sends an `agent.fault` that no transition takes to
62
+ * the Machine that invoked it, so `createMachine` does not refuse a Chart state with no route. Not on the Authoring
63
+ * surface (index.ts). The load walk (`partsOf`) refuses an author's Machine with no route, with or
64
+ * without the marker (ADR-0074).
61
65
  */
66
+ export const COMPILED_CHART: unique symbol = Symbol("jr2.compiledChart");
67
+
68
+ // Injected into every jr2Setup union. Dotted names by construction (`NAME_RE` forbids dots in
69
+ // workflow event names), so they can never collide with a def.
70
+ /** The events that jr2 itself sends into each Machine that `jr2Setup` makes. A Mechanism event's
71
+ * name has a dot, and a workflow event's name cannot, so the two never collide. */
62
72
  export type MechanismEvent = FaultTelemetry | WorkspaceLost;
63
73
 
74
+ // A Workspace's Continuity broke (ADR-0021): its Sandbox is gone, or Lost. `reason` and `message`
75
+ // are for a body whose policy tells the causes apart, or only reports them; no policy needs them.
64
76
  /**
65
- * A Workspace's Continuity broke (ADR-0021): its Sandbox is gone, or Lost. `reason` and `message`
66
- * say why — the pod's own reason (`Evicted`, `NodeShutdown`, ...) or the operator's (`PodDeleted`,
67
- * `NodeLost`), or `Deleted` for a Sandbox that is gone — for a body whose policy tells them apart,
68
- * or only reports them. Optional: the body's policy never needs them to act.
77
+ * jr2 sends `workspace.lost` to the body of a `workspace()` when its Sandbox is gone: its pod was
78
+ * evicted or deleted, or its node went. jr2 does not make the Sandbox again, and the commits that
79
+ * were not pushed go with it. The body decides what to do next: route the event with
80
+ * `on: { "workspace.lost": … }`, usually once, on the body's root. `workspace()` refuses a body
81
+ * with a state that is not final and has no route for it on itself or on a state above. When the
82
+ * event comes and no transition takes it (the route's guard is false), the run ends `error`, with
83
+ * the event, the state and the reason in its fault.
69
84
  */
70
- export type WorkspaceLost = { type: "workspace.lost"; reason?: string; message?: string };
85
+ export type WorkspaceLost = {
86
+ type: "workspace.lost";
87
+ /** Why the Sandbox is gone, for a person or a log: the pod's reason (`Evicted`, `NodeShutdown`),
88
+ * the operator's (`PodDeleted`, `NodeLost`), or `Deleted`. A body can act without it. */
89
+ reason?: string;
90
+ /** More text about the loss, when jr2 has it. */
91
+ message?: string;
92
+ };
71
93
 
72
- /** The full event union a jr2Setup machine sees: the defs' derived types plus the mechanism's. A
73
- * workflow event a party delivered carries its `sender` (ADR-0015); the Machine's own events
74
- * (`raise`, `sendTo` itself) carry none. */
94
+ // The defs' derived types plus the mechanism's (ADR-0015).
95
+ /** The type of `event` in a Machine that `jr2Setup` makes: each workflow event in its `events`,
96
+ * and each Mechanism event. A workflow event that an Agent or a Gate sent has `event.sender`. An
97
+ * event that the Machine sent to itself (`raise`, `sendTo`) has no `sender`. */
75
98
  export type WorkflowEvent<TDefs extends readonly EventDef[]> =
76
99
  | (EventFrom<TDefs[number]> & { readonly sender?: Sender })
77
100
  | MechanismEvent;
78
101
 
79
102
  /** The guard jr2Setup registers for every Machine — `sentBy(id)` (ADR-0015) — in the typed guard
80
- * map, so `sentBy("coder")` and `and([sentBy("coder"), "ready"])` type-check with no cast. */
103
+ * map, so `sentBy("coder")` and `and([sentBy("coder"), "ready"])` type-check with no cast.
104
+ *
105
+ * @internal
106
+ */
81
107
  type SentByGuards = { [SENT_BY]: { id: string } };
82
108
 
83
109
  /** The jr2 actor every workflow can invoke by name without listing it (ADR-0015). `gate` is the
@@ -86,25 +112,154 @@ type SentByGuards = { [SENT_BY]: { id: string } };
86
112
  const jr2Actors = { gate };
87
113
  type JR2Actors = typeof jr2Actors;
88
114
 
89
- /** Consumer actors merge OVER the pre-registered set: same name → the consumer's logic wins. */
115
+ /**
116
+ * Consumer actors merge OVER the pre-registered set: same name → the consumer's logic wins.
117
+ *
118
+ * @internal
119
+ */
90
120
  type MergedActors<TActors extends Record<string, UnknownActorLogic>> = Omit<JR2Actors, keyof TActors> & TActors;
91
121
 
92
122
  // Local equivalents of xstate's non-exported setup() mapped helpers (setup.d.ts) — same shapes,
93
123
  // so the declared signature below composes with the public `ActionFunction`/`GuardPredicate`.
124
+ /** @internal */
94
125
  type ToParameterizedObject<T extends Record<string, ParameterizedObject["params"] | undefined>> = {
95
126
  [K in keyof T & string]: { type: K; params: T[K] };
96
127
  }[keyof T & string];
128
+ /** @internal */
97
129
  type ToProvidedActor<TActors extends Record<string, UnknownActorLogic>> = {
98
130
  [K in keyof TActors & string]: { src: K; logic: TActors[K]; id: string | undefined };
99
131
  }[keyof TActors & string];
100
132
 
101
133
  /**
102
- * Author a workflow machine (ADR-0015). Like `setup()`, but `types.events` is gone: the event
103
- * union derives from `events` (defineEvent defs, taken as values) plus the mechanism events.
134
+ * Set up a Machine that takes workflow events: the jr2 form of xstate's `setup()`. Write a Machine
135
+ * with it when an Agent or a Gate sends events into the Machine, or when the Machine declares a
136
+ * door. Call `.createMachine(config)` on the result, as with `setup()`. The Machine you get is a
137
+ * plain xstate Machine.
138
+ *
139
+ * What it adds to `setup()`:
140
+ * - `events` replaces `types.events`. The event type of the Machine comes from the
141
+ * `defineEvent` events you list, plus the events jr2 itself sends (`agent.fault`,
142
+ * `workspace.lost`). An event that a party sent has `event.sender`. A Machine that takes no
143
+ * workflow event leaves out `events`.
144
+ * - The `gate` actor is ready to invoke by name: `invoke: { src: "gate" }`.
145
+ * - Each Agent slot (`actors: { coder: agent(…) }`) gets its Menu from the states that invoke it.
146
+ * - The `sentBy(id)` guard is ready to use.
147
+ * - `createMachine` also takes `input`: a zod object for the input that starts a run. jr2 checks
148
+ * each start against it. Without it, a run can start with any input. The `setup()` of xstate
149
+ * does not read this `input`, so a Machine with a door uses `jr2Setup`, also when it takes no
150
+ * workflow event. `jr2 up` and the publish door refuse a registered Machine that has a zod
151
+ * `input` if `jr2Setup` did not make the Machine: ``workflow "<name>": machine "<id>" has a zod `input` on
152
+ * createMachine, but jr2Setup did not make this Machine, so no door reads that input …``
153
+ *
154
+ * The output of a run is the root `output` of `createMachine`, beside `id` and `states`, as in
155
+ * xstate v5. xstate does not read the `output` of a final state as the output of the Machine. It
156
+ * gives that output to the root `output` as `event.output`. Write
157
+ * `output: ({ event }) => event.output as Output` at the root, or make the output from `context`
158
+ * there. `createMachine` refuses an `output` on a final state directly below the root when the root
159
+ * has no `output` (see the list below).
160
+ *
161
+ * `createMachine` refuses a Machine that it cannot run as written. Among the refusals:
162
+ * - an `on` key that no listed event has: ``machine "<id>" handles event "<key>", which no def in
163
+ * jr2Setup({ events }) declares (declared: <names>) — a typo, or a missing defineEvent``
164
+ * - a listed event that no state takes: ``machine "<id>" declares "<name>", which no state handles
165
+ * — a pick no state takes is on no Menu and no Gate: handle it, or drop it from
166
+ * jr2Setup({ events })``
167
+ * - an entry of `events` that `defineEvent` did not make, or one name listed twice:
168
+ * ``machine "<id>": `events` entry is not a defineEvent() def: <entry>``,
169
+ * ``machine "<id>": duplicate event "<name>" in `events` ``
170
+ * - an `agent()`, an Effect or a `workspace()` written on `invoke.src` instead of under `actors`:
171
+ * ``machine "<id>": state "<state>" invokes <part> inline — a Part is a key of actors, so it has
172
+ * a name that the ledger, the report and customize() share: actors: { <name>: <factory>(...) },
173
+ * then invoke: { src: "<name>" }``. `<part>` is `an Agent`, `an Effect` or `a Workspace`.
174
+ * `<factory>` is `agent`, the Effect's kind (such as `command`), or `workspace`.
175
+ * - an `output` on a final state directly below the root, when the root has no `output`:
176
+ * ``machine "<id>": final state "<state>" declares an output, and the root has no output — xstate
177
+ * v5 gives a run only its root output, so this output reaches nothing and the run ends with none:
178
+ * write it at the root, as output: ({ context }) => ..., or keep it on the final state and pass it
179
+ * on with output: ({ event }) => event.output at the root``. With more than one such final state,
180
+ * it names each one and puts the words in the plural: ``final states "<state>", "<state>"
181
+ * declare an output``, ``these outputs reach nothing``, ``write one at the root`` and ``keep
182
+ * them on the final states and pass them on``. An `output` on a
183
+ * final state inside a compound state is legal: the `onDone` of that compound state gets it as
184
+ * `event.output`.
185
+ * - a parallel state at or below a state that invokes an Agent: ``machine "<id>": state "<state>"
186
+ * is parallel, at or below Agent "<slot>"'s state "<state>" — a parallel region below an Agent's
187
+ * state is reserved for picks the Agent does not wait for, which jr2 does not build; background
188
+ * work goes above the Agent's state``
189
+ * - a state below an Agent's state that invokes something and that nothing enters: ``machine
190
+ * "<id>": state "<state>" below Agent "<slot>"'s state "<state>" is entered by nothing, so what
191
+ * it invokes (<src>) never runs — target it from a pick or a route, or delete it``
192
+ * - a state that invokes an Agent when that state and each state above it have no `agent.fault`
193
+ * route: ``machine "<id>": state "<state>" invokes an Agent, and no route for "agent.fault" is
194
+ * on it or on a state above — jr2 sends agent.fault when the Agent's Turn cannot go on, and with
195
+ * no route the run faults: route it on that state or on a state above it, as on: {
196
+ * "agent.fault": "<the state that handles it>" } (ADR-0074)``. A state that invokes an Open body
197
+ * that lends an Agent or allows a model gets the same line, with `invokes an Open body that can
198
+ * run an Agent`: the body sends it a Chart's unrouted `agent.fault`. When both kinds have no route,
199
+ * one line names both, joined by `, and`. One route on the root is a route for each state. A
200
+ * Chart's Machine does not get this refusal: in a Chart the route is optional (ADR-0070).
201
+ *
202
+ * It also refuses a `sentBy` that names no party (see `sentBy`). It refuses a pick param that
203
+ * nothing reads, where the pick enters a state that invokes an Effect (see `fromPick`). Shapes that
204
+ * are legal but probably not what you mean, such as a mode that nothing enters, are warnings that
205
+ * `jr2 up` prints.
206
+ *
207
+ * @param def - What `setup()` takes, with `events` in place of `types.events`. A Machine that
208
+ * needs none of it calls `jr2Setup()`.
209
+ * @returns What `setup()` returns. Call `.createMachine(config)` on it.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * import { z } from "zod";
214
+ * import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
215
+ *
216
+ * const proposed = defineEvent({
217
+ * name: "proposed",
218
+ * description: "Call this with your plan.",
219
+ * input: z.object({ plan: z.string() }),
220
+ * });
221
+ * const approve = defineEvent({ name: "approve", input: z.object({}) });
222
+ * const reject = defineEvent({ name: "reject", input: z.object({ reason: z.string() }) });
223
+ *
224
+ * export const machine = jr2Setup({
225
+ * types: {} as { input: { goal: string }; context: { goal: string } },
226
+ * events: [proposed, approve, reject],
227
+ * actors: {
228
+ * planner: agent({
229
+ * model: "anthropic/claude-haiku-5-5",
230
+ * instructions: "You plan small releases.",
231
+ * workspace: "none",
232
+ * }),
233
+ * fail: fail(),
234
+ * },
235
+ * }).createMachine({
236
+ * id: "plan",
237
+ * input: z.object({ goal: z.string() }),
238
+ * context: ({ input }) => ({ goal: input.goal }),
239
+ * on: { "agent.fault": ".failed" },
240
+ * initial: "planning",
241
+ * states: {
242
+ * planning: {
243
+ * invoke: { src: "planner", input: ({ context }) => ({ prompt: `Plan this: ${context.goal}` }) },
244
+ * on: { proposed: "review" },
245
+ * },
246
+ * review: {
247
+ * invoke: { src: "gate" },
248
+ * on: { approve: "approved", reject: "planning" },
249
+ * },
250
+ * approved: { type: "final" },
251
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
252
+ * },
253
+ * });
254
+ * ```
255
+ *
256
+ * @see docs/guides/write-a-workflow.md
257
+ * @see docs/concepts/machines-and-workflows.md
258
+ * @see docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md
104
259
  */
105
260
  export function jr2Setup<
106
261
  TContext extends MachineContext = MachineContext,
107
- const TDefs extends readonly EventDef[] = readonly EventDef[],
262
+ const TDefs extends readonly EventDef[] = readonly [],
108
263
  TActors extends Record<string, UnknownActorLogic> = {},
109
264
  TActions extends Record<string, ParameterizedObject["params"] | undefined> = {},
110
265
  TGuards extends Record<string, ParameterizedObject["params"] | undefined> = {},
@@ -114,44 +269,56 @@ export function jr2Setup<
114
269
  TOutput extends NonReducibleUnknown = NonReducibleUnknown,
115
270
  TEmitted extends EventObject = EventObject,
116
271
  TMeta extends MetaObject = MetaObject,
117
- >(def: {
118
- /** context/input/output/tags/emitted/meta — never `events`; the union is derived. */
119
- types?: {
120
- context?: TContext;
121
- input?: TInput;
122
- output?: TOutput;
123
- tags?: TTag;
124
- emitted?: TEmitted;
125
- meta?: TMeta;
126
- };
127
- /** This Machine's vocabulary, as values — the single source for types, validation, delivery. */
128
- events: TDefs;
129
- actors?: TActors;
130
- actions?: {
131
- [K in keyof TActions]: ActionFunction<
132
- TContext,
133
- WorkflowEvent<TDefs>,
134
- WorkflowEvent<TDefs>,
135
- TActions[K],
136
- ToProvidedActor<MergedActors<TActors>>,
137
- ToParameterizedObject<TActions>,
138
- ToParameterizedObject<TGuards>,
139
- TDelay,
140
- TEmitted
141
- >;
142
- };
143
- guards?: {
144
- [K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>;
145
- };
146
- delays?: {
147
- [K in TDelay]: DelayConfig<
148
- TContext,
149
- WorkflowEvent<TDefs>,
150
- ToParameterizedObject<TActions>["params"],
151
- WorkflowEvent<TDefs>
152
- >;
153
- };
154
- }): SetupReturn<
272
+ >(
273
+ def: {
274
+ /** The types of the Machine, as in `setup()`: `context`, `input`, `output`, `tags`, `emitted`
275
+ * and `meta`. Not `events`: the event type comes from `events`. */
276
+ types?: {
277
+ context?: TContext;
278
+ input?: TInput;
279
+ output?: TOutput;
280
+ tags?: TTag;
281
+ emitted?: TEmitted;
282
+ meta?: TMeta;
283
+ };
284
+ // The single source for types, validation and delivery.
285
+ /** The workflow events this Machine takes: each one made by `defineEvent`. Leave it out when the
286
+ * Machine takes none. Each listed event must be the name of an `on` key. Each `on` key must be
287
+ * listed, if it is not a jr2 or xstate event (an event with a `.` in its name). */
288
+ events?: TDefs;
289
+ /** The actors the states invoke by name, as in `setup()`: Agent slots (`agent(…)`), Effects
290
+ * (`command(…)`, `http(…)`, `push(…)`), child Machines, `workspace(…)` and plain xstate logic.
291
+ * The key is the slot's name. An actor named `gate` replaces jr2's Gate. */
292
+ actors?: TActors;
293
+ /** Named actions, as in `setup()`. Their `event` has the type of this Machine's events. */
294
+ actions?: {
295
+ [K in keyof TActions]: ActionFunction<
296
+ TContext,
297
+ WorkflowEvent<TDefs>,
298
+ WorkflowEvent<TDefs>,
299
+ TActions[K],
300
+ ToProvidedActor<MergedActors<TActors>>,
301
+ ToParameterizedObject<TActions>,
302
+ ToParameterizedObject<TGuards>,
303
+ TDelay,
304
+ TEmitted
305
+ >;
306
+ };
307
+ /** Named guards, as in `setup()`. `sentBy` is there already; do not name a guard `jr2.sentBy`. */
308
+ guards?: {
309
+ [K in keyof TGuards]: GuardPredicate<TContext, WorkflowEvent<TDefs>, TGuards[K], ToParameterizedObject<TGuards>>;
310
+ };
311
+ /** Named delays, as in `setup()`. */
312
+ delays?: {
313
+ [K in TDelay]: DelayConfig<
314
+ TContext,
315
+ WorkflowEvent<TDefs>,
316
+ ToParameterizedObject<TActions>["params"],
317
+ WorkflowEvent<TDefs>
318
+ >;
319
+ };
320
+ } = {},
321
+ ): SetupReturn<
155
322
  TContext,
156
323
  WorkflowEvent<TDefs>,
157
324
  MergedActors<TActors>,
@@ -165,6 +332,8 @@ export function jr2Setup<
165
332
  TEmitted,
166
333
  TMeta
167
334
  > {
335
+ // Like `setup()`, but `types.events` is gone (ADR-0015): the event union derives from `events`
336
+ // (defineEvent defs, taken as values) plus the mechanism events.
168
337
  const inner = setup({
169
338
  types: def.types,
170
339
  actors: { ...jr2Actors, ...(def.actors ?? {}) },
@@ -179,11 +348,16 @@ export function jr2Setup<
179
348
  // machine receives at creation — and is pulled OFF before xstate sees it: a zod schema is
180
349
  // not machine structure, and it must never reach the fingerprint/serialization paths that
181
350
  // read `machine.config`. It is attached beside the vocabulary below.
182
- const { input: inputSchema, ...machineConfig } = config as { input?: z.ZodObject } & Record<string, unknown>;
351
+ // A compiled Chart (ADR-0070) is marked, and the marker is pulled off as `input` is.
352
+ const {
353
+ input: inputSchema,
354
+ [COMPILED_CHART]: compiledChart,
355
+ ...machineConfig
356
+ } = config as { input?: z.ZodObject; [COMPILED_CHART]?: true } & Record<string, unknown>;
183
357
 
184
358
  // Resolve the defs first (duplicates and reserved semantics fail HERE, naming the machine —
185
359
  // the same loud failure `eventMap` gave the manifest, moved to machine-build time)…
186
- const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events);
360
+ const defs = eventMap((machineConfig as { id?: string }).id ?? "(machine)", def.events ?? []);
187
361
 
188
362
  // Every Open body slot (ADR-0070), found by its brand: each name it lends resolves against THIS
189
363
  // setup's own parts, or the Machine is refused naming it. The resolved set is stamped on the slot,
@@ -231,6 +405,12 @@ export function jr2Setup<
231
405
 
232
406
  // A Part written inline has no name (ADR-0049, ADR-0067): refused, with the slot form.
233
407
  refuseInlineParts(machine);
408
+ // A final state's output with no root output (ADR-0015): the run would end with none.
409
+ refuseLostOutput(machine);
410
+ // A state that invokes an Agent, or an Open body that can run one, with no `agent.fault` route
411
+ // (ADR-0074): the run would wait for ever. Not in a compiled Chart: there the route is optional,
412
+ // and the Open body sends the event that no transition takes to the Machine that invoked it (ADR-0070).
413
+ if (compiledChart !== true) refuseUnroutedAgentFault(machine);
234
414
  refuseParallelBelowAgents(machine, def.actors ?? {});
235
415
  refuseUnknownSentBy(machine);
236
416
  // Before the Effect checks: they ask who may send a writing (`mayTake`), which reads its audience.
@@ -894,6 +1074,9 @@ function wrapAgentInput(orig: unknown, derived: string[], agentName: string, men
894
1074
  // jr2 mints every id (ADR-0016, ADR-0057): the Turn's input carries no id to honor, so
895
1075
  // nothing a Machine writes can address a conversation jr2 did not derive.
896
1076
  instanceId: mintIid(consumer, agentName, args.self),
1077
+ // One per invocation (ADR-0016): two Turns that continue one conversation share the iid, the
1078
+ // admission ledger's key, and a restore must not re-attach one Turn to the other's admission.
1079
+ turnId: randomUUID(),
897
1080
  endpoint: consumer.endpoint,
898
1081
  sandbox: consumer.sandbox,
899
1082
  prompt: consumer.prompt,
package/src/size.ts CHANGED
@@ -23,15 +23,73 @@
23
23
  /** A Kubernetes quantity: `"2Gi"`, `"1500m"`, `"2"`, or a plain number (`2` cpus, bytes of memory). */
24
24
  export type Quantity = string | number;
25
25
 
26
+ // A Size (ADR-0060, GLOSSARY.md "Size"): `limits.memory` and `limits.cpu`, nothing else. A field
27
+ // left out resolves down the chain (`resolveSize`) — the Instance's `sandbox.resources`, then the
28
+ // kit default (`KIT_SIZE`). The same shape is the User Container's split (`userSeatOf`,
29
+ // `splitSize`), where a field left out means the seat has no limit of its own for it and shares
30
+ // the pod's budget.
26
31
  /**
27
- * A Size (ADR-0060, GLOSSARY.md "Size"): `limits.memory` and `limits.cpu`, nothing else. A field
28
- * left out resolves down the chain (`resolveSize`) — the Instance's `sandbox.resources`, then the
29
- * kit default of 2Gi and 1 cpu.
32
+ * The Size of a Workspace: the most memory and cpu that its whole Sandbox can use. jr2 reserves
33
+ * all of it on the node. Thus a Sandbox never gets less than its Size and never uses more.
30
34
  *
31
- * The same shape is the User Container's split (`user: { image, resources }`), where a field left
32
- * out means the seat has no limit of its own for it and shares the pod's budget.
35
+ * Write it as `{ limits: { memory, cpu } }`. You can write it in three places: the `resources`
36
+ * option of `workspace()`, `customize(machine, { resources })`, and `sandbox.resources` in
37
+ * `jr2.config.ts`. The Size is a fact of the Machine. The Instance's `sandbox.resources` applies
38
+ * only to a Workspace that states no Size. A field that neither states comes from the kit default:
39
+ * memory `2Gi`, cpu `1`.
40
+ *
41
+ * The same shape is the User Container's part of the Size, in `user: { image, resources }`. There,
42
+ * a field you leave out gives the User Container no limit of its own: it shares the pod's budget.
43
+ *
44
+ * It refuses any key but `limits.memory` and `limits.cpu`, where you write it. The message names
45
+ * the field: `<where>` is where you wrote the Size, for example `workspace(): resources`.
46
+ *
47
+ * - `<where>.requests is not accepted — jr2 reserves the whole Size, so every request equals its
48
+ * limit — a Size is { limits: { memory?, cpu? } } and nothing else — Agent Substrate's template
49
+ * size (ADR-0060)`
50
+ * - `<where>.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing else —
51
+ * Agent Substrate's template size (ADR-0060)`
52
+ * - `<where>.limits.<key> is not accepted — a Size is { limits: { memory?, cpu? } } and nothing
53
+ * else — Agent Substrate's template size (ADR-0060)`
54
+ * - `<where>.limits.memory: <value> is not a positive quantity (e.g. "2Gi", "512Mi")`, and for
55
+ * cpu, `<where>.limits.cpu: <value> is not a positive quantity (e.g. "2", "1500m")`
56
+ *
57
+ * `jr2 up` also refuses a Size that leaves the Harness container less than its floor. The message
58
+ * starts with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or
59
+ * with `the Size leaves the Harness container no cpu:`. The text that follows names each number
60
+ * and where it came from.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { customize, type Size } from "@jr2/orchestrator";
65
+ * import { task } from "@jr2/machines";
66
+ *
67
+ * const size: Size = { limits: { memory: "4Gi", cpu: "2" } };
68
+ *
69
+ * export const machine = customize(task, {
70
+ * repos: { app: { url: "https://github.com/you/app.git" } },
71
+ * agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
72
+ * resources: size,
73
+ * });
74
+ * ```
75
+ *
76
+ * @see docs/guides/size-a-workspace.md
77
+ * @see docs/concepts/workspaces.md
78
+ * @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
33
79
  */
34
- export type Size = { limits: { memory?: Quantity; cpu?: Quantity } };
80
+ export type Size = {
81
+ /** The two limits. Write `limits: {}` to state no field: each field then comes from the next
82
+ * source. */
83
+ limits: {
84
+ /** The memory limit, as a Kubernetes quantity: `"4Gi"`, `"512Mi"`, or a number of bytes.
85
+ * It must be more than zero. When you leave it out, it comes from the next source, as above. */
86
+ memory?: Quantity;
87
+ /** The cpu limit, as a Kubernetes quantity: `"2"`, `"1500m"`, or a number of cpus. It must be
88
+ * more than zero. jr2 enforces it. When you leave it out, it comes from the next source, as
89
+ * above. */
90
+ cpu?: Quantity;
91
+ };
92
+ };
35
93
 
36
94
  /** The kit's default Size (ADR-0060). Node gives each process a V8 heap of about 55% of its
37
95
  * container limit, and `tsc` on a mid-size repo aborted below about 1.1Gi, so 1Gi fails a common
@@ -303,11 +361,65 @@ export function splitSize(size: ResolvedSize, user?: Size): SizeSplit {
303
361
 
304
362
  // --- the User Container seat -------------------------------------------------------------------
305
363
 
364
+ // The User Container as a Machine states it (ADR-0005, ADR-0060): an image in ADR-0037's two
365
+ // shapes, and optionally its split of the Size. The object form exists for the split alone.
306
366
  /**
307
- * The User Container as a Machine states it (ADR-0005, ADR-0060): an image in ADR-0037's two
308
- * shapes, and optionally its split of the Size. The object form exists for the split alone.
367
+ * The User Container in its object form: its image, and its part of the Workspace's Size. The User
368
+ * Container is an optional container of your own in the Sandbox pod. It runs its own entrypoint
369
+ * with `/work` mounted, and jr2 never builds into it, probes it or sends it commands. Use it for a
370
+ * service that must run without an Agent, such as an sshd.
371
+ *
372
+ * Write it in the `user` option of `workspace()` or of `customize()`. That option also takes the
373
+ * image as a string; use this object form only to give the User Container a part of the Size. A
374
+ * `customize()` that names `user` replaces the whole User Container, its part of the Size
375
+ * included.
376
+ *
377
+ * It refuses, where you write it. `<where>` is `workspace()` or `customize()`:
378
+ *
379
+ * - an image that is not a non-empty string. The message starts with
380
+ * `` <where>: `user` must be a non-empty string (an image), or { image, resources? } ``.
381
+ * - `<where>: user.<key> is not accepted — the User Container is { image, resources? } (ADR-0005,
382
+ * ADR-0060)`
383
+ *
384
+ * `resources` is checked as a {@link Size}, with the same refusals.
385
+ *
386
+ * `jr2 up` refuses a part that leaves the Harness container less than its floor. The message starts
387
+ * with `the Size leaves the Harness container <memory> memory, below its 256Mi floor:` or with
388
+ * `the Size leaves the Harness container no cpu:`. The text that follows names each number and
389
+ * where it came from.
390
+ *
391
+ * @example
392
+ * ```ts
393
+ * import { customize, type UserContainer } from "@jr2/orchestrator";
394
+ * import { task } from "@jr2/machines";
395
+ *
396
+ * const sshd: UserContainer = {
397
+ * image: "registry.example.com/sshd:1.0",
398
+ * resources: { limits: { memory: "256Mi", cpu: "250m" } },
399
+ * };
400
+ *
401
+ * export const machine = customize(task, {
402
+ * repos: { app: { url: "https://github.com/you/app.git" } },
403
+ * agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
404
+ * user: sshd,
405
+ * });
406
+ * ```
407
+ *
408
+ * @see docs/concepts/workspaces.md
409
+ * @see docs/guides/size-a-workspace.md
410
+ * @see docs/reference/decisions/0005-sandbox-pod-composition.md
411
+ * @see docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md
309
412
  */
310
- export type UserContainer = { image: string; resources?: Size };
413
+ export type UserContainer = {
414
+ /** The image: a `file:` URL to a docker context that your module ships
415
+ * (`import.meta.resolve("./sshd")`), which `jr2 up` builds, or a registry ref. It must not be
416
+ * empty. */
417
+ image: string;
418
+ /** This container's part of the Workspace's Size, as a Size. jr2 takes it from the Harness
419
+ * container's part. A field you leave out gives this container no limit of its own for it: it
420
+ * shares the pod's budget. Default: no part. */
421
+ resources?: Size;
422
+ };
311
423
 
312
424
  /** The `user` option in either shape, read as its two halves. */
313
425
  export function userSeatOf(user: string | UserContainer | undefined): { image?: string; resources?: Size } {
@@ -4,7 +4,8 @@
4
4
  // store is owed — with `:memory:` for tests. A persist survives a process crash, not a power loss
5
5
  // (WAL, `synchronous=NORMAL`), and the write stays synchronous so the row is on disk before the
6
6
  // Machine's next side effect runs. `markLost` records a run whose live state could not be
7
- // re-hydrated (e.g. its flue handle is gone); a non-live row lives a week, then `sweep` deletes it.
7
+ // re-hydrated (e.g. its flue handle is gone), keeping any snapshot the row had; a non-live row
8
+ // lives a week, then `sweep` deletes it.
8
9
 
9
10
  import { DatabaseSync } from "node:sqlite";
10
11
  import type { StoredSnapshot } from "./durability.ts";
@@ -37,18 +38,31 @@ export interface SnapshotStore {
37
38
  * silently to the live one, which makes resolution unsound.
38
39
  */
39
40
  findIdsByPrefix(prefix: string, limit: number): Promise<string[]>;
41
+ /**
42
+ * The run whose blob carries the idempotency key `effect` (`dispatchedBy.effect`, ADR-0071), in
43
+ * any status, or undefined: what answers a re-sent dispatch with the run it already started. A
44
+ * row `markLost` wrote with no save before it has no snapshot, and so no key; a swept row is gone
45
+ * with its key.
46
+ */
47
+ findByDispatch(effect: string): Promise<string | undefined>;
40
48
  save(runId: string, snapshot: unknown, status?: string): Promise<void>;
49
+ /**
50
+ * Record a run whose live state could not be re-hydrated. An upsert: a row that was saved KEEPS
51
+ * its snapshot and its idempotency key, because a lost dispatched run must keep answering its
52
+ * key — a re-send from its caller would otherwise start a second run (ADR-0071). Only a run with
53
+ * no row before gets a row with no snapshot.
54
+ */
41
55
  markLost(runId: string, reason: string): Promise<void>;
42
56
  /**
43
57
  * Record a run this Orchestrator can no longer read (ADR-0030, ADR-0069) — refused, not resumed:
44
58
  * the Assembly its snapshot names is not on the volume, was published for another Kit version or
45
59
  * does not load, or its Machine's shape does not match the snapshot's stamp.
46
60
  *
47
- * Unlike {@link markLost} this KEEPS the snapshot. A lost run has nothing left to look at, so
48
- * nulling it costs nothing; a drifted run is intact and only unreadable by this Orchestrator,
49
- * and it is the one a human most needs to inspect. Nulling it would also hide the run
50
- * entirely: `read()` returns undefined for a null blob, so `jr2 status <id>` would answer
51
- * `no run` — a refusal indistinguishable from a run that never existed.
61
+ * An UPDATE of a row that is there, and it KEEPS the snapshot: a drifted run is intact and only
62
+ * unreadable by this Orchestrator, and it is the one a human most needs to inspect. Nulling it
63
+ * would also hide the run entirely: `read()` returns undefined for a null blob, so
64
+ * `jr2 status <id>` would answer `no run` — a refusal indistinguishable from a run that never
65
+ * existed. It keeps the row's idempotency key too (ADR-0071).
52
66
  */
53
67
  markDrifted(runId: string, reason: string): Promise<void>;
54
68
  close(): Promise<void>;
@@ -62,6 +76,20 @@ type Row = {
62
76
  updated_at: string;
63
77
  };
64
78
 
79
+ /** The lookup `findByDispatch` runs, exported so a test can ask the planner about THIS statement,
80
+ * not a copy of it: the column is indexed, so the plan is a seek. */
81
+ export const FIND_BY_DISPATCH_SQL = `SELECT run_id FROM machine_snapshots
82
+ WHERE dispatch_effect = ? ORDER BY rowid LIMIT 1`;
83
+
84
+ /** The idempotency key a blob carries at `dispatchedBy.effect` (ADR-0071), or null. */
85
+ function dispatchEffectOf(snapshot: unknown): string | null {
86
+ if (typeof snapshot !== "object" || snapshot === null) return null;
87
+ const by = (snapshot as { dispatchedBy?: unknown }).dispatchedBy;
88
+ if (typeof by !== "object" || by === null) return null;
89
+ const effect = (by as { effect?: unknown }).effect;
90
+ return typeof effect === "string" ? effect : null;
91
+ }
92
+
65
93
  export class SqliteSnapshotStore implements SnapshotStore {
66
94
  private readonly db: DatabaseSync;
67
95
 
@@ -83,6 +111,16 @@ export class SqliteSnapshotStore implements SnapshotStore {
83
111
  updated_at TEXT
84
112
  )
85
113
  `);
114
+ // The idempotency key of a dispatched run (ADR-0071) is copied out of its blob into a column of
115
+ // its own when `save` writes the row, and the column is indexed. Not an expression index over
116
+ // `json_extract(snapshot, …)`: that runs on every row and THROWS on a blob SQLite's JSON parser
117
+ // refuses (nested about a thousand deep), so one such row would fail its own save and every
118
+ // later `init()` over the volume. A column also costs no JSON parse a save.
119
+ const columns = this.db.prepare(`PRAGMA table_info(machine_snapshots)`).all() as Array<{ name: string }>;
120
+ if (!columns.some((c) => c.name === "dispatch_effect")) {
121
+ this.db.exec(`ALTER TABLE machine_snapshots ADD COLUMN dispatch_effect TEXT`);
122
+ }
123
+ this.db.exec(`CREATE INDEX IF NOT EXISTS machine_snapshots_dispatch_effect ON machine_snapshots(dispatch_effect)`);
86
124
  }
87
125
 
88
126
  async load(runId: string): Promise<StoredSnapshot | null> {
@@ -151,18 +189,24 @@ export class SqliteSnapshotStore implements SnapshotStore {
151
189
  return rows.map((row) => row.run_id);
152
190
  }
153
191
 
192
+ async findByDispatch(effect: string): Promise<string | undefined> {
193
+ const row = this.db.prepare(FIND_BY_DISPATCH_SQL).get(effect) as { run_id: string } | undefined;
194
+ return row?.run_id;
195
+ }
196
+
154
197
  async save(runId: string, snapshot: unknown, status = "live"): Promise<void> {
155
198
  this.db
156
199
  .prepare(
157
- `INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at)
158
- VALUES (?, ?, ?, NULL, ?)
200
+ `INSERT INTO machine_snapshots (run_id, status, snapshot, reason, updated_at, dispatch_effect)
201
+ VALUES (?, ?, ?, NULL, ?, ?)
159
202
  ON CONFLICT(run_id) DO UPDATE SET
160
203
  status = excluded.status,
161
204
  snapshot = excluded.snapshot,
162
205
  reason = NULL,
163
- updated_at = excluded.updated_at`,
206
+ updated_at = excluded.updated_at,
207
+ dispatch_effect = excluded.dispatch_effect`,
164
208
  )
165
- .run(runId, status, JSON.stringify(snapshot), new Date().toISOString());
209
+ .run(runId, status, JSON.stringify(snapshot), new Date().toISOString(), dispatchEffectOf(snapshot));
166
210
  }
167
211
 
168
212
  async markLost(runId: string, reason: string): Promise<void> {