@jr2/orchestrator 0.4.1 → 0.6.0

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 (361) 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 +478 -0
  10. package/docs/concepts/architecture.md +243 -0
  11. package/docs/concepts/charts.md +146 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +257 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +658 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +298 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +306 -0
  20. package/docs/concepts/the-menu.md +381 -0
  21. package/docs/concepts/workspaces.md +756 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
  27. package/docs/get-started/install.md +130 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +308 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +224 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/cut-a-worktree.md +560 -0
  34. package/docs/guides/fix-a-failed-converge.md +361 -0
  35. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  36. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  37. package/docs/guides/let-an-agent-write-a-chart.md +230 -0
  38. package/docs/guides/look-inside-a-sandbox.md +177 -0
  39. package/docs/guides/mirror-the-kit-images.md +176 -0
  40. package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
  41. package/docs/guides/open-the-console.md +91 -0
  42. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  43. package/docs/guides/push-and-sign-a-branch.md +532 -0
  44. package/docs/guides/run-a-chart-by-name.md +270 -0
  45. package/docs/guides/run-a-command-or-call-a-url.md +587 -0
  46. package/docs/guides/run-one-worker-per-item.md +322 -0
  47. package/docs/guides/set-an-agents-access-and-dials.md +420 -0
  48. package/docs/guides/size-a-workspace.md +217 -0
  49. package/docs/guides/start-a-run-from-a-run.md +237 -0
  50. package/docs/guides/start-a-run.md +207 -0
  51. package/docs/guides/upgrade-the-kit-version.md +240 -0
  52. package/docs/guides/use-a-hosted-provider.md +214 -0
  53. package/docs/guides/use-a-local-model.md +313 -0
  54. package/docs/guides/watch-a-run.md +349 -0
  55. package/docs/guides/write-a-workflow.md +318 -0
  56. package/docs/index.md +44 -0
  57. package/docs/llms.txt +237 -0
  58. package/docs/reference/authoring-surface/agent.md +151 -0
  59. package/docs/reference/authoring-surface/answer.md +156 -0
  60. package/docs/reference/authoring-surface/arg.md +82 -0
  61. package/docs/reference/authoring-surface/basic.md +62 -0
  62. package/docs/reference/authoring-surface/bearer.md +53 -0
  63. package/docs/reference/authoring-surface/branchname.md +15 -0
  64. package/docs/reference/authoring-surface/chartschema.md +23 -0
  65. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  66. package/docs/reference/authoring-surface/command.md +206 -0
  67. package/docs/reference/authoring-surface/customize.md +64 -0
  68. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  69. package/docs/reference/authoring-surface/defineevent.md +114 -0
  70. package/docs/reference/authoring-surface/dispatch.md +126 -0
  71. package/docs/reference/authoring-surface/fail.md +96 -0
  72. package/docs/reference/authoring-surface/file.md +142 -0
  73. package/docs/reference/authoring-surface/frompick.md +277 -0
  74. package/docs/reference/authoring-surface/http.md +259 -0
  75. package/docs/reference/authoring-surface/isfailure.md +58 -0
  76. package/docs/reference/authoring-surface/isready.md +14 -0
  77. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  78. package/docs/reference/authoring-surface/open.md +105 -0
  79. package/docs/reference/authoring-surface/openbody.md +86 -0
  80. package/docs/reference/authoring-surface/paramsof.md +143 -0
  81. package/docs/reference/authoring-surface/pool.md +186 -0
  82. package/docs/reference/authoring-surface/push.md +130 -0
  83. package/docs/reference/authoring-surface/readyof.md +21 -0
  84. package/docs/reference/authoring-surface/secret.md +139 -0
  85. package/docs/reference/authoring-surface/sentby.md +83 -0
  86. package/docs/reference/authoring-surface/source.md +51 -0
  87. package/docs/reference/authoring-surface/text.md +62 -0
  88. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  89. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  90. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  91. package/docs/reference/authoring-surface/type-agentslots.md +15 -0
  92. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  93. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  94. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  95. package/docs/reference/authoring-surface/type-arg.md +19 -0
  96. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  97. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  98. package/docs/reference/authoring-surface/type-binding.md +61 -0
  99. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  100. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  101. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  102. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  103. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  104. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  105. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  106. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  107. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  108. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  109. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  110. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  111. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  112. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  113. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  114. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  115. package/docs/reference/authoring-surface/type-childat.md +12 -0
  116. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  117. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  118. package/docs/reference/authoring-surface/type-commandinput.md +20 -0
  119. package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
  120. package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
  121. package/docs/reference/authoring-surface/type-customize.md +111 -0
  122. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  123. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  124. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  125. package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
  126. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  127. package/docs/reference/authoring-surface/type-duration.md +15 -0
  128. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  129. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  130. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  131. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  132. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  133. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  134. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  135. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  136. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  137. package/docs/reference/authoring-surface/type-failure.md +19 -0
  138. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  139. package/docs/reference/authoring-surface/type-filebody.md +37 -0
  140. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  141. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  142. package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
  143. package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
  144. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  145. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  146. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  147. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
  149. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  150. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  151. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  152. package/docs/reference/authoring-surface/type-lends.md +38 -0
  153. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  154. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  155. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  156. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  157. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  158. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
  159. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  160. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  161. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  162. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  163. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  164. package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
  165. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  166. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  167. package/docs/reference/authoring-surface/type-openof.md +12 -0
  168. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  169. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  170. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
  171. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
  172. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  173. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  174. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  175. package/docs/reference/authoring-surface/type-pushinput.md +34 -0
  176. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  177. package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
  178. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  179. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  180. package/docs/reference/authoring-surface/type-reached.md +12 -0
  181. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  182. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  183. package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
  184. package/docs/reference/authoring-surface/type-secret.md +25 -0
  185. package/docs/reference/authoring-surface/type-sender.md +19 -0
  186. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  187. package/docs/reference/authoring-surface/type-size.md +85 -0
  188. package/docs/reference/authoring-surface/type-slothandles.md +24 -0
  189. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  190. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  191. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  192. package/docs/reference/authoring-surface/type-text.md +19 -0
  193. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  194. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  195. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  196. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  197. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  198. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  199. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  200. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  201. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  202. package/docs/reference/authoring-surface/type-workspaced.md +115 -0
  203. package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
  204. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  205. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  206. package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
  207. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  208. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  209. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  210. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  211. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  212. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  213. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  214. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  215. package/docs/reference/authoring-surface/workspace.md +287 -0
  216. package/docs/reference/authoring-surface/worktree.md +71 -0
  217. package/docs/reference/authoring-surface.md +217 -0
  218. package/docs/reference/cli.md +433 -0
  219. package/docs/reference/config.md +765 -0
  220. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
  221. package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
  222. package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
  223. package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
  224. package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
  225. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  226. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  227. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  228. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  229. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  230. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  231. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
  232. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  233. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
  234. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
  235. package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
  236. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  237. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
  238. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  239. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
  240. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  241. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
  242. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
  243. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  244. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  245. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
  246. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
  247. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  248. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  249. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
  250. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  251. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
  252. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  253. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  254. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  255. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  256. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  257. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  258. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  259. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  260. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  261. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  262. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  263. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  264. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  265. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  266. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  267. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  268. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
  269. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
  270. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
  271. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  272. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  273. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  274. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  275. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
  276. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
  277. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  278. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  279. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
  280. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
  281. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  282. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  283. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
  284. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
  285. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
  286. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  287. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
  288. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  289. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  290. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  291. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  292. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  293. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  294. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  295. package/docs/reference/decisions.md +86 -0
  296. package/docs/reference/glossary.md +687 -0
  297. package/docs/reference/http-api.md +1386 -0
  298. package/docs/reference/machines.md +189 -0
  299. package/docs/reference/mechanism-events.md +72 -0
  300. package/docs/why.md +207 -0
  301. package/package.json +5 -3
  302. package/src/actor.ts +694 -106
  303. package/src/agent.ts +114 -38
  304. package/src/ambient.ts +3 -4
  305. package/src/assembly-checks.ts +189 -7
  306. package/src/cause.ts +234 -0
  307. package/src/chart.ts +345 -46
  308. package/src/charts.ts +43 -8
  309. package/src/config.ts +440 -178
  310. package/src/custodian.lua +17 -5
  311. package/src/custodian.ts +325 -99
  312. package/src/customize.ts +199 -49
  313. package/src/dispatch-effect.ts +945 -0
  314. package/src/door-compat.ts +217 -0
  315. package/src/durability.ts +11 -4
  316. package/src/effect-checks.ts +50 -3
  317. package/src/effect.ts +641 -128
  318. package/src/effector-wire.ts +165 -13
  319. package/src/fail.ts +222 -0
  320. package/src/harness-client.ts +587 -50
  321. package/src/harness-env.ts +33 -0
  322. package/src/held-secrets.ts +125 -16
  323. package/src/holds.ts +3 -0
  324. package/src/http-effect.ts +556 -58
  325. package/src/http.ts +480 -30
  326. package/src/index.ts +74 -82
  327. package/src/instance.ts +68 -8
  328. package/src/internal.ts +85 -0
  329. package/src/kube-client.ts +6 -3
  330. package/src/machine-doc.ts +24 -7
  331. package/src/mechanism-routes.ts +168 -0
  332. package/src/model-keys.generated.ts +53 -0
  333. package/src/model-keys.ts +42 -58
  334. package/src/names.ts +19 -6
  335. package/src/notices.ts +483 -41
  336. package/src/open-body.ts +242 -40
  337. package/src/open-effect.ts +81 -8
  338. package/src/open.ts +90 -6
  339. package/src/parts.ts +213 -30
  340. package/src/party.ts +101 -2
  341. package/src/pick.ts +497 -11
  342. package/src/pool.ts +282 -50
  343. package/src/registration.ts +133 -12
  344. package/src/repo-identity.ts +7 -3
  345. package/src/repos.ts +22 -3
  346. package/src/run-host.ts +665 -45
  347. package/src/run-output.ts +38 -0
  348. package/src/sandbox-effect.ts +457 -71
  349. package/src/sandbox-kube.ts +66 -17
  350. package/src/secret-placement.ts +59 -17
  351. package/src/server.ts +3 -0
  352. package/src/setup.ts +325 -62
  353. package/src/size.ts +121 -9
  354. package/src/snapshot-store.ts +54 -10
  355. package/src/stub-harness.ts +44 -16
  356. package/src/timers.ts +14 -1
  357. package/src/tokens.ts +107 -32
  358. package/src/vocabulary.ts +88 -14
  359. package/src/wire.ts +100 -26
  360. package/src/workspace.ts +771 -238
  361. package/src/worktree.ts +249 -0
