@jr2/orchestrator 0.5.1 → 0.7.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 (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -45,11 +45,13 @@ loudly narrated, safe to re-run:
45
45
  ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). Referenced-but-unmanaged Secrets
46
46
  (`envFrom` refs, git credentials) are **preflighted**: `up` fails naming the missing Secret with the exact creation
47
47
  hint — converting the `CreateContainerConfigError` hang into an immediate error. Sealed Secrets/External Secrets ride
48
- this seam untouched: jr2's contract is "a Secret named X exists", however it got there. A credential the Harness uses
49
- is a held secret ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): `up` writes a
50
- literal value into `jr2-held-secrets-<pod>` for each pod that names it, which only that pod's Custodian mounts, and
51
- checks that a referenced Secret and key exist. Like the signing key, the held-secret CA (`jr2-held-ca`) is kept across
52
- converges — minted on first need, never rotated by `up` — because a live pod trusts it.
48
+ this seam untouched: jr2's contract is "a Secret named X exists", however it got there. `up` also refuses a held
49
+ secret's name that is a key of an `envFrom` Secret, and names the Secret, the key and the held secret (ADR-0059, R14).
50
+ A credential the Harness uses is a held secret
51
+ ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): `up` writes a literal value into
52
+ `jr2-held-secrets-<pod>` for each pod that names it, which only that pod's Custodian mounts, and checks that a
53
+ referenced Secret and key exist. Like the signing key, the held-secret CA (`jr2-held-ca`) is kept across converges —
54
+ minted on first need, never rotated by `up` — because a live pod trusts it.
53
55
  - **Repos**: `up`'s walk of the registered Machines collects every bound Repo Slot (ADR-0049, ADR-0051) — to refuse an
54
56
  open slot nobody bound, and to see which urls need a key. The Orchestrator creates one `Repo` resource per url at
55
57
  boot, and the operator's cache agent clones each onto the nodes that need it (ADR-0004). There is no `repos` config
@@ -59,11 +61,17 @@ loudly narrated, safe to re-run:
59
61
  in-cluster deploy keypair (the recommended default, public key printed to register), a local key, or one pasted on
60
62
  stdin ([ADR-0047](0047-the-git-ssh-key-source-is-the-users-choice.md)) — declining bails. A personal key never enters
61
63
  a cluster silently: only by that explicit, warned choice.
62
- - **Provider preflight**: `up` checks each provider that a carried model names. It checks from inside the cluster, with
63
- one trivial tool-call completion. An endpoint that the pods cannot reach, a wrong or missing key, or a vLLM without
64
- `--enable-auto-tool-choice` then fails at converge time, not as `agent.fault` in a run. Reachability is the user's
65
- concern (`localhost` never works from a pod; a LAN address does on kind). The check must take the path that the
66
- Harness takes. If it does not, a pass means nothing.
64
+ - **Landlock**: `up` refuses to converge while a Sandbox node lacks Landlock. It reads the pods of the Repo cache
65
+ agent's DaemonSet, which probe Landlock on each Sandbox node, within the rollout wait it already makes for the cache.
66
+ It is an ordinary converge failure: it names each node whose agent reports none, the probe's reason, and the fix (keep
67
+ the node out with `sandbox.nodeSelector` or a taint, ADR-0052, or enable Landlock); a run after the fix converges
68
+ ([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)).
69
+ - **Provider preflight**: `up` checks each provider that a carried model names, and the tool list of each remote MCP
70
+ server that an Agent names (below). It checks from inside the cluster, with one trivial tool-call completion for a
71
+ model. An endpoint that the pods cannot reach, a wrong or missing key, or a vLLM without `--enable-auto-tool-choice`
72
+ then fails at converge time, not as `agent.fault` in a run. Reachability is the user's concern (`localhost` never
73
+ works from a pod; a LAN address does on kind). The check must take the path that the Harness takes. If it does not, a
74
+ pass means nothing.
67
75
  - **One path for every provider.** `up` checks the custom provider (`harness.provider`, ADR-0018) and a provider of
68
76
  pi's catalog (for example `anthropic/…`) in the same way. A key of either is a held secret, and only a Custodian can
69
77
  use it ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). So no key enters a pod
@@ -75,14 +83,17 @@ loudly narrated, safe to re-run:
75
83
  the config, each model's result says why, in words.
76
84
  - **The check pod.** It is a Harness pod with no Harness in it. It has a Custodian and the stock Harness image's
77
85
  `provider-preflight.ts`. The function that composes the Custodian of every Harness pod composes this Custodian too.
78
- Its token is a value that the Orchestrator never signed. `provider-preflight.ts` gets the env of the Harness
79
- container: the harness config, `harness.env`, `harness.envFrom`, the Stand-ins, the proxy and the trust. pi resolves
86
+ Its token is a value that the Orchestrator never signed. Its Stand-ins are secret and its own: `jr2 up` mints them
87
+ with the signing key, as it does for the Instance Harness
88
+ ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The check pod runs no Workspace
89
+ code, so it may hold them in env. `provider-preflight.ts` gets the env of the Harness container, with the Stand-ins
90
+ in it: the harness config, `harness.env`, `harness.envFrom`, the Stand-ins, the proxy and the trust. pi resolves
80
91
  each model and uses its api, as in a Turn. The `harness.catalog` gateway applies. The key never enters the check
81
92
  container, its argv or its log.
82
93
  - **Its own objects.** The check pod reads no object of the Instance. `up` writes copies from the values on the host:
83
94
  - a ConfigMap with the Custodian's files, the program and the harness config;
84
95
  - a ConfigMap with the trust bundles;
85
- - a Secret with the token and the literal values of the held secrets;
96
+ - a Secret with the token, the check pod's Stand-ins and the literal values of the held secrets;
86
97
  - a Secret with the leaves;
87
98
  - a Secret with the literal `harness.env` values.
88
99
 
@@ -96,8 +107,8 @@ loudly narrated, safe to re-run:
96
107
  model runs where the Instance Harness runs.
