@jr2/orchestrator 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +20 -2
  2. package/bin/server.ts +7 -4
  3. package/console/canvas.ts +27 -14
  4. package/console/components/app.ts +12 -1
  5. package/console/components/fleet.ts +52 -6
  6. package/console/components/machine-pane.ts +128 -2
  7. package/console/components/nav.ts +1 -1
  8. package/console/components/schema-form.ts +12 -4
  9. package/console/json-field.ts +23 -0
  10. package/console/main.ts +122 -39
  11. package/console/store.ts +321 -21
  12. package/console/style.css +111 -0
  13. package/docs/agents-block.md +5 -0
  14. package/docs/concepts/agents-and-turns.md +442 -0
  15. package/docs/concepts/architecture.md +230 -0
  16. package/docs/concepts/charts.md +143 -0
  17. package/docs/concepts/composition.md +357 -0
  18. package/docs/concepts/converge.md +254 -0
  19. package/docs/concepts/durable-runs.md +189 -0
  20. package/docs/concepts/effects.md +603 -0
  21. package/docs/concepts/gates.md +208 -0
  22. package/docs/concepts/machines-and-workflows.md +289 -0
  23. package/docs/concepts/pools-and-sources.md +230 -0
  24. package/docs/concepts/secrets-and-trust.md +222 -0
  25. package/docs/concepts/the-menu.md +378 -0
  26. package/docs/concepts/workspaces.md +678 -0
  27. package/docs/get-started/a-model-decides.md +563 -0
  28. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  29. package/docs/get-started/connect-a-model.md +248 -0
  30. package/docs/get-started/create-an-instance.md +194 -0
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  32. package/docs/get-started/install.md +126 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  34. package/docs/guides/answer-a-gate.md +304 -0
  35. package/docs/guides/bind-open-parts.md +223 -0
  36. package/docs/guides/cancel-a-run.md +146 -0
  37. package/docs/guides/clean-up.md +206 -0
  38. package/docs/guides/fix-a-failed-converge.md +361 -0
  39. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  41. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  42. package/docs/guides/look-inside-a-sandbox.md +174 -0
  43. package/docs/guides/mirror-the-kit-images.md +176 -0
  44. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  45. package/docs/guides/open-the-console.md +91 -0
  46. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  47. package/docs/guides/push-and-sign-a-branch.md +486 -0
  48. package/docs/guides/run-a-chart-by-name.md +270 -0
  49. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  50. package/docs/guides/run-one-worker-per-item.md +321 -0
  51. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  52. package/docs/guides/size-a-workspace.md +204 -0
  53. package/docs/guides/start-a-run-from-a-run.md +237 -0
  54. package/docs/guides/start-a-run.md +207 -0
  55. package/docs/guides/upgrade-the-kit-version.md +240 -0
  56. package/docs/guides/use-a-hosted-provider.md +208 -0
  57. package/docs/guides/use-a-local-model.md +313 -0
  58. package/docs/guides/watch-a-run.md +326 -0
  59. package/docs/guides/write-a-workflow.md +318 -0
  60. package/docs/index.md +44 -0
  61. package/docs/llms.txt +216 -0
  62. package/docs/reference/authoring-surface/agent.md +128 -0
  63. package/docs/reference/authoring-surface/answer.md +140 -0
  64. package/docs/reference/authoring-surface/arg.md +60 -0
  65. package/docs/reference/authoring-surface/basic.md +62 -0
  66. package/docs/reference/authoring-surface/bearer.md +53 -0
  67. package/docs/reference/authoring-surface/chartschema.md +23 -0
  68. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  69. package/docs/reference/authoring-surface/command.md +191 -0
  70. package/docs/reference/authoring-surface/customize.md +64 -0
  71. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  72. package/docs/reference/authoring-surface/defineevent.md +114 -0
  73. package/docs/reference/authoring-surface/dispatch.md +126 -0
  74. package/docs/reference/authoring-surface/fail.md +83 -0
  75. package/docs/reference/authoring-surface/file.md +122 -0
  76. package/docs/reference/authoring-surface/frompick.md +277 -0
  77. package/docs/reference/authoring-surface/http.md +159 -0
  78. package/docs/reference/authoring-surface/isfailure.md +58 -0
  79. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  80. package/docs/reference/authoring-surface/open.md +74 -0
  81. package/docs/reference/authoring-surface/openbody.md +86 -0
  82. package/docs/reference/authoring-surface/paramsof.md +125 -0
  83. package/docs/reference/authoring-surface/pool.md +186 -0
  84. package/docs/reference/authoring-surface/push.md +112 -0
  85. package/docs/reference/authoring-surface/secret.md +118 -0
  86. package/docs/reference/authoring-surface/sentby.md +83 -0
  87. package/docs/reference/authoring-surface/source.md +51 -0
  88. package/docs/reference/authoring-surface/text.md +62 -0
  89. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  90. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  91. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  92. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  93. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  94. package/docs/reference/authoring-surface/type-arg.md +19 -0
  95. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  96. package/docs/reference/authoring-surface/type-binding.md +62 -0
  97. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  98. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  99. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  100. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  101. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  102. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  103. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  104. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  105. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  106. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  107. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  108. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  109. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  110. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  111. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  112. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  113. package/docs/reference/authoring-surface/type-childat.md +12 -0
  114. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  115. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  116. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  117. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  118. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  119. package/docs/reference/authoring-surface/type-customize.md +109 -0
  120. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  121. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  122. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  124. package/docs/reference/authoring-surface/type-duration.md +15 -0
  125. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  126. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  127. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  128. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  129. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  130. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  131. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  132. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  133. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  134. package/docs/reference/authoring-surface/type-failure.md +19 -0
  135. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  136. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  137. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  138. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  139. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  140. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  141. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  142. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  143. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  144. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  145. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  146. package/docs/reference/authoring-surface/type-lends.md +38 -0
  147. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  148. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  149. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  150. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  151. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  152. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  153. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  154. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  155. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  156. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  157. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  158. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  159. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  160. package/docs/reference/authoring-surface/type-openof.md +12 -0
  161. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  162. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  163. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  164. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  165. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  166. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  167. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  168. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  169. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  170. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  171. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  172. package/docs/reference/authoring-surface/type-reached.md +12 -0
  173. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  174. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  175. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  176. package/docs/reference/authoring-surface/type-secret.md +25 -0
  177. package/docs/reference/authoring-surface/type-sender.md +19 -0
  178. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  179. package/docs/reference/authoring-surface/type-size.md +85 -0
  180. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  181. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  182. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  183. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  184. package/docs/reference/authoring-surface/type-text.md +19 -0
  185. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  186. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  187. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  188. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  189. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  190. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  191. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  192. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  193. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  194. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  195. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  196. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  197. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  198. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  199. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  200. package/docs/reference/authoring-surface/workspace.md +259 -0
  201. package/docs/reference/authoring-surface.md +197 -0
  202. package/docs/reference/cli.md +433 -0
  203. package/docs/reference/config.md +756 -0
  204. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  205. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  206. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  207. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  208. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  209. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  210. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  211. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  212. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  213. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  214. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  215. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  216. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  217. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  218. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  219. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  220. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  221. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  222. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  223. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  224. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  225. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  226. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  227. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  228. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  229. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  230. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  231. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  232. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  233. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  234. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  235. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  236. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  237. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  238. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  239. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  240. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  241. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  242. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  243. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  244. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  245. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  246. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  247. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  248. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  249. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  250. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  251. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  252. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  253. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  254. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  255. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  256. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  257. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  258. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  259. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  260. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  261. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  262. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  263. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  264. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  265. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  266. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  267. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  268. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  269. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  270. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  271. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  272. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  273. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  274. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  275. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  276. package/docs/reference/decisions.md +83 -0
  277. package/docs/reference/glossary.md +648 -0
  278. package/docs/reference/http-api.md +1348 -0
  279. package/docs/reference/machines.md +190 -0
  280. package/docs/reference/mechanism-events.md +72 -0
  281. package/docs/why.md +206 -0
  282. package/package.json +6 -4
  283. package/src/actor.ts +976 -61
  284. package/src/agent.ts +114 -38
  285. package/src/ambient.ts +4 -1
  286. package/src/assembly-checks.ts +347 -0
  287. package/src/assembly-tar.ts +494 -0
  288. package/src/assembly.ts +721 -0
  289. package/src/cause.ts +234 -0
  290. package/src/chart.ts +2182 -0
  291. package/src/charts.ts +748 -0
  292. package/src/config.ts +520 -162
  293. package/src/content-hash.ts +105 -0
  294. package/src/custodian.lua +6 -5
  295. package/src/custodian.ts +143 -39
  296. package/src/customize.ts +388 -55
  297. package/src/dispatch-effect.ts +945 -0
  298. package/src/door-compat.ts +217 -0
  299. package/src/durability.ts +11 -4
  300. package/src/effect-checks.ts +557 -0
  301. package/src/effect.ts +1202 -0
  302. package/src/effector-wire.ts +674 -0
  303. package/src/fail.ts +209 -0
  304. package/src/fingerprint.ts +4 -3
  305. package/src/gate.ts +8 -0
  306. package/src/git-clone.ts +65 -0
  307. package/src/git-push.ts +336 -0
  308. package/src/harness-client.ts +764 -48
  309. package/src/held-secrets.ts +82 -26
  310. package/src/holds.ts +156 -0
  311. package/src/http-effect.ts +1423 -0
  312. package/src/http.ts +916 -113
  313. package/src/images.ts +47 -21
  314. package/src/index.ts +62 -43
  315. package/src/instance.ts +505 -59
  316. package/src/internal.ts +84 -0
  317. package/src/kube-client.ts +333 -0
  318. package/src/machine-doc.ts +106 -6
  319. package/src/mechanism-routes.ts +168 -0
  320. package/src/model-keys.generated.ts +53 -0
  321. package/src/model-keys.ts +18 -58
  322. package/src/mount-digest.ts +53 -0
  323. package/src/names.ts +66 -8
  324. package/src/notices.ts +141 -0
  325. package/src/open-body.ts +1580 -0
  326. package/src/open-effect.ts +217 -0
  327. package/src/open.ts +59 -6
  328. package/src/parts.ts +709 -41
  329. package/src/party.ts +534 -0
  330. package/src/pick.ts +761 -0
  331. package/src/pool.ts +248 -37
  332. package/src/registration.ts +382 -19
  333. package/src/repo-fetch.ts +60 -85
  334. package/src/repos.ts +65 -75
  335. package/src/rest.ts +133 -0
  336. package/src/run-host.ts +2383 -115
  337. package/src/run-output.ts +38 -0
  338. package/src/sandbox-effect.ts +1126 -0
  339. package/src/sandbox-effector.ts +111 -0
  340. package/src/sandbox-kube.ts +1721 -0
  341. package/src/sandbox-watch.ts +260 -0
  342. package/src/secret-placement.ts +529 -0
  343. package/src/server.ts +284 -84
  344. package/src/setup.ts +819 -117
  345. package/src/size.ts +454 -0
  346. package/src/snapshot-store.ts +115 -20
  347. package/src/stub-harness.ts +49 -16
  348. package/src/sync-client.ts +54 -0
  349. package/src/timers.ts +191 -0
  350. package/src/tokens.ts +35 -4
  351. package/src/vocabulary.ts +135 -15
  352. package/src/wire.ts +238 -10
  353. package/src/workspace.ts +1195 -338
  354. package/src/sandbox-kubectl.ts +0 -1108