@@ -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
@@ -97,23 +107,50 @@ export type HarnessClient = {
97
107
  * `/work` — the stub-Harness run with no Workspace. */
98
108
  cwd?: string;
99
109
  definition: AgentDefinition;
110
+ /** The Agent's slot (ADR-0076), which the Harness states on each control request for this
111
+ * conversation. Omitted only by a caller that drives no Machine (a test of the wire). */
112
+ slot?: string;
100
113
  model?: string;
101
114
  thinkingLevel?: ThinkingLevel;
102
115
  /** The pending notices in this admission's scope (ADR-0062). Omitted when there are none. */
103
116
  notices?: Notice[];
117
+ /** The admission is for a conversation this process was told the Harness keeps (ADR-0062).
118
+ * The Harness that keeps none answers 404 and seats none: this verb throws
119
+ * {@link ConversationNotKept}. */
120
+ kept?: boolean;
104
121
  signal?: AbortSignal;
105
122
  },
106
123
  ): 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). */
124
+ /** Follow the stream to this Submission's Settlement: resolve on `completed` alone — the run
125
+ * returned with the Turn still open, which the Agent actor answers with the nudge or the wake —
126
+ * and reject with `SettlementFault` on `failed`/`ended`/`aborted`/404. `ended` is no fault, and no
127
+ * live actor reads it: a Submission settles `ended` only after the Turn's own pick took the
128
+ * Machine out of the Agent's state. That stopped the actor that waits on it (ADR-0024), and a
129
+ * stopped actor drops every rejection. A guard kill read on the way is handed to `onMemoryLimit` (ADR-0061). */
110
130
  wait(admission: AgentAdmission, opts?: AgentSettleOptions): Promise<void>;
