@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/custodian.ts CHANGED
@@ -217,30 +217,72 @@ function meta(fields: Record<string, string>): Json {
217
217
  return { filter_metadata: { [LUA_FILTER]: fields } };
218
218
  }
219
219
 
220
- /** An upstream that never answered, said as the Custodian's own 502 (§5.4) — the host and Envoy's
221
- * reason, never a retry without verification. */
222
- function unreachable(target: string): Json {
220
+ /** Envoy's flags for a request it did not send to the target (ADR-0059): the connection failed, no
221
+ * host was healthy, the DNS lookup failed. */
222
+ export const NOT_SENT_FLAGS = ["UF", "UH", "DF"] as const;
223
+
224
+ /** Envoy's flags for a request that may have reached the target before the stream ended (ADR-0059):
225
+ * the target closed the connection or reset the stream, Envoy reset it, a protocol error on either
226
+ * side (a body framing error can come after the headers went). `UMSDR` (the maximum stream
227
+ * duration) ends a stream that was sent. `URX` (the retry limit) comes with the last attempt's own
228
+ * flag (`UF,URX`, `UC,URX`), so alone it says nothing of the send; the mapper for
229
+ * {@link NOT_SENT_FLAGS} comes first, so `UF,URX` still reads "could not be reached". These routes
230
+ * set no retry policy and no `max_stream_duration`, so Envoy sets neither flag on them; they keep
231
+ * the union equal to ORCHESTRATOR_UNREACHABLE's. */
232
+ export const MAY_HAVE_SENT_FLAGS = ["UC", "DPE", "UMSDR", "URX", "UPE", "UR", "LR"] as const;
233
+
234
+ /** One of the Custodian's own 502s for a target that gave no answer (§5.4): the target, what
235
+ * jr2 knows about the request, and Envoy's reason — never a retry without verification. */
236
+ function noAnswer(target: string, flags: readonly string[], words: string): Json {
223
237
  return {
224
- mappers: [
225
- {
226
- filter: { response_flag_filter: { flags: ["UF", "URX", "UH", "UC", "DF", "DPE", "UMSDR"] } },
227
- status_code: 502,
228
- body_format_override: {
229
- text_format_source: {
230
- inline_string: `jr2 custodian: ${target} could not be reached: %RESPONSE_CODE_DETAILS% %UPSTREAM_TRANSPORT_FAILURE_REASON%\n`,
231
- },
232
- },
238
+ filter: { response_flag_filter: { flags: [...flags] } },
239
+ status_code: 502,
240
+ body_format_override: {
241
+ text_format_source: {
242
+ inline_string: `jr2 custodian: ${target} ${words}: %RESPONSE_CODE_DETAILS% %UPSTREAM_TRANSPORT_FAILURE_REASON%\n`,
233
243
  },
234
- ],
244
+ },
235
245
  };
236
246
  }
237
247
 
248
+ const NOT_REACHED = "could not be reached";
249
+ const MAY_HAVE_REACHED = "did not answer; the request may have reached it";
250
+
251
+ /**
252
+ * An intercepted target that gave no answer (ADR-0059). Two mappers, because the words must be
253
+ * true: after a `NOT_SENT_FLAGS` failure the target did not get the request, and after a
254
+ * `MAY_HAVE_SENT_FLAGS` one it may have it — so an Agent that sends a POST again can write twice,
255
+ * and the text must not tell it the first one went nowhere. One reply can carry several flags,
256
+ * and Envoy uses the first mapper that matches, in order: the `NOT_SENT_FLAGS` mapper is first, so
257
+ * a reply with `UF`, `UH` or `DF` reads "could not be reached" whatever else it carries. Without
258
+ * the mappers, Envoy answers each of these flags itself (503 for most, 502 for `UPE`) with a body
259
+ * that names no target, which a caller can read as the target's answer. No caller reads the flags;
260
+ * the Orchestrator route's go to the Harness, which tells "never reached" from "not known"
261
+ * (ADR-0002).
262
+ */
263
+ function unreachable(target: string): Json {
264
+ return {
265
+ mappers: [noAnswer(target, NOT_SENT_FLAGS, NOT_REACHED), noAnswer(target, MAY_HAVE_SENT_FLAGS, MAY_HAVE_REACHED)],
266
+ };
267
+ }
268
+
269
+ /** The tunnel's 502 (ADR-0059): every flag, one text. Envoy answers a `CONNECT` with 200 only after
270
+ * its TCP connection to the target is up, and no byte of the Agent's request goes before that 200.
271
+ * So a 502 on the tunnel always comes before the request reached the target, whatever its flag. */
272
+ function tunnelUnreachable(target: string): Json {
273
+ return { mappers: [noAnswer(target, [...NOT_SENT_FLAGS, ...MAY_HAVE_SENT_FLAGS], NOT_REACHED)] };
274
+ }
275
+
238
276
  /** The Orchestrator unreachable, said so the Harness can tell it from an answer and retry the
239
277
  * surface read the way ADR-0042 asks — the one route on this pod that retries. */
240
278
  const ORCHESTRATOR_UNREACHABLE: Json = {
241
279
  mappers: [
242
280
  {
243
- filter: { response_flag_filter: { flags: ["UF", "URX", "UH", "UC", "DF", "DPE", "UMSDR"] } },
281
+ // Envoy's own answers for a stream it reset (`UPE`, `UR`, `LR`: 503, or 502 for `UPE`) are said
282
+ // here too, so the Harness reads them as "not known" and not as a stopping Orchestrator's 503
283
+ // (ADR-0002). One text for
284
+ // both lists: the Harness reads the flags, not the words.
285
+ filter: { response_flag_filter: { flags: [...NOT_SENT_FLAGS, ...MAY_HAVE_SENT_FLAGS] } },
244
286
  status_code: 502,
245
287
  headers_to_add: [
246
288
  { header: { key: "x-jr2-custodian", value: "unreachable" }, append_action: "OVERWRITE_IF_EXISTS_OR_ADD" },
@@ -333,7 +375,7 @@ function egressReplies(): Json {
333
375
  },
334
376
  },
335
377
  },
336
- ...(unreachable("%REQ(:AUTHORITY)%").mappers as Json[]),
378
+ ...(tunnelUnreachable("%REQ(:AUTHORITY)%").mappers as Json[]),
337
379
  ],
338
380
  };
339
381
  }
@@ -794,6 +836,12 @@ export type CustodianCompositionOptions = {
794
836
  /** This pod's Sandbox — the name the ask is addressed by (ADR-0053). Absent on the Instance
795
837
  * Harness, which mounts no Repo and answers an ask by saying so. */
796
838
  sandbox?: string;
839
+ /** The names of the Instance objects that this Custodian reads in place of the defaults. The
840
+ * defaults are the Instance's own: `pod`'s literal values (`heldSecretsSecret(pod)`),
841
+ * `jr2-held-tls`, `jr2-ca`, and the program in `jr2-held`.
842
+ * The check pod (ADR-0019) reads its own copies, so a check pod writes nothing that a running pod
843
+ * of the Instance reads. */
844
+ reads?: { literals: string; tls: string; trust: string; program: string };
797
845
  /** The Instance ships a `harness.caBundle` (ADR-0020). */
798
846
  caBundle: boolean;
799
847
  /** The Custodian container's resources (ADR-0060) — the placement's to choose: a Sandbox's fixed
@@ -837,6 +885,12 @@ const CUSTODIAN_SECURITY = {
837
885
  export function custodianComposition(manifest: HeldManifest, opts: CustodianCompositionOptions): CustodianComposition {
838
886
  const held = manifest.secrets.length > 0;
839
887
  const literal = manifest.secrets.filter((s) => s.source.kind === "literal").map((s) => s.name);
888
+ const {
889
+ literals = heldSecretsSecret(opts.pod),
890
+ tls = HELD_TLS_SECRET,
891
+ trust: trustMap = CA_CONFIGMAP,
892
+ program = HELD_CONFIGMAP,
893
+ } = opts.reads ?? {};
840
894
 
841
895
  const harnessEnv: HarnessEnvVar[] = [
842
896
  { name: "JR2_CUSTODIAN_URL", value: CUSTODIAN_URL },
@@ -870,9 +924,7 @@ export function custodianComposition(manifest: HeldManifest, opts: CustodianComp
870
924
  defaultMode: 0o440,
871
925
  sources: [
872
926
  { secret: { name: opts.token.secret, items: [{ key: opts.token.key, path: SANDBOX_TOKEN_NAME }] } },
873
- ...(literal.length
874
- ? [{ secret: { name: heldSecretsSecret(opts.pod), items: literal.map((n) => ({ key: n, path: n })) } }]
875
- : []),
927
+ ...(literal.length ? [{ secret: { name: literals, items: literal.map((n) => ({ key: n, path: n })) } }] : []),
876
928
  ...manifest.secrets.flatMap((s) =>
877
929
  s.source.kind === "secret"
878
930
  ? [{ secret: { name: s.source.secret, items: [{ key: s.source.key, path: s.name }] } }]
@@ -896,20 +948,20 @@ export function custodianComposition(manifest: HeldManifest, opts: CustodianComp
896
948
  ],
897
949
  },
898
950
  },
899
- { configMap: { name: HELD_CONFIGMAP, items: [{ key: CUSTODIAN_PROGRAM_KEY, path: CUSTODIAN_PROGRAM_KEY }] } },
951
+ { configMap: { name: program, items: [{ key: CUSTODIAN_PROGRAM_KEY, path: CUSTODIAN_PROGRAM_KEY }] } },
900
952
  ],
901
953
  },
902
954
  },
903
955
  ...(held
904
956
  ? [
905
- { name: "custodian-tls", secret: { secretName: HELD_TLS_SECRET, defaultMode: 0o440 } },
957
+ { name: "custodian-tls", secret: { secretName: tls, defaultMode: 0o440 } },
906
958
  {
907
959
  name: "custodian-trust",
908
- configMap: { name: CA_CONFIGMAP, items: [{ key: "upstream.crt", path: "upstream.crt" }] },
960
+ configMap: { name: trustMap, items: [{ key: "upstream.crt", path: "upstream.crt" }] },
909
961
  },
910
962
  ]
911
963
  : []),
912
- ...(trust ? [{ name: "ca", configMap: { name: CA_CONFIGMAP } }] : []),
964
+ ...(trust ? [{ name: "ca", configMap: { name: trustMap } }] : []),
913
965
  ];
914
966
 
915
967
  const custodianContainer: Container = {
package/src/customize.ts CHANGED
@@ -77,7 +77,7 @@ import {
77
77
  import { isEffect, type Arg } from "./effect.ts";
78
78
  import { attachLent, copyOpenBody, lentIn, openBodiesOf, openBodyDeclarationOf } from "./open-body.ts";
79
79
  import { effectChecks } from "./effect-checks.ts";
80
- import type { Text } from "./http-effect.ts";
80
+ import type { NamedSecret, Text } from "./http-effect.ts";
81
81
  import {
82
82
  binderOf,
83
83
  effectBindingShape,
@@ -116,37 +116,61 @@ type SlotsOf<M extends AnyStateMachine> =
116
116
  ? TActor
117
117
  : never;
118
118
 
119
- /** The logic a Machine declares under one slot key. */
119
+ /**
120
+ * The logic a Machine declares under one slot key.
121
+ *
122
+ * @internal
123
+ */
120
124
  type LogicAt<M extends AnyStateMachine, K extends string> = Extract<SlotsOf<M>, { src: K }>["logic"];
121
125
 
122
- /** 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
+ */
123
131
  type AgentSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: AgentLogic }>["src"];
124
132
 
125
133
  /** The Effect slots with an Open part — told by the phantom `OpenEffect` their constructor's
126
- * overload states for an `open` url or argv, or a `secret(open)` (open-effect.ts). */
134
+ * overload states for an `open` url or argv, or a `secret(open)` (open-effect.ts).
135
+ *
136
+ * @internal
137
+ */
127
138
  type OpenEffectSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: OpenEffect<OpenEffectPart> }>["src"];