package/src/customize.ts CHANGED
@@ -9,24 +9,31 @@
9
9
  // `.provide()`, nothing to learn (ADR-0015's rule, restated by ADR-0049 — a `.with()` method and
10
10
  // a callable-machine hybrid were both rejected there).
11
11
  //
12
- // Three moves, and each is the smallest one that works:
12
+ // Four moves, and each is the smallest one that works:
13
13
  //
14
14
  // - An AGENT override is xstate's own `provide`: `machine.provide({ actors: { coder:
15
15
  // agent({ ...stock, ...override }) } })`. The slot already IS the Agent (ADR-0049), so
16
16
  // retuning one is substituting a slot's logic, which is what `provide` is for.
17
+ // - An EFFECT binding is the same substitution (ADR-0054): an Effect whose url or argv the
18
+ // package left `open`, or whose auth, header or env names `secret(open)`, carries a binder,
19
+ // which builds the Effect again with the composer's parts, so the bound slot is checked as a
20
+ // written one is. Once — a bound part is fixed from then on.
17
21
  // - A CHILD override is the same call one level down, recursing. Each level is exactly the
18
22
  // one-level reach ADR-0015 found `provide` has — held by the composer who owns the child
19
23
  // object, never host-side injection into somebody else's Machine.
20
- // - The IMAGE and the REPOS are the parts `provide` cannot carry: xstate copies implementations
24
+ // - The IMAGE, the REPOS and the SIZE are the parts `provide` cannot carry: xstate copies implementations
21
25
  // and passes the CONFIG through by reference, and every part a Machine carries is keyed on
