@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
package/src/http.ts CHANGED
@@ -5,17 +5,14 @@
5
5
  // TWO dialects, one primitive (ADR-0013). The Orchestrator does not speak MCP — nothing does: the
6
6
  // Harness presents the Menu to its model itself, and reaches these routes through the Custodian in
7
7
  // its pod, which holds the Sandbox token. What is left here are two thin adapters over the same
8
- // registration table:
8
+ // registration table: the Gate resource of ADR-0011, for a human, a webhook or CI
9
+ // (`GET /runs/:id`, `POST /runs/:id/gates/:gate/events`), and the Agent's surface, for the Agent's
10
+ // Harness through its Custodian and nothing else (`/agents/:iid/*`). `/sandboxes/:name/fetch` also
11
+ // serves the Harness's pod, but it is not an adapter over the registration table.
9
12
  //
10
- // # human / webhook / CI — the Gate resource of ADR-0011
11
- // GET /runs/:id open gates: accepts + schemas + meta [Instance token]
12
- // POST /runs/:id/gates/:gate/events validate + deliver [Instance token]
13
- //
14
- // # the Agent's Harness, through its Custodian, and nothing else
15
- // GET /agents/:iid/surface accepts + schemas + semantics [Sandbox token]
16
- // POST /agents/:iid/events validate + deliver → the turn receipt [Sandbox token]
17
- // GET /agents/:iid/deliveries/:id long-poll an outstanding pick's answer [Sandbox token]
18
- // POST /sandboxes/:name/fetch ask the node cache to fetch one Repo [Sandbox token]
13
+ // Every route, with its band, is a row of `HTTP_ROUTES` below. That table is data: the HTTP API
14
+ // Reference is generated from it (ADR-0072), and http-routes.test.ts holds it to the routes
15
+ // `createApp` registers, both ways.
19
16
  //
20
17
  // # named Charts (ADR-0070): a Chart under a name is a Workflow `<wrapper>/<name>`
21
18
  // GET /workflows/:wrapper/chart-schema the grammar, narrowed to the Open body [open]
@@ -53,16 +50,397 @@ import { Hono } from "hono";
53
50
  import type { Context, MiddlewareHandler } from "hono";
54
51
  import { accepts } from "hono/accepts";
55
52
  import { streamSSE } from "hono/streaming";
56
- import { EventValidationError, NotTakenError, OutstandingError, UnknownAddressError } from "./registration.ts";
53
+ import { z } from "zod";
54
+ import {
55
+ DISPATCHED_BY_HEADER,
56
+ EventValidationError,
57
+ NotTakenError,
58
+ OutstandingError,
59
+ UnknownAddressError,
60
+ type DispatchedBy,
61
+ } from "./registration.ts";
57
62
  import type { RepoStatus } from "./repos.ts";
58
63
  import { UnmountedRepoError, type FetchAnswer } from "./repo-fetch.ts";
59
- import { mayAskForSandbox, mayDeliverToAgent, type Authenticator, type Principal } from "./tokens.ts";
60
- import { observe, type RunFeedEvent, type RunHost, type RunStatus, type WorkflowFeedEvent } from "./run-host.ts";
64
+ import {
65
+ isAgentPrincipal,
66
+ mayAskForSandbox,
67
+ mayDeliverToAgent,
68
+ SLOT_HEADER,
69
+ type Authenticator,
70
+ type Principal,
71
+ } from "./tokens.ts";
72
+ import {
73
+ DispatchKeyConflictError,
74
+ keyAnswer,
75
+ keyConflict,
76
+ observe,
77
+ UnknownWorkflowError,
78
+ type RunFeedEvent,
79
+ type RunHost,
80
+ type RunStatus,
81
+ type WorkflowFeedEvent,
82
+ } from "./run-host.ts";
61
83
  import { KIT_VERSION } from "./config.ts";
62
84
  import type { AssemblyDoor } from "./assembly.ts";
63
85
  import { ChartRefusal } from "./chart.ts";
64
86
  import { chartName, NamedChartError, splitChartName, type NamedCharts } from "./charts.ts";
