@jr2/orchestrator 0.3.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.md +20 -2
  2. package/bin/server.ts +7 -4
  3. package/console/canvas.ts +27 -14
  4. package/console/components/app.ts +12 -1
  5. package/console/components/fleet.ts +52 -6
  6. package/console/components/machine-pane.ts +128 -2
  7. package/console/components/nav.ts +1 -1
  8. package/console/components/schema-form.ts +12 -4
  9. package/console/json-field.ts +23 -0
  10. package/console/main.ts +122 -39
  11. package/console/store.ts +321 -21
  12. package/console/style.css +111 -0
  13. package/docs/agents-block.md +5 -0
  14. package/docs/concepts/agents-and-turns.md +442 -0
  15. package/docs/concepts/architecture.md +230 -0
  16. package/docs/concepts/charts.md +143 -0
  17. package/docs/concepts/composition.md +357 -0
  18. package/docs/concepts/converge.md +254 -0
  19. package/docs/concepts/durable-runs.md +189 -0
  20. package/docs/concepts/effects.md +603 -0
  21. package/docs/concepts/gates.md +208 -0
  22. package/docs/concepts/machines-and-workflows.md +289 -0
  23. package/docs/concepts/pools-and-sources.md +230 -0
  24. package/docs/concepts/secrets-and-trust.md +222 -0
  25. package/docs/concepts/the-menu.md +378 -0
  26. package/docs/concepts/workspaces.md +678 -0
  27. package/docs/get-started/a-model-decides.md +563 -0
  28. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  29. package/docs/get-started/connect-a-model.md +248 -0
  30. package/docs/get-started/create-an-instance.md +194 -0
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  32. package/docs/get-started/install.md +126 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  34. package/docs/guides/answer-a-gate.md +304 -0
  35. package/docs/guides/bind-open-parts.md +223 -0
  36. package/docs/guides/cancel-a-run.md +146 -0
  37. package/docs/guides/clean-up.md +206 -0
  38. package/docs/guides/fix-a-failed-converge.md +361 -0
  39. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  41. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  42. package/docs/guides/look-inside-a-sandbox.md +174 -0
  43. package/docs/guides/mirror-the-kit-images.md +176 -0
  44. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  45. package/docs/guides/open-the-console.md +91 -0
  46. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  47. package/docs/guides/push-and-sign-a-branch.md +486 -0
  48. package/docs/guides/run-a-chart-by-name.md +270 -0
  49. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  50. package/docs/guides/run-one-worker-per-item.md +321 -0
  51. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  52. package/docs/guides/size-a-workspace.md +204 -0
  53. package/docs/guides/start-a-run-from-a-run.md +237 -0
  54. package/docs/guides/start-a-run.md +207 -0
  55. package/docs/guides/upgrade-the-kit-version.md +240 -0
  56. package/docs/guides/use-a-hosted-provider.md +208 -0
  57. package/docs/guides/use-a-local-model.md +313 -0
  58. package/docs/guides/watch-a-run.md +326 -0
  59. package/docs/guides/write-a-workflow.md +318 -0
  60. package/docs/index.md +44 -0
  61. package/docs/llms.txt +216 -0
  62. package/docs/reference/authoring-surface/agent.md +128 -0
  63. package/docs/reference/authoring-surface/answer.md +140 -0
  64. package/docs/reference/authoring-surface/arg.md +60 -0
  65. package/docs/reference/authoring-surface/basic.md +62 -0
  66. package/docs/reference/authoring-surface/bearer.md +53 -0
  67. package/docs/reference/authoring-surface/chartschema.md +23 -0
  68. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  69. package/docs/reference/authoring-surface/command.md +191 -0
  70. package/docs/reference/authoring-surface/customize.md +64 -0
  71. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  72. package/docs/reference/authoring-surface/defineevent.md +114 -0
  73. package/docs/reference/authoring-surface/dispatch.md +126 -0
  74. package/docs/reference/authoring-surface/fail.md +83 -0
  75. package/docs/reference/authoring-surface/file.md +122 -0
  76. package/docs/reference/authoring-surface/frompick.md +277 -0
  77. package/docs/reference/authoring-surface/http.md +159 -0
  78. package/docs/reference/authoring-surface/isfailure.md +58 -0
  79. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  80. package/docs/reference/authoring-surface/open.md +74 -0
  81. package/docs/reference/authoring-surface/openbody.md +86 -0
  82. package/docs/reference/authoring-surface/paramsof.md +125 -0
  83. package/docs/reference/authoring-surface/pool.md +186 -0
  84. package/docs/reference/authoring-surface/push.md +112 -0
  85. package/docs/reference/authoring-surface/secret.md +118 -0
  86. package/docs/reference/authoring-surface/sentby.md +83 -0
  87. package/docs/reference/authoring-surface/source.md +51 -0
  88. package/docs/reference/authoring-surface/text.md +62 -0
  89. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  90. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  91. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  92. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  93. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  94. package/docs/reference/authoring-surface/type-arg.md +19 -0
  95. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  96. package/docs/reference/authoring-surface/type-binding.md +62 -0
  97. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  98. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  99. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  100. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  101. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  102. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  103. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  104. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  105. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  106. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  107. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  108. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  109. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  110. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  111. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  112. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  113. package/docs/reference/authoring-surface/type-childat.md +12 -0
  114. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  115. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  116. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  117. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  118. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  119. package/docs/reference/authoring-surface/type-customize.md +109 -0
  120. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  121. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  122. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  124. package/docs/reference/authoring-surface/type-duration.md +15 -0
  125. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  126. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  127. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  128. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  129. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  130. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  131. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  132. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  133. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  134. package/docs/reference/authoring-surface/type-failure.md +19 -0
  135. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  136. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  137. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  138. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  139. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  140. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  141. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  142. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  143. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  144. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  145. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  146. package/docs/reference/authoring-surface/type-lends.md +38 -0
  147. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  148. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  149. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  150. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  151. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  152. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  153. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  154. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  155. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  156. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  157. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  158. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  159. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  160. package/docs/reference/authoring-surface/type-openof.md +12 -0
  161. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  162. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  163. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  164. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  165. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  166. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  167. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  168. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  169. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  170. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  171. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  172. package/docs/reference/authoring-surface/type-reached.md +12 -0
  173. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  174. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  175. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  176. package/docs/reference/authoring-surface/type-secret.md +25 -0
  177. package/docs/reference/authoring-surface/type-sender.md +19 -0
  178. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  179. package/docs/reference/authoring-surface/type-size.md +85 -0
  180. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  181. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  182. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  183. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  184. package/docs/reference/authoring-surface/type-text.md +19 -0
  185. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  186. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  187. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  188. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  189. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  190. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  191. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  192. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  193. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  194. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  195. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  196. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  197. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  198. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  199. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  200. package/docs/reference/authoring-surface/workspace.md +259 -0
  201. package/docs/reference/authoring-surface.md +197 -0
  202. package/docs/reference/cli.md +433 -0
  203. package/docs/reference/config.md +756 -0
  204. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  205. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  206. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  207. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  208. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  209. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  210. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  211. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  212. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  213. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  214. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  215. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  216. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  217. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  218. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  219. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  220. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  221. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  222. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  223. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  224. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  225. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  226. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  227. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  228. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  229. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  230. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  231. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  232. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  233. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  234. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  235. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  236. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  237. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  238. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  239. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  240. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  241. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  242. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  243. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  244. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  245. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  246. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  247. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  248. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  249. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  250. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  251. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  252. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  253. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  254. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  255. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  256. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  257. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  258. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  259. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  260. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  261. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  262. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  263. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  264. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  265. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  266. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  267. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  268. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  269. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  270. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  271. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  272. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  273. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  274. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  275. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  276. package/docs/reference/decisions.md +83 -0
  277. package/docs/reference/glossary.md +648 -0
  278. package/docs/reference/http-api.md +1348 -0
  279. package/docs/reference/machines.md +190 -0
  280. package/docs/reference/mechanism-events.md +72 -0
  281. package/docs/why.md +206 -0
  282. package/package.json +6 -4
  283. package/src/actor.ts +976 -61
  284. package/src/agent.ts +114 -38
  285. package/src/ambient.ts +4 -1
  286. package/src/assembly-checks.ts +347 -0
  287. package/src/assembly-tar.ts +494 -0
  288. package/src/assembly.ts +721 -0
  289. package/src/cause.ts +234 -0
  290. package/src/chart.ts +2182 -0
  291. package/src/charts.ts +748 -0
  292. package/src/config.ts +520 -162
  293. package/src/content-hash.ts +105 -0
  294. package/src/custodian.lua +6 -5
  295. package/src/custodian.ts +143 -39
  296. package/src/customize.ts +388 -55
  297. package/src/dispatch-effect.ts +945 -0
  298. package/src/door-compat.ts +217 -0
  299. package/src/durability.ts +11 -4
  300. package/src/effect-checks.ts +557 -0
  301. package/src/effect.ts +1202 -0
  302. package/src/effector-wire.ts +674 -0
  303. package/src/fail.ts +209 -0
  304. package/src/fingerprint.ts +4 -3
  305. package/src/gate.ts +8 -0
  306. package/src/git-clone.ts +65 -0
  307. package/src/git-push.ts +336 -0
  308. package/src/harness-client.ts +764 -48
  309. package/src/held-secrets.ts +82 -26
  310. package/src/holds.ts +156 -0
  311. package/src/http-effect.ts +1423 -0
  312. package/src/http.ts +916 -113
  313. package/src/images.ts +47 -21
  314. package/src/index.ts +62 -43
  315. package/src/instance.ts +505 -59
  316. package/src/internal.ts +84 -0
  317. package/src/kube-client.ts +333 -0
  318. package/src/machine-doc.ts +106 -6
  319. package/src/mechanism-routes.ts +168 -0
  320. package/src/model-keys.generated.ts +53 -0
  321. package/src/model-keys.ts +18 -58
  322. package/src/mount-digest.ts +53 -0
  323. package/src/names.ts +66 -8
  324. package/src/notices.ts +141 -0
  325. package/src/open-body.ts +1580 -0
  326. package/src/open-effect.ts +217 -0
  327. package/src/open.ts +59 -6
  328. package/src/parts.ts +709 -41
  329. package/src/party.ts +534 -0
  330. package/src/pick.ts +761 -0
  331. package/src/pool.ts +248 -37
  332. package/src/registration.ts +382 -19
  333. package/src/repo-fetch.ts +60 -85
  334. package/src/repos.ts +65 -75
  335. package/src/rest.ts +133 -0
  336. package/src/run-host.ts +2383 -115
  337. package/src/run-output.ts +38 -0
  338. package/src/sandbox-effect.ts +1126 -0
  339. package/src/sandbox-effector.ts +111 -0
  340. package/src/sandbox-kube.ts +1721 -0
  341. package/src/sandbox-watch.ts +260 -0
  342. package/src/secret-placement.ts +529 -0
  343. package/src/server.ts +284 -84
  344. package/src/setup.ts +819 -117
  345. package/src/size.ts +454 -0
  346. package/src/snapshot-store.ts +115 -20
  347. package/src/stub-harness.ts +49 -16
  348. package/src/sync-client.ts +54 -0
  349. package/src/timers.ts +191 -0
  350. package/src/tokens.ts +35 -4
  351. package/src/vocabulary.ts +135 -15
  352. package/src/wire.ts +238 -10
  353. package/src/workspace.ts +1195 -338
  354. package/src/sandbox-kubectl.ts +0 -1108