22
26
  // that config (parts.ts, vocabulary.ts). A new image or a bound slot therefore needs a new key
23
27
  // — so those fields clone the wrapper's config and rebuild it with the same implementations,
24
- // re-attaching what the original carried.
28
+ // re-attaching what the original carried. The load warnings of a bound argv are the same
29
+ // kind of part: the Machine the author wrote is rebuilt the same way, carrying the rechecked
30
+ // warnings in place of the Open Machine's.
25
31
  //
26
32
  // jr2's wrappers are TRANSPARENT: `agents`/`actors` route through `workspace()`'s `body` and
27
33
  // `pool()`'s `worker`, so a consumer customizing a Workspace-rooted workflow never writes `body`
28
- // and never has to know that jr2 wrapped anything. `image`/`user`/`repos` travel the same chain in
29
- // the other direction — down to the `workspace()` that owns the seats and the slots.
34
+ // and never has to know that jr2 wrapped anything. `image`/`user`/`repos`/`resources` travel the same
35
+ // chain in the other direction — down to the `workspace()` that owns the seats, the slots and the
36
+ // Size.
30
37
  //
31
38
  // Both halves route on the wrapper's own RECORD of being one, never on a slot's spelling: the
32
39
  // runtime reads `wrapperBodyOf` and the types read `JR2Wrapper` (parts.ts), which the wrappers
@@ -38,7 +45,7 @@
38
45
  // its slot MAP Open (`repos: open`, ADR-0051) declared exactly this: every slot is the composer's
39
46
  // to name, so the map they write is the declaration, not an addition to one.
40
47
 
41
- import { StateMachine, type AnyStateMachine, type InputFrom, type ProvidedActor } from "xstate";
48
+ import { StateMachine, type AnyStateMachine, type InputFrom, type ProvidedActor, type UnknownActorLogic } from "xstate";
42
49
  import type { AgentLogic } from "./actor.ts";
43
50
  import { isAgent, type AgentDefinition } from "./agent.ts";
44
51
  import { agent } from "./harness-client.ts";
@@ -56,7 +63,30 @@ import {
56
63
  type RepoSlot,
57
64
  type SandboxParts,
58
65
  } from "./parts.ts";
59
- import { attachInputSchema, attachVocabulary, inputSchemaOf, vocabularyOf } from "./vocabulary.ts";
66
+ import { assertSize, assertUserSeat, layerSize, type Size, type UserContainer } from "./size.ts";
67
+ import {
68
+ attachInputSchema,
69
+ attachLoadWarnings,
70
+ attachSeats,
71
+ attachVocabulary,
72
+ inputSchemaOf,
73
+ loadWarningsOf,
74
+ seatsOf,
75
+ vocabularyOf,
76
+ } from "./vocabulary.ts";
77
+ import { isEffect, type Arg } from "./effect.ts";
78
+ import { attachLent, copyOpenBody, lentIn, openBodiesOf, openBodyDeclarationOf } from "./open-body.ts";
79
+ import { effectChecks } from "./effect-checks.ts";
80
+ import type { NamedSecret, Text } from "./http-effect.ts";
81
+ import {
82
+ binderOf,
83
+ effectBindingShape,
84
+ openPartsOf,
85
+ openPartsText,
86
+ type EffectBinding,
87
+ type OpenEffect,
88
+ type OpenEffectPart,
89
+ } from "./open-effect.ts";
60
90
 
61
91
  // --- What a Machine declares, read at the TYPE level ---------------------------------------------
62
92
  // Everything below reads xstate's own `TActor` parameter — the `{ src, logic, id }` union
@@ -86,17 +116,61 @@ type SlotsOf<M extends AnyStateMachine> =
86
116
  ? TActor
87
117
  : never;
88
118
 
89
- /** The logic a Machine declares under one slot key. */
119
+ /**
120
+ * The logic a Machine declares under one slot key.
121
+ *
122
+ * @internal
123
+ */
90
124
  type LogicAt<M extends AnyStateMachine, K extends string> = Extract<SlotsOf<M>, { src: K }>["logic"];
91
125
 
92
- /** The Agent slots — told from every other slot by their logic, which no other actor kind has. */
126
+ /**
127
+ * The Agent slots — told from every other slot by their logic, which no other actor kind has.
128
+ *
129
+ * @internal
130
+ */
93
131
  type AgentSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: AgentLogic }>["src"];
94
132
 