97
108
  - The held set: a pod that holds a secret sends its HTTPS through its Custodian, and a tunnel carries a host that no
98
109
  secret is bound to. A pod that holds no secret sends its HTTPS direct. So the check pod holds the held set of the
99
- first pod of its class, and the keys that the other pods of the class hold. A class that holds nothing gets a
100
- check pod that holds nothing.
110
+ first pod of its class, and the keys and the MCP server secrets that the other pods of the class hold. A class
111
+ that holds nothing gets a check pod that holds nothing.
101
112
 
102
113
  When no pod of the class holds a key of the provider, the check pod holds none. Then pi refuses the call
103
114
  (`not configured`), and the converge fails. A signing credential in `harness.env` reaches the check pod, as it
@@ -105,6 +116,16 @@ loudly narrated, safe to re-run:
105
116
  knows, and for which jr2 knows no key name, is checked with no held key, as a Turn runs it. The check pod has the
106
117
  priority class of the Sandbox tier, as the Harness pods have (ADR-0060).
107
118
 
119
+ - **MCP servers.** The check pod also checks each remote MCP server that an Agent of its class names
120
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). It
121
+ reads the server's `tools/list` through its Custodian, with the Stand-in of the server's secret, as a Turn reads it.
122
+ An allowlisted tool name that the list does not have fails the converge, and the result names the server and the
123
+ tool. A server that refuses the request (a 401 or a 403: a wrong or expired token) fails the converge, as a model
124
+ key that the provider refuses does. A server that does not answer is a warning: the first Turn of a conversation
125
+ fails with a named error when the server does not answer then. A stdio server is not checked: it runs as Workspace
126
+ code in the Sandbox, and the check pod runs none. Each server has 60 s for its whole listing, after the models. Two
127
+ literals set it, `PREFLIGHT_MCP_BUDGET_SECONDS` in `preflight-line.ts` and `CHECK_MCP_SECONDS` in `up.ts`, and a
128
+ test holds them equal. The pod writes the list, and `up` compares it with the allowlists.
108
129
  - **The request.** The check's request shares these with a Turn's: the model, its api, its endpoint and key, the
109
130
  completion limit (what pi-ai sends for a Turn), the retries, no forced tool, and the level of a definition with no
110
131
  `thinkingLevel` (`off`: no `reasoning` is sent). It does not share these: it has no system prompt, one tool, no
@@ -185,16 +206,18 @@ loudly narrated, safe to re-run:
185
206
 
186
207
  It never deletes the objects of another Instance.
187
208
 
188
- - **Skipping it.** `jr2 up --skip-provider-preflight` runs no check pod. A check can fail when the model works, for
189
- example when a model loads for longer than the check waits. That fault must not stop every converge until jr2 is
190
- fixed. The skip is never silent: `up` writes one warning line, which names each model that it did not check. `up`
191
- still deletes the leftovers of earlier runs, because their Secrets can hold a key. The flag has no config key,
192
- because no flag of `up` has one (CI gives `--yes` as a flag). A model that does not work then fails in a run, as
193
- `agent.fault`.
209
+ - **Skipping it.** `jr2 up --skip-provider-preflight` runs no check pod, so it checks no model and no MCP server's
210
+ tool list. A check can fail when the model works, for example when a model loads for longer than the check waits.
211
+ That fault must not stop every converge until jr2 is fixed. The skip is never silent: `up` writes one warning line,
212
+ which names each model and each MCP server that it did not check. `up` still deletes the leftovers of earlier runs,
213
+ because their Secrets can hold a key. The flag has no config key, because no flag of `up` has one (CI gives `--yes`
214
+ as a flag). A model that does not work then fails in a run, as `agent.fault`.
194
215
  - **A limit.** The check pod has labels of its own. It does not get the labels of a Harness pod: the Instance Harness
195
216
  Service selects its pods by label, and would send Turns to the check pod. So a NetworkPolicy that selects Harness
196
217
  pods by label does not select the check pod. When such a policy closes the egress of the Harness pods, the check can
197
- pass while a Turn fails.
218
+ pass while a Turn fails. jr2's own egress policy, which closes the cloud metadata addresses
219
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)), gives the check pod one
220
+ of its own, so it does not have this limit.
198
221
 
199
222
  **`jr2 down`** removes the instance from the cluster. It asks for confirmation unless `--yes` is given (for CI). `--all`
200
223
  takes the operator too.
@@ -217,8 +240,9 @@ takes the operator too.
217
240
  instance error with "not deployed here — right context?". This is the guardrail against silently pointing at the wrong
218
241
  cluster, without a state file that can itself go stale.
219
242
  - **CLI transport**: run-verbs port-forward the Orchestrator Service via the kube API for the duration of the command
220
- and read the Instance token from its Secret — kube RBAC is the real gate. `--url`/`JR2_URL` (+ token env) is the
221
- escape hatch for ingress-exposed instances and callers without cluster creds.
243
+ and read the Instance token from its Secret — kube RBAC is the real gate. `--url`/`JR2_URL` (+ token env, any Caller's
244
+ credential, [ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)) is the escape
245
+ hatch for ingress-exposed instances and callers without cluster creds.
222
246
  - **A dead ADDRESS and an absent IDENTITY are two faults, and resolution names which.** They share a verb and a context
223
247
  but not a fix: one is the network, the other is `jr2 up`. A cluster that answered and holds no instance Secret is "not
224
248
  deployed here — right context?"; a cluster that could not answer at all is "cannot reach the cluster — context X: …",
@@ -262,9 +286,11 @@ from env.
262
286
 
263
287
  ## Consequences
264
288
 