111
131
  /** `GET /agents/:name/:id?view=history`: does the Harness still hold this conversation? Only a
112
132
  * 404 says no (ADR-0027: a GET on an unknown conversation is 404); anything else — another
113
133
  * status, a transport failure — says yes, because unknown is never loss (ADR-0062). */
114
134
  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>;
135
+ /** `GET /memory-kills`: the pod's guard kills (ADR-0061, ADR-0062). A request that never left
136
+ * this host, and a draining Harness's 503, are sent again until `signal` ends the read — the
137
+ * caller owns the read's limit (the Agent actor's 5 s); with no signal, it is sent once. Any other
138
+ * answer but a 200 that reads — a Harness that keeps none, a transport failure, the caller's
139
+ * timeout — is `undefined`, "could not tell": the Harness still holds what it kept, and the next
140
+ * admission reads again. A kill with no id or time, with an id or a time outside the wire's bounds
141
+ * (`wire.ts`), or with a time that does not parse, is skipped; one whose `peak` or `limit` is not
142
+ * a size is kept without sizes, and one whose `told` holds an entry that is not a conversation, or
143
+ * a name outside the bounds, is kept without that entry — each fault said. Absent sizes are no fault: the pod left them out.
144
+ * Each fault is one line, said when it starts. A read that works says nothing, and the next fault
145
+ * after it is said again. */
146
+ memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined>;
147
+ /** `POST /agents/:name/:id/abort`, with the body `{ cause, submissionId }` when a cause is given
148
+ * (`AbortRequest`). The `{ aborted }` answer is dropped (ADR-0024). */
149
+ abort(
150
+ agentName: string,
151
+ instanceId: string,
152
+ opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
153
+ ): Promise<void>;
117
154
  };
118
155
 
119
156
  export type HarnessClientOptions = {
@@ -148,8 +185,9 @@ export type HarnessClientOptions = {
148
185
  log?: (line: string) => void;
149
186
  };
150
187
 
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. */
188
+ /** A Submission that settled `failed`/`ended`/`aborted` — or whose conversation the Harness no
189
+ * longer has (404) — surfaced to the actor as an `agent.fault` while it still waits on the Turn.
190
+ * Replaces the SDK's execution error. */
153
191
  export class SettlementFault extends Error {
154
192
  /** The Settlement as the stream carried it; absent when the conversation itself was lost. */
155
193
  readonly settlement?: Settlement;
@@ -164,6 +202,83 @@ export class SettlementFault extends Error {
164
202
  }
165
203
  }
166
204
 
205
+ /** An admission for a kept conversation (`kept`) that the Harness refused with 404: it keeps no
206
+ * such conversation, and it seated none (ADR-0062). `notKept` is what the Agent actor reads, as it
207
+ * reads `lost`: that module is wire-free. */
208
+ export class ConversationNotKept extends Error {
209
+ readonly notKept = true;
210
+ constructor(message: string) {
211
+ super(message);
212
+ this.name = "ConversationNotKept";
213
+ }
214
+ }
215
+
216
+ /** The Harnesses whose memory kills this process could not read, each said once (`memoryKills`).
217
+ * Per process, not per client: an Agent actor builds a client for each Turn. */
218
+ const killReadFaults = new Set<string>();
219
+
220
+ /** The Harnesses whose kills — on the list or on a stream — held a field this process could not
221
+ * use, each said once until a clean read. Per process, like `killReadFaults`. Neither set is cleaned
222
+ * when a Workspace goes: no hook reaches this module from the Sandbox's destroy, and an entry is
223
+ * one URL per Sandbox this process ever read. */
224
+ const badKillFaults = new Set<string>();
225
+
226
+ /** Say once, until a clean read, that a Harness's kills held a field this process could not use. */
227
+ function badKill(baseUrl: string, log: (line: string) => void, what: string): void {
228
+ if (badKillFaults.has(baseUrl)) return;
229
+ badKillFaults.add(baseUrl);
230
+ log(`harness ${baseUrl}: ${what} (ADR-0062)`);
231
+ }
232
+
233
+ /** A size as the guard writes one (`1.9Gi`, `512Mi`), short. The Agent's uid can write the file
234
+ * the list is read from, and these words go into the next Agent's Briefing as they are, so
235
+ * nothing else passes (ADR-0062). */
236
+ function isSize(value: unknown): value is string {
237
+ return typeof value === "string" && value.length <= 16 && /^\d{1,6}(\.\d{1,3})?(Ki|Mi|Gi|Ti)$/.test(value);
238
+ }
239
+
240
+ /** A string no longer than `max` characters. The bounds are the wire's (`wire.ts`), the same as
241
+ * the Harness's: the Harness counts the bytes of the JSON text, which are never fewer, so a kill it
242
+ * keeps is one this process keeps. An answer larger than `MAX_KILLS_BODY` is no list to act on:
243
+ * the Harness answers no list that large. */
244
+ const fits = (v: unknown, max: number): v is string => typeof v === "string" && v.length <= max;
245
+
246
+ /** One kill of the list as this process can use it, or undefined when it has no id or time, when
247
+ * its id or time is outside the wire's bounds, or when its time does not parse — the only kills
248
+ * that are dropped. Sizes that are not sizes are left out, so the kill is kept with no sizes; a
249
+ * `told` entry that is not a conversation, or whose name is outside the bounds, is left out of
250
+ * `told`. */
251
+ function listedKill(value: unknown): ListedKill | undefined {
252
+ if (typeof value !== "object" || value === null) return undefined;
253
+ const { id, at, peak, limit, told } = value as Record<string, unknown>;
254
+ if (!fits(id, MAX_KILL_STAMP) || !fits(at, MAX_KILL_STAMP) || Number.isNaN(Date.parse(at))) return undefined;
255
+ const conversations = (Array.isArray(told) ? told.slice(0, MAX_TOLD) : []).filter(
256
+ (t): t is { agent: string; instanceId: string } =>
257
+ typeof t === "object" && t !== null && fits(t.agent, MAX_KILL_NAME) && fits(t.instanceId, MAX_KILL_NAME),
258
+ );
259
+ const sized = isSize(peak) && isSize(limit);
260
+ return {
261
+ id,
262
+ at,
263
+ told: conversations.map(({ agent, instanceId }) => ({ agent, instanceId })),
264
+ ...(sized ? { peak, limit } : {}),
265
+ };
266
+ }
267
+
268
+ /** Whether a listed kill came through as the wire states it. */
269
+ function whole(raw: unknown, kill: ListedKill | undefined): boolean {
270
+ if (kill === undefined) return false;
271
+ const { peak, limit, told } = raw as { peak?: unknown; limit?: unknown; told?: unknown };
272
+ // Sizes the pod left out are no fault; sizes it sent that are not sizes are.
273
+ if (kill.limit === undefined && (peak !== undefined || limit !== undefined)) return false;
274
+ return Array.isArray(told) && Math.min(told.length, MAX_TOLD) === kill.told.length && told.length <= MAX_TOLD;
275
+ }
276
+
277
+ /** A timeout's abort reason (`AbortSignal.timeout`): a read that ran out of time, not an abandon. */
278
+ function isTimeout(reason: unknown): boolean {
279
+ return reason instanceof DOMException && reason.name === "TimeoutError";
280
+ }
281
+
167
282
  /** Build the real wire client from connection options. */
168
283
  export function createHarnessClient(options: HarnessClientOptions): HarnessClient {
169
284
  const { baseUrl } = options;
@@ -270,17 +385,34 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
270
385
  // `machine-doc.ts` and the surface read it — and the Harness neither validates nor
271
386
  // uses it, so it has no business on the Turn's wire.
272
387
  definition: admittedDefinition(sendOptions.definition),
388
+ // Who the conversation is (ADR-0076): the Harness states it on each control request.
389
+ ...(sendOptions.slot === undefined ? {} : { slot: sendOptions.slot }),
273
390
  // Omitted when unset, so an admission with no dials is byte-identical to before.
274
391
  ...(sendOptions.model ? { model: sendOptions.model } : {}),
275
392
  ...(sendOptions.thinkingLevel ? { thinkingLevel: sendOptions.thinkingLevel } : {}),
276
393
  // The Briefing's news (ADR-0062): typed data, the Harness writes the words. Omitted
277
394
  // when there is none, so an admission without notices is byte-identical to before.
278
395
  ...(sendOptions.notices?.length ? { notices: sendOptions.notices } : {}),
396
+ // Omitted when unset, so an admission that is not for a kept conversation is
397
+ // byte-identical to before.
398
+ ...(sendOptions.kept ? { kept: true } : {}),
279
399
  }),