133
+ /** The Effect slots with an Open part — told by the phantom `OpenEffect` their constructor's
134
+ * overload states for an `open` url or argv, or a `secret(open)` (open-effect.ts).
135
+ *
136
+ * @internal
137
+ */
138
+ type OpenEffectSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: OpenEffect<OpenEffectPart> }>["src"];
139
+
140
+ /** What one Open part binds to: a url, an argv, a secret's name, a dispatch's target — another
141
+ * Orchestrator's origin and the secret that holds its Instance token, together (ADR-0071). */
142
+ type PartBinding<P> = P extends "url"
143
+ ? { url: string | Text }
144
+ : P extends "argv"
145
+ ? { argv: readonly (string | Arg)[] }
146
+ : P extends "secret"
147
+ ? { secret: string }
148
+ : P extends "target"
149
+ ? { target: { url: string; token: NamedSecret } }
150
+ : never;
151
+
152
+ /** A union of object types as their intersection: one binding object with every key. */
153
+ type Intersected<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
154
+
155
+ /** What one Open Effect binds to: its url, its argv or its secret — for an Effect with two Open
156
+ * parts, one object with both keys, the intersection of their bindings (ADR-0054).
157
+ *
158
+ * @internal
159
+ */
160
+ type BindingOf<L> = L extends OpenEffect<infer P> ? Intersected<PartBinding<P>> : never;
161
+
95
162
  /** The child-Machine slots: the slots composition reaches (ADR-0049 — a nested Machine is an
96
- * ordinary `invoke` of an ordinary slot). */
163
+ * ordinary `invoke` of an ordinary slot).
164
+ *
165
+ * @internal
166
+ */
97
167
  type ChildSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: AnyStateMachine }>["src"];
98
168
 
99
- /** The child Machine under one slot key, constrained so `Customize` may recurse into it. */
169
+ /**
170
+ * The child Machine under one slot key, constrained so `Customize` may recurse into it.
171
+ *
172
+ * @internal
173
+ */
100
174
  type ChildAt<M extends AnyStateMachine, K extends string> = Extract<LogicAt<M, K>, AnyStateMachine>;
101
175
 
102
176
  /**
@@ -110,6 +184,7 @@ type ChildAt<M extends AnyStateMachine, K extends string> = Extract<LogicAt<M, K
110
184
  // Each step is taken on the wrapper's MARKER (`JR2Wrapper`, parts.ts), never on a slot's spelling —
111
185
  // an author is free to name a slot `body` or `worker`, and routing through it because of the name
112
186
  // would offer the Agents of a Machine the composer never named.
187
+ /** @internal */
113
188
  type Reached<M extends AnyStateMachine> = M extends JR2Wrapper<infer TBody> ? Reached<TBody> : M;
114
189
 
115
190
  /**
@@ -117,89 +192,216 @@ type Reached<M extends AnyStateMachine> = M extends JR2Wrapper<infer TBody> ? Re
117
192
  * {@link Reached}: down through the wrappers to the first Machine that composes a Sandbox, which
118
193
  * is the one whose `JR2Repos` marker says which slots exist (ADR-0051). `never` when no wrapper on
119
194
  * the chain composes one, which is what makes `repos` unavailable there rather than `{}`.
195
+ *
196
+ * @internal
120
197
  */
121
198
  type WorkspaceOf<M extends AnyStateMachine> =
122
199
  M extends JR2Repos<any> ? M : M extends JR2Wrapper<infer TBody> ? WorkspaceOf<TBody> : never;
123
200
 
124
201
  /** The Repo Slot keys the reached `workspace()` declared. The `never` case is tested by itself,
125
202
  * because `never extends JR2Repos<infer TSlots>` is true with nothing to infer from — and an
126
- * uninferred `TSlots` widens to `string`, which would offer every key exactly where there are none. */
203
+ * uninferred `TSlots` widens to `string`, which would offer every key exactly where there are none.
204
+ *
205
+ * @internal
206
+ */
127
207
  type RepoSlotsOf<M extends AnyStateMachine> = [WorkspaceOf<M>] extends [never]
128
208
  ? never
129
209
  : WorkspaceOf<M> extends JR2Repos<infer TSlots>
130
210
  ? TSlots
131
211
  : never;
132
212
 
213
+ // What may be retuned on one Machine, mirroring the DECLARATION's own shape and recursively
214
+ // partial (ADR-0049): every key optional, every Agent override a partial definition, every child a
215
+ // `Customize` of that child. `image`/`user` are the `workspace()` options (ADR-0037, ADR-0005) and
216
+ // apply to the wrapper the chain reaches; a Machine that composes no Sandbox refuses them at build
217
+ // time, because there is nothing for them to name.
133
218
  /**
134
- * What may be retuned on one Machine, mirroring the DECLARATION's own shape and recursively
135
- * partial (ADR-0049): every key optional, every Agent override a partial definition, every child
136
- * a `Customize` of that child.
219
+ * The parts of one Machine that `customize()` can retune: its Agents, its Open Effects, the
220
+ * Machines it composes, and the Sandbox of its `workspace()`. The type comes from the Machine
221
+ * itself, so it offers only the parts that the Machine declares. A key for a part the Machine does
222
+ * not declare does not compile. The `agents`, `effects`, `actors` and `repos` keys take no value
223
+ * when the Machine has no such part. `image`, `user` and `resources` compile on every Machine, and
224
+ * `customize()` refuses them when the Machine composes no `workspace()`.
137
225
  *
138
- * `image`/`user` are the `workspace()` options (ADR-0037, ADR-0005) and apply to the wrapper the
139
- * chain reaches; a Machine that composes no Sandbox refuses them at build time, because there is
140
- * nothing for them to name.
226
+ * Every key is optional. Write only what you change.
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * import { customize, type Customize } from "@jr2/orchestrator";
231
+ * import { task } from "@jr2/machines";
232
+ *
233
+ * const parts: Customize<typeof task> = {
234
+ * repos: { app: { url: "https://github.com/you/app.git", ref: "main" } },
235
+ * agents: { coder: { model: "anthropic/claude-haiku-5-5", thinkingLevel: "high" } },
236
+ * };
237
+ *
238
+ * export const machine = customize(task, parts);
239
+ * ```
240
+ *
241
+ * @see docs/guides/bind-open-parts.md
242
+ * @see docs/concepts/composition.md
243
+ * @see docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md
141
244
  */