65
87
 
88
+ // ---- The route table (ADR-0072) -------------------------------------------------------------
89
+ // The routes `createApp` registers, as data. It is the source of the HTTP API Reference page, so a
90
+ // row says what a CALLER needs: the address, the credential, the body, the answer. Why a route is
91
+ // in its band stays in the comment above its handler. http-routes.test.ts compares the rows with
92
+ // the app's own registrations both ways and checks each row's band on the wire, so the table
93
+ // cannot drift from the code by forgetting.
94
+
95
+ /** The access bands of ADR-0014, named for the credential each requires, and the publish door's
96
+ * band (ADR-0069): the publish token alone. */
97
+ export type HttpBand = "open" | "authenticated" | "instanceOnly" | "publishOnly";
98
+
99
+ /** One route of the Orchestrator's HTTP API (ADR-0072). */
100
+ export type HttpRoute = {
101
+ method: "GET" | "POST" | "PUT" | "DELETE";
102
+ /** In Hono syntax, exactly as registered: `:param`, and `:param{regex}` for a constrained one. */
103
+ path: string;
104
+ band: HttpBand;
105
+ /** One line: what the route does. */
106
+ summary: string;
107
+ /** The request body, as a type name or in short; absent when the route reads none. */
108
+ body?: string;
109
+ /** The success answer, in short. Errors are `{ error }` with the status the handler says. */
110
+ response: string;
111
+ /** `true` when the answer is a Server-Sent Events stream. */
112
+ stream: boolean;
113
+ /** The decisions that shape the route, as bare ADR numbers: `"0014"`. */
114
+ adrs: readonly string[];
115
+ };
116
+
117
+ /** Every route of the Orchestrator's HTTP API, in the order `createApp` registers them. */
118
+ export const HTTP_ROUTES: readonly HttpRoute[] = [
119
+ {
120
+ method: "GET",
121
+ path: "/healthz",
122
+ band: "open",
123
+ summary: "Liveness, and what this instance is: its Kit version and image content hash.",
124
+ response: "{ ok: true, version, hash? }",
125
+ stream: false,
126
+ adrs: ["0009", "0019"],
127
+ },
128
+ {
129
+ method: "GET",
130
+ path: "/readyz",
131
+ band: "open",
132
+ summary: "Readiness.",
133
+ response: "{ ready: true }",
134
+ stream: false,
135
+ adrs: ["0009"],
136
+ },
137
+ {
138
+ method: "GET",
139
+ path: "/workflows",
140
+ band: "open",
141
+ summary:
142
+ "The Workflows a run can start, each with its origin, and each Chart wrapper's named Charts in its entry. A named Chart that cannot run now says why.",
143
+ response: "WorkflowListing[]",
144
+ stream: false,
145
+ adrs: ["0014", "0070"],
146
+ },
147
+ {
148
+ method: "GET",
149
+ path: "/assembly",
150
+ band: "open",
151
+ summary: "The current Assembly and the Assemblies that live runs hold.",
152
+ response: "AssemblyView",
153
+ stream: false,
154
+ adrs: ["0069"],
155
+ },
156
+ {
157
+ method: "PUT",
158
+ path: "/assembly",
159
+ band: "publishOnly",
160
+ summary:
161
+ "Publish the next Assembly. `jr2 up` calls it, and nothing else. A refused Assembly leaves the current one current.",
162
+ body: "the Assembly as a tar stream, with the headers `jr2-kit-version` and `jr2-assembly-hash`",
163
+ response:
164
+ "200 with the Assembly, its Workflows, the held Assemblies and the ones collected; 400, 409 or 422 with the reason",
165
+ stream: false,
166
+ adrs: ["0069"],
167
+ },
168
+ {
169
+ method: "GET",
170
+ path: "/workflows/:wrapper/chart-schema",
171
+ band: "open",
172
+ summary: "The Chart grammar, narrowed to the Open body of one Chart wrapper.",
173
+ response: "JSON Schema",
174
+ stream: false,
175
+ adrs: ["0070"],
176
+ },
177
+ {
178
+ method: "PUT",
179
+ path: "/workflows/:wrapper/charts/:name",
180
+ band: "instanceOnly",
181
+ summary: "Admit and keep a runtime Chart under a name. Its Workflow is `<wrapper>/<name>`.",
182
+ body: "the Chart text alone",
183
+ response: "201 { name, origin, hash } when new, 200 when it replaced one; 422 { error, report } when refused",
184
+ stream: false,
185
+ adrs: ["0070"],
186
+ },
187
+ {
188
+ method: "DELETE",
189
+ path: "/workflows/:wrapper/charts/:name",
190
+ band: "instanceOnly",
191
+ summary: "Remove a runtime Chart. A live run of it keeps its own text.",
192
+ response: "204",
193
+ stream: false,
194
+ adrs: ["0070"],
195
+ },
196
+ {
197
+ method: "POST",
198
+ path: "/workflows/:wrapper/charts/:name/runs",
199
+ band: "instanceOnly",
200
+ summary: "Start a run of a named Chart, as `POST /workflows/<wrapper>%2F<name>/runs` does.",
201
+ body: "the Chart wrapper's input, without the Chart",
202
+ response: "201 { runId, instanceId }; 409 when the named Chart cannot run now",
203
+ stream: false,
204
+ adrs: ["0070"],
205
+ },
206
+ {
207
+ method: "GET",
208
+ path: "/workflows/:name/runs/:runId/machine",
209
+ band: "open",
210
+ summary: "The Machine that a run binds, with each bound Chart compiled in, which the Console draws.",
211
+ response: "MachineDoc",
212
+ stream: false,
213
+ adrs: ["0070"],
214
+ },
215
+ {
216
+ method: "GET",
217
+ path: "/",
218
+ band: "open",
219
+ summary: "The Console page to a browser; a 404 to a JSON caller.",
220
+ response: "text/html (the Console shell)",
221
+ stream: false,
222
+ adrs: ["0032"],
223
+ },
224
+ {
225
+ method: "GET",
226
+ path: "/workflows/:name",
227
+ band: "open",
228
+ summary: "The Console page to a browser; to a JSON caller, the workflow and its declared input schema.",
229
+ response: "{ name, machineId, input: JSON Schema | null }, or text/html",
230
+ stream: false,
231
+ adrs: ["0032", "0033"],
232
+ },
233
+ {
234
+ method: "POST",
235
+ path: "/workflows/:name/runs",
236
+ band: "instanceOnly",
237
+ summary:
238
+ "Start a run of a registered workflow. A `dispatch` from another Orchestrator sends the header `jr2-dispatched-by`, and a repeated start with its key answers the run that it started.",
239
+ body: "the workflow's input, checked against its declared input schema",
240
+ response:
241
+ "201 { runId, instanceId }; 200 { runId, instanceId, existing: true } for a repeated dispatch; 422 { error, report } when the door refuses a Chart in the input",
242
+ stream: false,
243
+ adrs: ["0033", "0058", "0070", "0071"],
244
+ },
245
+ {
246
+ method: "GET",
247
+ path: "/workflows/:name/machine",
248
+ band: "open",
249
+ summary: "The registered template Machine's structure, which the Console draws.",
250
+ response: "MachineDoc",
251
+ stream: false,
252
+ adrs: ["0014"],
253
+ },
254
+ {
255
+ method: "GET",
256
+ path: "/workflows/:name/runs",
257
+ band: "open",
258
+ summary: "The live runs of one workflow, as observations: identity and state value, no context.",
259
+ response: "RunObservation[]",
260
+ stream: false,
261
+ adrs: ["0014"],
262
+ },
263
+ {
264
+ method: "GET",
265
+ path: "/workflows/:name/events",
266
+ band: "open",
267
+ summary: "The activity of every run of one workflow, as observations, on one connection.",
268
+ response: "SSE: runs, status, emit (type only), retry, gone",
269
+ stream: true,
270
+ adrs: ["0014", "0022"],
271
+ },
272
+ {
273
+ method: "GET",
274
+ path: "/workflows/:name/runs/:runId/events",
275
+ band: "open",
276
+ summary: "One live run's activity, as observations, until it settles.",
277
+ response: "SSE: status, emit (type only), retry",
278
+ stream: true,
279
+ adrs: ["0014", "0022"],
280
+ },
281
+ {
282
+ method: "GET",
283
+ path: "/assets/style.css",
284
+ band: "open",
285
+ summary: "The Console's stylesheet.",
286
+ response: "text/css",
287
+ stream: false,
288
+ adrs: ["0032"],
289
+ },
290
+ {
291
+ method: "GET",
292
+ path: "/assets/elk.js",
293
+ band: "open",
294
+ summary: "The Console's graph layout library (the elkjs bundle).",
295
+ response: "text/javascript",
296
+ stream: false,
297
+ adrs: ["0032"],
298
+ },
299
+ {
300
+ method: "GET",
301
+ path: "/assets/:file{[\\w.-]+\\.ts}",
302
+ band: "open",
303
+ summary: "A Console source module, with its types erased.",
304
+ response: "text/javascript",
305
+ stream: false,
306
+ adrs: ["0034"],
307
+ },
308
+ {
309
+ method: "GET",
310
+ path: "/assets/components/:file{[\\w.-]+\\.ts}",
311
+ band: "open",
312
+ summary: "A Console view module, with its types erased.",
313
+ response: "text/javascript",
314
+ stream: false,
315
+ adrs: ["0034"],
316
+ },
317
+ {
318
+ method: "GET",
319
+ path: "/assets/vendor/:file",
320
+ band: "open",
321
+ summary: "Preact's browser ESM, which the Console's import map names.",
322
+ response: "text/javascript",
323
+ stream: false,
324
+ adrs: ["0034"],
325
+ },
326
+ {
327
+ method: "GET",
328
+ path: "/runs",
329
+ band: "instanceOnly",
330
+ summary: "The live runs, with their full status.",
331
+ response: "RunStatus[]",
332
+ stream: false,
333
+ adrs: ["0009", "0014"],
334
+ },
335
+ {
336
+ method: "GET",
337
+ path: "/repos",
338
+ band: "instanceOnly",
339
+ summary: "The Repos as the cluster reports them.",
340
+ response: "{ dataPlane, repos: RepoStatus[] }",
341
+ stream: false,
342
+ adrs: ["0048", "0051"],
343
+ },
344
+ {
345
+ method: "GET",
346
+ path: "/runs/resolve",
347
+ band: "instanceOnly",
348
+ summary: "The run ids that start with a prefix (`?prefix=`, at least 4 characters).",
349
+ response: "{ prefix, runIds: string[], truncated }",
350
+ stream: false,
351
+ adrs: ["0009"],
352
+ },
353
+ {
354
+ method: "GET",
355
+ path: "/runs/:runId",
356
+ band: "instanceOnly",
357
+ summary: "One run's status, live or settled, with its open Gates.",
358
+ response: "RunStatus & { gates: GateView[] }",
359
+ stream: false,
360
+ adrs: ["0009", "0011"],
361
+ },
362
+ {
363
+ // `authenticated` in the code, and the handler refuses a Sandbox token itself (ADR-0013): the
364
+ // credential the route requires is the Instance token's, so this is its band.
365
+ method: "POST",
366
+ path: "/runs/:runId/gates/:gate/events",
367
+ band: "instanceOnly",
368
+ summary: "Deliver an event to an open Gate of a run.",
369
+ body: "{ type, ...payload }, checked against the Gate's schema for that event",
370
+ response: "{ ok: true }; 409 { error, acceptsNow } when the run does not take it now",
371
+ stream: false,
372
+ adrs: ["0011", "0013"],
373
+ },
374
+ {
375
+ method: "GET",
376
+ path: "/runs/:runId/events",
377
+ band: "instanceOnly",
378
+ summary: "One run's full activity until it settles; a settled run's final status, once.",
379
+ response: "SSE: status, emit, retry, admission, pick, placing, placed, child-error, effect, gone",
380
+ stream: true,
381
+ adrs: ["0022", "0023", "0064", "0067"],
382
+ },
383
+ {
384
+ method: "POST",
385
+ path: "/runs/:runId/events",
386
+ band: "instanceOnly",
387
+ summary: "Cancel a run: it ends, and its Workspaces go unless `keep` is true.",
388
+ body: '{ type: "CANCEL", keep?: boolean }',
389
+ response: "{ ok: true }",
390
+ stream: false,
391
+ adrs: ["0002", "0025"],
392
+ },
393
+ {
394
+ method: "GET",
395
+ path: "/agents/:instanceId/surface",
396
+ band: "authenticated",
397
+ summary:
398
+ "An Agent's Menu, this Turn's Allowed picks and the conversation's pending notices. A control token reads only its own pod's, for the slot it states.",
399
+ response: "AgentSurfaceView & { notices: Array<{ id, notice }> }",
400
+ stream: false,
401
+ adrs: ["0013", "0029"],
402
+ },
403
+ {
404
+ method: "POST",
405
+ path: "/agents/:instanceId/events",
406
+ band: "authenticated",
407
+ summary: "Deliver an Agent's pick into the state that invoked it.",
408
+ body: "{ type, ...payload }, checked against the pick's input schema",
409
+ response: 'AgentDeliveryReceipt; 409 { refused: "outstanding", outstanding?, allowed }',
410
+ stream: false,
411
+ adrs: ["0013", "0024", "0029"],
412
+ },
413
+ {
414
+ method: "GET",
415
+ path: "/agents/:instanceId/deliveries/:deliveryId",
416
+ band: "authenticated",
417
+ summary: "Long-poll the answer to an outstanding pick; 503 means ask again.",
418
+ response: "AgentDeliveryReceipt",
419
+ stream: false,
420
+ adrs: ["0013", "0024"],
421
+ },
422
+ {
423
+ method: "POST",
424
+ path: "/agents/:instanceId/notices",
425
+ band: "authenticated",
426
+ summary: "Acknowledge the notices a step boundary appended to the Turn: they are heard in its conversation.",
427
+ body: "{ heard: string[] }",
428
+ response: "{ heard: number }",
429
+ stream: false,
430
+ adrs: ["0062", "0076"],
431
+ },
432
+ {
433
+ method: "POST",
434
+ path: "/sandboxes/:name/fetch",
435
+ band: "authenticated",
436
+ summary: "Ask the node cache to fetch one Repo that this Sandbox mounts. A fetch token asks only for its own.",
437
+ body: "{ identity }",
438
+ response: "FetchAnswer: { fetched } | { stale, asOf }",
439
+ stream: false,
440
+ adrs: ["0053"],
441
+ },
442
+ ];
443
+
66
444
  /** A `POST /runs/:id/events` body: the down-channel event. CANCEL is all that is left of it
67
445
  * (ADR-0013): APPROVE and STEER rode the deferred/poll machinery: `deferred`
68
446
  * is retired (ADR-0006), `poll` reserved but not built.
@@ -89,6 +467,40 @@ async function readJson(text: Promise<string>): Promise<Record<string, unknown>>
89
467
  }
90
468
  }
91
469
 
470
+ /**
471
+ * The `jr2-dispatched-by` header a foreign `dispatch` sends (ADR-0071). `instance` is required: a
472
+ * start that comes over the door with a key comes from another Orchestrator by construction (an
473
+ * in-process dispatch never reaches the door). Unknown fields are dropped, as a door drops them: a
474
+ * caller at a later Kit version may send more.
475
+ */
476
+ const DispatchedByHeader = z.object({
477
+ run: z.string().min(1),
478
+ slot: z.string().min(1),
479
+ effect: z.string().min(1),
480
+ depth: z.number().int().positive(),
481
+ instance: z.string().min(1),
482
+ });
483
+
484
+ /** The header's {@link DispatchedBy}, absent when no header was sent, or why it is refused. */
485
+ function dispatchedByOf(raw: string | undefined): { by?: DispatchedBy } | { error: string } {
486
+ if (raw === undefined) return {};
487
+ let json: unknown;
488
+ try {
489
+ json = JSON.parse(raw);
490
+ } catch (err) {
491
+ return { error: `the ${DISPATCHED_BY_HEADER} header is not JSON: ${errMessage(err)} (ADR-0071)` };
492
+ }
493
+ const parsed = DispatchedByHeader.safeParse(json);
494
+ if (!parsed.success) {
495
+ return {
496
+ error:
497
+ `the ${DISPATCHED_BY_HEADER} header must be { run, slot, effect, depth, instance }: ` +
498
+ `${parsed.error.issues.map((i) => `${i.path.join(".") || "(header)"}: ${i.message}`).join("; ")} (ADR-0071)`,
499
+ };
500
+ }
501
+ return { by: parsed.data };
502
+ }
503
+
92
504
  /** The message of a thrown error, for the `{ error }` 404 body. */