265
- - The everyday story is two commands ever: `jr2 init`, `jr2 up`. The full CLI: `init`, `up`, `down`, `gc` (ADR-0039),
266
- `run`, `runs`, `status`, `logs`, `send`, `kit push` (ADR-0044), plus the workspace verbs `ls`, `ssh`, `rm` (ADR-0009;
267
- decided, not yet in the binary).
289
+ - The everyday story is two commands ever: `jr2 init`, `jr2 up`. The rest of the CLI is in ADR-0009's table, and the
290
+ workspace verbs `workspace ls|shell|forward|rm` are among them.
291
+ - **The workspace verbs are not run-verbs.** They address the Sandbox CRs of the current context with its kube
292
+ credentials alone: no port-forward to the Orchestrator, no Instance token and no Caller scope. So they work when the
293
+ Orchestrator is down, and kube RBAC on the Sandbox CRs and their pods is the whole gate.
268
294
  - **Simplification must not surprise**: `up` prompts exactly when meeting a cluster that isn't yet home, and run-verbs
269
295
  print the context they're talking to — the ambient-context magic stays visible.
270
296
  - The snapshot store (sqlite) moves onto a PVC in the instance's namespace; `.jr2/` shrinks to scratch.
@@ -31,7 +31,8 @@ end. And a restarted Harness answered a `continue` with no memory of the convers
31
31
  Sandbox goes to the terminal phase **`Lost`**, with the pod's own reason and message (for a deleted pod, the last it
32
32
  saw). It never returns to `Pending` and is never given a second pod: `work` is an `emptyDir`, so a new pod could
33
33
  continue nothing, and it would hold a whole Size for nobody while hiding the loss. The CR stays, so `jr2 status` can
34
- say why, until teardown or the idle GC.
34
+ say why, until teardown or the idle GC. A Sandbox delete waits for its pod, because a retry can take the name of a pod
35
+ that is still going away.
35
36
  - **The operator judges identity, because it knows which pod it created.** It still publishes `status.podUID`; the
36
37
  Orchestrator reads `Lost`, not a UID comparison.
37
38
  - **The watch answers; the lease asserts.** The Orchestrator watches its Sandboxes
@@ -56,6 +57,19 @@ end. And a restarted Harness answered a `continue` with no memory of the convers
56
57
  attached pod. Its lifetime is the state's lifetime, which xstate already manages: it re-invokes on snapshot restore,
57
58
  so restore-reconcile stops being a special case and becomes the first tick of the normal loop; and it stops on every
58
59
  exit — body final, run stopped, run faulted.
60
+ - **The Lease marks its hold on the CR.** The CR's first write sets the annotation `jr2.dev/hold: held`, and each
61
+ renewal writes it again. When the Lease stops for the body's final state, a fault, a cancel or a parent that leaves
62
+ the Workspace's state, it sets `jr2.dev/hold: released` and renews no more. A defect that a parent catches does not
63
+ stop the Lease, because xstate stops no child of a Machine that ended in error: at its next renewal the Lease sees
64
+ that its Workspace is over, and sets `released` then. A Lease does not set `released` on a name that a live Workspace
65
+ of the run holds: a parent that catches the defect can invoke the same Machine again, and the new Workspace gets the
66
+ same Sandbox name and a Lease of its own. A step of the wrapper from one state to the next writes nothing: the next
67
+ state has a Lease of its own. A park (`RunHost.stop`, [ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)) and a
68
+ process shutdown write nothing, so a crash keeps the mark `held` and the restored run's Lease writes it again. A
69
+ restore that ends a run `drifted` or `lost` without hosting it starts no Lease, so the Orchestrator sets `released` on
70
+ each CR with that run's `jr2.dev/run` label. So `jr2 workspace rm` tells a held Sandbox from a free one with the
71
+ cluster alone ([ADR-0009](0009-cli-and-instance-interface.md)): the keepalive's age cannot tell it, because a renewal
72
+ can be late by more than one interval and a few missed renewals are survivable.
59
73
 
60
74
  That last point deletes machinery rather than adding it. Three mechanisms with three different owners and lifetimes —
61
75
  the process-global heartbeat map, the one-shot probe, and `release(runId)` (which label-queried the cluster to stop
@@ -97,4 +111,4 @@ behavior `release()` was written to produce is emergent.
97
111
  independently. This falls out of invoking it beside the body rather than owning it at the host.
98
112
  - `SandboxPort` has no optional method: `renew` is a write, `continuity` is a subscription to the watch, and
99
113
  `memoryFault` (ADR-0061) answers undefined from a backend that cannot see the container. The `jr2.dev/run` label
100
- survives for `jr2 ls` and as the watch's selector, no longer load-bearing for lease bookkeeping.
114
+ survives for the `jr2 workspace` verbs and as the watch's selector, no longer load-bearing for lease bookkeeping.
@@ -34,8 +34,8 @@ can get away with fetching once, because a person can press a button; an API can
34
34
  so there is no gap to detect and no cursor to resume from. Same reconciliation idiom as the Lease (ADR-0021) and
35
35
  `jr2 up` (ADR-0019): say the whole truth, repeatedly, and let the reader converge.
36
36
  - **Two granularities, one vocabulary.** The per-run feed stays — tracking exactly one run is a legitimate thing to
37
- want, and it is what `jr2 run` and `jr2 logs -f` already do. Every run-scoped frame carries `runId` even where the
38
- route makes it redundant, so one parser serves both.
37
+ want, and it is what `jr2 run` and `jr2 watch` do. Every run-scoped frame carries `runId` even where the route makes
38
+ it redundant, so one parser serves both.
39
39
  - **`gone` is a fact, not an inference.** A run can leave the live set _without_ a terminal status: `stop()`
40
40
  deliberately leaves the stored status `"live"` so a later `restore()` picks the run back up. A client watching for
41
41
  `status === "done"` would show a stopped run as live forever, so departure is stated outright.
@@ -48,7 +48,7 @@ can get away with fetching once, because a person can press a button; an API can
48
48
  which is why it is wired to the handler's exit.
49
49
  - **A settled run is the client's own memory.** A run appears in the page's `settled` list iff _this page_ watched it
50
50
  leave. After a hard reload they are gone, and that is correct rather than a defect: serving a finished run's state to
51
- an unauthenticated reader is a read-through, which stays behind the Instance token on `/runs/:id` (ADR-0014).
51
+ an unauthenticated reader is a read-through, which stays behind the `read` scope on `/runs/:id` (ADR-0014).
52
52
  - **`jr2 visualize` is deleted.** Its job was to find an orchestrator, forward a port, and open a browser. With the page
53
53
  now useful on its own, that is one `kubectl port-forward` the reader already knows how to run — and the command's
54
54
  ephemeral-fallback path was quietly forwarding to a _different_ instance than the deployed one. Its one unique piece
@@ -61,7 +61,7 @@ can get away with fetching once, because a person can press a button; an API can
61
61
  tick, and a reader that keeps up drains them before any ping judges it. Closing is safe because the feed is
62
62
  level-triggered: the client reconnects and receives the whole current truth, having missed no state — only activity
63
63
  (Emits, retries) that fell in the gap. A page reconnects with EventSource's own retry; the CLI (`jr2 run`,
64
- `jr2 logs -f`) re-attaches any feed that ends before a terminal status, and never reads such an end as the run's —
64
+ `jr2 watch`) re-attaches any feed that ends before a terminal status, and never reads such an end as the run's —
65
65
  except `gone`: a run stopped before it settled (parked, ADR-0025) reads through as `active`, so its one-shot feed says
