@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
@@ -32,9 +32,10 @@
32
32
  // Sandbox's Service, and a CR at `phase: Ready` is not yet routable).
33
33
  //
34
34
  // `abort` is the third verb, and ADR-0024 wires it to the END OF THE INVOCATION: a turn ends with
35
- // the state that asked for it. It sweeps the running Submission and everything queued behind it to
36
- // the distinct `aborted` Settlement — worth having for observability, though jr2 never reads it
37
- // (the actor is stopped by then; see actor.ts).
35
+ // the state that asked for it. It sweeps the running Submission and everything queued behind it,
36
+ // and says its cause: `pick` when the Turn's own pick ended it, which settles the running
37
+ // Submission `ended`; no cause otherwise, which settles it `aborted`. The outcome is worth having for
38
+ // observability, though jr2 never reads it (the actor is stopped by then; see actor.ts).
38
39
 
39
40
  import { agentActorWith } from "./actor.ts";
40
41
  import type {
@@ -44,6 +45,7 @@ import type {
44
45
  AgentRunInput,
45
46
  AgentRunPort,
46
47
  AgentSettleOptions,
48
+ ListedKill,
47
49
  } from "./actor.ts";
48
50
  import type { AgentDeclaration, AgentDefinition, ThinkingLevel } from "./agent.ts";
49
51
  import {
@@ -66,12 +68,20 @@ import {
66
68
  STREAM_NEXT_OFFSET_HEADER,
67
69
  VIEW_HISTORY,
68
70
  VIEW_UPDATES,
71
+ type AbortCause,
72
+ type AbortRequest,
69
73
  type EchoEvent,
70
74
  type ExecRead,
71
75
  type ExecRequest,
72
76
  type LiveConversation,
73
77
  type LiveSetRequest,
74
78
  type LiveSetResponse,
79
+ type MemoryKillsResponse,
80
+ type NotKeptResponse,
81
+ MAX_KILL_NAME,
82
+ MAX_KILL_STAMP,
83
+ MAX_KILLS_BODY,
84
+ MAX_TOLD,
75
85
  type Notice,
76
86
  type Settlement,
77
87
  type StreamEvent,
@@ -81,8 +91,8 @@ import {
81
91
  /** The three-verb wire client (the injectable seam — structurally what `@flue/sdk`'s
82
92
  * `agents.{send,wait,abort}` was, minus the SDK). */
83
93
  export type HarnessClient = {
84
- /** `POST /agents/:name/:id {message, cwd?, definition, model?, thinkingLevel?}` → the Admission,
85
- * with `streamUrl` resolved absolute. The FRAME is `message` and `cwd` — what this Turn is about
94
+ /** `POST /agents/:name/:id {message, cwd?, definition, model?, thinkingLevel?, notices?, kept?}`
95
+ * → the Admission, with `streamUrl` resolved absolute. The FRAME is `message` and `cwd` — what this Turn is about
86
96
  * and where it works (ADR-0057), flat siblings on the body. The DEFINITION is the slot's,
87
97
  * carried by the Machine and sent with every admission (ADR-0049) — the Harness keeps no roster,
88
98
  * so `:name` alone would name nothing there — and it travels as IDENTITY alone, because this
@@ -101,19 +111,43 @@ export type HarnessClient = {
101
111
  thinkingLevel?: ThinkingLevel;
102
112
  /** The pending notices in this admission's scope (ADR-0062). Omitted when there are none. */
103
113
  notices?: Notice[];
114
+ /** The admission is for a conversation this process was told the Harness keeps (ADR-0062).
115
+ * The Harness that keeps none answers 404 and seats none: this verb throws
116
+ * {@link ConversationNotKept}. */
117
+ kept?: boolean;
104
118
  signal?: AbortSignal;
105
119
  },
106
120
  ): Promise<AgentAdmission>;
107
- /** Follow the stream to this Submission's Settlement: resolve on `completed`, reject with
108
- * `SettlementFault` on `failed`/`aborted`/404. A guard kill read on the way is handed to
109
- * `onMemoryLimit` (ADR-0061). */
121
+ /** Follow the stream to this Submission's Settlement: resolve on `completed` alone — the run
122
+ * returned with the Turn still open, which the Agent actor answers with the nudge or the wake —
123
+ * and reject with `SettlementFault` on `failed`/`ended`/`aborted`/404. `ended` is no fault, and no
124
+ * live actor reads it: a Submission settles `ended` only after the Turn's own pick took the
125
+ * Machine out of the Agent's state. That stopped the actor that waits on it (ADR-0024), and a
126
+ * stopped actor drops every rejection. A guard kill read on the way is handed to `onMemoryLimit` (ADR-0061). */
110
127
  wait(admission: AgentAdmission, opts?: AgentSettleOptions): Promise<void>;
111
128
  /** `GET /agents/:name/:id?view=history`: does the Harness still hold this conversation? Only a
112
129
  * 404 says no (ADR-0027: a GET on an unknown conversation is 404); anything else — another
113
130
  * status, a transport failure — says yes, because unknown is never loss (ADR-0062). */
114
131
  holds(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<boolean>;
115
- /** `POST /agents/:name/:id/abort`. The `{ aborted }` answer is dropped (ADR-0024). */
116
- abort(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<void>;
132
+ /** `GET /memory-kills`: the pod's guard kills (ADR-0061, ADR-0062). A request that never left
133
+ * this host, and a draining Harness's 503, are sent again until `signal` ends the read — the
134
+ * caller owns the read's limit (the Agent actor's 5 s); with no signal, it is sent once. Any other
135
+ * answer but a 200 that reads — a Harness that keeps none, a transport failure, the caller's
136
+ * timeout — is `undefined`, "could not tell": the Harness still holds what it kept, and the next
137
+ * admission reads again. A kill with no id or time, with an id or a time outside the wire's bounds
138
+ * (`wire.ts`), or with a time that does not parse, is skipped; one whose `peak` or `limit` is not
139
+ * a size is kept without sizes, and one whose `told` holds an entry that is not a conversation, or
140
+ * a name outside the bounds, is kept without that entry — each fault said. Absent sizes are no fault: the pod left them out.
141
+ * Each fault is one line, said when it starts. A read that works says nothing, and the next fault
142
+ * after it is said again. */
143
+ memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined>;
144
+ /** `POST /agents/:name/:id/abort`, with the body `{ cause, submissionId }` when a cause is given
145
+ * (`AbortRequest`). The `{ aborted }` answer is dropped (ADR-0024). */
146
+ abort(
147
+ agentName: string,
148
+ instanceId: string,
149
+ opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
150
+ ): Promise<void>;
117
151
  };
118
152
 
119
153
  export type HarnessClientOptions = {
@@ -148,8 +182,9 @@ export type HarnessClientOptions = {
148
182
  log?: (line: string) => void;
149
183
  };
150
184
 
151
- /** A Submission that settled `failed`/`aborted` — or whose conversation the Harness no longer
152
- * has (404) — surfaced to the actor as an `agent.fault`. Replaces the SDK's execution error. */
185
+ /** A Submission that settled `failed`/`ended`/`aborted` — or whose conversation the Harness no
186
+ * longer has (404) — surfaced to the actor as an `agent.fault` while it still waits on the Turn.
187
+ * Replaces the SDK's execution error. */
153
188
  export class SettlementFault extends Error {
154
189
  /** The Settlement as the stream carried it; absent when the conversation itself was lost. */
155
190
  readonly settlement?: Settlement;
@@ -164,6 +199,83 @@ export class SettlementFault extends Error {
164
199
  }
165
200
  }
166
201
 
202
+ /** An admission for a kept conversation (`kept`) that the Harness refused with 404: it keeps no
203
+ * such conversation, and it seated none (ADR-0062). `notKept` is what the Agent actor reads, as it
204
+ * reads `lost`: that module is wire-free. */
205
+ export class ConversationNotKept extends Error {
206
+ readonly notKept = true;
207
+ constructor(message: string) {
208
+ super(message);
209
+ this.name = "ConversationNotKept";
210
+ }
211
+ }
212
+
213
+ /** The Harnesses whose memory kills this process could not read, each said once (`memoryKills`).
214
+ * Per process, not per client: an Agent actor builds a client for each Turn. */
215
+ const killReadFaults = new Set<string>();
216
+
217
+ /** The Harnesses whose kills — on the list or on a stream — held a field this process could not
218
+ * use, each said once until a clean read. Per process, like `killReadFaults`. Neither set is cleaned
219
+ * when a Workspace goes: no hook reaches this module from the Sandbox's destroy, and an entry is
220
+ * one URL per Sandbox this process ever read. */
221
+ const badKillFaults = new Set<string>();
222
+
223
+ /** Say once, until a clean read, that a Harness's kills held a field this process could not use. */
224
+ function badKill(baseUrl: string, log: (line: string) => void, what: string): void {
225
+ if (badKillFaults.has(baseUrl)) return;
226
+ badKillFaults.add(baseUrl);
227
+ log(`harness ${baseUrl}: ${what} (ADR-0062)`);
228
+ }
229
+
230
+ /** A size as the guard writes one (`1.9Gi`, `512Mi`), short. The Agent's uid can write the file
231
+ * the list is read from, and these words go into the next Agent's Briefing as they are, so
232
+ * nothing else passes (ADR-0062). */
233
+ function isSize(value: unknown): value is string {
234
+ return typeof value === "string" && value.length <= 16 && /^\d{1,6}(\.\d{1,3})?(Ki|Mi|Gi|Ti)$/.test(value);
235
+ }
236
+
237
+ /** A string no longer than `max` characters. The bounds are the wire's (`wire.ts`), the same as
238
+ * the Harness's: the Harness counts the bytes of the JSON text, which are never fewer, so a kill it
239
+ * keeps is one this process keeps. An answer larger than `MAX_KILLS_BODY` is no list to act on:
240
+ * the Harness answers no list that large. */
241
+ const fits = (v: unknown, max: number): v is string => typeof v === "string" && v.length <= max;
242
+
243
+ /** One kill of the list as this process can use it, or undefined when it has no id or time, when
244
+ * its id or time is outside the wire's bounds, or when its time does not parse — the only kills
245
+ * that are dropped. Sizes that are not sizes are left out, so the kill is kept with no sizes; a
246
+ * `told` entry that is not a conversation, or whose name is outside the bounds, is left out of
247
+ * `told`. */
248
+ function listedKill(value: unknown): ListedKill | undefined {
249
+ if (typeof value !== "object" || value === null) return undefined;
250
+ const { id, at, peak, limit, told } = value as Record<string, unknown>;
251
+ if (!fits(id, MAX_KILL_STAMP) || !fits(at, MAX_KILL_STAMP) || Number.isNaN(Date.parse(at))) return undefined;
252
+ const conversations = (Array.isArray(told) ? told.slice(0, MAX_TOLD) : []).filter(
253
+ (t): t is { agent: string; instanceId: string } =>
254
+ typeof t === "object" && t !== null && fits(t.agent, MAX_KILL_NAME) && fits(t.instanceId, MAX_KILL_NAME),
255
+ );
256
+ const sized = isSize(peak) && isSize(limit);
257
+ return {
258
+ id,
259
+ at,
260
+ told: conversations.map(({ agent, instanceId }) => ({ agent, instanceId })),
261
+ ...(sized ? { peak, limit } : {}),
262
+ };
263
+ }
264
+
265
+ /** Whether a listed kill came through as the wire states it. */
266
+ function whole(raw: unknown, kill: ListedKill | undefined): boolean {
267
+ if (kill === undefined) return false;
268
+ const { peak, limit, told } = raw as { peak?: unknown; limit?: unknown; told?: unknown };
269
+ // Sizes the pod left out are no fault; sizes it sent that are not sizes are.
270
+ if (kill.limit === undefined && (peak !== undefined || limit !== undefined)) return false;
271
+ return Array.isArray(told) && Math.min(told.length, MAX_TOLD) === kill.told.length && told.length <= MAX_TOLD;
272
+ }
273
+
274
+ /** A timeout's abort reason (`AbortSignal.timeout`): a read that ran out of time, not an abandon. */
275
+ function isTimeout(reason: unknown): boolean {
276
+ return reason instanceof DOMException && reason.name === "TimeoutError";
277
+ }
278
+
167
279
  /** Build the real wire client from connection options. */
168
280
  export function createHarnessClient(options: HarnessClientOptions): HarnessClient {
169
281
  const { baseUrl } = options;
@@ -276,11 +388,26 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
276
388
  // The Briefing's news (ADR-0062): typed data, the Harness writes the words. Omitted
277
389
  // when there is none, so an admission without notices is byte-identical to before.
278
390
  ...(sendOptions.notices?.length ? { notices: sendOptions.notices } : {}),
391
+ // Omitted when unset, so an admission that is not for a kept conversation is
392
+ // byte-identical to before.
393
+ ...(sendOptions.kept ? { kept: true } : {}),
279
394
  }),
280
395
  signal: sendOptions.signal,
281
396
  },
282
397
  sendOptions.signal,
283
398
  );
399
+ if (sendOptions.kept && res.status === 404) {
400
+ // "Not kept" only with the Harness's marker: a 404 from anything else — a proxy, a route
401
+ // that moved — is a failed admission, as before.
402
+ const body = (await res.json().catch(() => undefined)) as Partial<NotKeptResponse> | undefined;
403
+ const detail = typeof body?.error === "string" ? body.error : res.statusText || "no detail";
404
+ if (body?.kept === false) {
405
+ throw new ConversationNotKept(
406
+ `harness admission refused (404): ${detail} — the Harness does not keep this conversation (ADR-0062)`,
407
+ );
408
+ }
409
+ throw new Error(`harness admission failed (404): ${detail}`);
410
+ }
284
411
  if (!res.ok) {
285
412
  throw new Error(`harness admission failed (${res.status}): ${await errorDetail(res)}`);
286
413
  }
@@ -340,8 +467,19 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
340
467
 
341
468
  for (const event of events) {
342
469
  if (event.type === "memory-limit") {
343
- const { peak, limit, conversationId, position } = event;
344
- opts?.onMemoryLimit?.({ peak, limit, source: `${conversationId}@${position.batch}.${position.index}` });
470
+ const { id, peak, limit, conversationId, position } = event;
471
+ // A size that is not one is not put in a Briefing (the Agent's uid can write the record):
472
+ // the kill is kept, with no sizes, and the fault is said.
473
+ const sized = isSize(peak) && isSize(limit);
474
+ if (!sized)
475
+ badKill(baseUrl, log, "a memory kill on a stream has sizes that are not sizes — kept without them");
476
+ // An older Harness's event has no id: keyed by its place on the stream, exactly as the
477
+ // Orchestrator before raised it, so a replayed read is the same kill and it can never
478
+ // mark a later kill seen. An id too long to be one is no id.
479
+ const kill = fits(id, MAX_KILL_STAMP)
480
+ ? { id }
481
+ : { id: `${conversationId}@${position.batch}.${position.index}`, legacy: true as const };
482
+ opts?.onMemoryLimit?.({ ...kill, ...(sized ? { peak, limit } : {}) });
345
483
  continue;
346
484
  }
347
485
  if (event.type !== "submission-settled" || event.submissionId !== admission.submissionId) continue;
@@ -355,21 +493,108 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
355
493
  async holds(agentName, instanceId, opts) {
356
494
  const url = new URL(conversationUrl(agentName, instanceId));
357
495
  url.searchParams.set("view", VIEW_HISTORY);
358
- try {
359
- const res = await fetchImpl(url.toString(), { headers: authorization, signal: opts?.signal });
360
- // Drain the body so the socket is released; the snapshot itself is not the question.
361
- await res.body?.cancel().catch(() => undefined);
362
- return res.status !== 404;
363
- } catch (err) {
364
- if (opts?.signal?.aborted) throw err;
365
- return true;
496
+ // Asked as a restart is seen, when the new container may not listen yet: a request that
497
+ // never left this host, and a draining Harness's 503, are sent again until the caller's
498
+ // signal ends the wait — then, and on any other failure, unknown is never loss.
499
+ let backoffMs = backoffInitialMs;
500
+ for (;;) {
501
+ let again = false;
502
+ try {
503
+ const res = await fetchImpl(url.toString(), { headers: authorization, signal: opts?.signal });
504
+ // Drain the body so the socket is released; the snapshot itself is not the question.
505
+ await res.body?.cancel().catch(() => undefined);
506
+ if (res.status !== DRAINING) return res.status !== 404;
507
+ again = true;
508
+ } catch (err) {
509
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
510
+ again = !opts?.signal?.aborted && neverDelivered(err);
511
+ }
512
+ if (!again || opts?.signal === undefined) return true;
513
+ try {
514
+ await sleep(jittered(backoffMs), opts.signal);
515
+ } catch (err) {
516
+ if (opts.signal.aborted && !isTimeout(opts.signal.reason)) throw err;
517
+ return true;
518
+ }
519
+ backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
520
+ }
521
+ },
522
+
523
+ async memoryKills(opts) {
524
+ const url = new URL("/memory-kills", baseUrl).toString();
525
+ // Idempotent, so re-sent on what `postAdmission` re-sends — a request that never left this
526
+ // host, a draining Harness's 503 — on the same ladder, until the caller's signal ends it. The
527
+ // limit is the caller's alone, so there is one clock for it.
528
+ let backoffMs = backoffInitialMs;
529
+ let why: string;
530
+ for (;;) {
531
+ let again = false;
532
+ try {
533
+ const res = await fetchImpl(url, { headers: authorization, signal: opts?.signal });
534
+ if (res.status === 200 && Number(res.headers.get("content-length") ?? 0) > MAX_KILLS_BODY) {
535
+ await res.body?.cancel().catch(() => undefined);
536
+ why = `the answer is larger than ${MAX_KILLS_BODY} bytes`;
537
+ } else if (res.status === 200) {
538
+ const text = await res.text();
539
+ const kills =
540
+ text.length > MAX_KILLS_BODY ? undefined : (JSON.parse(text) as Partial<MemoryKillsResponse>).kills;
541
+ if (Array.isArray(kills)) {
542
+ // A read that works ends the fault: the next fault is said again.
543
+ killReadFaults.delete(baseUrl);
544
+ const read = kills.map(listedKill);
545
+ const usable = read.filter((kill): kill is ListedKill => kill !== undefined);
546
+ const flawed = kills.filter((raw, i) => !whole(raw, read[i])).length;
547
+ if (flawed === 0) badKillFaults.delete(baseUrl);
548
+ else {
549
+ badKill(
550
+ baseUrl,
551
+ log,
552
+ `${flawed} of its memory kills are not the wire's shape — those with no id or time, or with one outside the wire's bounds or a time that does not parse, are skipped, the others are kept without the fields that were wrong`,
553
+ );
554
+ }
555
+ return usable;
556
+ }
557
+ why = "the answer is not a list of kills";
558
+ } else {
559
+ why = `HTTP ${res.status}: ${await errorDetail(res)}`;
560
+ again = res.status === DRAINING;
561
+ }
562
+ } catch (err) {
563
+ // A local abandon propagates, as in every other verb; a timeout the caller set is none.
564
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
565
+ why = transportDetail(err);
566
+ again = !opts?.signal?.aborted && neverDelivered(err);
567
+ }
568
+ if (!again || opts?.signal === undefined) break;
569
+ try {
570
+ await sleep(jittered(backoffMs), opts?.signal);
571
+ } catch (err) {
572
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
573
+ why = "the read's time ran out";
574
+ break;
575
+ }
576
+ backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
577
+ }
578
+ // One line for each fault: said when it starts. A read that works says nothing, and the next
579
+ // fault after it is said again.
580
+ if (!killReadFaults.has(baseUrl)) {
581
+ killReadFaults.add(baseUrl);
582
+ log(
583
+ `harness ${baseUrl}: cannot read its memory kills (${why}) — no notice from them now; the Harness keeps them, and the next admission reads again (ADR-0062)`,
584
+ );
366
585
  }
586
+ return undefined;
367
587
  },
368
588
 
369
589
  async abort(agentName, instanceId, opts) {
590
+ // No cause, no body: the request an Orchestrator from before `cause` sent, byte for byte.
591
+ const body: AbortRequest | undefined = opts?.cause
592
+ ? { cause: opts.cause, ...(opts.submissionId ? { submissionId: opts.submissionId } : {}) }
593
+ : undefined;
370
594
  const res = await fetchImpl(`${conversationUrl(agentName, instanceId)}/abort`, {
371
595
  method: "POST",
372
- headers: authorization,
596
+ headers: body ? { ...authorization, "content-type": "application/json" } : authorization,
597
+ ...(body ? { body: JSON.stringify(body) } : {}),
373
598
  signal: opts?.signal,
374
599
  });
375
600
  if (!res.ok) {
@@ -407,6 +632,7 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
407
632
  model: input.model,
408
633
  thinkingLevel: input.thinkingLevel,
409
634
  ...(opts.notices?.length ? { notices: opts.notices } : {}),
635
+ ...(opts.kept ? { kept: true } : {}),
410
636
  signal: opts.signal,
411
637
  });
412
638
  },
@@ -419,11 +645,23 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
419
645
  return await client.holds(agentName, instanceId, opts);
420
646
  },
421
647
 
422
- async abort(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<void> {
648
+ async memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined> {
649
+ return await client.memoryKills(opts);
650
+ },
651
+
652
+ async abort(
653
+ agentName: string,
654
+ instanceId: string,
655
+ opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
656
+ ): Promise<void> {
423
657
  // The result (`{ aborted }` — whether there was work to end) is dropped: by the time this
424
658
  // runs the state has already moved on, and an idle instance is exactly as fine as a
425
659
  // stopped one. Settlement is asynchronous and nothing here is listening (ADR-0024).
426
- await client.abort(agentName, instanceId, { signal: opts?.signal });
660
+ await client.abort(agentName, instanceId, {
661
+ signal: opts?.signal,
662
+ ...(opts?.cause ? { cause: opts.cause } : {}),
663
+ ...(opts?.submissionId ? { submissionId: opts.submissionId } : {}),
664
+ });
427
665
  },
428
666
  };
429
667
  }
@@ -508,22 +746,130 @@ export function createLiveSetPush(options: {
508
746
  }
509
747
 
510
748
  /**
511
- * THE authoring surface for an Agent (ADR-0049): one definition in, one actor slot out —
512
- * `jr2Setup({ actors: { coder: agent({ model, instructions }) } })`, invoked as `src: "coder"`.
513
- * The slot key is the Agent's name (the Harness route, the minted iid, the markers), so a name
514
- * the Machine does not carry is a compile error on `src`, and two Machines in one run may each
515
- * carry their own `coder`.
749
+ * Make an Agent: an actor slot that runs one Turn of a model each time a state invokes it. Put it
750
+ * under `actors` in `jr2Setup`, and invoke it by its key: `actors: { coder: agent(…) }`, then
751
+ * `invoke: { src: "coder", input: { prompt } }`. The key is the Agent's name.
752
+ *
753
+ * The invoke's `input` is the Turn: `prompt` (required), `cwd`, `model`, `thinkingLevel` and
754
+ * `continue`. `cwd` is the absolute directory where the Working tools work. Absent, it is the
755
+ * Worktree of the Workspace's one Repo Slot. `continue: true` continues the Agent's conversation
756
+ * in this Machine. Absent, each Turn starts a new conversation. `continue` is the literal `true`,
757
+ * not a `boolean`. A Turn can join only a conversation that an earlier Turn started with
758
+ * `continue: true`. Thus in a retry loop, put `continue: true` on each Turn, also on the first, so
759
+ * that attempt 2 sees attempt 1. Then a Runaway on the first Turn gets no second start, because a
760
+ * continued Turn gets none (ADR-0035). Spread it only when a state must start a clean conversation
761
+ * on some Turns only: `...(context.clean ? {} : { continue: true as const })`. A clean Turn does not
762
+ * start the continued conversation again: the next Turn with `continue: true` joins the earlier
763
+ * one. The Turn ends when the state that invoked the Agent exits, usually on a pick from the
764
+ * Agent's Menu.
516
765
  *
517
- * Everything live is constructed per-invocation from serializable input: the wire client from
518
- * `input.endpoint` (which Sandbox's Harness — or the wire-compatible dev stub; either way it's
519
- * only a URL, one code path). Lives here, not in actor.ts, so the actor logic and its unit tests
520
- * never touch the wire.
766
+ * The actor of an Agent never finishes, so an `onDone` on its invoke never runs. Give the Agent a
767
+ * pick, such as `finish`, and route on it. When a Turn fails with no pick, jr2 sends the event
768
+ * `agent.fault` to the Machine. It is an event, not an error of the invoke, so an `onError` does
769
+ * not get it. Route it on each state that invokes an Agent, or once on a state above them:
770
+ * `on: { "agent.fault": "failed" }`. `jr2 check` and `jr2 up` refuse a state that invokes an
771
+ * Agent with no route for it, and an `agent.fault` that no transition takes ends the run `error`.
521
772
  *
522
- * It takes a DECLARATION (ADR-0054), so a packaged Machine may write `agent({ model: open, … })`
523
- * and leave the one part it cannot honestly fill to whoever registers it. What rides the Turn is
524
- * still an `AgentDefinition`: the actor narrows on start and refuses an Open one there.
773
+ * An Agent with `workspace: "write"` or `"read"` works in a Sandbox, so invoke it inside a
774
+ * `workspace()` body. An Agent with `workspace: "none"` can run anywhere. A model can lack the
775
+ * definition's thinking level: see {@link ThinkingLevel}.
776
+ *
777
+ * When a Turn starts, it refuses:
778
+ * - a key in `input` that a Turn does not have: ``agent "<slot>": the Turn's input carries
779
+ * `<key>`, which a Turn has no use for — its input is its Frame (`prompt`, `cwd`), its Dials
780
+ * (`model`, `thinkingLevel`), and `continue` (ADR-0057)``
781
+ * - no `cwd` under a Workspace with more than one Repo Slot: ``turn <id>: agent "<slot>" works
782
+ * under a Workspace carrying more than one Repo Slot (<slots>) and its Turn framed no `cwd` —
783
+ * say which Worktree it works in (`cwd: context.workspace.repos.<slot>`, one of those), because
784
+ * the kit gives no slot a meaning (ADR-0051) and a Turn's Frame is where it works (ADR-0057)``
785
+ * - a `cwd` that is not an absolute path: ``agent "<slot>": the Turn's Frame carries a `cwd` that
786
+ * is not an absolute path — the Frame says WHERE this Turn works (ADR-0057), and a Turn rooted
787
+ * anywhere else would work in the wrong directory silently``. The Harness makes this refusal
788
+ * when it admits the Turn, and the error starts with `harness admission failed (400): `.
789
+ * - an Agent with Working tools outside a `workspace()`: ``turn <id>: agent "<slot>" has
790
+ * workspace: "<access>" — invoke it inside a workspace() (ambient resolution), or pass an
791
+ * explicit `endpoint` (workspace-less stub path)``
792
+ * - a `model` that is still `open`: ``agent "<slot>" has an Open model — nobody bound it; bind it
793
+ * where the Machine is registered (`customize(<machine>, { agents: { <slot>: { model:
794
+ * "<provider>/<model>" } } })`), because a packaged Machine cannot pick a model on your behalf
795
+ * (ADR-0054)``. `jr2 up` refuses this before it builds anything.
796
+ *
797
+ * @param declaration - The Agent's definition (see {@link AgentDefinition}). `model` may be `open`
798
+ * in a Machine that you publish as a package.
799
+ * @returns The actor logic of the Agent's slot.
800
+ *
801
+ * @example
802
+ * ```ts
803
+ * import { z } from "zod";
804
+ * import { agent, defineEvent, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
805
+ *
806
+ * const finish = defineEvent({
807
+ * name: "finish",
808
+ * description: "Call this when `node --test` passes.",
809
+ * input: z.object({}),
810
+ * });
811
+ * const runInput = z.object({ branch: z.string() });
812
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
813
+ *
814
+ * const body = jr2Setup({
815
+ * types: {} as { input: BodyInput; context: BodyInput },
816
+ * events: [finish],
817
+ * actors: {
818
+ * coder: agent({
819
+ * model: "anthropic/claude-haiku-5-5",
820
+ * instructions: "You fix failing tests. You change no test.",
821
+ * }),
822
+ * fail: fail(),
823
+ * },
824
+ * }).createMachine({
825
+ * id: "fix",
826
+ * context: ({ input }) => input,
827
+ * on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
828
+ * initial: "coding",
829
+ * states: {
830
+ * coding: {
831
+ * invoke: {
832
+ * src: "coder",
833
+ * input: ({ context }) => ({
834
+ * prompt: "Make `node --test` pass, then commit.",
835
+ * cwd: context.workspace.repos.app,
836
+ * thinkingLevel: "high",
837
+ * }),
838
+ * },
839
+ * on: { finish: "done" },
840
+ * },
841
+ * done: { type: "final" },
842
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
843
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
844
+ * },
845
+ * });
846
+ *
847
+ * export const machine = workspace(body, {
848
+ * input: runInput,
849
+ * repos: { app: "https://github.com/you/app.git" },
850
+ * spec: ({ input }) => ({ branch: input.branch }),
851
+ * });
852
+ * ```
853
+ *
854
+ * @see docs/concepts/agents-and-turns.md
855
+ * @see docs/guides/write-a-workflow.md
856
+ * @see docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md
857
+ * @see docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
525
858
  */
526
859
  export function agent(declaration: AgentDeclaration): AgentLogic {
860
+ // THE authoring surface for an Agent (ADR-0049): one definition in, one actor slot out. The
861
+ // slot key is the Agent's name (the Harness route, the minted iid, the markers), so a name the
862
+ // Machine does not carry is a compile error on `src`, and two Machines in one run may each carry
863
+ // their own `coder`.
864
+ //
865
+ // Everything live is constructed per-invocation from serializable input: the wire client from
866
+ // `input.endpoint` (which Sandbox's Harness — or the wire-compatible dev stub; either way it's
867
+ // only a URL, one code path). Lives here, not in actor.ts, so the actor logic and its unit
868
+ // tests never touch the wire.
869
+ //
870
+ // It takes a DECLARATION (ADR-0054), so a packaged Machine may write `agent({ model: open, … })`
871
+ // and leave the one part it cannot honestly fill to whoever registers it. What rides the Turn
872
+ // is still an `AgentDefinition`: the actor narrows on start and refuses an Open one there.
527
873
  return agentActorWith(
528
874
  (endpoint, bearer) => createHarnessAgentRunClient({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
529
875
  declaration,
@@ -600,26 +946,174 @@ export function createExecPort(options: {
600
946
  }
601
947
 
602
948
  /**
603
- * THE authoring surface for a `command` Effect (ADR-0067): `actors: { tests: command(["pnpm",
604
- * "test"]) }`, invoked as `src: "tests"`. `argv` is never shell text — an `arg("x")` entry is ONE
605
- * argument, filled from the invoke's input — and `sh -c` is the author's explicit choice. It runs
606
- * in the Harness container of the enclosing Workspace's Sandbox, in a Repo Slot's Worktree
607
- * (`slot`, needed only where the Workspace carries several); outside a Workspace it is refused.
608
- * Every outcome is output: `exited | timeout | killed | unknown`.
949
+ * Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a
950
+ * state invokes it. Put it under `actors` and invoke it by its key: `actors: { tests:
951
+ * command(["node", "--test"]) }`, then `invoke: { src: "tests", onDone: … }`.
952
+ *
953
+ * `argv` is a list, never shell text: each entry is one argument. An `arg("name")` entry is filled
954
+ * from the invoke's `input` (`input: { name: "…" }`), as one argument whatever its text. To use a
955
+ * shell, write it: `["sh", "-c", "…"]`.
956
+ *
957
+ * Where it runs:
958
+ * - With no Effect secret in `env`, in the Harness container of the enclosing Workspace's Sandbox.
959
+ * It runs in the Worktree of a Repo Slot, or in `/work` in a Workspace with no Repo Slot. The
960
+ * Agent's Working tools also run in this container.
961
+ * - With an Effect secret in `env`, in the Sandbox Effector, against a commit of the branch. This
962
+ * is the branch tip when it starts, or with `pin: true` the commit that the invoke's `input.sha`
963
+ * names. The output then says `dirty: true` when the Worktree held edits that the command did
964
+ * not see.
965
+ *
966
+ * Every outcome is output for `onDone`, never an error: `{ outcome: "exited", code, tail }`,
967
+ * `{ outcome: "timeout", tail }`, `{ outcome: "killed", signal, tail }`, or `{ outcome: "unknown",
968
+ * reason }` when jr2 cannot know what happened. `tail` is the end of the output stream.
969
+ *
970
+ * After an Orchestrator restart:
971
+ * - A command with no Effect secret in `env` never runs twice. jr2 attaches to it again, or settles
972
+ * `unknown`.
973
+ * - A command with an Effect secret in `env` runs again when the Sandbox Effector no longer holds
974
+ * it. It runs against the same commit. If jr2 has no commit for it, it settles `unknown`.
975
+ *
976
+ * When the state exits, the command stops: jr2 kills its process group. `onError` is only for a
977
+ * Machine or config defect.
978
+ *
979
+ * It refuses, when you call it:
980
+ * - an empty `argv`: `command(argv): argv is a non-empty list — each entry one argument, never
981
+ * shell text`
982
+ * - an entry that is not a string or `arg()`: `command(argv): an argv entry is a string or
983
+ * arg("name"), got <entry>`
984
+ * - a `timeout` that is not a positive duration: `command: timeout "<timeout>" is not a positive
985
+ * duration (like "90s", "10m", "1h")`
986
+ * - an `env` name that is not a variable name, or `PATH`, `HOME` or `TMPDIR`: ``command(argv, {
987
+ * env }): "<name>" is not a variable the author sets — PATH, HOME and TMPDIR are the Effector's
988
+ * (ADR-0067)``
989
+ * - `pin: true` with no Effect secret in `env`: ``command(argv, { pin: true }) names no secret in
990
+ * its env: `pin` chooses the commit a credentialed command runs against in the Sandbox
991
+ * Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its
992
+ * secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``
993
+ *
994
+ * And when it starts, with no Effect secret in `env`:
995
+ * - outside a `workspace()`: `command "<slot>" runs in the Harness container of a Workspace, and
996
+ * its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
997
+ * - no `slot` under a Workspace with more than one Repo Slot: `command "<slot>" runs under a
998
+ * Workspace carrying more than one Repo Slot (<slots>) and names none — say which Worktree it
999
+ * runs in: command(argv, { slot: "<slot>" }) (ADR-0051)`
1000
+ * - a `slot` that the Workspace does not have: `command "<slot>" names Repo Slot "<name>", which
1001
+ * its Workspace does not carry (<slots>)`
1002
+ * - an `arg()` with no string in `input`: `command "<slot>": argv slot <<name>> has no string
1003
+ * value in its input (got <type>) — map it in the invoke's input`. The angle brackets around the
1004
+ * arg's name are in the text: `argv slot <file>`. `<type>` is `nothing` when `input` has no such
1005
+ * field, or the type of its value, such as `number`.
1006
+ *
1007
+ * And when it starts, with an Effect secret in `env`:
1008
+ * - outside a `workspace()`: `command "<slot>" runs in the Sandbox Effector of a Workspace, and its
1009
+ * state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
1010
+ * - no `slot` under a Workspace with more than one Repo Slot: `command "<slot>" runs under a
1011
+ * Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" }
1012
+ * (ADR-0051)`
1013
+ * - a Workspace with no Repo Slot: `command "<slot>" runs under a Workspace with no Repo Slot: a
1014
+ * command with an Effect secret runs against one commit of a Repo Slot's branch, and there is
1015
+ * none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent
1016
+ * (ADR-0051)`. `jr2 up` refuses it first, with the same reason
1017
+ * - a `slot` that the Workspace does not have: `command "<slot>" names Repo Slot "<name>", which
1018
+ * its Workspace does not carry (<slots>)`
1019
+ * - `pin: true` and an `input.sha` that is not a full commit id: `command "<slot>" (pin): its
1020
+ * input's sha is not a full commit id`
1021
+ * - an `arg()` with no string in `input`: the same text as above.
1022
+ *
1023
+ * @param argv - The program and its arguments: strings and `arg("name")` entries. Or `open`, in a
1024
+ * Machine that you publish as a package, for the Instance that registers it to bind with
1025
+ * `customize()`.
1026
+ * @param options - Optional. `slot`: the Repo Slot whose Worktree the command runs in; needed only
1027
+ * where the Workspace has more than one. `timeout`: `"90s"`, `"10m"` or `"1h"`, default
1028
+ * `"10m"`; when it expires, the output is `timeout`. `tail`: how many characters of the end of
1029
+ * the output stream to keep, default 4096. `env`: environment variables. Each value is
1030
+ * `secret("name")` for the value of an Effect secret, or `secret("name").path` for the path of a
1031
+ * file that holds it. `pin`: `true` to run against the commit in `input.sha`. Use `pin` only
1032
+ * with an Effect secret in `env`.
1033
+ * @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
1034
+ *
1035
+ * @example
1036
+ * ```ts
1037
+ * import { z } from "zod";
1038
+ * import { arg, command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
1039
+ *
1040
+ * const runInput = z.object({ branch: z.string() });
1041
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
1042
+ *
1043
+ * const body = jr2Setup({
1044
+ * types: {} as { input: BodyInput },
1045
+ * actors: {
1046
+ * tests: command(["node", "--test", arg("file")], { timeout: "5m" }),
1047
+ * fail: fail(),
1048
+ * },
1049
+ * }).createMachine({
1050
+ * id: "check",
1051
+ * on: { "workspace.lost": ".lost" },
1052
+ * initial: "testing",
1053
+ * states: {
1054
+ * testing: {
1055
+ * invoke: {
1056
+ * src: "tests",
1057
+ * input: { file: "test/greet.test.js" },
1058
+ * onDone: [
1059
+ * {
1060
+ * guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
1061
+ * target: "passed",
1062
+ * },
1063
+ * { target: "failed" },
1064
+ * ],
1065
+ * },
1066
+ * },
1067
+ * passed: { type: "final" },
1068
+ * // Not final states: each one fails the run, and the run ends `failed`.
1069
+ * failed: { invoke: { src: "fail", input: { reason: "the test did not pass" } } },
1070
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
1071
+ * },
1072
+ * });
1073
+ *
1074
+ * export const machine = workspace(body, {
1075
+ * input: runInput,
1076
+ * repos: { app: "https://github.com/you/app.git" },
1077
+ * spec: ({ input }) => ({ branch: input.branch }),
1078
+ * });
1079
+ * ```
1080
+ *
1081
+ * @see docs/concepts/effects.md
1082
+ * @see docs/guides/run-a-command-or-call-a-url.md
1083
+ * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
1084
+ * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
1085
+ */
1086
+ export function command<const A extends readonly (string | Arg)[], const Opt extends CommandOptions = CommandOptions>(
1087
+ argv: A,
1088
+ options?: Opt,
1089
+ ): EffectLogic<CommandInput<A, Opt["pin"]>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
1090
+ /**
1091
+ * Make a `command` Effect whose argv is Open: `command(open, options)`, in a Machine that you
1092
+ * publish as a package. The Instance that registers the Machine binds the argv one time:
1093
+ * `customize(machine, { effects: { <slot>: { argv } } })`. {@link command} tells the rest, and
1094
+ * what it refuses.
1095
+ *
1096
+ * @param argv - `open`.
1097
+ * @param options - Optional. The same options as for a written argv.
1098
+ * @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
1099
+ *
1100
+ * @see docs/guides/bind-open-parts.md
1101
+ * @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
609
1102
  */
610
1103
  export function command<const Opt extends CommandOptions = CommandOptions>(
611
1104
  argv: typeof open,
612
1105
  options?: Opt,
613
1106
  ): EffectLogic<OpenCommandInput<Opt["pin"]>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>;
614
- export function command<const A extends readonly (string | Arg)[], const Opt extends CommandOptions = CommandOptions>(
615
- argv: A,
616
- options?: Opt,
617
- ): EffectLogic<CommandInput<A, Opt["pin"]>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
618
1107
  export function command(
619
1108
  argv: readonly (string | Arg)[] | typeof open,
620
1109
  options: CommandOptions = {},
621
1110
  // The implementation of the overloads above: each one states the input type.
622
1111
  ): EffectLogic<any, CommandOutput> {
1112
+ // THE authoring surface for a `command` Effect (ADR-0067). `argv` is never shell text — an
1113
+ // `arg("x")` entry is ONE argument, filled from the invoke's input — and `sh -c` is the author's
1114
+ // explicit choice. Bound to the real exec port here; `commandWith` in effect.ts is the seam the
1115
+ // unit tests share.
1116
+ //
623
1117
  // An Open argv (ADR-0054) is the composer's to bind with `customize()`; see open-effect.ts.
624
1118
  return commandWith(
625
1119
  (endpoint, bearer) => createExecPort({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),