93
505
  function errMessage(err: unknown): string {
94
506
  return err instanceof Error ? err.message : String(err);
@@ -434,7 +846,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
434
846
  * hand every Agent in the cluster a delivery API, which is the hole this ADR exists to close. */
435
847
  const authenticated: MiddlewareHandler<JR2Env> = async (c, next) => {
436
848
  if (!auth) return next(); // no authenticator configured: tests only (see the doc comment)
437
- const principal = auth(bearerOf(c));
849
+ // The slot a control request states (ADR-0076): a control token without one resolves to nothing.
850
+ const principal = auth(bearerOf(c), c.req.header(SLOT_HEADER));
438
851
  if (!principal) return c.json({ error: "unauthorized" }, 401);
439
852
  if (principal.kind === "publisher") return c.json({ error: PUBLISHER_ONLY }, 403);
440
853
  c.set("principal", principal);
@@ -453,7 +866,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
453
866
  */
454
867
  const instanceOnly: MiddlewareHandler<JR2Env> = async (c, next) => {
455
868
  if (!auth) return next(); // no authenticator configured: tests only (see the doc comment)
456
- const principal = auth(bearerOf(c));
869
+ const principal = auth(bearerOf(c), c.req.header(SLOT_HEADER));
457
870
  if (!principal) return c.json({ error: "unauthorized" }, 401);
458
871
  if (principal.kind === "publisher") return c.json({ error: PUBLISHER_ONLY }, 403);
459
872
  if (principal.kind !== "instance") {
@@ -581,18 +994,35 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
581
994
  * Start a run of a Workflow (ADR-0009). A named Chart that cannot run now is 409 with why
582
995
  * (ADR-0070); a Chart in the input that the door refuses is 422 with the report; input failing
583
996
  * the door schema is 400 (ADR-0033); an unknown Workflow is 404.
997
+ *
998
+ * A foreign `dispatch` sends `jr2-dispatched-by` (ADR-0071): its key makes a repeated start one
999
+ * run, answered `200 { runId, instanceId, existing: true }` where a new one is `201`. The key is
1000
+ * asked first, so a re-send answers its run even when the name would be refused now; a key a
1001
+ * run of another Workflow carries is 409. A header that does not parse is 400. `jr2 run` and the
1002
+ * Console send none. Any throw that is not a refusal is a defect, 500: a foreign caller reads a
1003
+ * 404 as "nothing was started", and a defect may come after a start.
584
1004
  */
585
1005
  const startRun = async (c: Context<JR2Env>, name: string): Promise<Response> => {
1006
+ const header = dispatchedByOf(c.req.header(DISPATCHED_BY_HEADER));
1007
+ if ("error" in header) return c.json({ error: header.error }, 400);
1008
+ const { by } = header;
1009
+ if (by) {
1010
+ const found = await host.dispatched(by.effect);
1011
+ if (found && found.workflow !== name) return c.json({ error: keyConflict(found, name, by.effect) }, 409);
1012
+ if (found) return c.json({ ...keyAnswer(found, name, by.effect), existing: true }, 200);
1013
+ }
586
1014
  const unrunnable = opts.charts?.get(name)?.unrunnable;
587
1015
  if (unrunnable !== undefined) return c.json({ error: `named Chart "${name}" cannot run: ${unrunnable}` }, 409);
588
1016
  const input = await readJson(c.req.text());
589
1017
  try {
590
- const { runId, instanceId } = await host.start(name, input);
591
- return c.json({ runId, instanceId }, 201);
1018
+ const { runId, instanceId, existing } = await host.start(name, input, by ? { dispatchedBy: by } : {});
1019
+ return existing ? c.json({ runId, instanceId, existing }, 200) : c.json({ runId, instanceId }, 201);
592
1020
  } catch (err) {
593
1021
  if (err instanceof ChartRefusal) return c.json({ error: "refused", report: err.report }, 422);
594
1022
  if (err instanceof EventValidationError) return c.json({ error: errMessage(err) }, 400);
595
- return c.json({ error: errMessage(err) }, 404);
1023
+ if (err instanceof DispatchKeyConflictError) return c.json({ error: errMessage(err) }, 409);
1024
+ if (err instanceof UnknownWorkflowError) return c.json({ error: errMessage(err) }, 404);
1025
+ return c.json({ error: errMessage(err) }, 500);
596
1026
  }
597
1027
  };
598
1028
 
@@ -660,7 +1090,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
660
1090
  });
661
1091
  });