128
139
 
129
- /** What one Open part binds to: a url, an argv, a secret's name. */
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). */
130
142
  type PartBinding<P> = P extends "url"
131
143
  ? { url: string | Text }
132
144
  : P extends "argv"
133
145
  ? { argv: readonly (string | Arg)[] }
134
146
  : P extends "secret"
135
147
  ? { secret: string }
136
- : never;
148
+ : P extends "target"
149
+ ? { target: { url: string; token: NamedSecret } }
150
+ : never;
137
151
 
138
152
  /** A union of object types as their intersection: one binding object with every key. */
139
153
  type Intersected<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
140
154
 
141
155
  /** What one Open Effect binds to: its url, its argv or its secret — for an Effect with two Open
142
- * parts, one object with both keys, the intersection of their bindings (ADR-0054). */
156
+ * parts, one object with both keys, the intersection of their bindings (ADR-0054).
157
+ *
158
+ * @internal
159
+ */
143
160
  type BindingOf<L> = L extends OpenEffect<infer P> ? Intersected<PartBinding<P>> : never;
144
161
 
145
162
  /** The child-Machine slots: the slots composition reaches (ADR-0049 — a nested Machine is an
146
- * ordinary `invoke` of an ordinary slot). */
163
+ * ordinary `invoke` of an ordinary slot).
164
+ *
165
+ * @internal
166
+ */
147
167
  type ChildSlots<M extends AnyStateMachine> = Extract<SlotsOf<M>, { logic: AnyStateMachine }>["src"];