280
400
  signal: sendOptions.signal,
281
401
  },
282
402
  sendOptions.signal,
283
403
  );
404
+ if (sendOptions.kept && res.status === 404) {
405
+ // "Not kept" only with the Harness's marker: a 404 from anything else — a proxy, a route
406
+ // that moved — is a failed admission, as before.
407
+ const body = (await res.json().catch(() => undefined)) as Partial<NotKeptResponse> | undefined;
408
+ const detail = typeof body?.error === "string" ? body.error : res.statusText || "no detail";
409
+ if (body?.kept === false) {
410
+ throw new ConversationNotKept(
411
+ `harness admission refused (404): ${detail} — the Harness does not keep this conversation (ADR-0062)`,
412
+ );
413
+ }
414
+ throw new Error(`harness admission failed (404): ${detail}`);
415
+ }
284
416
  if (!res.ok) {
285
417
  throw new Error(`harness admission failed (${res.status}): ${await errorDetail(res)}`);
286
418
  }
@@ -340,8 +472,19 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
340
472
 
341
473
  for (const event of events) {
342
474
  if (event.type === "memory-limit") {
343
- const { peak, limit, conversationId, position } = event;
344
- opts?.onMemoryLimit?.({ peak, limit, source: `${conversationId}@${position.batch}.${position.index}` });
475
+ const { id, peak, limit, conversationId, position } = event;
476
+ // A size that is not one is not put in a Briefing (the Agent's uid can write the record):
477
+ // the kill is kept, with no sizes, and the fault is said.
478
+ const sized = isSize(peak) && isSize(limit);
479
+ if (!sized)
480
+ badKill(baseUrl, log, "a memory kill on a stream has sizes that are not sizes — kept without them");
481
+ // An older Harness's event has no id: keyed by its place on the stream, exactly as the
482
+ // Orchestrator before raised it, so a replayed read is the same kill and it can never
483
+ // mark a later kill seen. An id too long to be one is no id.
484
+ const kill = fits(id, MAX_KILL_STAMP)
485
+ ? { id }
486
+ : { id: `${conversationId}@${position.batch}.${position.index}`, legacy: true as const };
487
+ opts?.onMemoryLimit?.({ ...kill, ...(sized ? { peak, limit } : {}) });
345
488
  continue;
346
489
  }
347
490
  if (event.type !== "submission-settled" || event.submissionId !== admission.submissionId) continue;
@@ -355,21 +498,108 @@ export function createHarnessClient(options: HarnessClientOptions): HarnessClien
355
498
  async holds(agentName, instanceId, opts) {
356
499
  const url = new URL(conversationUrl(agentName, instanceId));
357
500
  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;
501
+ // Asked as a restart is seen, when the new container may not listen yet: a request that
502
+ // never left this host, and a draining Harness's 503, are sent again until the caller's
503
+ // signal ends the wait — then, and on any other failure, unknown is never loss.
504
+ let backoffMs = backoffInitialMs;
505
+ for (;;) {
506
+ let again = false;
507
+ try {
508
+ const res = await fetchImpl(url.toString(), { headers: authorization, signal: opts?.signal });
509
+ // Drain the body so the socket is released; the snapshot itself is not the question.
510
+ await res.body?.cancel().catch(() => undefined);
511
+ if (res.status !== DRAINING) return res.status !== 404;
512
+ again = true;
513
+ } catch (err) {
514
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
515
+ again = !opts?.signal?.aborted && neverDelivered(err);
516
+ }
517
+ if (!again || opts?.signal === undefined) return true;
518
+ try {
519
+ await sleep(jittered(backoffMs), opts.signal);
520
+ } catch (err) {
521
+ if (opts.signal.aborted && !isTimeout(opts.signal.reason)) throw err;
522
+ return true;
523
+ }
524
+ backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
525
+ }
526
+ },
527
+
528
+ async memoryKills(opts) {
529
+ const url = new URL("/memory-kills", baseUrl).toString();
530
+ // Idempotent, so re-sent on what `postAdmission` re-sends — a request that never left this
531
+ // host, a draining Harness's 503 — on the same ladder, until the caller's signal ends it. The
532
+ // limit is the caller's alone, so there is one clock for it.
533
+ let backoffMs = backoffInitialMs;
534
+ let why: string;
535
+ for (;;) {
536
+ let again = false;
537
+ try {
538
+ const res = await fetchImpl(url, { headers: authorization, signal: opts?.signal });
539
+ if (res.status === 200 && Number(res.headers.get("content-length") ?? 0) > MAX_KILLS_BODY) {
540
+ await res.body?.cancel().catch(() => undefined);
541
+ why = `the answer is larger than ${MAX_KILLS_BODY} bytes`;
542
+ } else if (res.status === 200) {
543
+ const text = await res.text();
544
+ const kills =
545
+ text.length > MAX_KILLS_BODY ? undefined : (JSON.parse(text) as Partial<MemoryKillsResponse>).kills;
546
+ if (Array.isArray(kills)) {
547
+ // A read that works ends the fault: the next fault is said again.
548
+ killReadFaults.delete(baseUrl);
549
+ const read = kills.map(listedKill);
550
+ const usable = read.filter((kill): kill is ListedKill => kill !== undefined);
551
+ const flawed = kills.filter((raw, i) => !whole(raw, read[i])).length;
552
+ if (flawed === 0) badKillFaults.delete(baseUrl);
553
+ else {
554
+ badKill(
555
+ baseUrl,
556
+ log,
557
+ `${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`,
558
+ );
559
+ }
560
+ return usable;
561
+ }
562
+ why = "the answer is not a list of kills";
563
+ } else {
564
+ why = `HTTP ${res.status}: ${await errorDetail(res)}`;
565
+ again = res.status === DRAINING;
566
+ }
567
+ } catch (err) {
568
+ // A local abandon propagates, as in every other verb; a timeout the caller set is none.
569
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
570
+ why = transportDetail(err);
571
+ again = !opts?.signal?.aborted && neverDelivered(err);
572
+ }
573
+ if (!again || opts?.signal === undefined) break;
574
+ try {
575
+ await sleep(jittered(backoffMs), opts?.signal);
576
+ } catch (err) {
577
+ if (opts?.signal?.aborted && !isTimeout(opts.signal.reason)) throw err;
578
+ why = "the read's time ran out";
579
+ break;
580
+ }
581
+ backoffMs = Math.min(backoffMs * 2, backoffMaxMs);
582
+ }
583
+ // One line for each fault: said when it starts. A read that works says nothing, and the next
584
+ // fault after it is said again.
585
+ if (!killReadFaults.has(baseUrl)) {
586
+ killReadFaults.add(baseUrl);
587
+ log(
588
+ `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)`,
589
+ );
366
590
  }
591
+ return undefined;
367
592
  },
368
593
 
369
594
  async abort(agentName, instanceId, opts) {
595
+ // No cause, no body: the request an Orchestrator from before `cause` sent, byte for byte.
596
+ const body: AbortRequest | undefined = opts?.cause
597
+ ? { cause: opts.cause, ...(opts.submissionId ? { submissionId: opts.submissionId } : {}) }
598
+ : undefined;
370
599
  const res = await fetchImpl(`${conversationUrl(agentName, instanceId)}/abort`, {
371
600
  method: "POST",
372
- headers: authorization,
601
+ headers: body ? { ...authorization, "content-type": "application/json" } : authorization,
602
+ ...(body ? { body: JSON.stringify(body) } : {}),
373
603
  signal: opts?.signal,
374
604
  });
375
605
  if (!res.ok) {
@@ -404,9 +634,11 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
404
634
  // to the Harness's refusal if it is not a path.
405
635
  ...(input.cwd === undefined ? {} : { cwd: input.cwd }),
406
636
  definition: opts.definition,
637
+ slot: opts.slot,
407
638
  model: input.model,
408
639
  thinkingLevel: input.thinkingLevel,
409
640
  ...(opts.notices?.length ? { notices: opts.notices } : {}),
641
+ ...(opts.kept ? { kept: true } : {}),
410
642
  signal: opts.signal,
411
643
  });
412
644
  },