66
66
  `gone` after the status, and the CLI ends there instead of re-attaching in a loop. The server keeps no per-client
67
67
  queue and no slow-client accounting beyond counting the subscriber's writes (scaling review R10, 2026-09-30).
@@ -1,4 +1,4 @@
1
- # The Harness prints the conversation, and the reasoning is the part that prints
1
+ # The Harness prints the conversation and the Orchestrator keeps it, and the reasoning is the part that prints
2
2
 
3
3
  A running workflow is watchable and its Agents are not. The visualizer shows which states are lit (ADR-0022); the run
4
4
  feed carries status and Emits (ADR-0009). What an Agent is actually _doing_ — what it read, what it tried, what it
@@ -16,12 +16,18 @@ both user and assistant messages, plus text, thinking, and tool-call content
16
16
  ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)) — every line of that jq filter,
17
17
  replayable from any offset. The gap was a read decision, not a missing capability, and the fix is not new plumbing.
18
18
 
19
+ **A conversation that lives only in its pod is gone when a person needs it.** A run that failed overnight is read the
20
+ next day. By then its Sandbox is torn down (ADR-0012) or reaped (ADR-0021), and the log plane, if the cluster has one,
21
+ is not jr2's and is not always there. So the print stays for the cluster's log plane, and the Orchestrator also keeps
22
+ each conversation for as long as it keeps its run.
23
+
19
24
  ## Decision
20
25
 
21
26
  - **The Harness prints, and `kubectl logs` reads.** The Harness writes its own conversation to container stdout —
22
27
  in-process; the loopback subscription this originally required retired with the foreign runtime (ADR-0027).
23
- `kubectl logs`, `stern`, `k9s`, and any log shipper work on it the day it lands, with no jr2 surface to build, learn,
24
- or version.
28
+ `kubectl logs`, `stern`, `k9s`, and any log shipper work on it, with no jr2 surface between them and the pod. The
29
+ print stays when the Orchestrator keeps the conversation (below): a cluster that ships its logs reads every
30
+ conversation in one place, with its own retention.
25
31
  - **The Harness is the right seat because it already holds both halves of the label.** It knows its Agent's name and the
26
32
  iid arrives per Submission. So a line is `[<agent>] [<iid>] …` with no lookup, no correlation table, and no
27
33
  orchestrator round trip. Every line of a body carries both, the later lines of a body that spans lines too. The iid is
@@ -34,11 +40,14 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
34
40
  invisible to every other jr2 process there.
35
41
  - **Reasoning prints, and so does each pick's answer; a Working tool's result does not.** Prompts, assistant text,
36
42
  thinking, tool calls with truncated inputs, and the answer to each Menu pick. The file contents, command output, and
37
- API responses a Working tool returns never print. This is one cut serving two purposes, which is why it is a boundary
38
- and not an unfinished implementation: a Working tool's results are whatever the Agent happened to read, which is where
39
- a leaked secret lands, and they are also most of the bytes. A pick's answer is one bounded text per pick, and the
40
- bullet below states what it can still carry. Every user message the model reads prints as a prompt — the Frame's
41
- prompt with its Turn part, a nudge, and a Turn part the Harness restated at a step boundary
43
+ API responses a Working tool returns never print, and neither does the result of an MCP tool
44
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), for
45
+ the same reasons. An MCP call prints as `<server>/<tool>`, so it never prints as a pick does. This is one cut serving
46
+ two purposes, which is why it is a boundary and not an unfinished implementation: a Working tool's results are
47
+ whatever the Agent happened to read, which is where a leaked secret lands, and they are also most of the bytes. A
48
+ pick's answer is one bounded text per pick, and the bullet below states what it can still carry. Every user message
49
+ the model reads prints as a prompt — the Frame's prompt with its Turn part, a nudge, and a Turn part the Harness
50
+ restated at a step boundary
42
51
  ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), which prints once as
43
52
  `[<agent>] [<iid>] [prompt] <jr2-turn>` where the model read it.
44
53
  - **A pick's answer prints after its call, as the model received it.** A Menu call's result is jr2's own text: the
@@ -91,6 +100,63 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
91
100
  - **The writer structures, the reader colorizes.** Lines carry a parseable prefix and no ANSI. jr's colors were correct
92
101
  for an fzf preview, which is a terminal; `kubectl logs` output is piped, grepped, and ingested, where escape codes are
93
102
  corruption. A future reader may colorize on `isatty`.