148
168
 
149
- /** 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
+ */
150
174
  type ChildAt<M extends AnyStateMachine, K extends string> = Extract<LogicAt<M, K>, AnyStateMachine>;
151
175
 
152
176
  /**
@@ -160,6 +184,7 @@ type ChildAt<M extends AnyStateMachine, K extends string> = Extract<LogicAt<M, K
160
184
  // Each step is taken on the wrapper's MARKER (`JR2Wrapper`, parts.ts), never on a slot's spelling —
161
185
  // an author is free to name a slot `body` or `worker`, and routing through it because of the name
162
186
  // would offer the Agents of a Machine the composer never named.
187
+ /** @internal */
163
188
  type Reached<M extends AnyStateMachine> = M extends JR2Wrapper<infer TBody> ? Reached<TBody> : M;
164
189
 
165
190
  /**
@@ -167,67 +192,125 @@ type Reached<M extends AnyStateMachine> = M extends JR2Wrapper<infer TBody> ? Re
167
192
  * {@link Reached}: down through the wrappers to the first Machine that composes a Sandbox, which
168
193
  * is the one whose `JR2Repos` marker says which slots exist (ADR-0051). `never` when no wrapper on
169
194
  * the chain composes one, which is what makes `repos` unavailable there rather than `{}`.
195
+ *
196
+ * @internal
170
197
  */