@@ -419,11 +651,23 @@ export function harnessAgentRunPort(client: HarnessClient): AgentRunPort {
419
651
  return await client.holds(agentName, instanceId, opts);
420
652
  },
421
653
 
422
- async abort(agentName: string, instanceId: string, opts?: { signal?: AbortSignal }): Promise<void> {
654
+ async memoryKills(opts?: { signal?: AbortSignal }): Promise<ListedKill[] | undefined> {
655
+ return await client.memoryKills(opts);
656
+ },
657
+
658
+ async abort(
659
+ agentName: string,
660
+ instanceId: string,
661
+ opts?: { signal?: AbortSignal; cause?: AbortCause; submissionId?: string },
662
+ ): Promise<void> {
423
663
  // The result (`{ aborted }` — whether there was work to end) is dropped: by the time this
424
664
  // runs the state has already moved on, and an idle instance is exactly as fine as a
425
665
  // stopped one. Settlement is asynchronous and nothing here is listening (ADR-0024).
426
- await client.abort(agentName, instanceId, { signal: opts?.signal });
666
+ await client.abort(agentName, instanceId, {
667
+ signal: opts?.signal,
668
+ ...(opts?.cause ? { cause: opts.cause } : {}),
669
+ ...(opts?.submissionId ? { submissionId: opts.submissionId } : {}),
670
+ });
427
671
  },
428
672
  };
429
673
  }