103
+ - **The Harness also sends the conversation's entries to the Orchestrator, during the Turn.** The entries are jr2's own
104
+ shape, not pi's: one entry for each body that the print makes, with the same cut — a prompt, the model's text, its
105
+ thinking, a call with its input cut to the print's bound, a pick's answer, a mechanic line — and never a Working
106
+ tool's result or an MCP tool's result. A body that spans lines is one entry. Only a body that carries an iid is an
107
+ entry. The narrative echo (below) is not: the Orchestrator already holds those events in its feed. An entry is
108
+ `{ id, seq, at, agent, kind, text }`: `kind` is the body's label (`prompt`, `text`, `thinking`, a tool's printed name,
109
+ `<pick> answer`, `compacted`), `text` is the rest of the body, and the Orchestrator sets `agent` from its ledger.
110
+ `seq` goes up for as long as the iid is kept. A new record starts it at the clock, not at 0, because a conversation
111
+ that the Harness lost with its pod starts again under the same iid, and its new entries must come after the old ones.
112
+ An entry is made when it is complete (at `message_end` for the model's words), so text and thinking deltas are not in
113
+ it. The Harness writes each entry locally first, and then sends the entries in batches to the Orchestrator while the
114
+ Turn runs, on `POST /agents/<iid>/conversation`, through its Custodian, with its pod's control token
115
+ ([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md),
116
+ [ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): a Sandbox's control token, or the
117
+ Instance Harness's own control token, which is signed for its placement. The Instance token is never one of them. The
118
+ route takes entries only for an iid that the run's admission ledger places in the token's pod, and only when the slot
119
+ that the Harness states is the ledger's
120
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). It checks the ledger, not
121
+ the live registration: the ledger outlives the Turn and the run's end, so a batch that comes after the Turn's sweep or
122
+ after the run ended is still kept, and those are the entries that tell how a run ended. The Harness starts a send of
123
+ its pending entries when the model's answer ends (before its picks run) and at a Turn's end, and does not wait for it.
124
+ On SIGTERM it tries again for a few seconds. In a Sandbox the Custodian can exit first
125
+ ([ADR-0001](0001-operator-owned-generic-sandbox.md)), so the entries since the last send (about one second) can be
126
+ lost. While a live run's ledger does not name the iid yet (the batch came before the admission's save), the route
127
+ answers 409 and the Harness sends the batch again. The Instance-token door of ADR-0013 does not open it: a holder of
128
+ the Instance token can speak as an Agent, but it cannot write an Agent's kept conversation. Each entry's `seq` makes a
129
+ send idempotent: the Orchestrator keeps each `seq` of a conversation once, so a batch that the Harness sends again
130
+ after a failure or a restart adds nothing twice. A batch that fails stays in the local copy and goes with the next
131
+ batch. Menu-only Agents send the same way, from the Instance Harness
132
+ ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). The kept conversation holds what the print holds:
133
+ no Working tool's result and no MCP tool's result, but the residual of a pick's answer stated above (a Held value that
134
+ an upstream echoed into a `command` Effect's output, an Effect secret in a form that the scrub does not catch). The
135
+ Orchestrator keeps that residual for `runs.retain`, behind `read`.
136
+ - **The Orchestrator keeps it in `conversations.db`.** It is a second SQLite file beside `state.db` on the
137
+ Orchestrator's state volume ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)), with
138
+ plain JSON rows, so a person or a tool can query it with SQLite alone. It is a second file because its writes are
139
+ larger and more frequent than a snapshot's, and its sweep frees pages: neither must slow the snapshot's synchronous
140
+ write. The file sets `auto_vacuum = INCREMENTAL` at its creation, and each sweep frees the pages that it emptied, so
141
+ the file gets smaller after a sweep. A kept conversation has a cap of 8 MiB. When an entry would go above it, the
142
+ Orchestrator keeps no more entries of that conversation and writes one marker entry that says so, and the
143
+ conversation's `cut` is true. The cap keeps the start of a long conversation, which is the part that tells why the
144
+ Agent did what it did. A kept conversation goes with its run: when ADR-0065's sweep deletes the run's row, it deletes
145
+ the run's conversations, and the space guard there deletes the oldest kept conversations of ended runs first. There is
146
+ no compression in the kept conversations now. Compression can come later and stays internal: Node's own zstd on the
147
+ entry text, with the queryable columns kept.
148
+ - **`jr2 logs` and the Console read the kept conversation.** `GET /runs/:runId/conversations` lists a run's
149
+ conversations, `[{ agent, slot, instanceId, open, bytes, cut }]`.
150
+ `GET /runs/:runId/conversations/:instanceId?after=<seq>` gives the entries after a sequence number, and `.../events`
151
+ follows a conversation as SSE while it is open (the iid is URL-encoded in the path, because it holds `/`). A
152
+ conversation is open while a Turn of it is registered on a live run; the feed ends a few seconds after it is not open,
153
+ so the batch that a Turn's end sends is still read. A conversation that the space guard swept lists
154
+ `swept: "no space"`, and its run says `swept: "conversation swept: no space"` in `GET /runs/:runId` and `GET /runs`.
155
+ The `read` scope opens all three ([ADR-0014](0014-observation-is-open-run-state-is-not.md)).
156
+ `jr2 logs <run> [agent] [-f]` prints the run's conversations, or one Agent slot's, in the printed form of this ADR,
157
+ Menu-only Agents too, and `-f` follows them until the run ends ([ADR-0009](0009-cli-and-instance-interface.md)). The
158
+ Console shows a conversation in its bottom drawer ([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)).
159
+ An ended run reads the same as a live one, for as long as the run is kept.
94
160
  - **A Harness also prints the run's narrative — feed events the Orchestrator pushes to an echo endpoint.** A Workspace's
95
161
  pod log should read as the whole story of the run that owns it: not just the conversations hosted there, but the
96
162
  decisions and Emits that happened around them — a ticket claimed before the Workspace existed, a Menu pick a
@@ -113,33 +179,47 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
113
179
  transition. The replay buffer follows the same rule: it keeps the markers and Emits (bounded) and the latest status,
114
180
  so the preamble reads "the story so far, and where the run stands now" — old statuses are not interleaved between old
115
181
  markers; the live tail keeps the interleaving (scaling review R10, 2026-09-30).