@@ -0,0 +1,107 @@
1
+ # A release is a pushed tag, and the publish guard is the credential
2
+
3
+ [ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md) made the accidental-publish guard
4
+ a manifest line — `publishConfig.registry: http://localhost:4873` — and named the edit of that line the "we are ready"
5
+ commit. Preparing the first real publish (2026-09-20) found the guard has no exit: `publishConfig` outranks `--registry`
6
+ by design, so the edit that lets the kit reach npmjs is the same edit that breaks every reader of the guard at once —
7
+ `packages/cli/test/publish.test.ts` asserts a localhost registry, `scripts/dist-publish.sh` refuses any other, and the
8
+ `@dist` fixture publishes to whatever the manifest names. After the flip the local loop cannot publish at all, and
9
+ flipping it back per release keeps the unit gate red for the length of the release. The property that made the guard
10
+ strong is the property that makes it unreleasable. This ADR amends ADR-0043's guard and settles the rest of the release:
11
+ version, images, gate, trigger.
12
+
13
+ ## Decision
14
+
15
+ - **The guard is the credential, not the manifest.** `publishConfig.registry` leaves every manifest. The only holder of
16
+ an npmjs credential is the release job, and it holds no token: it publishes with npm trusted publishing (OIDC from
17
+ GitHub Actions), so nothing long-lived exists to leak. A `pnpm -r publish` on a dev box reaches npmjs and fails
18
+ `ENEEDAUTH`, which is the guard — an absence that cannot be edited past, where the manifest line was a presence that
19
+ had to be. `publishConfig.access: public` stays on all four, because a scoped package's first publish is 402 without
20
+ it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that **no** public
21
+ manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a release to
22
+ localhost.
23
+ - **Lockstep, and `main` carries the next version.** One number across `packages/*` and `templates/default`, because
24
+ [ADR-0019](0019-one-converging-command-against-the-current-context.md)'s one release train already reads a single
25
+ `KIT_VERSION`, ADR-0043's scaffold pins two packages at one exact number, and `workspace:*` packs to an exact
26
+ inter-dependency — independent versions would need ranges, and 0.x ranges break. `0.0.0` stops being special: it was
27
+ the ephemeral registry's value only because nothing was released, and a fresh verdaccio publishes any version cleanly.
28
+ `just release patch|minor|major` (or an explicit version) computes the number the way `npm version` does, writes it to
29
+ every manifest and the template, runs the unit gate (`init.test.ts` is the tripwire), commits, and tags `v<ver>`.
30
+ Nothing rewrites a version at publish time: a loop that edits manifests tests edited manifests (ADR-0043), and a
31
+ checkout `KIT_VERSION` must read the number the checkout will ship.
32
+ - **The tag push is the release, and the train runs in order.** `release.yml` runs on `v*`. Its first job refuses if the
33
+ tag disagrees with any manifest, and then asks every other refusal of the publish
34
+ (`scripts/publish.sh --stage --check`: the tree, the ref, the strict docs build, no output block not yet run). These
35
+ take seconds, so a tag that cannot publish starts no tier. Then the full gate, as parallel jobs that each need that
36
+ first one — unit, harness conformance, the default e2e profile, the docs site build and the Custodian suite in one
37
+ job; `@kind` as four shards, each on a kind cluster its own runner creates (the operator's own e2e job already works
38
+ this way); and `@dist` on one more — so the installed path is proven on the tree that publishes, not on a maintainer's
39
+ box the day before. The tiers are jobs and not steps of one job because one runner ran them in series: v0.5.0's job
40
+ spent 37 minutes in `@kind` alone, two workers being the most a 4-vCPU runner holds, and then failed at a refusal of
41
+ the publish that takes a second. Only when every tier job passes does the one job that holds the rights run:
42
+ `scripts/kit-push.sh ghcr.io/snapwich` (multi-arch, `GITHUB_TOKEN` with `packages: write`), and **only then** npm:
43
+ each public package is packed with pnpm, which rewrites `workspace:*` to the exact version, and **staged** with
44
+ `npm stage publish`, the client that speaks trusted publishing — in pnpm's topological order, dependencies first,
45
+ skipping a version npm already holds live so a re-run converges. The order is the contract: a package whose images are
46
+ not at the home fails at pull on the user's first `jr2 up`
47
+ ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)); images with no package are inert,
48
+ and stay so while the packages sit staged. A second job, `pages`, follows the release job and deploys the docs site of
49
+ the same tree to GitHub Pages
50
+ ([ADR-0072](0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md)). It does not wait for the
51
+ npm approval, so the site can be ahead of the live packages until the maintainer approves them.
52
+ - **The job stages; the maintainer approves.** Each trusted publisher is configured for `npm stage publish` only, so the
53
+ tag produces four staged tarballs and one 2FA approval per package (`npm stage approve`, dependencies first) makes
54
+ them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is bound to the
55
+ repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise publish under
56
+ the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be remembered in
57
+ order, the staged tarballs wait.
58
+ - **Two executors of one train.** `scripts/publish.sh` is the irreversible half — images, then packages, dependencies
59
+ first, live versions skipped — and the job runs it with `--stage`. A maintainer runs it as `just publish` from the
60
+ pushed tag when a 0.x release cannot wait the job's hour: `npm publish` with a 2FA prompt per package in place of an
61
+ approval, and the job converges behind it. The checks are the same in both (lockstep, clean tree, HEAD is the pushed
62
+ tag), and so is the guard: a login alone publishes nothing under write-2FA, so a dev box holds no credential that
63
+ publishes by itself.
64
+ - **`ci.yml` on push and PR is the light gate**: frozen install, typecheck, format check, `pnpm -r test`, the default
65
+ Cucumber profile — minutes, no docker. The heavy tiers and the Custodian suite run on the tag and by hand
66
+ (`just e2e-kind`, `just e2e-dist`, `just custodian-test`) when a change touches build, deploy, or dist code.
67
+ - **`@jr2/machines` peers on `@jr2/orchestrator` exactly.** `workspace:*` stays; a Machine is written against one
68
+ orchestrator, and a mismatched pair fails at install with a peer error instead of at run.
69
+ - **`engines.node: ">=24"` on every public package and the root.** Every runtime the kit runs in is Node 24 already
70
+ (both Kit Dockerfiles, the instance Dockerfile, the `registerHooks` bin); the root's `>=22` was the straggler.
71
+
72
+ ## Considered options
73
+
74
+ - **Keep the localhost line; the release job rewrites it before publish.** Rejected by ADR-0043's own rule — the
75
+ published manifest would be one no tier tested.
76
+ - **Point the manifests at npmjs; the dist loop rewrites them to localhost.** The same objection inverted: the tier that
77
+ proves the tarballs would test a manifest the release never ships.
78
+ - **A `prepublishOnly` guard** (refuse unless localhost or `JR2_RELEASE=1`). In-tree, but it rests on pnpm exposing the
79
+ effective registry to lifecycle scripts, and a one-variable bypass is a guard shaped like a suggestion.
80
+ - **CI stamps the version from the tag; `main` stays `0.0.0`.** Rejected twice: edited manifests, and a checkout's
81
+ `KIT_VERSION` would read `0.0.0` forever.
82
+ - **Changesets in fixed mode.** The right tool once there are contributors and changelogs; today it is a version PR in
83
+ front of a five-file edit. The contract it would implement — tag equals manifests, lockstep — is the one chosen here,
84
+ so adopting it later changes no ADR.
85
+ - **`workflow_dispatch` with a version input.** CI would make the bump commit, so the gate runs on a tree the maintainer
86
+ never saw at that version.
87
+ - **Manual `just kit-push` before tagging.** A runbook step in front of a release (ADR-0019 calls that an interface
88
+ failure), and nothing checks the images exist before npm publish.
89
+
90
+ ## Consequences
91
+
92
+ - ADR-0043's "What publishes, and the guard" is amended: the guard is credential absence; the "we are ready" commit is
93
+ no longer an edit, it is the first pushed tag. Its consequence "localhost `publishConfig` … is the real shipping
94
+ configuration" no longer holds; the rest of that list does.
95
+ - **Bootstrap, once:** create the `jr2` org on npmjs (a scope is a user or an org), publish the first version by hand
96
+ with a token — npm attaches a trusted publisher only to a package that already exists — then configure the trusted
97
+ publisher on all four packages and revoke the token. After that no token that publishes on its own exists anywhere:
98
+ the job's identity stages, a maintainer's login needs its second factor. Likewise once: make the three GHCR packages
99
+ public after the first push, because GHCR creates them private.
100
+ - `publish.test.ts` asserts no registry, `access: public`, and `engines`; `dist-publish.sh` drops its manifest read-back
101
+ and passes `--registry`; the `@dist` fixture takes its registry from `JR2_DIST_PORT`'s default rather than the
102
+ manifest; `kit-push.sh`'s cli-vs-orchestrator cross-check widens to every manifest; `just release` is new;
103
+ `.github/workflows/` gains `ci.yml` and `release.yml`.
104
+ - A release costs a runner kind cluster and roughly ten minutes of docker. Accepted: it is the only time the installed
105
+ path must be proven on the exact tree that ships.
106
+ - The four tarballs gain the npm-page fields (`description`, `repository` with `directory`, `homepage`, `bugs`,
107
+ `keywords`) and a README each; the root README's "only scaffolding exists" line goes.
@@ -0,0 +1,62 @@
1
+ # A global `jr2` hands off to the Instance's own, and an Instance has one Kit version
2
+
3
+ [ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md) made `jr2 init` pin `@jr2/cli` as
4
+ an exact devDependency beside `@jr2/orchestrator`, and said instances upgrade "by editing two dep lines". That pin only
5
+ means something if the Instance's copy of the CLI is the one that runs — and nothing made it so. `bin/jr2.js` ran
6
+ itself, whatever cwd it was run from, and `KIT_VERSION` was read from the `@jr2/orchestrator` the **running CLI**
7
+ resolved, never the one the Instance resolved. The first bump after 0.1.0 (2026-09-20) showed the hole: a global
8
+ `jr2@0.1.1` in an Instance pinning `@jr2/orchestrator@0.1.2` and `@jr2/cli@0.1.0` converged 0.1.1 Kit images around a
9
+ 0.1.2 bundle, and nothing said a word. Published `@jr2/cli@X` depends on `@jr2/orchestrator@X` exact, so a mismatched
10
+ pin quietly nests a **second** orchestrator under the CLI — the class-identity failure commit 2685d9b had already
11
+ patched one symptom of. This ADR settles which binary runs, and what "the Instance's version" is.
12
+
13
+ ## Decision
14
+
15
+ - **A global `jr2` hands off (the gulp/grunt model).** Before any verb, the binary walks up from cwd to `jr2.config.ts`
16
+ (ADR-0009's root marker). If that folder resolves `@jr2/cli` to a real path other than its own, it runs that package's
17
+ `bin.jr2` with the same arguments and stdio, and exits with its code. Nothing else — no flag parsing, no verb
18
+ exceptions (`--url` inside an Instance still hands off; the rule stays one sentence). _Amended 2026-09-20:_ plus one
19
+ env var, `JR2_HANDOFF_FROM=<version> <realpath>`, naming the copy that handed off. `jr2 version` prints it as the
20
+ `global` line, so "is my global out of date" is answered without leaving the Instance; nothing else reads it. It is
21
+ the only fact the local copy cannot learn on its own (`npm ls -g` is slow and per-manager). A global that predates the
22
+ variable hands off without it, and the report then shows no `global` line at all — indistinguishable from `npx jr2`,
23
+ and there is no third source to ask.
24
+ - **Spawn, not import.** gulp loads the local gulp in-process because local gulp is a _library_ its CLI drives; here the
25
+ local is a _binary_. Spawning couples the global to one contract — the local package's `bin` field, npm's own, stable
26
+ across versions — and the local's preamble runs: its pinned `ts-blank-space`, its hooks, whatever a future bin adds.
27
+ Importing `src/cli.ts` would make the local's internal layout and `main` signature a cross-version API the global must
28
+ honor forever, and the global is the copy that cannot be updated once it ships. One extra Node start per invocation is
29
+ the price. Ctrl-C is unchanged: both share the process group.
30
+ - **An Instance has one Kit version: the `@jr2/orchestrator` it resolves**, because that is the Kit image its
31
+ Orchestrator runs and the version its Assembly is written against
32
+ ([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)). Wherever the CLI loads
33
+ an Instance (the root walk every Instance verb goes through; `--url` verbs skip the walk per ADR-0009, `jr2 init` has
34
+ no Instance yet), it resolves `@jr2/orchestrator/package.json` from the Instance root and from itself and requires the
35
+ two **real paths** to be equal, and that copy's version to be the CLI's own. Not equal — or absent — is a refusal that
36
+ names both versions and the two lines to edit, then says reinstall. Real-path identity first: same file means same
37
+ version by construction under npm hoisting, pnpm dedup, and checkout symlinks alike, and it is exactly the property
38
+ the class-identity bug needs. But one copy is not yet one version: when the Instance edits only the
39
+ `@jr2/orchestrator` line, the root's own pin wins over the CLI's peer, the one copy is hoisted, and the old CLI
40
+ resolves it at the same real path. So the CLI's own number must equal it too, which one release train makes true for a
41
+ correct pin ([ADR-0055](0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md)).
42
+ - **`@jr2/orchestrator` is a peer of `@jr2/cli`, not a dependency.** `peerDependencies: workspace:*` (pnpm rewrites it
43
+ exact at publish) plus the same under `devDependencies` for the checkout's own tests. A mismatched pin is then a peer
44
+ conflict at install. npm does not always fail it: the root's own `@jr2/orchestrator` line wins over the peer of a
45
+ devDependency with `npm warn ERESOLVE overriding peer dependency`, and pnpm and bun warn too, so the check above is
46
+ what refuses. The CLI stops carrying an orchestrator of its own, so a second copy can only come from the Instance's
47
+ resolution — the copy we want. The CLI stays a **devDependency** of every Instance: the bundle installs
48
+ `--omit=dev`/`--prod`, and the image has no use for a CLI or its erasure hook; peers of a devDependency still
49
+ auto-install and still `ERESOLVE`.
50
+ - **`jr2 init` reads the CLI's own version.** A global with no Instance around it must not need its peer resolved, so
51
+ the scaffold's exact pin comes from `@jr2/cli`'s `package.json`. The numbers are equal by construction: one release
52
+ train, lockstep ([ADR-0055](0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md)).
53
+
54
+ ## Consequences
55
+
56
+ - ADR-0043's "edit two dep lines" is amended: an Instance is upgraded by editing two lines **to the same number**, and
57
+ editing one is now a refusal (or an install error), never a silent skew.
58
+ - A global installed before this ADR does not hand off; the user updates it once. After that the global's version stops
59
+ mattering inside an Instance, which is the point.
60
+ - The `@dist` tier exercises the handoff for free: its installed global drives an Instance whose own `@jr2/cli` came
61
+ from the same verdaccio at a different real path. The mode-line assertion it already makes now asserts the local
62
+ copy's line.
@@ -0,0 +1,152 @@
1
+ # A Turn is its Frame, its Dials, and whether it continues
2
+
3
+ A live `task` run (2026-09-20, `fa45e88f`, seven tool calls, eight minutes, zero bytes of the target read) exposed the
4
+ seam: the prompt said `Work in /work/target/summarize` while every Working tool resolved relative paths at `/work`,
5
+ because `cwd` lived on the Agent definition and a definition is written before any run — before any worktree — exists.
6
+ The package compensated with prose, and a weak model ignored prose: it wrote `SUMMARY.md` outside the worktree, called
7
+ `finish`, and the run parked at `review` claiming done. The audit that followed found the invoke input carrying four
8
+ ways to name one conversation (`session: "continue"`, `scope`, `conversation`, and an undeclared `instanceId`), one of
9
+ them (`scope`) with two unrelated author intents, none with a stated precedence, and every `@kind` fixture bypassing all
10
+ of them. ADR-0028 already said the reviewing state hands a per-round worktree path as the reviewer's "cwd and prompt";
11
+ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thing had no field to land in.
12
+
13
+ ## Decision
14
+
15
+ - **A Turn's input is its Frame, its Dials, and whether it continues — nothing else.**
16
+
17
+ ```ts
18
+ { prompt, cwd?, continue?, model?, thinkingLevel? }
19
+ ```
20
+
21
+ Identity — `instructions`, `workspace` — stays on the definition, and the definition is exactly identity plus one
22
+ default: `agent({ model, instructions, workspace?, thinkingLevel?, description? })`.
23
+
24
+ - **The Frame is `prompt` and `cwd`: what this Turn is about and where it works.** Neither identity nor a Dial. A
25
+ worktree path is per run by nature — `/work/<slot>/<branch>` exists only once a run has a branch — so no definition
26
+ can name it, and a directory is not a property of a persona any more than a prompt is. `cwd` leaves `AgentDefinition`.
27
+ The Harness roots the Working tools at the Frame's `cwd`, which rides the admit body beside the prompt and the Dials.
28
+
29
+ - **The Briefing is not the Frame.** jr2's Turn part
30
+ ([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)) follows the prompt — the resolved working
31
+ directory, the Allowed picks and any notices — in a block of its own. The prompt stays the Machine state's words
32
+ alone; jr2 never writes into it.
33
+
34
+ - **Under a Workspace, an absent `cwd` resolves to the only Repo Slot's Worktree, and is refused when there is more than
35
+ one.** With one slot there is no choice, so no convention is being smuggled in (ADR-0051: the kit gives no slot a
36
+ meaning). With two or more, the state must say which — `cwd: context.workspace.repos.target` — and the actor refuses
37
+ the Turn before admission, naming the slots and the line to add. The trap is accepted with open eyes: a composer who
38
+ adds a second slot on the `customize` line can make a Machine that ran yesterday refuse today, in a file the Machine's
39
+ author never sees. The refusal is explicit, at the first Turn, before any pod is spent; `jr2 up` cannot see it because
40
+ the invoke's input is a function. A `workspace: "none"` Agent has no Working tools and no `cwd` (ADR-0028); a
41
+ stub-Harness run with no Workspace keeps `/work`. `task` always passes its `cwd`, because "the first slot is the one
42
+ the coder edits" is its convention and a convention is stated, not defaulted.
43
+
44
+ - **`continue: true` is the whole continuation surface.** Absent, every invocation is a fresh conversation — jr's lossy
45
+ handoff, ADR-0016's default, unchanged. Present, the Turn lands on **this Agent's one conversation in this Machine
46
+ instance**, whichever state of the Machine invokes it. The Instance ID is structural —
47
+ `<runId>/<machine actor path>/<agent>` — so the author writes a boolean, not a key: in a state machine the identity is
48
+ already on the page (the run, the Machine instance, the Agent slot), and a caller-chosen string would only restate it,
49
+ or contradict it. Every surveyed orchestration framework makes the caller hold a key (LangGraph `thread_id`, Temporal
50
+ Workflow Id, Mastra `threadId`) because nothing else can; here something else can.
51
+
52
+ - **Fan-out is safe by construction.** A Pool spawns each worker with the item id as its actor id (pool.ts), so three
53
+ `task` children each mint their own `…/backlog.worker.<item>/coder`. The packaged Machine never learns it is under a
54
+ Pool.
55
+ - **States of one Machine share by saying `continue`.** States differ by Menu — a coder in `implement` offers
56
+ `finish`, the same coder in `fix` offers `finish` and `dispute` — and the same conversation is wanted across them. A
57
+ state that wants a clean context leaves `continue` off. A Turn joins only a conversation that an earlier Turn
58
+ started with `continue: true`, so a retry loop says `continue` on each Turn, the first too: a first attempt without
59
+ it is not in the conversation that attempt 2 continues. Thus the docs show `continue: true` on each Turn of a retry
60
+ loop, and a conditional spread (`...(clean ? {} : { continue: true as const })`) only for a state that must start a
61
+ clean conversation on some Turns only (a state that always starts clean leaves `continue` off). A clean Turn does
62
+ not start the continued conversation again: the next Turn with `continue: true` joins the earlier one, so attempt 4
63
+ after a clean attempt 3 continues attempts 1 and 2. The alternative, a spread on `attempts > 0`, keeps attempt 1 out
64
+ of the conversation, so attempt 2 does not see it. The cost of `continue: true` on attempt 1 is the Runaway reroll
65
+ (ADR-0035): a continued Turn gets none, so a Runaway on attempt 1 sends `agent.fault` at once, and burns the
66
+ conversation. The docs accept this cost, so that attempt 2 sees attempt 1.
67
+ - **Two Agents never share a conversation.** The agent name is in the id, and it always was; a second persona under
68
+ one transcript would swap the system prompt mid-conversation. The memory-Agent shape ("read the coder's
69
+ conversation, store what matters") is served by handing the transcript as prompt _data_ to an Agent with its own
70
+ conversation — Letta's sleep-time agents — not by joining; how a transcript reaches a Machine as data is a follow-on
71
+ decision.
72
+ - **A faulted conversation is never continued.** No fault leaves a conversation worth continuing: a runaway either had
73
+ its reroll or has a poisoned context; infra and no-signal mean the Harness that held it is unreachable (ADR-0035).
74
+ jr2 keeps an **epoch** per continued conversation in the ledger beside the snapshot; the terminal `agent.fault`
75
+ bumps it, and the next `continue` on that Agent mints `<id>/<epoch>` — a virgin conversation. The author deletes
76
+ their generation counter. What stays with the author is re-briefing: the fault event is the signal that the next
77
+ prompt must carry the whole task again, and jr2 cannot write that prompt. The one fault that is not the
78
+ conversation's is a Harness restart that cut the Turn (`Turn lost`, or `memory limit` behind it): the rebuilt
79
+ Harness holds the conversation ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)), so the epoch
80
+ stays and the next `continue` lands on it; a rebuild that could not read it answers that Turn's `holds` check, which
81
+ gives a `conversation-new` notice (ADR-0062).
82
+ - **The reroll stays closed to continued conversations** (ADR-0035): the one recovery jr2 knows is the identical
83
+ prompt on a fresh conversation, and a continued Turn's prompt ("Continue.") is meaningless fresh. Fault route plus
84
+ re-brief is the recovery.
85
+ - **Two live invocations of one continued id — parallel states — are refused at the registration table**, one live
86
+ surface per address; a Submission that arrives behind a live one on the _Harness_ queues (ADR-0027). Sequential
87
+ states never race, because ADR-0024 ends the previous Turn before the next state invokes.
88
+
89
+ - **`session`, `scope`, `conversation`, and `instanceId` retire.** `session: "continue"` was already this boolean under
90
+ a longer name — its id was the invoking Machine's actor path plus `scope` — so what retires there is the name and the
91
+ suffix; `conversation` was the cross-Machine pin (a name _replacing_ the path, so two Machine instances — two Pool
92
+ children — collided by construction, the hazard ADR-0049 recorded and could not refuse); `scope` suffixed all of them
93
+ and carried two intents (a subject under `continue`, a generation counter in `task`); `instanceId` bypassed minting
94
+ for tests. One boolean replaces all four because the id is deterministic when continued, so nothing needs a handle to
95
+ address it.
96
+
97
+ - **The author type carries no mechanism.** `endpoint` and `sandbox` — set only by stub-tier fixtures that have no
98
+ `workspace()` to resolve from — move to a mechanism type the tests import. `tools`, the Menu override, retires: a
99
+ hand-written Menu can only agree with the invoking state's transitions or lie about them, and the lie is a pick that
100
+ moves nothing (ADR-0029). The one shape it hid — picks written on the CHILD STATES of the invoking state — is derived:
101
+ those child states are the state's modes (ADR-0015). A state that allows no picks stays legal, because a state a Gate
102
+ or a timer moves asks its Agent for text and nothing else. A real run never sets any of them: a Sandbox Agent resolves
103
+ ambiently (ADR-0016), a `workspace: "none"` Agent lands on the Instance Harness (ADR-0031).
104
+
105
+ ## Considered options
106
+
107
+ - **Prompt-only** — tell the model that relative paths resolve at `/work` and demand absolute ones. Rejected by the
108
+ incident: ADR-0024 already established that the same prompt produces compliance on one run and not the next, and every
109
+ other Machine would repeat the sentence.
110
+ - **`cwd` stays on the definition, overridable per Turn.** Rejected: a definition-level `cwd` can only ever say `/work`,
111
+ the parent of every checkout, where grep and glob hit the pristine `default/` clone beside the worktree. A default
112
+ that is wrong under every `workspace()` is not a default.
113
+ - **`cwd` as a third Dial.** Rejected on the word: Dials say how hard to run. A directory says where, which is the same
114
+ kind of fact as the prompt — hence Frame.
115
+ - **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`"). The single-slot
116
+ default is not the same thing — with one slot there is nothing to privilege.
117
+ - **Refuse an absent `cwd` under every Workspace.** The safer twin of the single-slot default: one line per invoke, no
118
+ composition trap. Not taken: the trap's failure is explicit and early, and the line is noise for the common
119
+ single-repo Machine.
120
+ - **A caller-chosen conversation key (`conversation: "coder"`).** Rejected: it either restates the structure or
121
+ contradicts it. Path-scoping the key was proposed and made the key redundant — once the id is
122
+ `<run>/<machine path>/<agent>`, the author has nothing left to name.
123
+ - **Continuation scoped to the invoking state.** Rejected: splits one conversation across the states that differ only by
124
+ Menu, which is the case continuation exists for.
125
+ - **The cross-Machine pin kept as an opt-in.** Rejected: zero users since `examples/coding` went (ADR-0054), never sound
126
+ — the two states it joined sat on different Harnesses, and a conversation lives on one server (ADR-0031) — and an
127
+ unused opt-in is the fourth mechanism back under a new name. If a sound case appears it is a new decision.
128
+ - **Continue a faulted conversation, author's choice.** Rejected: there is no case. Every fault class leaves either a
129
+ poisoned context or no server — except a Harness restart, which cuts the Turn and keeps the conversation (above).
130
+
131
+ ## Consequences
132
+
133
+ - `AgentTurnInput` is `{ prompt, cwd?, continue?, model?, thinkingLevel? }`; `AgentDefinition` loses `cwd`. The Harness
134
+ admit body gains the Frame's `cwd`; `ResolvedDefinition.cwd` resolves from it, not the definition; the Harness stops
135
+ validating `description`, which the Orchestrator strips before admission (it is Console material).
136
+ - **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case, and it
137
+ says `/work` — a stated default. A relative path has no such answer: it would resolve against whatever directory the
138
+ Harness process sits in, which is the silent wrong directory this ADR was written for, one level further from the
139
+ author. The refusal is a 400 at admission, before a pod is spent.
140
+ - `task` passes `cwd: worktreeOf(context).path` and `continue: true`, and drops `generation` (and its `scope`). It keeps
141
+ `turns`, reset on `agent.fault`, because re-briefing is prompt policy.
142
+ - The ledger record for a continued conversation carries its epoch; the input mapper reads it at mint time, so the
143
+ minted id persists with the input and restore re-attaches the same conversation (ADR-0016's mapper rule holds — the
144
+ fresh suffix is still the random component).
145
+ - The `@kind` fixtures drop `tools:` and `instanceId:`; the steps compute the continued id from the actor path, as they
146
+ already computed today's pin.
147
+ - A regression scenario for the silent success: a `task` whose model uses relative paths must produce its file inside
148
+ the worktree, and a multi-slot Machine with no `cwd` must be refused with the slots named.
149
+ - GLOSSARY.md: **Frame** enters; **Dials** loses `cwd`; **Instance ID** states the `continue` scope and that a faulted
150
+ conversation is not continued. ADR-0016 (continuation), ADR-0018 (identity line), ADR-0031 (placement scenario),
151
+ ADR-0035 (reroll gate wording), ADR-0049 (persona hazard), and ADR-0054 (`task`'s continuation) are edited to state
152
+ this truth.
@@ -0,0 +1,106 @@
1
+ # A Harness answers the Orchestrator alone
2
+
3
+ A red-team review (2026-09-22) found that the Harness wire had no authentication. `POST /agents/:name/:id`, the stream
4
+ and history views, and `.../abort` checked no bearer, and the server bound `0.0.0.0:8080`. Iids are derivable
5
+ (`<runId>/<machine>/<agent>` for a continued conversation,
6
+ [ADR-0057](0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md)), so any pod that could reach a Harness could
7
+ admit a Turn into another conversation, read its history, or abort it. An Agent with bash in one Sandbox could do this
8
+ to another Sandbox, or to the Instance Harness, which holds every Menu-only conversation in the Instance
9
+ ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). No NetworkPolicy existed to limit the reach. The
10
+ review also found that the echo ([ADR-0023](0023-the-harness-prints-the-conversation.md)) sent the raw Instance token to
11
+ every Sandbox Harness on each push. The env held only its digest, but the token itself passed through the process that
12
+ the Agent executes code beside. So we authenticate the wire with a bearer that each Harness can verify and cannot mint,
13
+ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harness pod.
14
+
15
+ ## Decision
16
+
17
+ - **One bearer per placement.** The Orchestrator presents
18
+ `harnessToken(key, placement) = HMAC(signing key, "harness:" + placement)` on every Harness wire call: admit, each
19
+ long-poll, abort, and echo. `placement` is the name of the pod that hosts the Turn: the Sandbox's name, or
20
+ `jr2-instance-harness`. This is the same name the registration records as its delivery scope
21
+ ([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)), so one name scopes both
22
+ directions. The Custodian there may speak for the Turn's picks, and only this bearer may drive the conversation there.
23
+ - **A key per Effector placement, derived the same way**
24
+ ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)).
25
+ `effectorKey(key, placement) = HMAC(signing key, "effector:" + placement)` signs every request the Orchestrator sends
26
+ an Effector and every answer it takes back. `placement` is `instance` for the Instance Effector and the Sandbox's name
27
+ for a Sandbox Effector, so each Sandbox pod has its own key and a request signed for one Sandbox is refused by every
28
+ other; no Sandbox name is `instance`, since each one starts `ws-`. The `effector:` prefix keeps it apart from every
29
+ Harness bearer and Sandbox token. Unlike a Harness, an Effector holds the key itself — it must sign its answers — in a
30
+ Secret only its own container mounts. `jr2 up` writes the Instance Effector's (`jr2-instance-effector`) from the kept
31
+ signing key. The Orchestrator writes a Sandbox Effector's at provision, as it writes the Sandbox token: into
32
+ `<sandbox>-effector`, a Secret the Sandbox CR owns, so it goes with the pod. Either way the Orchestrator stores
33
+ nothing and derives the key again after a restart, and a re-provision applies the same value.
34
+ - **The Harness holds only the digest.** Its env carries `JR2_HARNESS_TOKEN_SHA256 = sha256(bearer)`. The Agent can read
35
+ that env, and a digest inverts to nothing. So a Harness can verify its own bearer, and it can mint none, for itself or
36
+ for another pod. The HMAC key never leaves the Orchestrator's Secret.
37
+ - **The bearer is derived, not stored.** This is the reason the Sandbox token is a signed name (`tokens.ts`). The
38
+ Orchestrator holds no per-pod state. A restarted Orchestrator derives the same bearer that a live Harness already
39
+ checks, so re-attach ([ADR-0012](0012-workspace-wrapper-machine.md)) survives with no change. The `harness:` prefix
40
+ separates it from the Sandbox token: no DNS-label name produces it.
41
+ - **Every route is gated, and the gate runs first.** Admit, stream, history, abort and echo answer 401 without the
42
+ bearer. A 401 comes before a 404, so a refused caller does not learn which conversations exist. A refused admission
43
+ creates no conversation. The Harness requires `JR2_HARNESS_TOKEN_SHA256` at boot and fails loudly without it.
44
+ - **The echo uses the same bearer.** `JR2_ECHO_TOKEN_SHA256` is removed. The Instance token no longer enters any pod,
45
+ which is what [ADR-0032](0032-the-console-unlocks-with-the-instance-token.md) and
46
+ [Secrets and trust](../../concepts/secrets-and-trust.md) say.
47
+ - **A refused stream is a fault, not a reconnect.** The bearer is derived, so a Harness that refuses it once refuses it
48
+ on every retry. `wait` settles a 401 or 403 as a `SettlementFault` and does not park the Turn forever.
49
+ - **Ingress to every Harness pod comes from the Orchestrator alone.** `jr2 up` converges two NetworkPolicies in the
50
+ instance namespace. `jr2-sandbox-ingress` selects every pod that has the operator's `sandbox.jr2.dev/name` label.
51
+ `jr2-instance-harness-ingress` selects `app: jr2-instance-harness`. Both admit ingress only from this instance's
52
+ Orchestrator pods (`app: jr2-orchestrator`, `jr2.dev/instance: <name>`). They are converged unconditionally, so a
53
+ Harness pod is never created before its policy exists. The Instance Effector gets a third of the same form,
54
+ `jr2-instance-effector`, applied with it in one List (ADR-0068).
55
+ - **Starting a run needs the Instance token.** `POST /workflows/:name/runs` moves from `authenticated` to
56
+ `instanceOnly`. The scope of a Sandbox token is its own agent surface. The Custodian's route allowlist does not
57
+ forward this route, but that is not a control.
58
+
59
+ ## Considered options
60
+
61
+ - **Ed25519, one short-lived token per admission, scoped to one iid.** This was the first proposal. Rejected. `wait`
62
+ reconnects indefinitely by design, and re-attach after a restart follows a ledgered admission of any age, so a short
63
+ expiry breaks both unless each poll gets a new token. That adds a public key in every pod and a dependency on clock
64
+ sync. What it buys over a per-placement bearer is scoping inside one pod. But the Agent already has code execution in
65
+ that pod's Harness container, so that scope protects nothing.
66
+ - **The Instance token's digest, as the echo used.** Rejected. It is one secret for every pod, and it sends the Instance
67
+ token, the full-trust credential, into each Harness process.
68
+ - **One Instance-wide Harness token.** Rejected. A digest does not let a Harness mint, but the bearer arrives in every
69
+ Harness process on every call. A per-placement bearer that leaks from one pod opens only that pod, which the Agent
70
+ there already controls.
71
+ - **NetworkPolicy alone.** Rejected, for the reason ADR-0013 gives: a policy bounds where a pod may talk, and only a
72
+ token bounds what it may do. The policy cannot separate the Harness from the Custodian in one pod, and a cluster whose
73
+ CNI does not enforce policy gets nothing from it.
74
+
75
+ ## Consequences
76
+
77
+ - **The stub Harness checks nothing.** It is a host-side test fixture that is reached by an explicit `endpoint`, and a
78
+ run on the stub path names no placement, so it sends no bearer. This is the second documented divergence from the
79
+ normative model ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)). Conformance tests
80
+ inject the check (`checkBearer`), and `auth.test.ts` in `@jr2/harness` pins the routing of the gate.
81
+ - **kindnet enforces NetworkPolicy.** On the kind version this repo pins, a deny-ingress policy blocks pod-to-pod
82
+ traffic, so the `@kind` tier proves both controls. A Sandbox's own Harness refuses an unauthenticated admission over
83
+ loopback, where no policy applies. A Sandbox cannot connect to the Instance Harness at all. The second test asserts a
84
+ transport failure, not a status, because a 401 would mean the bearer stopped it and the policy did not.
85
+ - **Port-forward and probes are not affected.** `kubectl port-forward` enters the pod's network namespace through the
86
+ kubelet, and the kubelet's probes come from the node. On kind, neither is filtered by an ingress policy. So the CLI,
87
+ the Console, and a human's shell into the User Container ([ADR-0005](0005-sandbox-pod-composition.md)) keep working.
88
+ If routable access to the User Container is added later (ADR-0005 leaves it open), it must add its own ingress rule.
89
+ - **The `@kind` history reads present the bearer.** The steps derive it from the instance Secret's signing key, as the
90
+ CLI reads the Instance token: over the kube API, with RBAC as the gate. A human reading a conversation does the same.
91
+ - **Model keys are not in the Harness container.** `JR2_PROVIDER_API_KEY` and every other held secret live in the
92
+ Custodian, and the Harness container's env holds their Stand-ins (ADR-0059). `jr2 up` refuses a known model key in
93
+ `harness.env` or in an `envFrom` Secret.
94
+ - **Open gaps, recorded here and not fixed:**
95
+ - **Sandbox egress.** An Agent with bash can reach the internet and send out anything it can read — which no longer
96
+ includes a held key: a client that goes around the Custodian carries only Stand-ins. A default-deny egress policy
97
+ needs an allowlist: DNS, the Orchestrator, the model provider, and whatever the work needs (package registries, for
98
+ example). That needs a config surface and cluster CIDRs, so it is a separate decision.
99
+ - **A held key can be spent.** The Agent can use a key through the Custodian while its pod lives, and a SigV4 or ADC
100
+ credential cannot be held at all ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
101
+ - **The Orchestrator's Secret writes are namespace-wide.** Its Role may create and patch every Secret, and may not get
102
+ or list one ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)): it applies its own
103
+ per-Sandbox Secrets and holds no git credential and no Effect secret. RBAC cannot pin `create` to a name, and
104
+ `resourceNames` has no prefix form. Two residuals stay, named in ADR-0068: a PATCH answers with the object, so a
105
+ caller that knows a Secret's name can still read it; and a Role that creates pods (through the Sandbox CR) can mount
106
+ a Secret into one.