171
198
  type WorkspaceOf<M extends AnyStateMachine> =
172
199
  M extends JR2Repos<any> ? M : M extends JR2Wrapper<infer TBody> ? WorkspaceOf<TBody> : never;
173
200
 
174
201
  /** The Repo Slot keys the reached `workspace()` declared. The `never` case is tested by itself,
175
202
  * because `never extends JR2Repos<infer TSlots>` is true with nothing to infer from — and an
176
- * 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
+ */
177
207
  type RepoSlotsOf<M extends AnyStateMachine> = [WorkspaceOf<M>] extends [never]
178
208
  ? never
179
209
  : WorkspaceOf<M> extends JR2Repos<infer TSlots>
180
210
  ? TSlots
181
211
  : never;
182
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.
183
218
  /**
184
- * What may be retuned on one Machine, mirroring the DECLARATION's own shape and recursively
185
- * partial (ADR-0049): every key optional, every Agent override a partial definition, every child
186
- * 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()`.
225
+ *
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
+ * };
187
237
  *
188
- * `image`/`user` are the `workspace()` options (ADR-0037, ADR-0005) and apply to the wrapper the
189
- * chain reaches; a Machine that composes no Sandbox refuses them at build time, because there is
190
- * nothing for them to name.
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
191
244
  */