116
- - **Pod-bound, and jr2 promises nothing beyond the pod.** A conversation lives as long as its Sandbox (ADR-0027,
117
- ADR-0031), so a Sandbox teardown (ADR-0012) or a lost Workspace (ADR-0021) takes it with it — the same contract
118
- ADR-0012 already set for the pod-local clone. A cluster that ships logs will outlive the pod anyway; that is the log
119
- plane's property, not a jr2 guarantee, and no jr2 behavior may come to depend on it.
182
+ - **The print lasts as long as the pod; the kept conversation lasts as long as the run.** A pod's log goes with its
183
+ Sandbox (ADR-0012, ADR-0021) and rotates at the kubelet's bound; a cluster that ships logs keeps them by its own
184
+ rules, and no jr2 behavior depends on that. The kept conversation is jr2's: it is kept while its run is kept
185
+ (`runs.retain`, ADR-0065), and `jr2 logs` reads it after the Sandbox is gone.
120
186
 
121
187
  ## Considered options
122
188
 
123
189
  - **Per-`agentRun` containers, or a pod per Agent.** Rejected on lifetime before merit: the container list is fixed when
124
190
  the CR is created, before the body has run, while `agentRun` is a per-turn invocation. Pre-declaring per _Agent_ is
125
- possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005) to run
126
- one at a time. It buys no isolation — containers in a pod share the network namespace, so it does not even fix the
127
- port contention that motivates it (ADR-0005:31) — and the git contention it would address is prohibited anyway
128
- (ADR-0004:95). What it actually buys is `kubectl logs -c <agent>`: a container, to avoid a prefix.
191
+ possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005). It
192
+ buys no isolation — containers in a pod share the network namespace, so it does not even fix the port contention that
193
+ motivates it (ADR-0005:31) — and it does not remove the git contention, because the containers share one `/work` and
194
+ one clone (ADR-0004). What it actually buys is `kubectl logs -c <agent>`: a container, to avoid a prefix.
129
195
  - **A trace from the Custodian.** Tempting, and wrong on the facts. The Custodian carries the Menu's reads and the
130
196
  Agent's picks — a handful of deliveries per turn — and the model calls, and it logs names only, never a body
131
197
  ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The Agent's working tools execute in
132
198
  the Harness, never touching it. A Custodian trace shows `action=intercept … method=POST` and none of the four things
133
199
  worth watching.
134
- - **Tee the stream host-side, in the Orchestrator** (the one place holding an admission and an open stream). Genuinely
135
- viable, and better on governance: addressable by run, and `instanceOnly` puts it behind the band ADR-0014 already
136
- enforces. Rejected as the larger build for the smaller reader — it needs an API, a CLI, and a format, to serve a
137
- reader `kubectl` already serves. Not foreclosed: it reads the same stream, so it remains the answer if the exception
138
- below stops being acceptable.
139
- - **Persist transcripts beside the run snapshot.** Rejected: the Harness already serves the stream replayable from any
140
- offset for the life of the pod (ADR-0027), and jr2 holds the coordinates in the admission ledger (`run-host.ts:651`);
141
- copying the contents buys retention in exchange for owning growth, retention policy, and a redaction surface, in a
142
- store defaulting to sqlite.
200
+ - **Keep no conversation past its pod.** The Harness serves the stream from any offset for the life of the pod
201
+ (ADR-0027), and a copy costs growth, a retention policy and a redaction surface. Rejected: a conversation that goes
202
+ with its pod is gone when a person needs it most, after a run failed. What a copy costs is bounded: `runs.retain` sets
203
+ the retention (ADR-0065), a cap for each conversation and a space guard bound the growth, and a kept conversation
204
+ holds only what the print holds: never a Working tool's or an MCP tool's result, but the residual of a pick's answer
205
+ stated above, behind `read` for `runs.retain`.
206
+ - **The Orchestrator reads the Harness's stream and writes the kept conversation.** Rejected: the Orchestrator holds a
207
+ stream only while it waits on a Submission, so a restart of the Orchestrator loses what the stream sent in the gap,
208
+ and the Orchestrator would have to render pi's events, which is the Harness's craft. The Harness makes the print, so
209
+ it makes the entries, with the same cut.
210
+ - **Read the conversation from the pod logs.** Rejected: the Orchestrator has no RBAC on Pods and must not get it for
211
+ this, and the kubelet rotates a log at 10Mi, so a long Turn loses its start before anyone reads it.
212
+ - **Copy the conversation at the end of the run.** Rejected: a pod that dies (an eviction, a lost node, a memory kill)
213
+ takes the copy with it, and that is the run a person wants to read. The entries go to the Orchestrator during the
214
+ Turn.
215
+ - **A separate archive service.** Rejected for now: every system with a run store keeps its history in that store, fed
216
+ by the worker during the run, and a separate archive (Tekton Results, Temporal's archival, Argo's archive) is an
217
+ optional extra beside it. jr2 has a run store, so the store keeps the conversations, with no new component to deploy.
218
+ - **Files, one for each conversation.** Rejected: a sweep, a cap, a list with sizes and a read after a sequence number
219
+ are each a query in SQLite, and each is code of jr2's own over files. SQLite is already the Orchestrator's store
220
+ (ADR-0065), and it gives the space back with an incremental vacuum.
221
+ - **`sqlite-zstd` for compression.** Rejected: it is a native extension under the LGPL, which its author calls not ready
222
+ for production. Compression, when it comes, is Node's own zstd on the entry text.
143
223
  - **Print a Working tool's results, truncated.** Rejected: the first 200 bytes of a leaked config is where the good
144
224
  stuff is, and the volume it re-admits is what kubelet's 10Mi rotation would then silently discard — losing the _start_
145
225
  of a long turn, which is the part worth reading. A `command` Effect's output does print, inside its pick's answer and
@@ -153,24 +233,25 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
153
233
  ADR-0058), so Emit payloads may ride it — the tokenless observation band still carries Emit types alone. What it does
154
234
  widen is the pod-log surface: a run's narrative becomes readable by whoever can read the Workspace pod's logs, the