142
245
  export type Customize<M extends AnyStateMachine> = {
143
- /** Retune an Agent this Machine carries: the override is layered over the stock definition, so
144
- * `{ model }` alone keeps the instructions the author wrote — and BINDS the model when the
145
- * author left it Open (ADR-0054), which is the common consumer line for a packaged Machine. The
146
- * override is a `Partial<AgentDefinition>`, never a declaration: a composer binds, and binding
147
- * to `open` again is not a thing anyone means. A Machine carrying no Agent takes `never` rather
148
- * than `{}`: an empty object type accepts any literal, which would make the one case with
149
- * nothing to name the one case with nothing checked. */
246
+ // `{ model }` alone BINDS the model when the author left it Open (ADR-0054), which is the common
247
+ // consumer line for a packaged Machine. The override is a `Partial<AgentDefinition>`, never a
248
+ // declaration: a composer binds, and binding to `open` again is not a thing anyone means
249
+ // (ADR-0054). A Machine carrying no Agent takes `never` rather than `{}`: an empty object type
250
+ // accepts any literal, which would make the one case with nothing to name the one case with
251
+ // nothing checked.
252
+ /** Retune the Agents that this Machine carries, by slot name: `{ coder: { model } }`. Each
253
+ * override goes over the definition that the author wrote, field by field, so `{ model }` alone
254
+ * keeps the author's instructions. When the author left the model Open, this is where you bind
255
+ * it. Allowed fields: the fields of an `agent()` definition. At run time, `customize()` refuses a
256
+ * slot that holds no Agent: `customize(): machine "<id>" carries no Agent slot "<slot>" — its
257
+ * Agents are: <slots>`. */
150
258
  agents?: [AgentSlots<Reached<M>>] extends [never]
151
259
  ? never
152
260
  : { [K in AgentSlots<Reached<M>>]?: Partial<AgentDefinition> };
153
- /** Retune a Machine this one composes — the same shape, one level down. */
261
+ // `{ url }` is for an http Effect whatever its body (ADR-0054); `{ target }` binds a dispatch's
262
+ // url and token as one (ADR-0071). Bound once, here — a value, never a mapper over the door, so
263
+ // the host is fixed before any run (ADR-0054). Only an Effect with an Open part is offered: a
264
+ // part its author fixed stays fixed. A Machine with none takes `never`, as `agents` does.
265
+ /** Bind the Open parts of this Machine's Effects, by slot name: `{ url }` for an `http` Effect
266
+ * whose url is Open, `{ argv }` for a `command` whose argv is Open, `{ secret }` for an Effect
267
+ * that names `secret(open)`, `{ target: { url, token: secret("<name>") } }` for a `dispatch`
268
+ * whose target is Open. For an Effect with two Open parts, one object holds both keys, and
269
+ * a binding that leaves one out does not compile. Only Effects with an Open part are offered.
270
+ * See {@link EffectBinding} for the values and the refusals. */
271
+ effects?: [OpenEffectSlots<Reached<M>>] extends [never]
272
+ ? never
273
+ : { [K in OpenEffectSlots<Reached<M>>]?: BindingOf<LogicAt<Reached<M>, K>> };
274
+ /** Retune a Machine that this Machine composes, by slot name. The value has the same shape as
275
+ * these parts, one level down. At run time, `customize()` refuses a slot that holds no Machine:
276
+ * `customize(): machine "<id>" composes no Machine under "<slot>"`. */
154
277
  actors?: [ChildSlots<Reached<M>>] extends [never]
155
278
  ? never
156
279
  : { [K in ChildSlots<Reached<M>>]?: Customize<ChildAt<Reached<M>, K>> };
157
- /** The Sandbox Image (ADR-0037): a `file:` URL to a docker context this module ships, or a
158
- * registry ref. */
280
+ // The Sandbox Image (ADR-0037).
281
+ /** The Sandbox Image: a `file:` URL to a docker context that your module ships
282
+ * (`import.meta.resolve("./image")`), which `jr2 up` builds, or a registry ref. It replaces the
283
+ * image of the `workspace()`. When the Machine composes no `workspace()`, `customize()` refuses it
284
+ * at run time. */
159
285
  image?: string;
160
- /** The User Container's image (ADR-0005), in the same two shapes. */
161
- user?: string;
162
- /** Bind the Repo Slots the reached `workspace()` declared (ADR-0051) — an open slot to a url,
163
- * or a bound or per-run one to a different Binding; any of the three forms, so a consumer can
164
- * also bind a mapper over the wrapper's door. A slot the Machine does not declare is a compile
165
- * error, and a Machine that composes no Sandbox takes `never`, as `agents` does. A `workspace()`
166
- * that declared its map Open takes any keys — they are the composer's words, in the order the
167
- * Machine documents — and at least one, checked at the call. */
286
+ // The User Container (ADR-0005), in the same two shapes, or `{ image, resources }` with its split
287
+ // of the Size (ADR-0060). Replaces the seat whole — split included.
288
+ /** The User Container: its image, as a `file:` URL or a registry ref, or a
289
+ * {@link UserContainer} that also gives its part of the Size. It replaces the whole User
290
+ * Container, its part of the Size included. When the Machine composes no `workspace()`,
291
+ * `customize()` refuses it at run time. */
292
+ user?: string | UserContainer;
293
+ // The Size (ADR-0060), layered over the `workspace()`'s own: the composer's Repo may be bigger
294
+ // than the author's.
295
+ /** The Size, layered field by field over the Size of the `workspace()`: `{ limits: { memory } }`
296
+ * keeps the author's cpu. Only `limits.memory` and `limits.cpu`. See {@link Size}. Only for a
297
+ * Machine that composes a `workspace()`. */
298
+ resources?: Size;
299
+ // Any of the three slot forms, so a consumer can also bind a mapper over the wrapper's door
300
+ // (ADR-0051). A Machine that composes no Sandbox takes `never`, as `agents` does. A
301
+ // `workspace()` that declared its map Open takes any keys — they are the composer's words, in the
302
+ // order the Machine documents — and at least one, checked at the call.
303
+ /** Bind the Repo Slots of the `workspace()`, by slot name. Each value is one of these:
304
+ *
305
+ * - a url
306
+ * - a Binding `{ url, ref? }`
307
+ * - a function `({ input }) => url | { url, ref? }` over the run input
308
+ * - `open`, to leave the slot for the next composer to bind
309
+ *
310
+ * You can bind an Open slot, or give a bound or per-run slot a different Binding. A slot that
311
+ * the Machine does not declare does not compile. Some Machines, such as `task`, leave their
312
+ * whole slot map Open. Then you name every slot, and at least one. The keys are your names for
313
+ * the Repos, and the Machine says what their order means. */
168
314
  repos?: [RepoSlotsOf<M>] extends [never] ? never : { [K in RepoSlotsOf<M>]?: RepoSlot<InputFrom<WorkspaceOf<M>>> };
169
315
  };