192
245
  export type Customize<M extends AnyStateMachine> = {
193
- /** Retune an Agent this Machine carries: the override is layered over the stock definition, so
194
- * `{ model }` alone keeps the instructions the author wrote — and BINDS the model when the
195
- * author left it Open (ADR-0054), which is the common consumer line for a packaged Machine. The
196
- * override is a `Partial<AgentDefinition>`, never a declaration: a composer binds, and binding
197
- * to `open` again is not a thing anyone means. A Machine carrying no Agent takes `never` rather
198
- * than `{}`: an empty object type accepts any literal, which would make the one case with
199
- * 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>`. */
200
258
  agents?: [AgentSlots<Reached<M>>] extends [never]
201
259
  ? never
202
260
  : { [K in AgentSlots<Reached<M>>]?: Partial<AgentDefinition> };
203
- /** Bind an Effect's Open parts (ADR-0054): `{ url }` for an http Effect (whatever its body) whose url
204
- * the package left Open, `{ argv }` for a command whose argv it left Open, `{ secret }` for an
205
- * Effect that names `secret(open)` — one object with both keys for an Effect with two, so a binding
206
- * that leaves one out does not compile. Bound once, here — a value, never a mapper over the door,
207
- * so the host is fixed before any run. Only an Effect with an Open part is offered: a part its
208
- * author fixed stays fixed. A Machine with none takes `never`, as `agents` does. */
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. */
209
271
  effects?: [OpenEffectSlots<Reached<M>>] extends [never]
210
272
  ? never
211
273
  : { [K in OpenEffectSlots<Reached<M>>]?: BindingOf<LogicAt<Reached<M>, K>> };
212
- /** Retune a Machine this one composes — the same shape, one level down. */
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>"`. */
213
277
  actors?: [ChildSlots<Reached<M>>] extends [never]
214
278
  ? never
215
279
  : { [K in ChildSlots<Reached<M>>]?: Customize<ChildAt<Reached<M>, K>> };
216
- /** The Sandbox Image (ADR-0037): a `file:` URL to a docker context this module ships, or a
217
- * 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. */
218
285
  image?: string;
219
- /** The User Container (ADR-0005): its image in the same two shapes, or `{ image, resources }`
220
- * with its split of the Size (ADR-0060). Replaces the seat whole — split included. */
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. */
221
292
  user?: string | UserContainer;
222
- /** The Size (ADR-0060), layered field by field over the `workspace()`'s own: the composer's
223
- * Repo may be bigger than the author's. `limits.memory` and `limits.cpu`, nothing else. */
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()`. */
224
298
  resources?: Size;
225
- /** Bind the Repo Slots the reached `workspace()` declared (ADR-0051) — an open slot to a url,
226
- * or a bound or per-run one to a different Binding; any of the three forms, so a consumer can
227
- * also bind a mapper over the wrapper's door. A slot the Machine does not declare is a compile
228
- * error, and a Machine that composes no Sandbox takes `never`, as `agents` does. A `workspace()`
229
- * that declared its map Open takes any keys — they are the composer's words, in the order the
230
- * Machine documents — and at least one, checked at the call. */
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. */
231
314
  repos?: [RepoSlotsOf<M>] extends [never] ? never : { [K in RepoSlotsOf<M>]?: RepoSlot<InputFrom<WorkspaceOf<M>>> };
232
315
  };
233
316
 
@@ -242,10 +325,55 @@ type LooseParts = {
242
325
  resources?: Size;
243
326
  };
244
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.
245
331
  /**
246
- * Retune the parts a Machine carries, returning a NEW Machine of the same type — the original is
247
- * untouched, so two customizations of one import are two independent Machines (ADR-0049's
248
- * `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
249
377
  */
250
378
  export function customize<M extends AnyStateMachine>(machine: M, parts: Customize<M>): M {
251
379
  const { agents, effects, actors, image, user, repos, resources } = parts as LooseParts;
@@ -360,7 +488,7 @@ function retune(machine: AnyStateMachine, parts: LooseParts): AnyStateMachine {
360
488
  /**
361
489
  * Bind one Effect's Open parts (ADR-0054): the slot must be an Effect, each key of the binding one
362
490
  * of its Open parts, and each value the value that part takes — a url (a string, or a `text` with a
363
- * secret in its path), an argv, or a secret's name. A key left out leaves that part Open. The binder
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
364
492
  * builds the Effect again, whole, so the bound one meets every check its constructor runs: the url's
365
493
  * literal host, the argv's entries, the secret's name. The compiler's check of the
366
494
  * invoke's input against the argv's slots does not reach it (an Open argv's input is one string per
@@ -400,6 +528,17 @@ function bindEffect(machine: AnyStateMachine, name: string, binding: EffectBindi
400
528
  `${value === open ? "`open` again" : "a function of the run"}: no run chooses an Effect's ${part} (ADR-0054)`,
401
529
  );
402
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
+ }
403
542
  if (part === "secret" && typeof value !== "string") {
404
543
  throw new Error(
405
544
  `customize(): the ${logic.effect.kind} Effect "${name}"'s secret is bound to a secret's name, a string — ` +
@@ -410,6 +549,9 @@ function bindEffect(machine: AnyStateMachine, name: string, binding: EffectBindi
410
549
  return binder(binding);
411
550
  }
412
551
 
552
+ const isSecret = (value: unknown): boolean =>
553
+ typeof value === "object" && value !== null && typeof (value as { secret?: unknown }).secret === "string";
554
+
413
555
  /**
414
556
  * The load walk's Effect checks (ADR-0067), again, on a Machine whose Open argv this `customize()`
415
557
  * bound: at build its argv slots were unknown and every pick param counted as read; now the bound