155
235
  same audience the conversation bullet below already grants.
156
- - **A scoped exception to ADR-0014**, recorded there. Observation is open and everything sensitive sits behind
157
- `instanceOnly`; this puts an Agent's reasoning, and the answers jr2 gave its picks, in front of whoever can read pod
158
- logs. Excluding a Working tool's results bounds it to what an Agent reasons aloud and what jr2 told it, rather than
159
- what it happened to read; what jr2 told it includes an Effect's output, so a `command` Effect's output and a Held
160
- value echoed into it reach the log as they reach the model, and so does an Effect secret in a form an Effector's scrub
161
- does not catch (a transformed secret, a partial echo; ADR-0068). Fine on kind; a real decision on a shared cluster.
162
- - **It depends on an invariant that is documented but not enforced.** One Agent at a time per Workspace (ADR-0004:95,
163
- "structural since ADR-0012") is what makes two conversations never interleave on one container's stdout. Nothing
164
- refuses a body that invokes concurrent `agentRun`s; such a body corrupts a shared `default/.git` before it garbles a
165
- log, so enforcement is owed on its own merits — the ambient registrar (ADR-0012:32, ADR-0016) is the one thing that
166
- sees every Agent in a Workspace, and the place to refuse a second concurrent admission.
236
+ - **A scoped exception to ADR-0014**, recorded there. Observation is open and everything sensitive sits behind a
237
+ Caller's scope; the print puts an Agent's reasoning, and the answers jr2 gave its picks, in front of whoever can read
238
+ pod logs. The kept conversation is not part of the exception: it is behind `read`. Excluding a Working tool's results
239
+ bounds it to what an Agent reasons aloud and what jr2 told it, rather than what it happened to read; what jr2 told it
240
+ includes an Effect's output, so a `command` Effect's output and a Held value echoed into it reach the log as they
241
+ reach the model, and so does an Effect secret in a form an Effector's scrub does not catch (a transformed secret, a
242
+ partial echo; ADR-0068). Fine on kind; a real decision on a shared cluster.
243
+ - **The conversations of concurrent Agents interleave in one log.** The author chooses how many Agents work in a
244
+ Workspace ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), so two conversations can
245
+ print at one time on one container's stdout. The prefix keeps them apart: every line carries its label and its iid, so
246
+ a reader keeps one conversation's lines by its iid.
167
247
  - **Truncation is load-bearing, not cosmetic.** Kubelet rotates container logs at 10Mi by default and drops the
168
248
  remainder. jr's filter capped `Bash` at 100 chars and unknown tools at 150 and reduced `Write`/`Edit` to a path; that
169
249
  was readability, and it is now also what keeps a long turn inside the window.
170
- - **Live tails may miss partial output.** Text and thinking deltas are best-effort live progress, with `message_end`
171
- authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive. Correct for a
172
- watcher; anything wanting exactness reads the completed message.
173
- - **`jr2 logs` remains a status feed** and is now more confusingly named than before. Not renamed here — this ADR adds
174
- no command — but the collision is real and belongs to whoever next touches that surface.
175
- - **The visualizer's eventual extension has a source.** When it grows past the graph, it reads the same stream; a page
176
- is a third reader of one feed, not a third way of collecting the data.
250
+ - **Live tails of the print may miss partial output.** Text and thinking deltas are best-effort live progress, with
251
+ `message_end` authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive.
252
+ Correct for a watcher; anything wanting exactness reads the kept conversation, which holds the completed message.
253
+ - **`jr2 logs` prints conversations, and `jr2 watch` prints a run's status feed**
254
+ ([ADR-0009](0009-cli-and-instance-interface.md)). A pod's log stays `kubectl logs`.
255
+ - **The Orchestrator's state volume holds the kept conversations.** Its default size is `5Gi` (`runs.volume`, ADR-0065).
256
+ - **The Console's conversation drawer has a source.** It reads the kept conversation through the routes above; it is a
257
+ reader of one store, not a second way of collecting the data.
@@ -34,8 +34,7 @@ state. A prompt fix cannot be relied on and cannot even be shown to have worked.
34
34
  Workspace; the Machine's states are sequential, but the Agents are not. Throughout `reviewing`, the orphaned coder was
35
35
  still live in the worktree the reviewer was reviewing — and still live at `humanReview`, the park that deliberately
36
36
  retains the Sandbox so a human can exec in and inspect the diff. In the runs we watched, the orphan only read. Nothing
37
- prevented it writing. An un-ended turn is an unaccounted-for concurrent writer in a Workspace the Machine believes is
38
- single-threaded.
37
+ prevented it writing. An un-ended turn is a concurrent writer in a Workspace that the Machine did not put there.
39
38
 
40
39
  ## Decision
41
40
 
@@ -190,41 +189,40 @@ single-threaded.
190
189
  is local to its Machine: two Machines of one tree, and the workers of a pool, which run one body, may each have a
191
190
  child of one id, and one never reads or changes the other's entry. A child started on an event that names no Agent
192
191
  anywhere else is no Agent's. The other Agent's pick keeps its default answer, and when the Agent the event names has