170
316
 
171
317
  /** The same shape with the types erased — what the implementation walks. */
172
318
  type LooseParts = {
173
319
  agents?: Record<string, Partial<AgentDefinition> | undefined>;
320
+ effects?: Record<string, EffectBinding | undefined>;
174
321
  actors?: Record<string, LooseParts | undefined>;
175
322
  image?: string;
176
- user?: string;
323
+ user?: string | UserContainer;
177
324
  repos?: Record<string, RepoSlot | undefined>;
325
+ resources?: Size;
178
326
  };
179
327
 
328
+ // Returns a NEW Machine of the same type — the original is untouched, so two customizations of one
329
+ // import are two independent Machines (ADR-0049's `deep`/`quick`), and a run of either carries what
330
+ // it was given.
180
331
  /**
181
- * Retune the parts a Machine carries, returning a NEW Machine of the same type — the original is
182
- * untouched, so two customizations of one import are two independent Machines (ADR-0049's
183
- * `deep`/`quick`), and a run of either carries what it was given.
332
+ * Retune the parts that a Machine carries, without a change to the module that exports it. Use it
333
+ * where you register a Machine as a Workflow. It binds the parts that a packaged Machine leaves
334
+ * Open: its Repo Slots, an Agent's model, or an Effect's url, argv or secret. It also changes the
335
+ * parts that its author fixed: an Agent's instructions, the Sandbox Image, or the Size.
336
+ *
337
+ * It gives back a new Machine of the same type. The original does not change, so two
338
+ * `customize()` calls on one import give two Machines. Only the parts the Machine declares can be
339
+ * retuned. You cannot add a part: a Machine that needs another Agent is a new Machine. The types
340
+ * refuse most mistakes when you compile. At run time, `customize()` also throws:
341
+ *
342
+ * - `customize(): machine "<id>" carries no Agent slot "<slot>" — its Agents are: <slots>`
343
+ * - `customize(): machine "<id>" composes no Machine under "<slot>" — the Machines it composes are: <slots>`
344
+ * - `customize(): machine "<id>" carries no Effect slot "<slot>" — its Effects are: <slots>`
345
+ * - `customize(): machine "<id>" declares no Repo Slot "<slot>" — its slots are: <slots>`
346
+ * - `customize(): machine "<id>" declares its Repo Slots open as a map — name at least one`
347
+ * - `` customize(): `repos` say what a SANDBOX is made of, how big it is and which Repos it
348
+ * attaches, and machine "<id>" composes none `` (for `resources`, the text starts
349
+ * `` customize(): `resources` ``; for `image` or `user`, it starts `` customize(): `image`/`user` ``)
350
+ * - `customize(): machine "<id>": <errors>`, when an Effect argv that you bind fails the checks
351
+ * that the Machine passed when it was built
352
+ * - the refusals of {@link EffectBinding}, {@link RepoSlot}, {@link Size} and
353
+ * {@link UserContainer}, which start with `customize()`
354
+ *
355
+ * @param machine - The Machine to retune: one you import from a package, or one you wrote. When
356
+ * its root is a `workspace()` or a `pool()`, `customize()` reaches through to the Machine inside,
357
+ * so you never name the wrapper's own slot.
358
+ * @param parts - The parts to change. See {@link Customize}.
359
+ * @returns A new Machine of the same type, with the parts changed.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * import { customize } from "@jr2/orchestrator";
364
+ * import { task } from "@jr2/machines";
365
+ *
366
+ * export const machine = customize(task, {
367
+ * repos: { app: { url: "https://github.com/you/app.git" } },
368
+ * agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
369
+ * resources: { limits: { memory: "4Gi" } },
370
+ * });
371
+ * ```
372
+ *
373
+ * @see docs/guides/bind-open-parts.md
374
+ * @see docs/concepts/composition.md
375
+ * @see docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md
376
+ * @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
184
377
  */
185
378
  export function customize<M extends AnyStateMachine>(machine: M, parts: Customize<M>): M {
186
- const { agents, actors, image, user, repos } = parts as LooseParts;
379
+ const { agents, effects, actors, image, user, repos, resources } = parts as LooseParts;
187
380
  let out: AnyStateMachine = machine;
188
381
  // Order matters in one direction only: the Sandbox seats and slots REBUILD the wrapper, so they
189
382
  // go last and carry the retuned implementations with them.
190
- if (agents || actors) out = retune(out, { agents, actors });
383
+ if (agents || effects || actors) out = retune(out, { agents, effects, actors });
191
384
  const seats: Seats = {
192
385
  ...(image !== undefined ? { image } : {}),
193
386
  ...(user !== undefined ? { user } : {}),
194
387
  ...(repos !== undefined ? { repos } : {}),
388
+ ...(resources !== undefined ? { resources } : {}),
195
389
  };
196
- if (seats.image !== undefined || seats.user !== undefined || seats.repos !== undefined) out = reseat(out, seats);
390
+ // Refused where the composer wrote them, by field (ADR-0060), before any wrapper is rebuilt.
391
+ if (seats.user !== undefined) assertUserSeat("customize()", seats.user);
392
+ if (seats.resources !== undefined) assertSize("customize(): resources", seats.resources);
393
+ if (Object.keys(seats).length > 0) out = reseat(out, seats);
197
394
  return out as M;
198
395
  }
199
396
 
200
- /** What `reseat` carries down the chain: the two image seats and the slot overrides, each present
201
- * only when the composer named it. */
202
- type Seats = { image?: string; user?: string; repos?: Record<string, RepoSlot | undefined> };
397
+ /** What `reseat` carries down the chain: the two image seats, the slot overrides and the Size, each
398
+ * present only when the composer named it. */
399
+ type Seats = {
400
+ image?: string;
401
+ user?: string | UserContainer;
402
+ repos?: Record<string, RepoSlot | undefined>;
403
+ resources?: Size;
404
+ };
203
405
 