662
1092
 
663
- // Push work: start a run of a registered workflow. Unknown workflow → host.start throws → 404.
1093
+ // Push work: start a run of a registered workflow. Unknown workflow → UnknownWorkflowError → 404.
664
1094
  // A body failing the machine's declared input schema (ADR-0033) → 400 naming the accepted
665
1095
  // shape — the same error class, and the same wire mapping, as a gate delivery failing its
666
1096
  // schema (`POST /runs/:id/gates/:gate/events` below). Instance-band (ADR-0058): starting work is
@@ -877,11 +1307,11 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
877
1307
  // the gated state. Unknown gate (never opened, state exited, run settled) → 404; a name the gate
878
1308
  // doesn't accept, or a payload failing its schema → 400 naming what IS accepted.
879
1309
  //
880
- // A Gate is a HUMAN's decision (or a webhook's, or CI's). An Agent holding a Sandbox token is
881
- // refused here unconditionally — this is the exact line between "the Agent reports an outcome"
882
- // and "the Agent approves its own PR" (ADR-0013).
1310
+ // A Gate is a HUMAN's decision (or a webhook's, or CI's). Every Agent principal — a control
1311
+ // token or a fetch token — is refused here unconditionally: this is the exact line between "the
1312
+ // Agent reports an outcome" and "the Agent approves its own PR" (ADR-0013, ADR-0076).
883
1313
  app.post("/runs/:runId/gates/:gate/events", authenticated, async (c) => {
884
- if (principalOf(c).kind === "sandbox") {
1314
+ if (isAgentPrincipal(principalOf(c))) {
885
1315
  return c.json({ error: "a Sandbox token cannot deliver to a gate — gates are not on the Agent's surface" }, 403);
886
1316
  }
887
1317
  const body = await readJson(c.req.text());
@@ -1008,7 +1438,9 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
1008
1438
  // and delivery are implemented once, in `registration.ts`.
1009
1439
 
1010
1440
  /** Guard: the surface must exist, and this principal must be allowed to speak for it. */
1011
- const agentRegistration = (c: Context<JR2Env, "/agents/:instanceId/surface" | "/agents/:instanceId/events">) => {
1441
+ const agentRegistration = (
1442
+ c: Context<JR2Env, "/agents/:instanceId/surface" | "/agents/:instanceId/events" | "/agents/:instanceId/notices">,
1443
+ ) => {
1012
1444
  const instanceId = c.req.param("instanceId");
1013
1445
  // Only a Turn that is over answers 404 (ADR-0026). While the host is stopping its runs are
1014
1446
  // parked for restore, not over, so every `/agents/*` call answers 503: the Harness re-asks a
@@ -1031,9 +1463,10 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
1031
1463
  // iid) → there is no surface to serve. 404 BEFORE the scope check — a caller with a valid
1032
1464
  // token learns nothing from it that it did not already know.
1033
1465
  if (!surface) return { error: c.json({ error: `no live agent surface for instance "${instanceId}"` }, 404) };
1034
- if (!mayDeliverToAgent(principalOf(c), surface.sandbox)) {
1035
- // A Sandbox token for a DIFFERENT Sandbox (or for a workspace-less run, which no Sandbox
1036
- // owns). This is the check that keeps one feature's coder out of another's reviewer.
1466
+ if (!mayDeliverToAgent(principalOf(c), surface)) {
1467
+ // A control token for a DIFFERENT pod (or for a workspace-less run, which no pod owns), a
1468
+ // stated slot that is not this registration's (ADR-0076), or a fetch token. The first is the
1469
+ // check that keeps one feature's coder out of another's reviewer.
1037
1470
  return { error: c.json({ error: `this token cannot speak for instance "${instanceId}"` }, 403) };
1038
1471
  }
1039
1472
  return { surface };
@@ -1043,11 +1476,28 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
1043
1476
  // semantics, fixed for the conversation — and this Turn's Allowed picks (ADR-0029). A transition
1044
1477
  // swaps the registration, which swaps the Allowed picks, never the Menu; the Harness reads both
1045
1478
  // per Submission and needs no `list_changed`.
1046
- app.get("/agents/:instanceId/surface", authenticated, (c) => {
1479
+ // The same read at a step boundary carries the conversation's pending notices in its audience
1480
+ // (ADR-0062), reserved for this read: the Harness appends them and acknowledges them below.
1481
+ app.get("/agents/:instanceId/surface", authenticated, async (c) => {
1047
1482
  const { surface, error } = agentRegistration(c);
1048
1483
  if (error) return error;
1049
1484
  host.agentSurfaceServed(c.req.param("instanceId"));
1050
- return c.json(surface);
1485
+ const notices = await host.boundaryNotices(c.req.param("instanceId"));
1486
+ return c.json({ ...surface, notices });
1487
+ });
1488
+
1489
+ // The Harness appended a step boundary's notices to the Turn (ADR-0062, ADR-0076): they are heard
1490
+ // in that conversation, ledgered in one save. An id this conversation did not reserve is ignored,
1491
+ // so the call says nothing a caller did not already know.
1492
+ app.post("/agents/:instanceId/notices", authenticated, async (c) => {
1493
+ const { error } = agentRegistration(c);
1494
+ if (error) return error;
1495
+ const body = await readJson(c.req.text());
1496
+ const heard = (body as { heard?: unknown }).heard;
1497
+ if (!Array.isArray(heard) || heard.some((id) => typeof id !== "string")) {
1498
+ return c.json({ error: "an acknowledgment names the notices heard: { heard: string[] }" }, 400);
1499
+ }
1500
+ return c.json({ heard: host.noticesHeard(c.req.param("instanceId"), heard as string[]) });
1051
1501
  });
1052
1502
 
1053
1503
  // The Agent's pick, delivered into the state that invoked it. The receipt describes itself
@@ -1103,7 +1553,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
1103
1553
  return c.json({ error: restorePendingMessage(`delivery "${deliveryId}"`) }, 503);
1104
1554
  }
1105
1555
  if (!scope) return c.json({ error: `no delivery "${deliveryId}" for instance "${instanceId}"` }, 404);
1106
- if (!mayDeliverToAgent(principalOf(c), scope.sandbox)) {
1556
+ if (!mayDeliverToAgent(principalOf(c), scope)) {
1107
1557
  return c.json({ error: `this token cannot speak for instance "${instanceId}"` }, 403);
1108
1558
  }
1109
1559
  const receipt = await host.awaitDelivery(instanceId, deliveryId, opts.deliveryWaitMs, c.req.raw.signal);