@@ -508,22 +752,152 @@ export function createLiveSetPush(options: {
508
752
  }
509
753
 
510
754
  /**
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`.
516
- *
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.
521
- *
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.
755
+ * Make an Agent: an actor slot that runs one Turn of a model each time a state invokes it. Put it
756
+ * under `actors` in `jr2Setup`, and invoke it by its key: `actors: { coder: agent(…) }`, then
757
+ * `invoke: { src: "coder", input: { prompt } }`. The key is the Agent's name.
758
+ *
759
+ * The invoke's `input` is the Turn: `prompt` (required), `cwd`, `model`, `thinkingLevel` and
760
+ * `continue`. `cwd` is the absolute directory where the Working tools work. Under Repo Slots it
761
+ * is required: a Worktree that a cut made, `<slot>/default` to read, or `/work`. An Agent with
762
+ * `workspace: "none"`, and a Workspace with no Repo Slot, need no `cwd`. `continue: true` continues the Agent's conversation
763
+ * in this Machine. Absent, each Turn starts a new conversation. `continue` is the literal `true`,
764
+ * not a `boolean`. A Turn can join only a conversation that an earlier Turn started with
765
+ * `continue: true`. Thus in a retry loop, put `continue: true` on each Turn, also on the first, so
766
+ * that attempt 2 sees attempt 1. Then a Runaway on the first Turn gets no second start, because a
767
+ * continued Turn gets none (ADR-0035). Spread it only when a state must start a clean conversation
768
+ * on some Turns only: `...(context.clean ? {} : { continue: true as const })`. A clean Turn does not
769
+ * start the continued conversation again: the next Turn with `continue: true` joins the earlier
770
+ * one. The Turn ends when the state that invoked the Agent exits, usually on a pick from the
771
+ * Agent's Menu.
772
+ *
773
+ * The actor of an Agent never finishes, so an `onDone` on its invoke never runs. Give the Agent a
774
+ * pick, such as `finish`, and route on it. When a Turn fails with no pick, jr2 sends the event
775
+ * `agent.fault` to the Machine. It is an event, not an error of the invoke, so an `onError` does
776
+ * not get it. Route it on each state that invokes an Agent, or once on a state above them:
777
+ * `on: { "agent.fault": "failed" }`. `jr2 check` and `jr2 up` refuse a state that invokes an
778
+ * Agent with no route for it, and an `agent.fault` that no transition takes ends the run `error`.
779
+ *
780
+ * An Agent with `workspace: "write"` or `"read"` works in a Sandbox, so invoke it inside a
781
+ * `workspace()` body. An Agent with `workspace: "none"` can run anywhere. A model can lack the
782
+ * definition's thinking level: see {@link ThinkingLevel}.
783
+ *
784
+ * When a Turn starts, it refuses:
785
+ * - a key in `input` that a Turn does not have: ``agent "<slot>": the Turn's input carries
786
+ * `<key>`, which a Turn has no use for — its input is its Frame (`prompt`, `cwd`), its Dials
787
+ * (`model`, `thinkingLevel`), and `continue` (ADR-0057)``
788
+ * - no `cwd` under Repo Slots: ``turn <id>: agent "<slot>" works under a Workspace with Repo Slots
789
+ * (<slots>) and its Turn framed no `cwd`: say where it works, a Worktree, or `cwd: "/work"` above
790
+ * them (ADR-0057)``
791
+ * - a `cwd` that is not an absolute path: ``agent "<slot>": the Turn's Frame carries a `cwd` that
792
+ * is not an absolute path — the Frame says WHERE this Turn works (ADR-0057), and a Turn rooted
793
+ * anywhere else would work in the wrong directory silently``. The Harness makes this refusal
794
+ * when it admits the Turn, and the error starts with `harness admission failed (400): `.
795
+ * - an Agent with Working tools outside a `workspace()`: ``turn <id>: agent "<slot>" has
796
+ * workspace: "<access>" — invoke it inside a workspace() (ambient resolution), or pass an
797
+ * explicit `endpoint` (workspace-less stub path)``
798
+ * - a `model` that is still `open`: ``agent "<slot>" has an Open model — nobody bound it; bind it
799
+ * where the Machine is registered (`customize(<machine>, { agents: { <slot>: { model:
800
+ * "<provider>/<model>" } } })`), because a packaged Machine cannot pick a model on your behalf
801
+ * (ADR-0054)``. `jr2 up` refuses this before it builds anything.
802
+ *
803
+ * @param declaration - The Agent's definition (see {@link AgentDefinition}). `model` may be `open`
804
+ * in a Machine that you publish as a package.
805
+ * @returns The actor logic of the Agent's slot.
806
+ *
807
+ * @example
808
+ * ```ts
809
+ * import { z } from "zod";
810
+ * import {
811
+ * agent,
812
+ * defineEvent,
813
+ * fail,
814
+ * isReady,
815
+ * jr2Setup,
816
+ * workspace,
817
+ * worktree,
818
+ * type Workspaced,
819
+ * } from "@jr2/orchestrator";
820
+ *
821
+ * const finish = defineEvent({
822
+ * name: "finish",
823
+ * description: "Call this when `node --test` passes.",
824
+ * input: z.object({}),
825
+ * });
826
+ * const runInput = z.object({ branch: z.string() });
827
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
828
+ *
829
+ * const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
830
+ *
831
+ * const body = jr2Setup({
832
+ * types: {} as { input: BodyInput; context: BodyInput },
833
+ * events: [finish],
834
+ * actors: {
835
+ * coder: agent({
836
+ * model: "anthropic/claude-haiku-5-5",
837
+ * instructions: "You fix failing tests. You change no test.",
838
+ * }),
839
+ * cut: worktree(),
840
+ * fail: fail(),
841
+ * },
842
+ * }).createMachine({
843
+ * id: "fix",
844
+ * context: ({ input }) => input,
845
+ * on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
846
+ * initial: "cutting",
847
+ * states: {
848
+ * cutting: {
849
+ * invoke: {
850
+ * src: "cut",
851
+ * input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
852
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
853
+ * },
854
+ * },
855
+ * coding: {
856
+ * invoke: {
857
+ * src: "coder",
858
+ * input: ({ context }) => ({
859
+ * prompt: "Make `node --test` pass, then commit.",
860
+ * cwd: fixTree(context),
861
+ * thinkingLevel: "high",
862
+ * }),
863
+ * },
864
+ * on: { finish: "done" },
865
+ * },
866
+ * done: { type: "final" },
867
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
868
+ * failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
869
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
870
+ * },
871
+ * });
872
+ *
873
+ * export const machine = workspace(body, {
874
+ * input: runInput,
875
+ * repos: { app: "https://github.com/you/app.git" },
876
+ * });
877
+ * ```
878
+ *
879
+ * @see docs/concepts/agents-and-turns.md
880
+ * @see docs/guides/write-a-workflow.md
881
+ * @see docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md
882
+ * @see docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
525
883
  */
884
+ export function agent<const D extends AgentDeclaration>(
885
+ declaration: D,
886
+ ): AgentLogic & (D extends { workspace: "none" } ? { readonly definition: { readonly workspace: "none" } } : unknown);
526
887
  export function agent(declaration: AgentDeclaration): AgentLogic {
888
+ // THE authoring surface for an Agent (ADR-0049): one definition in, one actor slot out. The
889
+ // slot key is the Agent's name (the Harness route, the minted iid, the markers), so a name the
890
+ // Machine does not carry is a compile error on `src`, and two Machines in one run may each carry
891
+ // their own `coder`.
892
+ //
893
+ // Everything live is constructed per-invocation from serializable input: the wire client from
894
+ // `input.endpoint` (which Sandbox's Harness — or the wire-compatible dev stub; either way it's
895
+ // only a URL, one code path). Lives here, not in actor.ts, so the actor logic and its unit
896
+ // tests never touch the wire.
897
+ //
898
+ // It takes a DECLARATION (ADR-0054), so a packaged Machine may write `agent({ model: open, … })`
899
+ // and leave the one part it cannot honestly fill to whoever registers it. What rides the Turn
900
+ // is still an `AgentDefinition`: the actor narrows on start and refuses an Open one there.
527
901
  return agentActorWith(
528
902
  (endpoint, bearer) => createHarnessAgentRunClient({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
529
903
  declaration,
@@ -600,26 +974,189 @@ export function createExecPort(options: {
600
974
  }
601
975
 
602
976
  /**
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`.
977
+ * Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a
978
+ * state invokes it. Put it under `actors` and invoke it by its key: `actors: { tests:
979
+ * command(["node", "--test"]) }`, then `invoke: { src: "tests", onDone: … }`.
980
+ *
981
+ * `argv` is a list, never shell text: each entry is one argument. An `arg("name")` entry is filled
982
+ * from the invoke's `input` (`input: { name: "…" }`), as one argument whatever its text. To use a
983
+ * shell, write it: `["sh", "-c", "…"]`.
984
+ *
985
+ * Where it runs:
986
+ * - With no Effect secret in `env`, in the Harness container of the enclosing Workspace's Sandbox.
987
+ * It runs in the Worktree that `worktree` of the invoke's input names, or in `/work` in a
988
+ * Workspace with no Repo Slot. The Agent's Working tools also run in this container.
989
+ * - With an Effect secret in `env`, in the Sandbox Effector, against a commit of the branch. This
990
+ * is the branch tip when it starts, or with `pin: true` the commit that the invoke's `input.sha`
991
+ * names. The output then says `dirty: true` when the Worktree held edits that the command did
992
+ * not see.
993
+ *
994
+ * Every outcome is output for `onDone`, never an error: `{ outcome: "exited", code, tail }`,
995
+ * `{ outcome: "timeout", tail }`, `{ outcome: "killed", signal, tail }`, or `{ outcome: "unknown",
996
+ * reason }` when jr2 cannot know what happened. `tail` is the end of the output stream.
997
+ *
998
+ * After an Orchestrator restart:
999
+ * - A command with no Effect secret in `env` never runs twice. jr2 attaches to it again, or settles
1000
+ * `unknown`.
1001
+ * - A command with an Effect secret in `env` runs again when the Sandbox Effector no longer holds
1002
+ * it. It runs against the same commit. If jr2 has no commit for it, it settles `unknown`.
1003
+ *
1004
+ * When the state exits, the command stops: jr2 kills its process group. `onError` is only for a
1005
+ * Machine or config defect.
1006
+ *
1007
+ * It refuses, when you call it:
1008
+ * - an empty `argv`: `command(argv): argv is a non-empty list — each entry one argument, never
1009
+ * shell text`
1010
+ * - an entry that is not a string or `arg()`: `command(argv): an argv entry is a string or
1011
+ * arg("name"), got <entry>`
1012
+ * - a `timeout` that is not a positive duration: `command: timeout "<timeout>" is not a positive
1013
+ * duration (like "90s", "10m", "1h")`
1014
+ * - an `env` name that is not a variable name, or `PATH`, `HOME` or `TMPDIR`: ``command(argv, {
1015
+ * env }): "<name>" is not a variable the author sets — PATH, HOME and TMPDIR are the Effector's
1016
+ * (ADR-0067)``
1017
+ * - `pin: true` with no Effect secret in `env`: ``command(argv, { pin: true }) names no secret in
1018
+ * its env: `pin` chooses the commit a credentialed command runs against in the Sandbox
1019
+ * Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its
1020
+ * secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``
1021
+ *
1022
+ * And when it starts, with no Effect secret in `env`:
1023
+ * - outside a `workspace()`: `command "<slot>" runs in the Harness container of a Workspace, and
1024
+ * its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
1025
+ * - no `worktree` under Repo Slots: `` command "<slot>" runs under a Workspace with Repo Slots
1026
+ * (<slots>) and its input names no `worktree`: say which, a Worktree that a cut gave or
1027
+ * `<slot>/default` ``
1028
+ * - an `arg()` with no string in `input`: `command "<slot>": argv slot <<name>> has no string
1029
+ * value in its input (got <type>) — map it in the invoke's input`. The angle brackets around the
1030
+ * arg's name are in the text: `argv slot <file>`. `<type>` is `nothing` when `input` has no such
1031
+ * field, or the type of its value, such as `number`.
1032
+ *
1033
+ * And when it starts, with an Effect secret in `env`:
1034
+ * - outside a `workspace()`: `command "<slot>" runs in the Sandbox Effector of a Workspace, and its
1035
+ * state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
1036
+ * - a Workspace with no Repo Slot: `command "<slot>" runs under a Workspace with no Repo Slot: a
1037
+ * command with an Effect secret runs against one commit of a Repo Slot's branch, and there is
1038
+ * none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent
1039
+ * (ADR-0051)`. `jr2 up` refuses it first, with the same reason
1040
+ * - a `worktree` that no cut made in this Sandbox: the output is `exited` 127, with `no cut made
1041
+ * <path> in this Sandbox: cut it with worktree()` in its tail. A detached Worktree gets the same
1042
+ * form
1043
+ * - `pin: true` and an `input.sha` that is not a full commit id: `command "<slot>" (pin): its
1044
+ * input's sha is not a full commit id`
1045
+ * - an `arg()` with no string in `input`: the same text as above.
1046
+ *
1047
+ * @param argv - The program and its arguments: strings and `arg("name")` entries. Or `open`, in a
1048
+ * Machine that you publish as a package, for the Instance that registers it to bind with
1049
+ * `customize()`.
1050
+ * @param options - Optional. `timeout`: `"90s"`, `"10m"` or `"1h"`, default
1051
+ * `"10m"`; when it expires, the output is `timeout`. `tail`: how many characters of the end of
1052
+ * the output stream to keep, default 4096. `env`: environment variables. Each value is
1053
+ * `secret("name")` for the value of an Effect secret, or `secret("name").path` for the path of a
1054
+ * file that holds it. `pin`: `true` to run against the commit in `input.sha`. Use `pin` only
1055
+ * with an Effect secret in `env`.
1056
+ * @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
1057
+ *
1058
+ * @example
1059
+ * ```ts
1060
+ * import { z } from "zod";
1061
+ * import {
1062
+ * arg,
1063
+ * command,
1064
+ * fail,
1065
+ * isReady,
1066
+ * jr2Setup,
1067
+ * workspace,
1068
+ * worktree,
1069
+ * type Workspaced,
1070
+ * } from "@jr2/orchestrator";
1071
+ *
1072
+ * const runInput = z.object({ branch: z.string() });
1073
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
1074
+ *
1075
+ * const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
1076
+ *
1077
+ * const body = jr2Setup({
1078
+ * types: {} as { input: BodyInput; context: BodyInput },
1079
+ * actors: {
1080
+ * cut: worktree(),
1081
+ * tests: command(["node", "--test", arg("file")], { timeout: "5m" }),
1082
+ * fail: fail(),
1083
+ * },
1084
+ * }).createMachine({
1085
+ * id: "check",
1086
+ * context: ({ input }) => input,
1087
+ * on: { "workspace.lost": ".lost" },
1088
+ * initial: "cutting",
1089
+ * states: {
1090
+ * cutting: {
1091
+ * invoke: {
1092
+ * src: "cut",
1093
+ * input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
1094
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
1095
+ * },
1096
+ * },
1097
+ * testing: {
1098
+ * invoke: {
1099
+ * src: "tests",
1100
+ * input: ({ context }) => ({ file: "test/greet.test.js", worktree: testTree(context) }),
1101
+ * onDone: [
1102
+ * {
1103
+ * guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
1104
+ * target: "passed",
1105
+ * },
1106
+ * { target: "failed" },
1107
+ * ],
1108
+ * },
1109
+ * },
1110
+ * passed: { type: "final" },
1111
+ * // Not final states: each one fails the run, and the run ends `failed`.
1112
+ * failed: { invoke: { src: "fail", input: { reason: "the test did not pass" } } },
1113
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
1114
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
1115
+ * },
1116
+ * });
1117
+ *
1118
+ * export const machine = workspace(body, {
1119
+ * input: runInput,
1120
+ * repos: { app: "https://github.com/you/app.git" },
1121
+ * });
1122
+ * ```
1123
+ *
1124
+ * @see docs/concepts/effects.md
1125
+ * @see docs/guides/run-a-command-or-call-a-url.md
1126
+ * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
1127
+ * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
609
1128
  */
610
- export function command<const Opt extends CommandOptions = CommandOptions>(
611
- argv: typeof open,
612
- options?: Opt,
613
- ): EffectLogic<OpenCommandInput<Opt["pin"]>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>;
614
1129
  export function command<const A extends readonly (string | Arg)[], const Opt extends CommandOptions = CommandOptions>(
615
1130
  argv: A,
616
1131
  options?: Opt,
617
- ): EffectLogic<CommandInput<A, Opt["pin"]>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
1132
+ ): EffectLogic<CommandInput<A, Opt["pin"], Opt>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>;
1133
+ /**
1134
+ * Make a `command` Effect whose argv is Open: `command(open, options)`, in a Machine that you
1135
+ * publish as a package. The Instance that registers the Machine binds the argv one time:
1136
+ * `customize(machine, { effects: { <slot>: { argv } } })`. {@link command} tells the rest, and
1137
+ * what it refuses.
1138
+ *
1139
+ * @param argv - `open`.
1140
+ * @param options - Optional. The same options as for a written argv.
1141
+ * @returns The actor logic of the Effect's slot. Its output is a `CommandOutput`.
1142
+ *
1143
+ * @see docs/guides/bind-open-parts.md
1144
+ * @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
1145
+ */
1146
+ export function command<const Opt extends CommandOptions = CommandOptions>(
1147
+ argv: typeof open,
1148
+ options?: Opt,
1149
+ ): EffectLogic<OpenCommandInput<Opt["pin"], Opt>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>;
618
1150
  export function command(
619
1151
  argv: readonly (string | Arg)[] | typeof open,
620
1152
  options: CommandOptions = {},
621
1153
  // The implementation of the overloads above: each one states the input type.
622
1154
  ): EffectLogic<any, CommandOutput> {
1155
+ // THE authoring surface for a `command` Effect (ADR-0067). `argv` is never shell text — an
1156
+ // `arg("x")` entry is ONE argument, filled from the invoke's input — and `sh -c` is the author's
1157
+ // explicit choice. Bound to the real exec port here; `commandWith` in effect.ts is the seam the
1158
+ // unit tests share.
1159
+ //
623
1160
  // An Open argv (ADR-0054) is the composer's to bind with `customize()`; see open-effect.ts.
624
1161
  return commandWith(
625
1162
  (endpoint, bearer) => createExecPort({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),