204
406
  /** The slot keys whose logic answers `pred` — what an error names, so the message is the
205
407
  * Machine's own declaration rather than advice. */
@@ -246,6 +448,8 @@ function retune(machine: AnyStateMachine, parts: LooseParts): AnyStateMachine {
246
448
  // walk reports it again (ADR-0054).
247
449
  actors[name] = agent({ ...logic.definition, ...override });
248
450
  }
451
+ const bound = Object.entries(parts.effects ?? {}).filter(([, b]) => b !== undefined);
452
+ for (const [name, binding] of bound) actors[name] = bindEffect(machine, name, binding!);
249
453
  for (const [name, childParts] of Object.entries(parts.actors ?? {})) {
250
454
  if (!childParts) continue;
251
455
  const child = asMachine((machine.implementations.actors as Record<string, unknown>)[name]);
@@ -257,15 +461,128 @@ function retune(machine: AnyStateMachine, parts: LooseParts): AnyStateMachine {
257
461
  }
258
462
  actors[name] = customize(child, childParts as Customize<AnyStateMachine>);
259
463
  }
260
- return substitute(machine, actors);
464
+ // An Open body lends by slot name (ADR-0070), and its stamped lent set must be the retuned slots,
465
+ // not the import's: each body is copied (the import keeps its own stamp) and stamped again over
466
+ // the retuned actors.
467
+ if (Object.keys(actors).length > 0) {
468
+ const impl = machine.implementations as Parameters<typeof lentIn>[0];
469
+ const merged = { ...impl, actors: { ...(impl.actors ?? {}), ...actors } };
470
+ for (const { slot, logic } of openBodiesOf(machine)) {
471
+ const copy = copyOpenBody(logic);
472
+ attachLent(copy, lentIn(merged, openBodyDeclarationOf(logic)));
473
+ actors[slot] = copy;
474
+ }
475
+ }
476
+ const out = substitute(machine, actors);
477
+ if (bound.length === 0) return out;
478
+ // The bound Machine's warnings differ from the Open one's, and `provide` kept the Open one's
479
+ // config, which is what every attachment keys on (ADR-0049) — so it gets a config of its own,
480
+ // carrying what the original carried and the rechecked warnings, and any later `provide()` of
481
+ // it (a run's, ADR-0015) keeps them.
482
+ const warnings = recheck(machine, out);
483
+ const rebuilt = cloneConfig(out);
484
+ attachLoadWarnings(rebuilt, warnings);
485
+ return rebuilt;
486
+ }
487
+
488
+ /**
489
+ * Bind one Effect's Open parts (ADR-0054): the slot must be an Effect, each key of the binding one
490
+ * of its Open parts, and each value the value that part takes — a url (a string, or a `text` with a
491
+ * secret in its path), an argv, a secret's name, or a dispatch's target (ADR-0071). A key left out leaves that part Open. The binder
492
+ * builds the Effect again, whole, so the bound one meets every check its constructor runs: the url's
493
+ * literal host, the argv's entries, the secret's name. The compiler's check of the
494
+ * invoke's input against the argv's slots does not reach it (an Open argv's input is one string per
495
+ * key): a slot the invoke does not supply faults when the Effect starts (ADR-0054).
496
+ */
497
+ function bindEffect(machine: AnyStateMachine, name: string, binding: EffectBinding): unknown {
498
+ const logic = (machine.implementations.actors as Record<string, unknown>)[name];
499
+ if (!isEffect(logic)) {
500
+ throw new Error(
501
+ `customize(): machine "${machine.id}" carries no Effect slot "${name}" — its Effects are: ` +
502
+ `${listed(slotsWhere(machine, isEffect))} (ADR-0067). Only declared parts can be bound.`,
503
+ );
504
+ }
505
+ const parts = openPartsOf(logic.effect);
506
+ const binder = binderOf(logic);
507
+ if (parts.length === 0 || binder === undefined) {
508
+ throw new Error(
509
+ `customize(): the ${logic.effect.kind} Effect "${name}" of machine "${machine.id}" leaves nothing Open — its ` +
510
+ "parts are fixed in its definition, and only a part a package left `open` is bound, once (ADR-0054).",
511
+ );
512
+ }
513
+ // One object binds them, each key an Open part; a key it leaves out leaves that part Open, and
514
+ // the walk reports it again (ADR-0054).
515
+ const given = typeof binding === "object" && binding !== null ? (binding as Record<string, unknown>) : {};
516
+ const keys = Object.keys(given).filter((k) => given[k] !== undefined);
517
+ if (keys.length === 0 || keys.some((k) => !parts.includes(k as OpenEffectPart))) {
518
+ throw new Error(
519
+ `customize(): the ${logic.effect.kind} Effect "${name}" leaves its ${openPartsText(parts)} Open — bind it ` +
520
+ `with ${effectBindingShape(parts)} (ADR-0054)`,
521
+ );
522
+ }
523
+ for (const part of keys) {
524
+ const value = given[part];
525
+ if (value === open || typeof value === "function") {
526
+ throw new Error(
527
+ `customize(): the ${logic.effect.kind} Effect "${name}"'s ${part} is bound once, to a value — not ` +
528
+ `${value === open ? "`open` again" : "a function of the run"}: no run chooses an Effect's ${part} (ADR-0054)`,
529
+ );
530
+ }
531
+ if (part === "target") {
532
+ // The whole target is the Open part (ADR-0071): an origin and a named secret, bound together.
533
+ // The dispatch's own constructor checks the url and the name when the binder rebuilds it.
534
+ const target = value as { url?: unknown; token?: unknown } | null;
535
+ if (typeof target !== "object" || target === null || typeof target.url !== "string" || !isSecret(target.token)) {
536
+ throw new Error(
537
+ `customize(): the dispatch Effect "${name}"'s target is bound to another Orchestrator's origin and the ` +
538
+ `secret that holds its Instance token: ${effectBindingShape("target")} (ADR-0071)`,
539
+ );
540
+ }
541
+ }
542
+ if (part === "secret" && typeof value !== "string") {
543
+ throw new Error(
544
+ `customize(): the ${logic.effect.kind} Effect "${name}"'s secret is bound to a secret's name, a string — ` +
545
+ `the file the Effector reads it from: { secret: "<name>" } (ADR-0054, ADR-0068)`,
546
+ );
547
+ }
548
+ }
549
+ return binder(binding);
550
+ }
551
+
552
+ const isSecret = (value: unknown): boolean =>
553
+ typeof value === "object" && value !== null && typeof (value as { secret?: unknown }).secret === "string";
554
+
555
+ /**
556
+ * The load walk's Effect checks (ADR-0067), again, on a Machine whose Open argv this `customize()`
557
+ * bound: at build its argv slots were unknown and every pick param counted as read; now the bound
558
+ * argv names them. A param it does not read is refused as at build, and a warning the walk gives
559
+ * for the bound argv (a param only an action the probe does not run may read) replaces the
560
+ * findings it gave for the Open one: the result is the warnings the same Machine written with that
561
+ * argv carries, which the caller attaches to the bound Machine's own config.
562
+ */
563
+ function recheck(before: AnyStateMachine, machine: AnyStateMachine): string[] {
564
+ const defs = vocabularyOf(machine);
565
+ if (!defs) return loadWarningsOf(before);
566
+ const checks = (m: AnyStateMachine) =>
567
+ effectChecks(m, m.implementations.actors as Record<string, UnknownActorLogic>, defs);
568
+ const { errors, warnings } = checks(machine);
569
+ if (errors.length) throw new Error(`customize(): machine "${machine.id}": ${errors.join("; ")}`);
570
+ const unbound = new Set(checks(before).warnings);
571
+ const kept = loadWarningsOf(before).filter((warning) => !unbound.has(warning));
572
+ return [...new Set([...kept, ...warnings])];
261
573
  }