193
- no pick being answered, it sets nothing. When one Agent slot is invoked in two regions, it shapes the pick at the same
194
- invoke as the pick that sent the event or started the child, and nothing otherwise: with both regions' picks being
195
- answered, the done event of a child shapes the pick whose invoke started it; a pick in its own send is shaped while
196
- the slot's other pick is outstanding; and the done event of a child that one region's pick started after that pick's
197
- Turn ended (its child runs on) shapes no pick, while the other region's pick keeps its default answer. When the event
198
- names no Agent (a timer, a Gate's answer, an outside send, the done event of a child no Agent's pick started), it
199
- shapes the pick in its own send; outside one, it shapes the one such pick outstanding only while one Agent's state is
200
- active. Outside a pick's own send, while two Agents' states are active, it sets nothing, whatever is outstanding: the
201
- event cannot say whose pick it is. On an entry or an exit below no Agent's state it sets nothing, whatever is
202
- outstanding, and the load walk warns, naming the Agent states it could be moved under: an entry or exit there is no
203
- transition an Agent's event takes. One action object written in two places runs from each by that place's rule alone,
204
- never by their sum: written below two Agents' states, from A's state it shapes only A's pick and from B's only B's;
205
- written below A's state and on a transition below none, from A's state it shapes only A's pick and from the transition
206
- the pick of the Agent the event names; written on an entry or exit below no Agent's state as well, it sets nothing as
207
- that entry or exit. An `answer` that an `enqueueActions` enqueues, at any depth, runs from the place the enqueuing
208
- action is written, by that place's rule: enqueued on an entry or exit below no Agent's state it sets nothing, and
209
- enqueued below an Agent's state it shapes only that Agent's pick. The load walk cannot see inside an `enqueueActions`,
210
- so it does not warn on one enqueued on an entry or exit below no Agent's state. An `answer` that runs while no pick of
211
- that Agent is being answered (a state's first entry, a later Turn's re-entry, another Agent's region of a parallel
212
- state) sets nothing. A reply comes from a child invoked on an active state below the Agent's state, or from a party
213
- whose Gate invoke id or Agent slot is invoked there (its sender, ADR-0015). The events the Machine sent itself (a
214
- `raise`, a `sendTo(self)`, a timer) are no reply, and neither is an event from outside that set (a Gate in a sibling
215
- region, a root Gate, an outside send), nor a child's error: every outcome of work is its output, and an error is a jr2
216
- or config defect, so by default its message never reaches the model. The operator reads it: while a pick is
217
- outstanding, an error from a child below the Agent's state goes on the run's feed (`child-error`: the Agent, its pick,
218
- the child and the message; Instance-token band, as `fault` is), and `jr2 run` and `jr2 logs` print it. An author who
219
- wants the Agent told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a
220
- state whose prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came
221
- back for it"): never that nothing ran, because a timer or an outside event may have stopped work that did run. The
222
- pick's `POST` answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the
223
- one tool call (ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final
224
- as it starts — replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's
225
- state, the `POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that
226
- enters a state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it
227
- was, and is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An
192
+ no pick being answered, it sets nothing. One Agent slot can be invoked by two states that are not active together (two
193
+ states one after the other). Then it shapes the pick at the same invoke as the pick that sent the event or started the
194
+ child, and nothing otherwise: the done event of a child that the earlier state's pick started, after that pick's Turn
195
+ ended (its child runs on), shapes no pick, and the later Turn's pick keeps its default answer. When the event names no
196
+ Agent (a timer, a Gate's answer, an outside send, the done event of a child no Agent's pick started), it shapes the
197
+ pick in its own send; outside one, it shapes the one such pick outstanding only while one Agent's state is active.
198
+ Outside a pick's own send, while two Agents' states are active, it sets nothing, whatever is outstanding: the event
199
+ cannot say whose pick it is. On an entry or an exit below no Agent's state it sets nothing, whatever is outstanding,
200
+ and the load walk warns, naming the Agent states it could be moved under: an entry or exit there is no transition an
201
+ Agent's event takes. One action object written in two places runs from each by that place's rule alone, never by their
202
+ sum: written below two Agents' states, from A's state it shapes only A's pick and from B's only B's; written below A's
203
+ state and on a transition below none, from A's state it shapes only A's pick and from the transition the pick of the
204
+ Agent the event names; written on an entry or exit below no Agent's state as well, it sets nothing as that entry or
205
+ exit. An `answer` that an `enqueueActions` enqueues, at any depth, runs from the place the enqueuing action is
206
+ written, by that place's rule: enqueued on an entry or exit below no Agent's state it sets nothing, and enqueued below
207
+ an Agent's state it shapes only that Agent's pick. The load walk cannot see inside an `enqueueActions`, so it does not
208
+ warn on one enqueued on an entry or exit below no Agent's state. An `answer` that runs while no pick of that Agent is
209
+ being answered (a state's first entry, a later Turn's re-entry, another Agent's region of a parallel state) sets
210
+ nothing. A reply comes from a child invoked on an active state below the Agent's state, or from a party whose Gate
211
+ invoke id or Agent slot is invoked there (its sender, ADR-0015). The events the Machine sent itself (a `raise`, a
212
+ `sendTo(self)`, a timer) are no reply, and neither is an event from outside that set (a Gate in a sibling region, a
213
+ root Gate, an outside send), nor a child's error: every outcome of work is its output, and an error is a jr2 or config
214
+ defect, so by default its message never reaches the model. The operator reads it: while a pick is outstanding, an
215
+ error from a child below the Agent's state goes on the run's feed (`child-error`: the Agent, its pick, the child and
216
+ the message; Instance-token band, as `fault` is), and `jr2 run` and `jr2 watch` print it. An author who wants the
217
+ Agent told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a state
218
+ whose prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came back for
219
+ it"): never that nothing ran, because a timer or an outside event may have stopped work that did run. The pick's
220
+ `POST` answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the one tool
221
+ call (ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final as it
222
+ starts — replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's state,
223
+ the `POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that enters
224
+ a state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it was,
225
+ and is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An
228
226
  outstanding pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain
229
227
  receipt with its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is
230
228
  the state it was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a
@@ -252,7 +250,7 @@ single-threaded.
252
250
  since the pick, the answer carries none. A restored hold is the Turn of the first registration of its address in the
253
251
  restored tree: a restore that ends that Turn and starts another at the same address before anything settles answers
254
252
  the pick with `turnComplete`. There is no default time bound on a held Turn; the Console shows each outstanding pick
255
- "held since".
253
+ "outstanding since".
256
254
  - **The one exception is the Orchestrator ending it for its own reasons**, which is ADR-0007's requirement and exactly
257
255
  one place: `RunHost.stop()`. Process shutdown never stops actors at all (`instance.ts` calls `host.close()`, which
258
256
  ends observation feeds and nothing else), and restore is a fresh process. So "the host did this" is a flag it sets,