@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/notices.ts CHANGED
@@ -13,8 +13,17 @@ import type { Notice } from "./wire.ts";
13
13
 
14
14
  /** Where an admission may hear notices: its continued conversation's base Instance ID (ADR-0057)
15
15
  * and its Workspace's Sandbox name. Either may be absent — a fresh Turn has no conversation to be
16
- * told about, and a Menu-only Agent works in no Workspace. */
17
- export type NoticeScope = { conversation?: string; workspace?: string };
16
+ * told about, and a Menu-only Agent works in no Workspace. `only`, when set, narrows the take to
17
+ * the notices raised by these sources: what a nudge or a wake carries (ADR-0062). `except` leaves
18
+ * out the notices raised by these: what a re-admission of the same Turn was told already;
19
+ * `exceptPrefix` every notice whose source starts so — all guard kills, when that is not known. */
20
+ export type NoticeScope = {
21
+ conversation?: string;
22
+ workspace?: string;
23
+ only?: readonly string[];
24
+ except?: readonly string[];
25
+ exceptPrefix?: string;
26
+ };
18
27
 
19
28
  /** One pending notice: the notice, and the conversation or Workspace it goes to. `source` names
20
29
  * the event that raised it, so the same event raised twice — two Turns lost to one kernel kill, a
@@ -26,7 +35,13 @@ export type PendingNotice = { id: string; notice: Notice; to: string; source?: s
26
35
  export type NoticeLedgerState = { pending: PendingNotice[]; seen: string[] };
27
36
 
28
37
  /** What one admission takes: the notices it carries, and their ids for the ledger write. */
29
- export type TakenNotices = { ids: string[]; notices: Notice[] };
38
+ export type TakenNotices = {
39
+ ids: string[];
40
+ notices: Notice[];
41
+ /** Every notice taken, with the source it was raised with — what `restore` puts back when no
42
+ * model heard the admission. */
43
+ raised?: Array<{ notice: Notice; source?: string }>;
44
+ };
30
45
 