262
574
 
263
575
  /** The Sandbox seats and slots, applied to the `workspace()` the chain reaches. */
264
576
  function reseat(machine: AnyStateMachine, seats: Seats): AnyStateMachine {
265
577
  if (composesSandbox(machine)) {
266
- const { repos: override, ...images } = seats;
578
+ const { repos: override, resources, ...images } = seats;
267
579
  const parts = sandboxPartsOf(machine);
268
- if (parts.repos === open) return rebuild(machine, { ...parts, ...images, repos: nameSlots(machine, override) });
580
+ // The Size layers over the author's, field by field — a composer who raises memory keeps the
581
+ // author's cpu (ADR-0060).
582
+ const size = layerSize(parts.resources, resources);
583
+ const sized = size !== undefined ? { resources: size } : {};
584
+ if (parts.repos === open)
585
+ return rebuild(machine, { ...parts, ...images, ...sized, repos: nameSlots(machine, override) });
269
586
  const repos = { ...parts.repos };
270
587
  // Only DECLARED slots can be bound (ADR-0051): the Machine's own word for each Repo is the
271
588
  // key, and a key it never declared would attach a repository the body has no handle for.
@@ -285,15 +602,16 @@ function reseat(machine: AnyStateMachine, seats: Seats): AnyStateMachine {
285
602
  assertRepoSlot("customize()", slot, value);
286
603
  repos[slot] = value;
287
604
  }
288
- return rebuild(machine, { ...parts, ...images, repos });
605
+ return rebuild(machine, { ...parts, ...images, ...sized, repos });
289
606
  }
290
607
  const inner = bodyOf(machine);
291
608
  if (!inner) {
292
- const named = seats.repos !== undefined ? "`repos`" : "`image`/`user`";
609
+ const named =
610
+ seats.repos !== undefined ? "`repos`" : seats.resources !== undefined ? "`resources`" : "`image`/`user`";
293
611
  throw new Error(
294
- `customize(): ${named} say what a SANDBOX is made of and which Repos it attaches, and machine ` +
295
- `"${machine.id}" composes none — only a workspace() wrapper carries those seats and slots ` +
296
- "(ADR-0049, ADR-0037, ADR-0051).",
612
+ `customize(): ${named} say what a SANDBOX is made of, how big it is and which Repos it attaches, and ` +
613
+ `machine "${machine.id}" composes none — only a workspace() wrapper carries those seats, that Size ` +
614
+ "and those slots (ADR-0049, ADR-0037, ADR-0051, ADR-0060).",
297
615
  );
298
616
  }
299
617
  return substitute(machine, { [inner.slot]: reseat(inner.body, seats) });
@@ -336,13 +654,28 @@ function nameSlots(machine: AnyStateMachine, override: Seats["repos"]): SandboxP
336
654
  * fingerprint a restore compares (ADR-0030) — is bit-identical to the original's.
337
655
  */
338
656
  function rebuild(machine: AnyStateMachine, seats: SandboxParts): AnyStateMachine {
657
+ const clone = cloneConfig(machine);
658
+ attachSandboxParts(clone, seats);
659
+ return clone;
660
+ }
661
+
662
+ /**
663
+ * The Machine around a NEW config object — a shallow clone, so the shape and the fingerprint are
664
+ * the original's — with the same implementations, and every attachment the original carried
665
+ * re-attached under the new key (ADR-0049): the vocabulary, the door, the seats, the load
666
+ * warnings and a wrapper's body. What the caller attaches after is the clone's alone.
667
+ */
668
+ function cloneConfig(machine: AnyStateMachine): AnyStateMachine {
339
669
  const clone = new StateMachine({ ...machine.config }, machine.implementations) as AnyStateMachine;
340
670
  const vocabulary = vocabularyOf(machine);
341
671
  if (vocabulary) attachVocabulary(clone, vocabulary);
342
672
  const door = inputSchemaOf(machine);
343
673
  if (door) attachInputSchema(clone, door);
674
+ const seats = seatsOf(machine);
675
+ if (seats.length > 0) attachSeats(clone, seats);
676
+ const warnings = loadWarningsOf(machine);
677
+ if (warnings.length > 0) attachLoadWarnings(clone, warnings);
344
678
  const slot = wrapperBodyOf(machine);
345
679
  if (slot) attachWrapperBody(clone, slot);
346
- attachSandboxParts(clone, seats);
347
680
  return clone;
348
681
  }