31
46
  export class NoticeLedger {
32
47
  readonly #pending: PendingNotice[];
@@ -41,30 +56,68 @@ export class NoticeLedger {
41
56
  }
42
57
 
43
58
  /** Raise a notice for `to` (a conversation's base id, or a Sandbox name — the notice's own
44
- * `scope` says which). False when `source` was already raised, or the same notice is already
45
- * pending for `to`. */
46
- raise(notice: Notice, to: string, source?: string): boolean {
59
+ * `scope` says which). `older` are the sources of earlier events that this one tells the same as —
60
+ * a burst of guard kills, read at once: they are marked raised and give no notice of their own.
61
+ * Answers whether anything changed — a notice pending, or a source marked — so the caller saves
62
+ * only then. */
63
+ raise(notice: Notice, to: string, source?: string, older: readonly string[] = []): boolean {
64
+ let changed = false;
65
+ for (const seen of older) {
66
+ if (this.#seen.has(seen)) continue;
67
+ this.#seen.add(seen);
68
+ changed = true;
69
+ }
47
70
  if (source !== undefined) {
48
- if (this.#seen.has(source)) return false;
71
+ if (this.#seen.has(source)) return changed;
49
72
  this.#seen.add(source);
73
+ } else {
74
+ // A notice with no source is one notice with a pending twin that has none either. A notice
75
+ // WITH a source keeps its own entry, so a take by source (`only`, `except`) finds it; equal
76
+ // ones are one notice in the admission (`take`).
77
+ const same = JSON.stringify(notice);
78
+ if (this.#pending.some((p) => p.source === undefined && p.to === to && JSON.stringify(p.notice) === same)) {
79
+ return changed;
80
+ }
50
81
  }
51
- // The same news, still unheard, is one notice: the guard writes one kill to the stream of
52
- // every conversation running in the pod, and each Turn following one reads it.
53
- const same = JSON.stringify(notice);
54
- if (this.#pending.some((p) => p.to === to && JSON.stringify(p.notice) === same)) return false;
55
82
  this.#pending.push({ id: this.#newId(), notice, to, ...(source === undefined ? {} : { source }) });
56
83
  return true;
57
84
  }
58
85
 
59
- /** Reserve every unreserved notice in this admission's scope, in the order raised. */
86
+ /** Reserve every unreserved notice in this admission's scope, in the order raised. Equal notices —
87
+ * two kills that tell the same thing — are said once, and every id is reserved. */
60
88
  take(scope: NoticeScope): TakenNotices {
61
89
  const taken = this.#pending.filter(
62
90
  (p) =>
63
91
  !this.#reserved.has(p.id) &&
92
+ (scope.only === undefined || (p.source !== undefined && scope.only.includes(p.source))) &&
93
+ (scope.except === undefined || p.source === undefined || !scope.except.includes(p.source)) &&
94
+ (scope.exceptPrefix === undefined || p.source === undefined || !p.source.startsWith(scope.exceptPrefix)) &&
64
95
  (p.notice.scope === "conversation" ? p.to === scope.conversation : p.to === scope.workspace),
65
96
  );
66
97
  for (const p of taken) this.#reserved.add(p.id);
67
- return { ids: taken.map((p) => p.id), notices: taken.map((p) => p.notice) };
98
+ const said = new Set<string>();
99
+ const notices = taken.flatMap((p) => {
100
+ const key = JSON.stringify(p.notice);
101
+ if (said.has(key)) return [];
102
+ said.add(key);
103
+ return [p.notice];
104
+ });
105
+ return {
106
+ ids: taken.map((p) => p.id),
107
+ notices,
108
+ raised: taken.map((p) => ({ notice: p.notice, ...(p.source === undefined ? {} : { source: p.source }) })),
109
+ };
110
+ }
111
+
112
+ /** Put back a notice an admission carried that no model heard, with the source it was raised
113
+ * with — `seen` already holds it, so a raise would drop it. Nothing changes when a notice with
114
+ * that source is pending already. Answers whether anything changed. */
115
+ restore(notice: Notice, to: string, source?: string): boolean {
116
+ if (source === undefined) return this.raise(notice, to);
117
+ if (this.#pending.some((p) => p.source === source && p.to === to)) return false;
118
+ this.#seen.add(source);
119
+ this.#pending.push({ id: this.#newId(), notice, to, source });
120
+ return true;
68
121
  }
69
122
 
70
123
  /** The admission that carried them was ledgered: they are delivered. */
package/src/open-body.ts CHANGED
@@ -44,27 +44,31 @@ import {
44
44
  type ChartTransitions,
45
45
  } from "./chart.ts";
46
46
  import { inputKeysOf, isEffect, type Duration } from "./effect.ts";
47
+ import { fail, FAILURE_GUARD, failureGuard } from "./fail.ts";
47
48
  import { agent } from "./harness-client.ts";
48
49
  import { SENT_BY } from "./party.ts";
49
50
  import { wrapperBodyOf } from "./parts.ts";
50
51
  import { actorPath, boundRun } from "./registration.ts";
51
- import { jr2Setup } from "./setup.ts";
52
+ import { COMPILED_CHART, jr2Setup } from "./setup.ts";
52
53
  import { attachBoundMachine, boundMachineFor } from "./vocabulary.ts";
53
54
 
55
+ // ADR-0070: "counters are the Open body's".
54
56
  /**
55
- * The ceilings an Open body sets on the Chart that binds it (ADR-0070: "counters are the Open
56
- * body's"). Each is a positive bound; reaching one settles the body with a `ceiling` output.
57
+ * The limits that an Open body sets on its Chart. Each one is a positive bound. When the Chart gets
58
+ * to one, the Open body stops it and ends with `{ outcome: "ceiling", ceiling, detail }`.
57
59
  */
58
60
  export type Ceilings = {
59
- /** The Agent Turns started under the body, at any depth: a lent Machine's Turns count too. */
61
+ /** The Agent Turns that start under the Open body, at all levels. The Turns of a lent Machine
62
+ * count too. */
60
63
  turns: number;
61
- /** The wall clock from the body's start. */
64
+ /** The time from the start of the Open body, such as `"30m"`. At most about 596 hours. */
62
65
  wallClock: Duration;
63
- /** The microsteps the body takes, over the whole run: a budget, since xstate has no loop guard. */
66
+ /** The transitions of the Chart in the whole run. xstate has no guard against a loop, so this is
67
+ * the guard. */
64
68
  transitions: number;
65
- /** The children live under the body at once. */
69
+ /** The children that run under the Open body at one time. */
66
70
  fanOut: number;
67
- /** The Chart's canonical JSON, in bytes: the door's own limit. At most {@link MAX_CHART_SIZE}. */
71
+ /** The size of the Chart, as JSON, in bytes. At most 1048576 (1 MiB). */
68
72
  size: number;
69
73
  };
70
74
 
@@ -106,8 +110,11 @@ export function jsonOver(value: unknown, cap: number): boolean {
106
110
  return false;
107
111
  }
108
112
 
109
- /** The kinds of part an Open body lends, each as a list of the wrapper's own slot names; and the
110
- * kit's Gate, lent whole (`gate: true`), since there is one and it has no name to list. */
113
+ /** The parts of the Chart wrapper that a Chart can use, by their keys in `actors`, `guards`,
114
+ * `actions` and `delays`. `gate: true` lends the Gate, so that a Chart can wait for an answer from
115
+ * outside the run. `fail: true` lends the Part that fails the run, so that a Chart can end its run
116
+ * as a Failure (ADR-0073): the Machine decides which ends a Chart can choose. A Chart can use no
117
+ * other part. */
111
118
  export type Lends = {
112
119
  agents?: string[];
113
120
  effects?: string[];
@@ -116,13 +123,23 @@ export type Lends = {
116
123
  actions?: string[];
117
124
  delays?: string[];
118
125
  gate?: boolean;
126
+ fail?: boolean;
119
127
  };
120
128
 
121
- /** What an Open body states (ADR-0070): the parts it lends, what a Chart may define, the ceilings. */
129
+ /**
130
+ * What an Open body declares: the parts that it lends a Chart, what the Agents of a Chart can
131
+ * have, and the limits of a Chart. Give it to {@link openBody}, and to `chartSchemaFor` for the
132
+ * pick of a planner.
133
+ *
134
+ * @see docs/concepts/charts.md
135
+ */
122
136
  export type OpenBodyDeclaration = {
137
+ /** The parts that a Chart can use. */
123
138
  lends: Lends;
124
- /** What a Chart's own Agents may have: a model from `models`, a Workspace access from `workspace`. */
139
+ /** What the Agents that a Chart adds can have: a model from `models`, and a Workspace access
140
+ * from `workspace`. With no model, a Chart cannot add an Agent. */
125
141
  allows: { models: string[]; workspace: WorkspaceAccess[] };
142
+ /** The limits of one Chart. */
126
143
  ceilings: Ceilings;
127
144
  };
128
145
 
@@ -151,6 +168,8 @@ const reservedName = (kind: (typeof LEND_KINDS)[number], v: string): string | un
151
168
  if (v.includes(".")) return `"${v}" holds a dot; a lent name is a slot key, and dotted names are jr2's and xstate's`;
152
169
  if ((kind === "agents" || kind === "effects" || kind === "machines") && v === "gate")
153
170
  return `"gate" is the kit's Gate; lend a slot of another name`;
171
+ if ((kind === "agents" || kind === "effects" || kind === "machines") && v === "fail")
172
+ return `"fail" is the kit's Part that fails the run, lent as \`fail: true\`; lend a slot of another name`;
154
173
  if (kind === "actions" && (GENERIC_ACTIONS as readonly string[]).includes(v))
155
174
  return `"${v}" is a generic action every Chart has; lend an action of another name`;
156
175
  if (kind === "delays" && /^(0|[1-9][0-9]*)$/.test(v))
@@ -201,8 +220,12 @@ export function declarationFaults(decl: unknown): string[] {
201
220
  if (typeof (lends as { gate?: unknown }).gate !== "boolean") {
202
221
  faults.push("lends.gate: the kit's Gate is lent whole: true or false");
203
222
  }
223
+ } else if (key === "fail") {
224
+ if (typeof (lends as { fail?: unknown }).fail !== "boolean") {
225
+ faults.push("lends.fail: the kit's Part that fails the run is lent whole: true or false");
226
+ }
204
227
  } else if (!(LEND_KINDS as readonly string[]).includes(key)) {
205
- faults.push(`lends.${key}: an Open body lends ${LEND_KINDS.join(", ")} and gate only`);
228
+ faults.push(`lends.${key}: an Open body lends ${LEND_KINDS.join(", ")}, gate and fail only`);
206
229
  }
207
230
  }
208
231
  for (const kind of LEND_KINDS) {
@@ -261,6 +284,7 @@ function frozen(decl: OpenBodyDeclaration): OpenBodyDeclaration {
261
284
  if (v !== undefined) lends[kind] = Object.freeze([...v]) as string[];
262
285
  }
263
286
  if (decl.lends.gate !== undefined) lends.gate = decl.lends.gate;
287
+ if (decl.lends.fail !== undefined) lends.fail = decl.lends.fail;
264
288
  return Object.freeze({
265
289
  lends: Object.freeze(lends),
266
290
  allows: Object.freeze({
@@ -271,11 +295,78 @@ function frozen(decl: OpenBodyDeclaration): OpenBodyDeclaration {
271
295
  });
272
296
  }
273
297
 
298
+ // It validates the declaration now, so a bad one fails where the Machine is written, and returns
299
+ // actor logic branded with it. `jr2Setup` resolves every lent name against its own `actors`,
300
+ // `guards`, `actions` and `delays` at createMachine, and stamps the result with `attachLent`
301
+ // (ADR-0070).
274
302
  /**
275
- * Declare an Open body (ADR-0070). It validates the declaration now, so a bad one fails where the
276
- * Machine is written, and returns actor logic branded with it. `jr2Setup` resolves every lent name
277
- * against its own `actors`, `guards`, `actions` and `delays` at createMachine, and stamps the
278
- * result with {@link attachLent}.
303
+ * Make an Open body: the actor slot that a Chart binds. Put it in `actors`, and invoke it from a
304
+ * state with `{ chart, ...input }`. `chart` is the text of a Chart, or `{ name }` for a named
305
+ * Chart of this wrapper. The other keys are the input of the Chart, which it reads as
306
+ * `{{input.<key>}}`. A Machine with an Open body is a Chart wrapper.
307
+ *
308
+ * Each Agent Turn of the Chart runs where the Open body runs: in the Sandbox of its `workspace()`,
309
+ * or on the Instance Harness. The Open body ends with an {@link OpenBodyOutput}.
310
+ *
311
+ * It refuses a declaration that is not correct: `openBody: <problem>; <problem>`, for example
312
+ * `ceilings.turns: missing; an Open body states every ceiling`. `createMachine` refuses a lent
313
+ * name that the Machine does not have: `machine "<id>": Open body "<slot>": lends.agents: "<name>"
314
+ * is no actor slot of this Machine; its actors: <slots>`.
315
+ *
316
+ * @param decl - What a Chart can use, and its limits (see {@link OpenBodyDeclaration}).
317
+ * @returns The actor logic of the slot.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * import { assign } from "xstate";
322
+ * import { z } from "zod";
323
+ * import { agent, chartSchema, fail, jr2Setup, openBody, type OpenBodyOutput } from "@jr2/orchestrator";
324
+ *
325
+ * // `chart` is a Chart, so a run input can carry one, and Charts can have names.
326
+ * const door = z.object({ topic: z.string(), chart: chartSchema });
327
+ * type Input = z.infer<typeof door>;
328
+ * type Context = Input & { result?: OpenBodyOutput };
329
+ *
330
+ * export const machine = jr2Setup({
331
+ * types: {} as { context: Context; input: Input },
332
+ * actors: {
333
+ * writer: agent({
334
+ * model: "anthropic/claude-haiku-5-5",
335
+ * workspace: "none",
336
+ * instructions: "You write clear, short text for engineers.",
337
+ * }),
338
+ * body: openBody({
339
+ * lends: { agents: ["writer"] },
340
+ * allows: { models: [], workspace: [] },
341
+ * ceilings: { turns: 6, wallClock: "15m", transitions: 200, fanOut: 1, size: 65536 },
342
+ * }),
343
+ * fail: fail(),
344
+ * },
345
+ * }).createMachine({
346
+ * id: "notes",
347
+ * input: door,
348
+ * context: ({ input }) => ({ ...input }),
349
+ * // The fault of `writer` when a Chart does not route it: the Open body sends it here.
350
+ * on: { "agent.fault": ".failed" },
351
+ * initial: "running",
352
+ * states: {
353
+ * running: {
354
+ * invoke: {
355
+ * src: "body",
356
+ * input: ({ context }: { context: Context }) => ({ chart: context.chart, topic: context.topic }),
357
+ * onDone: { target: "done", actions: assign({ result: ({ event }) => event.output as OpenBodyOutput }) },
358
+ * },
359
+ * },
360
+ * done: { type: "final" },
361
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
362
+ * },
363
+ * });
364
+ * ```
365
+ *
366
+ * @see docs/concepts/charts.md
367
+ * @see docs/guides/run-a-chart-by-name.md
368
+ * @see docs/guides/let-an-agent-write-a-chart.md
369
+ * @see docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md
279
370
  */
280
371
  export function openBody(decl: OpenBodyDeclaration): OpenBodyLogic {
281
372
  return openBodyWith(decl);
@@ -358,7 +449,16 @@ export function lentOf(logic: OpenBodyLogic): LentParts | undefined {
358
449
  /** What an Open body is invoked with: the Chart, by text or by name, and its `input` (the rest). */
359
450
  export type OpenBodyInput = { chart: ChartText | { name: string } } & Record<string, unknown>;
360
451
 
361
- /** How an Open body settles: the Chart's output, the door's refusal, or the ceiling it reached. */
452
+ /**
453
+ * The output of an Open body. Route on `outcome` in the `onDone` of the state that invokes it:
454
+ *
455
+ * - `done`: the Chart got to a final state. `output` is the output of the Chart.
456
+ * - `refused`: the door refused the Chart. `report` has one line for each problem. Give it to the
457
+ * Agent that wrote the Chart, so that it can send a corrected one.
458
+ * - `ceiling`: the Chart got to a ceiling, and the Open body stopped it. `ceiling` names it.
459
+ *
460
+ * @see docs/concepts/charts.md
461
+ */
362
462
  export type OpenBodyOutput =
363
463
  | { outcome: "done"; output: unknown }
364
464
  | { outcome: "refused"; report: string[] }
@@ -863,13 +963,16 @@ export type CompileOptions = OpenBodyOptions & {
863
963
 
864
964
  /**
865
965
  * Build the Machine a Chart describes (ADR-0070), with `jr2Setup` over: the lent actors by their
866
- * slot names, `agent(def)` for each Agent the Chart mints, and the kit's `gate`; the lent guards
867
- * and `jr2.sentBy`; the lent actions and the four generic ones; the lent delays. Every reference
868
- * resolves at invoke time, so the Machine holds the text's shape and no run's data. The Chart's
966
+ * slot names, `agent(def)` for each Agent the Chart mints, the kit's `gate`, and `fail()` where the
967
+ * body lends it; the lent guards, `jr2.sentBy` and `jr2.failure`; the lent actions and the four
968
+ * generic ones; the lent delays. Every reference resolves at invoke time, so the Machine holds the
969
+ * text's shape and no run's data. The Chart's
869
970
  * input is the context key {@link CHART_INPUT_KEY}; a done Chart outputs `{ outcome: "done",
870
971
  * output }`, its `output` resolved (or, without one, its final state's). The transitions ceiling
871
972
  * is xstate's `maxIterations` as well, so a guarded `always` loop that never ends in one macrostep
872
- * still ends. `jr2Setup`'s own refusals (a parallel state below an Agent's) throw here.
973
+ * still ends. `jr2Setup`'s own refusals (a parallel state below an Agent's) throw here, but not its
974
+ * `agent.fault` refusal: in a Chart that route is optional, and the body sends an `agent.fault` that
975
+ * no transition of the Chart takes to the Machine that invoked it (ADR-0074).
873
976
  */
874
977
  export function compileChart(
875
978
  admitted: AdmittedChart,
@@ -882,6 +985,8 @@ export function compileChart(
882
985
  const minted = mapValues(admitted.agents, (def) => def);
883
986
  const actors: Record<string, UnknownActorLogic> = chartActors(lent.actors);
884
987
  for (const [slot, def] of Object.entries(minted)) actors[slot] = mint(slot, def);
988
+ // The kit's Part that fails the run, where the body lends it (ADR-0073): no slot of the wrapper's.
989
+ if (decl.lends.fail === true) actors.fail = fail();
885
990
  // The slots an invoke runs an Agent Turn in: the lent Agents and the Chart's own.
886
991
  const agentSlots = new Set([
887
992
  ...Object.keys(lent.actors).filter((slot) => isAgent(lent.actors[slot])),
@@ -905,11 +1010,15 @@ export function compileChart(
905
1010
  : (event as { output?: unknown } | undefined)?.output,
906
1011
  }) satisfies OpenBodyOutput,
907
1012
  options: { maxIterations: decl.ceilings.transitions },
1013
+ // In a Chart the `agent.fault` route is optional (ADR-0070): the Open body sends an `agent.fault`
1014
+ // that no transition takes to its parent, so `createMachine` does not refuse a state with none.
1015
+ [COMPILED_CHART]: true,
908
1016
  };
909
1017
  return jr2Setup({
910
1018
  events: admitted.events,
911
1019
  actors,
912
- guards: lent.guards as never,
1020
+ // `jr2.failure` is in every Chart, as `jr2.sentBy` is: a predicate, with no capability (ADR-0073).
1021
+ guards: { ...(lent.guards as Record<string, unknown>), [FAILURE_GUARD]: failureGuard } as never,
913
1022
  actions: { ...(lent.actions as Record<string, unknown>), ...GENERIC } as never,
914
1023
  delays: lent.delays as never,
915
1024
  }).createMachine(config as never) as unknown as AnyStateMachine;
@@ -1097,6 +1206,20 @@ function watchTurns(self: AnyActorRef, turns: number, ceiling: number): TurnWatc
1097
1206
  return watch;
1098
1207
  }
1099
1208
 
1209
+ /**
1210
+ * Does a transition of the Chart take this event now? xstate's own selection, guards and all. A
1211
+ * guard that throws (a reference that resolved to nothing) counts as a take: the step then runs
1212
+ * the guard again, and settles the body `refused` with the reference named.
1213
+ */
1214
+ function takes(machine: AnyStateMachine, snapshot: BodySnapshot, event: AnyEventObject): boolean {
1215
+ try {
1216
+ return machine.getTransitionData(snapshot as never, event as never).length > 0;
1217
+ } catch {
1218
+ runFault = undefined;
1219
+ return true;
1220
+ }
1221
+ }
1222
+
1100
1223
  /** The actor logic of an Open body: a custom ActorLogic that delegates to the compiled Chart. */
1101
1224
  export function openBodyLogic(decl: OpenBodyDeclaration, options: OpenBodyOptions = {}): UnknownActorLogic {
1102
1225
  const wallMs = durationMs(decl.ceilings.wallClock) ?? Number.POSITIVE_INFINITY;
@@ -1392,6 +1515,19 @@ export function openBodyLogic(decl: OpenBodyDeclaration, options: OpenBodyOption
1392
1515
  ceiling("transitions", transitionsDetail(fields.counters.transitions), snapshot, fields, scope),
1393
1516
  );
1394
1517
  }
1518
+ // An `agent.fault` that no transition of the Chart takes goes to the Machine that invoked the
1519
+ // body, unchanged (ADR-0074): in a Chart the route is optional, and the parent's `agent.fault`
1520
+ // route is the route for a Chart's Agents too. The body does not step the Chart: the parent's
1521
+ // route decides, and its exit stops the body. With no parent route that takes it, the run
1522
+ // host faults the run, as for any `agent.fault` that no transition took.
1523
+ if (event.type === "agent.fault" && !takes(machine, snapshot, event)) {
1524
+ // Relayed from the body, as `sendBack` relays from an Agent: the run host reads a relayed
1525
+ // event's source, so a pick held in the parent is answered as for an Agent of its own.
1526
+ const parent = scope.self._parent;
1527
+ const system = scope.system as unknown as { _relay(from: unknown, to: unknown, e: AnyEventObject): void };
1528
+ if (parent) scope.defer(() => system._relay(scope.self, parent, event));
1529
+ return snapshot;
1530
+ }
1395
1531
  return release(
1396
1532
  scope.self,
1397
1533
  step(
@@ -25,18 +25,82 @@
25
25
  import type { ActorLogic, AnyActorRef, EventObject } from "xstate";
26
26
  import { open } from "./open.ts";
27
27
  import type { Arg, EffectLogic, EffectParts, EffectSnapshot } from "./effect.ts";
28
- import type { Text } from "./http-effect.ts";
28
+ import type { Secret, Text } from "./http-effect.ts";
29
29
 
30
- /** The parts of an Effect a package may leave Open: an http url, a command's argv, a secret's name. */
31
- export type OpenEffectPart = "url" | "argv" | "secret";
30
+ /** The parts of an Effect a package may leave Open: an http url, a command's argv, a secret's name,
31
+ * a dispatch's target (its url and token together, ADR-0071). */
32
+ export type OpenEffectPart = "url" | "argv" | "secret" | "target";
32
33
 
33
- /** What a composer binds an Effect's Open parts to, each key one part: a url (a string, or a `text`
34
- * with a secret in its path), an argv (each entry one argument, never shell text), a secret's name
35
- * (its file in the Effector's mount, ADR-0068). One object for an Effect with two Open parts. */
34
+ // What a composer binds an Effect's Open parts to, each key one part (ADR-0054); a dispatch's
35
+ // target is its url and token, bound as one (ADR-0071). One object for an
36
+ // Effect with two Open parts. `customize()` reads each Effect's own binding type off its logic
37
+ // (`BindingOf`, customize.ts), so this loose shape is what the implementation walks.
38
+ /**
39
+ * The values that bind the Open parts of one Effect. A packaged Machine leaves a part of an Effect
40
+ * Open when it cannot know it. That part is the url of an `http` Effect, the argv of a `command`,
41
+ * the name of a secret, written `secret(open)`, or the target of a `dispatch`: the url of another
42
+ * Orchestrator and the secret that holds its Instance token, bound as one. You bind it once, where you register the
43
+ * Machine, with `customize(machine, { effects: { <slot>: binding } })`. Each key binds one Open
44
+ * part. For an Effect with two Open parts, one object holds both keys.
45
+ *
46
+ * The binding is a value, never a function of the run: no run chooses an Effect's url, argv or
47
+ * secret. `customize()` builds the Effect again with the bound parts, so a bound part gets every
48
+ * check that a part written in the definition gets.
49
+ *
50
+ * `customize()` refuses:
51
+ * - an empty binding, or a key for a part that is not Open: `customize(): the <kind> Effect
52
+ * "<slot>" leaves its <parts> Open — bind it with <binding> (ADR-0054)`
53
+ * - an Effect with no Open part: ``customize(): the <kind> Effect "<slot>" of machine "<id>"
54
+ * leaves nothing Open — its parts are fixed in its definition, and only a part a package left
55
+ * `open` is bound, once (ADR-0054).``
56
+ * - `open` as a value: ``customize(): the <kind> Effect "<slot>"'s <part> is bound once, to a
57
+ * value — not `open` again: no run chooses an Effect's <part> (ADR-0054)``. A function as a
58
+ * value gives the same text, with `a function of the run` for `` `open` again ``.
59
+ * - a secret that is not a string: `customize(): the <kind> Effect "<slot>"'s secret is bound to
60
+ * a secret's name, a string — the file the Effector reads it from: { secret: "<name>" }
61
+ * (ADR-0054, ADR-0068)`
62
+ *
63
+ * `jr2 up` refuses a registered Machine with an Open part that nobody bound, and prints the
64
+ * `customize` line that binds it.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { customize, http, jr2Setup, open, type EffectBinding } from "@jr2/orchestrator";
69
+ *
70
+ * const notifier = jr2Setup({
71
+ * actors: { notify: http({ method: "POST", url: open }) },
72
+ * }).createMachine({
73
+ * id: "notifier",
74
+ * initial: "sending",
75
+ * states: {
76
+ * sending: { invoke: { src: "notify", onDone: "done" } },
77
+ * done: { type: "final" },
78
+ * },
79
+ * });
80
+ *
81
+ * const notify = { url: "https://hooks.example.com/build" } satisfies EffectBinding;
82
+ *
83
+ * export const machine = customize(notifier, { effects: { notify } });
84
+ * ```
85
+ *
86
+ * @see docs/guides/bind-open-parts.md
87
+ * @see docs/concepts/effects.md
88
+ * @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
89
+ * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
90
+ */
36
91
  export type EffectBinding = {
92
+ /** The url of an `http` Effect whose url is Open: a string, or a `text` template with a secret
93
+ * in its path, such as `` text`https://hooks.example.com/services/${secret("hook")}` ``. Its
94
+ * scheme and host must be literal. */
37
95
  readonly url?: string | Text;
96
+ /** The argv of a `command` whose argv is Open: one entry for each argument, never shell text.
97
+ * An entry is a string or an `arg("<param>")` that the invoke's input fills. It must not be
98
+ * empty. */
38
99
  readonly argv?: readonly (string | Arg)[];
100
+ /** The name of the secret that `secret(open)` stands for: an entry of `secrets` in
101
+ * `jr2.config.ts`. The Effector reads it from a file of that name. */
39
102
  readonly secret?: string;
103
+ readonly target?: { readonly url: string; readonly token: Secret };
40
104
  };
41
105
 
42
106
  declare const openPart: unique symbol;
@@ -46,10 +110,16 @@ declare const openPart: unique symbol;
46
110
  * `effects` offers exactly the Open slots, each with the binding its parts take — the intersection
47
111
  * of each part's, for an Effect with two. Phantom, like `JR2Wrapper`: the property exists in the
48
112
  * type alone.
113
+ *
114
+ * @internal
49
115
  */
50
116
  export type OpenEffect<P extends OpenEffectPart> = { readonly [openPart]: P };
51
117
 
52
- /** The phantom for these Open parts, or nothing for none: what a constructor's type adds. */
118
+ /**
119
+ * The phantom for these Open parts, or nothing for none: what a constructor's type adds.
120
+ *
121
+ * @internal
122
+ */
53
123
  export type OpenOf<P extends OpenEffectPart> = [P] extends [never] ? unknown : OpenEffect<P>;
54
124
 
55
125
  /** The binder an Open Effect carries, under a `Symbol.for` key so a second copy of this module (an
@@ -58,11 +128,13 @@ const BIND: unique symbol = Symbol.for("jr2.effect.bind");
58
128
 
59
129
  type Binder = (binding: EffectBinding) => EffectLogic<unknown, unknown>;
60
130
 
61
- /** Which parts of these parts are Open, in binding order: the url or the argv, then the secret. */
131
+ /** Which parts of these parts are Open, in binding order: the url, the argv or the target, then the
132
+ * secret. A dispatch's target is one part: its token is never `secret(open)` (ADR-0071). */
62
133
  export function openPartsOf(parts: EffectParts): OpenEffectPart[] {
63
134
  const out: OpenEffectPart[] = [];
64
135
  if (parts.kind === "http" && parts.url === open) out.push("url");
65
136
  if (parts.kind === "command" && parts.argv === open) out.push("argv");
137
+ if (parts.kind === "dispatch" && parts.target === open) out.push("target");
66
138
  if (parts.kind !== "push" && parts.openSecret) out.push("secret");
67
139
  return out;
68
140
  }
@@ -78,6 +150,7 @@ const BINDING_SAMPLE: Record<OpenEffectPart, string> = {
78
150
  url: 'url: "https://<host>/<path>"',
79
151
  argv: 'argv: ["<program>", "<arg>"]',
80
152
  secret: 'secret: "<name>"',
153
+ target: 'target: { url: "https://<host>", token: secret("<name>") }',
81
154
  };
82
155
 
83
156
  /** One binding object for these Open parts — `{ url: "…", secret: "<name>" }` for two. */
package/src/open.ts CHANGED
@@ -5,13 +5,66 @@
5
5
  // and a `workspace()`'s Repo Slots (parts.ts, which already imports agent.ts). A module with
6
6
  // nothing but the sentinel in it is what lets both import it and neither import the other.
7
7
 
8
+ // `Symbol.for`, so an Instance's own copy of this module and the CLI's walk agree on it — a
9
+ // packaged Machine may be built against one and walked by the other.
8
10
  /**
9
- * Open (GLOSSARY.md, ADR-0054): this part is a composer's to bind, with `customize()` — a Repo
10
- * Slot with no url, an Agent with no model. The shape a packaged Machine ships in, because a
11
- * package cannot know the repository or pay for the model. `jr2 up` refuses an Open part nobody
12
- * bound and names the line that binds it; a run never sees one.
11
+ * Leave a part of a Machine Open: for the Instance that registers the Machine to bind with
12
+ * `customize()`. Write it where a value goes, in a Machine that you publish as a package, for a
13
+ * part that the package cannot know or pay for:
14
+ * - a Repo Slot: `repos: { app: open }`, or the whole map: `repos: open`;
15
+ * - an Agent's model: `agent({ model: open, … })`;
16
+ * - an Effect's url or argv: `http({ url: open, … })`, `command(open)`;
17
+ * - the secret of an Effect's auth, of a header, or of a command's `env`: `secret(open)`.
13
18
  *
14
- * `Symbol.for`, so an Instance's own copy of this module and the CLI's walk agree on it — a
15
- * packaged Machine may be built against one and walked by the other.
19
+ * `jr2 up` refuses a Workflow with an Open part that nobody bound, and prints the `customize()`
20
+ * line that binds it: ``refusing: workflow "<name>" leaves <part> open — bind it where the Machine
21
+ * is registered: export const machine = customize(<import>, …) (<adr>)``. A run never sees an
22
+ * Open part.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { z } from "zod";
27
+ * import { agent, customize, defineEvent, fail, jr2Setup, open, workspace } from "@jr2/orchestrator";
28
+ *
29
+ * const finish = defineEvent({
30
+ * name: "finish",
31
+ * description: "Call this when `node --test` passes.",
32
+ * input: z.object({}),
33
+ * });
34
+ *
35
+ * const body = jr2Setup({
36
+ * events: [finish],
37
+ * actors: { coder: agent({ model: open, instructions: "You fix the failing test." }), fail: fail() },
38
+ * }).createMachine({
39
+ * id: "fix",
40
+ * on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
41
+ * initial: "coding",
42
+ * states: {
43
+ * coding: {
44
+ * invoke: { src: "coder", input: { prompt: "Make `node --test` pass." } },
45
+ * on: { finish: "done" },
46
+ * },
47
+ * done: { type: "final" },
48
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
49
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
50
+ * },
51
+ * });
52
+ *
53
+ * // What a package exports: the repository and the model are Open.
54
+ * export const fix = workspace(body, {
55
+ * repos: { app: open },
56
+ * spec: () => ({ branch: "jr2/fix" }),
57
+ * });
58
+ *
59
+ * // What an Instance's `workflows/fix.ts` exports: the same Machine, its Open parts bound.
60
+ * export const machine = customize(fix, {
61
+ * repos: { app: { url: "https://github.com/you/app.git" } },
62
+ * agents: { coder: { model: "anthropic/claude-haiku-5-5" } },
63
+ * });
64
+ * ```
65
+ *
66
+ * @see docs/guides/bind-open-parts.md
67
+ * @see docs/concepts/composition.md
68
+ * @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
16
69
  */
17
70
  export const open: unique symbol = Symbol.for("jr2.open");