@jr2/orchestrator 0.6.0 → 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 (301) 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 +86 -9
  14. package/docs/concepts/architecture.md +41 -28
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +7 -5
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +4 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +50 -22
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +5 -4
  23. package/docs/concepts/pools-and-sources.md +187 -11
  24. package/docs/concepts/secrets-and-trust.md +73 -33
  25. package/docs/concepts/the-menu.md +11 -0
  26. package/docs/concepts/workspaces.md +6 -5
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
  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/guides/add-tools-to-the-sandbox.md +55 -11
  32. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  33. package/docs/guides/answer-a-gate.md +74 -37
  34. package/docs/guides/bind-open-parts.md +24 -8
  35. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  36. package/docs/guides/cancel-a-run.md +26 -16
  37. package/docs/guides/fix-a-failed-converge.md +18 -6
  38. package/docs/guides/give-a-caller-a-scope.md +203 -0
  39. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  41. package/docs/guides/let-an-agent-write-a-chart.md +9 -6
  42. package/docs/guides/look-inside-a-sandbox.md +101 -31
  43. package/docs/guides/open-the-console.md +63 -14
  44. package/docs/guides/read-a-conversation.md +225 -0
  45. package/docs/guides/run-a-chart-by-name.md +17 -16
  46. package/docs/guides/run-one-worker-per-item.md +15 -9
  47. package/docs/guides/set-an-agents-access-and-dials.md +2 -0
  48. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  49. package/docs/guides/start-a-run-from-a-run.md +18 -16
  50. package/docs/guides/start-a-run.md +24 -18
  51. package/docs/guides/steer-an-agent.md +332 -0
  52. package/docs/guides/use-a-hosted-provider.md +1 -1
  53. package/docs/guides/watch-a-run.md +127 -121
  54. package/docs/guides/write-a-hook-kind.md +238 -0
  55. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  56. package/docs/guides/write-a-workflow.md +17 -7
  57. package/docs/llms.txt +81 -15
  58. package/docs/reference/authoring-surface/agent.md +1 -1
  59. package/docs/reference/authoring-surface/answer.md +1 -1
  60. package/docs/reference/authoring-surface/arg.md +1 -1
  61. package/docs/reference/authoring-surface/basic.md +1 -1
  62. package/docs/reference/authoring-surface/bearer.md +1 -1
  63. package/docs/reference/authoring-surface/branchname.md +1 -1
  64. package/docs/reference/authoring-surface/carry.md +23 -0
  65. package/docs/reference/authoring-surface/chartschema.md +1 -1
  66. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  67. package/docs/reference/authoring-surface/command.md +1 -1
  68. package/docs/reference/authoring-surface/customize.md +4 -1
  69. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  70. package/docs/reference/authoring-surface/defineevent.md +1 -1
  71. package/docs/reference/authoring-surface/dispatch.md +5 -4
  72. package/docs/reference/authoring-surface/fail.md +1 -1
  73. package/docs/reference/authoring-surface/file.md +1 -1
  74. package/docs/reference/authoring-surface/frompick.md +1 -1
  75. package/docs/reference/authoring-surface/gate.md +118 -0
  76. package/docs/reference/authoring-surface/group-agents.md +15 -0
  77. package/docs/reference/authoring-surface/group-charts.md +16 -0
  78. package/docs/reference/authoring-surface/group-composition.md +25 -0
  79. package/docs/reference/authoring-surface/group-config.md +17 -0
  80. package/docs/reference/authoring-surface/group-effects.md +48 -0
  81. package/docs/reference/authoring-surface/group-failure.md +13 -0
  82. package/docs/reference/authoring-surface/group-gates.md +16 -0
  83. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  84. package/docs/reference/authoring-surface/group-steering.md +12 -0
  85. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  86. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  87. package/docs/reference/authoring-surface/http.md +1 -1
  88. package/docs/reference/authoring-surface/isfailure.md +1 -1
  89. package/docs/reference/authoring-surface/isready.md +1 -1
  90. package/docs/reference/authoring-surface/jr2setup.md +13 -3
  91. package/docs/reference/authoring-surface/mcp.md +107 -0
  92. package/docs/reference/authoring-surface/open.md +5 -3
  93. package/docs/reference/authoring-surface/openbody.md +1 -1
  94. package/docs/reference/authoring-surface/paramsof.md +1 -1
  95. package/docs/reference/authoring-surface/pool.md +77 -9
  96. package/docs/reference/authoring-surface/push.md +1 -1
  97. package/docs/reference/authoring-surface/readyof.md +1 -1
  98. package/docs/reference/authoring-surface/reread.md +19 -0
  99. package/docs/reference/authoring-surface/secret.md +1 -1
  100. package/docs/reference/authoring-surface/sentby.md +1 -1
  101. package/docs/reference/authoring-surface/skip.md +22 -0
  102. package/docs/reference/authoring-surface/source.md +56 -1
  103. package/docs/reference/authoring-surface/steer.md +93 -0
  104. package/docs/reference/authoring-surface/text.md +1 -1
  105. package/docs/reference/authoring-surface/type-agentdefinition.md +14 -1
  106. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  107. package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
  108. package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
  109. package/docs/reference/authoring-surface/type-binding.md +1 -1
  110. package/docs/reference/authoring-surface/type-caller.md +40 -0
  111. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  112. package/docs/reference/authoring-surface/type-carry.md +19 -0
  113. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  114. package/docs/reference/authoring-surface/type-commandoutput.md +1 -1
  115. package/docs/reference/authoring-surface/type-customize.md +16 -1
  116. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  117. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  118. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  119. package/docs/reference/authoring-surface/type-dispatchoutput.md +1 -1
  120. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  121. package/docs/reference/authoring-surface/type-framepath.md +1 -1
  122. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  123. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  124. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  125. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  126. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  127. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  128. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  129. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  130. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  131. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  132. package/docs/reference/authoring-surface/type-hostinjectedinput.md +1 -1
  133. package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
  134. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  135. package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
  136. package/docs/reference/authoring-surface/type-lends.md +5 -1
  137. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  138. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  139. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  140. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  141. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  142. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  143. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  144. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  145. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  146. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  147. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  148. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  149. package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
  150. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  151. package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
  152. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  153. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  154. package/docs/reference/authoring-surface/type-reposlot.md +1 -1
  155. package/docs/reference/authoring-surface/type-reread.md +18 -0
  156. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  157. package/docs/reference/authoring-surface/type-sender.md +26 -1
  158. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  159. package/docs/reference/authoring-surface/type-size.md +1 -1
  160. package/docs/reference/authoring-surface/type-skip.md +19 -0
  161. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  162. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  163. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  164. package/docs/reference/authoring-surface/type-sourcespec.md +16 -7
  165. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  166. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  167. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  168. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  169. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  170. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  171. package/docs/reference/authoring-surface/type-workspaced.md +1 -1
  172. package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
  173. package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
  174. package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
  175. package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
  176. package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
  177. package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
  178. package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
  179. package/docs/reference/authoring-surface/workspace.md +1 -1
  180. package/docs/reference/authoring-surface/worktree.md +1 -1
  181. package/docs/reference/authoring-surface.md +88 -167
  182. package/docs/reference/cli.md +413 -74
  183. package/docs/reference/config.md +235 -2
  184. package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
  185. package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
  186. package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
  187. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  188. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  189. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  190. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  191. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +9 -2
  192. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
  193. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  194. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +18 -14
  195. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
  196. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  197. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +32 -18
  198. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
  199. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  200. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +116 -34
  201. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
  202. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  203. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +7 -6
  204. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
  205. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  206. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  207. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -18
  208. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  209. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +4 -3
  210. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  211. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +5 -4
  212. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
  213. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  214. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  215. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  216. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +7 -0
  217. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
  218. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
  219. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
  220. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
  221. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  222. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  223. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +51 -23
  224. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
  225. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
  226. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  227. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +17 -13
  228. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  229. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  230. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
  231. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
  232. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  233. 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
  234. package/docs/reference/decisions.md +6 -4
  235. package/docs/reference/glossary.md +96 -42
  236. package/docs/reference/hooks.md +1077 -0
  237. package/docs/reference/http-api.md +976 -149
  238. package/docs/why.md +2 -1
  239. package/package.json +10 -3
  240. package/src/actor.ts +36 -10
  241. package/src/agent.ts +50 -0
  242. package/src/assembly-checks.ts +530 -4
  243. package/src/callers.ts +245 -0
  244. package/src/chart.ts +52 -7
  245. package/src/config.ts +550 -1
  246. package/src/conversations.ts +453 -0
  247. package/src/custodian.ts +8 -2
  248. package/src/customize.ts +143 -4
  249. package/src/dispatch-effect.ts +142 -26
  250. package/src/durability.ts +2 -0
  251. package/src/duration.ts +20 -0
  252. package/src/effect-checks.ts +2 -1
  253. package/src/effect.ts +60 -7
  254. package/src/effector-wire.ts +272 -27
  255. package/src/gate-brand.ts +16 -0
  256. package/src/gate.ts +634 -48
  257. package/src/harness-client.ts +10 -4
  258. package/src/held-secrets.ts +72 -23
  259. package/src/holds.ts +2 -2
  260. package/src/hook-kinds/discord.ts +92 -0
  261. package/src/hook-kinds/door.ts +107 -0
  262. package/src/hook-kinds/github.ts +89 -0
  263. package/src/hook-kinds/gitlab.ts +72 -0
  264. package/src/hook-kinds/hook.ts +7 -0
  265. package/src/hook-kinds/index.ts +465 -0
  266. package/src/hook-kinds/slack.ts +151 -0
  267. package/src/hook-kinds/webhook.ts +113 -0
  268. package/src/hooks.ts +506 -0
  269. package/src/http-effect.ts +76 -38
  270. package/src/http.ts +889 -94
  271. package/src/index.ts +16 -5
  272. package/src/instance.ts +196 -26
  273. package/src/internal.ts +22 -3
  274. package/src/machine-doc.ts +26 -1
  275. package/src/mcp-effect.ts +424 -0
  276. package/src/mount-digest.ts +41 -7
  277. package/src/names.ts +64 -2
  278. package/src/notices.ts +138 -10
  279. package/src/open-body.ts +25 -11
  280. package/src/open-effect.ts +17 -4
  281. package/src/open.ts +4 -2
  282. package/src/parts.ts +87 -9
  283. package/src/party.ts +2 -1
  284. package/src/pick.ts +2 -2
  285. package/src/pool.ts +1142 -163
  286. package/src/registration.ts +203 -13
  287. package/src/run-host.ts +1509 -171
  288. package/src/run-list.ts +191 -0
  289. package/src/sandbox-kube.ts +53 -5
  290. package/src/secret-placement.ts +239 -33
  291. package/src/server.ts +53 -4
  292. package/src/setup.ts +72 -32
  293. package/src/snapshot-store.ts +565 -48
  294. package/src/steer.ts +111 -0
  295. package/src/stub-harness.ts +6 -2
  296. package/src/tokens.ts +263 -22
  297. package/src/wire.ts +17 -0
  298. package/src/workspace.ts +50 -6
  299. package/src/worktree.ts +3 -3
  300. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  301. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -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,10 +179,10 @@ 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
 
@@ -131,15 +197,29 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
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,12 +233,13 @@ 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.
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.
162
243
  - **The conversations of concurrent Agents interleave in one log.** The author chooses how many Agents work in a
163
244
  Workspace ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), so two conversations can
164
245
  print at one time on one container's stdout. The prefix keeps them apart: every line carries its label and its iid, so
@@ -166,10 +247,11 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
166
247
  - **Truncation is load-bearing, not cosmetic.** Kubelet rotates container logs at 10Mi by default and drops the
167
248
  remainder. jr's filter capped `Bash` at 100 chars and unknown tools at 150 and reduced `Write`/`Edit` to a path; that
168
249
  was readability, and it is now also what keeps a long turn inside the window.
169
- - **Live tails may miss partial output.** Text and thinking deltas are best-effort live progress, with `message_end`
170
- authoritative, so a reader that attaches mid-generation can miss earlier fragments until they arrive. Correct for a
171
- watcher; anything wanting exactness reads the completed message.
172
- - **`jr2 logs` remains a status feed** and is now more confusingly named than before. Not renamed here — this ADR adds
173
- no command — but the collision is real and belongs to whoever next touches that surface.
174
- - **The visualizer's eventual extension has a source.** When it grows past the graph, it reads the same stream; a page
175
- 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.
@@ -213,43 +213,44 @@ prevented it writing. An un-ended turn is a concurrent writer in a Workspace tha
213
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
214
  defect, so by default its message never reaches the model. The operator reads it: while a pick is outstanding, an
215
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 logs` print it. An author who wants the Agent
217
- told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a state whose
218
- prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came back for it"):
219
- never that nothing ran, because a timer or an outside event may have stopped work that did run. The pick's `POST`
220
- answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the one tool call
221
- (ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final as it starts
222
- — replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's state, the
223
- `POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that enters a
224
- state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it was, and
225
- is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An outstanding
226
- pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain receipt with
227
- its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is the state it
228
- was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a guard
229
- straight back, and an answered pick back at Rest) ends its state part with the same words, "Your turn is not over.":
230
- the plain receipt once said "the workflow is still in the state that asked for this turn, so it is not over yet", and
231
- real Haiku 5.5 runs stopped after it, because "it" can be the workflow or the Turn. The refusals and the notices of a
232
- cut or unsent pick keep their own words. A route that leaves the Agent's state ends the Turn like any other: the poll
233
- reads `turnComplete`, the next state's prompt is the author's, and the answer is a `pick-answer` notice to the Agent
234
- that picked: the next Turn of the conversation the picking Turn continued (`continue: true`, ADR-0062). A FRESH
235
- picking Turn's conversation is never heard again, and a later continued Turn of the same slot never made the pick, so
236
- its answer goes nowhere: the ledger keeps no notice for it. The route of a reply includes the events the Machine sends
237
- itself AT ONCE while it runs (a `raise`, or a `sendTo(self)` with no delay, in `onDone` or in the next state's entry):
238
- a reply whose route leaves the Agent's state that way is the answer, never stopped. Something other than the route can
239
- leave the Agent's state while the pick's work still runs below it — a timer the Machine set (a Turn timeout `after`, a
240
- named delay, a delayed `raise` or `sendTo(self)`), an event from outside the states below, a Gate's event, the Agent's
241
- own end. The exit stops that work (an invoke is stopped when its state exits), and the answer says so (`stopped`): the
242
- poll reads "The workflow left the state that asked for this turn while it ran your pick, and that stopped the pick's
243
- work before it finished: your turn is over.", and the next continued Turn's notice reads "Your pick `X` did not
244
- finish: the workflow left the state that asked for your last turn while it ran, and that stopped it." Both keep the
245
- last reply an earlier link gave, if any, and state it: an earlier link's reply is never dropped because a later link
246
- was stopped. The run host tells a fired timer from an event sent at once by the scheduler's own table: it watches what
247
- the actor system arms, cancels and fires (`timers.ts`, the same xstate internals as the timer table, ADR-0007). Back
248
- at Rest INSIDE the Agent's state — a per-step timeout `after` on the child state that exits the work — the answer is
249
- the last reply since the pick, with the Allowed picks; with no reply since the pick, the answer carries none. A
250
- restored hold is the Turn of the first registration of its address in the restored tree: a restore that ends that Turn
251
- and starts another at the same address before anything settles answers the pick with `turnComplete`. There is no
252
- default time bound on a held Turn; the Console shows each outstanding pick "held since".
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
226
+ outstanding pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain
227
+ receipt with its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is
228
+ the state it was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a
229
+ guard straight back, and an answered pick back at Rest) ends its state part with the same words, "Your turn is not
230
+ over.": the plain receipt once said "the workflow is still in the state that asked for this turn, so it is not over
231
+ yet", and real Haiku 5.5 runs stopped after it, because "it" can be the workflow or the Turn. The refusals and the
232
+ notices of a cut or unsent pick keep their own words. A route that leaves the Agent's state ends the Turn like any
233
+ other: the poll reads `turnComplete`, the next state's prompt is the author's, and the answer is a `pick-answer`
234
+ notice to the Agent that picked: the next Turn of the conversation the picking Turn continued (`continue: true`,
235
+ ADR-0062). A FRESH picking Turn's conversation is never heard again, and a later continued Turn of the same slot never
236
+ made the pick, so its answer goes nowhere: the ledger keeps no notice for it. The route of a reply includes the events
237
+ the Machine sends itself AT ONCE while it runs (a `raise`, or a `sendTo(self)` with no delay, in `onDone` or in the
238
+ next state's entry): a reply whose route leaves the Agent's state that way is the answer, never stopped. Something
239
+ other than the route can leave the Agent's state while the pick's work still runs below it — a timer the Machine set
240
+ (a Turn timeout `after`, a named delay, a delayed `raise` or `sendTo(self)`), an event from outside the states below,
241
+ a Gate's event, the Agent's own end. The exit stops that work (an invoke is stopped when its state exits), and the
242
+ answer says so (`stopped`): the poll reads "The workflow left the state that asked for this turn while it ran your
243
+ pick, and that stopped the pick's work before it finished: your turn is over.", and the next continued Turn's notice
244
+ reads "Your pick `X` did not finish: the workflow left the state that asked for your last turn while it ran, and that
245
+ stopped it." Both keep the last reply an earlier link gave, if any, and state it: an earlier link's reply is never
246
+ dropped because a later link was stopped. The run host tells a fired timer from an event sent at once by the
247
+ scheduler's own table: it watches what the actor system arms, cancels and fires (`timers.ts`, the same xstate
248
+ internals as the timer table, ADR-0007). Back at Rest INSIDE the Agent's state — a per-step timeout `after` on the
249
+ child state that exits the work — the answer is the last reply since the pick, with the Allowed picks; with no reply
250
+ since the pick, the answer carries none. A restored hold is the Turn of the first registration of its address in the
251
+ restored tree: a restore that ends that Turn and starts another at the same address before anything settles answers
252
+ the pick with `turnComplete`. There is no default time bound on a held Turn; the Console shows each outstanding pick
253
+ "outstanding since".
253
254
  - **The one exception is the Orchestrator ending it for its own reasons**, which is ADR-0007's requirement and exactly
254
255
  one place: `RunHost.stop()`. Process shutdown never stops actors at all (`instance.ts` calls `host.close()`, which
255
256
  ends observation feeds and nothing else), and restore is a fresh process. So "the host did this" is a flag it sets,
@@ -33,16 +33,21 @@ carved out for "the Orchestrator ending a run for its own reasons" was being spe
33
33
  - **`stop()` stays public with no production caller, and is deliberately not on the wire.** It is correct, it is what
34
34
  the durability suite drives, and it is what a future graceful shutdown should call. What nobody asked for is a
35
35
  user-facing "park this run" verb.
36
+ - **A cancel of a standing run is also a pause.** The run ends `cancelled` as any run does, and jr2 does not start its
37
+ Workflow again until `jr2 run <workflow>` resumes it, with the items it held
38
+ ([ADR-0017](0017-pool-over-a-source-port.md)). Without the pause, jr2 would start the standing run again at the next
39
+ boot or publish, and a person could not stop it. The runs that it dispatched go on, as for any dispatch.
36
40
  - **A cancelled run's Workspaces are destroyed, unless the cancel says `keep`.** The run is over, so nothing can use
37
41
  them, and each holds its whole Size (ADR-0060) — a placed pod left to the idle GC is capacity for nobody. The
38
42
  wrapper's `teardown` is a STATE, entered when the body reaches final, and an actor stop skips it; so the host destroys
39
43
  every Sandbox the run owns after the stop, the same idempotent delete `teardown` runs.
40
44
  `POST /runs/:id/events { type: "CANCEL", keep: true }` — `jr2 send <run> --event CANCEL --keep` — leaves them for
41
- inspection instead: with no Lease they age out at the operator's idle timeout, and `jr2 ssh` reaches them until then.
42
- Whether to keep is a fact of the moment of the cancel, never a config key. A **faulted** run keeps its Workspaces as
43
- before ([ADR-0012](0012-workspace-wrapper-machine.md)'s destroy-less terminal, ADR-0021): a fault is the case nobody
44
- chose, and the pod is the evidence. A **failed** run deletes them, as a `done` run does: a Failure is an end that the
45
- Machine chose ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)).
45
+ inspection instead: with no Lease they age out at the operator's idle timeout, and `jr2 workspace shell` reaches them
46
+ until then; `jr2 workspace rm` removes one sooner. Whether to keep is a fact of the moment of the cancel, never a
47
+ config key. A **faulted** run keeps its Workspaces as before ([ADR-0012](0012-workspace-wrapper-machine.md)'s
48
+ destroy-less terminal, ADR-0021): a fault is the case nobody chose, and the pod is the evidence. A **failed** run
49
+ deletes them, as a `done` run does: a Failure is an end that the Machine chose
50
+ ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)).
46
51
 
47
52
  ## Considered options
48
53
 
@@ -57,8 +57,8 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
57
57
  ([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md)). The model sent
58
58
  `bash` with `git add … && git commit …` and the pick `publish` in one model answer. In 3 of 10 trials, `publish`
59
59
  pushed before the commit existed. A `write` and a `bash` test run in one model answer had the same race. The rule is
60
- for all tools, the Working tools and the Menu. The cost is that independent reads of one model answer do not run at
61
- the same time. Four facts follow:
60
+ for all tools: the Working tools, the MCP tools and the Menu. The cost is that independent reads of one model answer
61
+ do not run at the same time. Four facts follow:
62
62
  - Only the first pick of a model answer runs
63
63
  ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)). The model wrote a later one before it
64
64
  read the first one's answer. The seam states two more facts, so that the Turn can find the first pick by itself. The
@@ -177,10 +177,11 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
177
177
  long as its run** (ADR-0031). The Harness persists each conversation to a directory as it goes and rebuilds it on
178
178
  boot, so a Harness restart inside a living pod keeps it; a Submission in flight at the restart settles `failed`.
179
179
  What is durable across a lost pod is Orchestrator-side — the admission ledger and snapshot
180
- ([ADR-0007](0007-durable-machine-state.md)), and `wait`'s reconnect-from-offset. A conversation that is gone
181
- surfaces as 404 → fault → workflow policy, the same family as `workspace.lost`. Provider-stream retry moves inside
182
- the turn: the Harness states 2 retries, and pi-ai runs them (`maxRetries`) — the seat ADR-0016 delegated to flue's
183
- `durability{}`.
180
+ ([ADR-0007](0007-durable-machine-state.md)), `wait`'s reconnect-from-offset, and the kept conversation, which a
181
+ person reads and no Turn continues from ([ADR-0023](0023-the-harness-prints-the-conversation.md)). A conversation
182
+ that is gone surfaces as 404 → fault → workflow policy, the same family as `workspace.lost`. Provider-stream retry
183
+ moves inside the turn: the Harness states 2 retries, and pi-ai runs them (`maxRetries`) — the seat ADR-0016
184
+ delegated to flue's `durability{}`.
184
185
  - **[ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)'s enabling fact is reproduced
185
186
  as explicit code.** Each Submission reads `$JR2_CUSTODIAN_URL/agents/<iid>/surface` afresh and presents it to pi as
186
187
  the Menu, so the menu is per-turn with no push channel, no turn index, no second port. Menu tools keep the
@@ -31,9 +31,12 @@ definition can state.
31
31
  Workspace (it is this ADR's title). A string field, JSON-serializable, riding the existing ConfigMap channel unchanged
32
32
  (ADR-0018's constraint holds).
33
33
  - **`"none"` is the Menu-only Agent — and the value also names the Harness that hosts the Agent.** `"none"` withholds
34
- the entire Working toolset: the Agent converses and picks from its Menu, nothing else — the decisioning persona (read
35
- inputs, pick the next event). Because such an Agent touches no Workspace, it does not run in one: `"write"`/`"read"`
36
- place a Turn on the enclosing Workspace's Harness; `"none"` places it on the
34
+ the entire Working toolset: the Agent converses, picks from its Menu and calls the MCP tools of the remote servers it
35
+ names
36
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), and
37
+ runs no code — the decisioning persona (read inputs, pick the next event). "Menu-only" means "no Working tools": such
38
+ an Agent can also hold MCP tools. Because such an Agent touches no Workspace, it does not run in one:
39
+ `"write"`/`"read"` place a Turn on the enclosing Workspace's Harness; `"none"` places it on the
37
40
  [Instance Harness](0031-menu-only-agents-run-on-the-instance-harness.md), always — even when a `workspace()` encloses
38
41
  the invocation. Placement is definitional, which is this ADR's thesis extended one step: what an Agent may do to the
39
42
  Workspace decides where it runs. (`cwd` is moot for `"none"` — only Working tools consume it.)
@@ -50,6 +53,18 @@ definition can state.
50
53
  - **Two layers, one structure: the tool list is the hint, the worktree is the guarantee** — the same shape as ADR-0024's
51
54
  receipt (hint) and abort (guarantee). Neither layer alone: tools without the worktree leak through bash; the worktree
52
55
  without tools invites the model to fight its checkout instead of declining politely.
56
+ - **An MCP allowlist is a guarantee about the credential, and jr2 offers it only where it can keep it.** An Agent's MCP
57
+ tools ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md))
58
+ spend the server's token, so the allowlist guards that token, not the Workspace. A remote server needs a list (tool
59
+ names, or `"*"`): the Harness offers and runs only those tools. Where Workspace code of the Sandbox in which the
60
+ Agent's Turns run can spend that secret, Workspace code can call every tool, so `jr2 up` refuses a list and takes only
61
+ `"*"`. That is the case when the Sandbox's `workspace()` grants the secret, when a credentialed `command` of that
62
+ Workspace names it, or when an `http` Effect of that Workspace whose body is a Worktree file names it: the Agent
63
+ writes that body ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). A `"none"` Agent's
64
+ Turns run on the Instance Harness, where no Workspace code runs, so it keeps its list. A stdio server runs as
65
+ Workspace code and gets no list. The token's own scopes are the outer limit. This is not the shape of
66
+ `workspace: "read"` above: there the tool list is the hint, because `bash` can write; here the list is the guarantee,
67
+ because no Workspace code can spend the secret.
53
68
  - **A state before the reviewing state cuts the review Worktree on each round.** The sha under review moves between
54
69
  rounds, so each round cuts the one folder again at the new sha. The cut forces the folder to the commit and cleans it
55
70
  with `-ffd`. Ignored files stay, because the reviewer runs the tests. The folder's path frames the reviewer's turn —
@@ -79,10 +94,11 @@ definition can state.
79
94
  - **The reviewer definition becomes `workspace: "read"`**, and the prose ban stays in its instructions as intent — the
80
95
  half of the contract a model reads — no longer as the mechanism. A decisioner becomes `workspace: "none"`.
81
96
  - **The definition stays plain data.** No code enters the ConfigMap channel; composition by re-export/spread (ADR-0018)
82
- is preserved. Custom tool _implementations_ remain out of the contract — this decision adds a restriction vocabulary,
83
- not an extension one. Growing extension tools is a separate, later decision.
84
- - **GLOSSARY.md arbitrates the three senses of "tools"**: the Menu (control plane — what an Agent may say) and Working
85
- tools (data plane — what it may do) become glossary entries; unqualified "tools" joins the Avoid lists.
97
+ is preserved. Tool _implementations_ stay out of the contract — this decision adds a restriction vocabulary, not an
98
+ extension one. The extension is MCP: an Agent names servers, and a server holds the code (ADR-0079).
99
+ - **GLOSSARY.md arbitrates the senses of "tools"**: the Menu (control plane — what an Agent may say), Working tools
100
+ (data plane — what it may do) and MCP tools (what it may do through a service, ADR-0079) are glossary entries;
101
+ unqualified "tools" joins the Avoid lists.
86
102
  - **The `@kind` tier gains the containment scenario**: from the review worktree, a write probe followed by a commit must
87
103
  leave the branch ref and the coder's worktree untouched.
88
104
  - **The zero-support guard stays available to workflows**: a workflow can still have both Agents report the sha and gate
@@ -21,12 +21,19 @@ whole history again. It also left the history calling tools the request no longe
21
21
 
22
22
  ## Decision
23
23
 
24
- - **The Menu is the whole of what an Agent can ever be offered in its Machine, and it is the tools block.** It is the
25
- union of the derived sets of every state that invokes an Agent of that name — the same scope as its continued
26
- conversation's id (ADR-0057). `jr2Setup.createMachine` derives it in ADR-0015's walk, in a fixed order, so it is
27
- byte-stable for every Turn of the conversation and for every fresh conversation of that Agent. The descriptions are
28
- the event defs' own and do not vary by state, so a def's description must hold at every writing of it the Agent can
29
- reach; the load warns where its writings on one path differ (ADR-0015).
24
+ - **The Menu is the whole of what an Agent can ever be offered in its Machine, and it is the Menu's part of the tools
25
+ block.** It is the union of the derived sets of every state that invokes an Agent of that name — the same scope as its
26
+ continued conversation's id (ADR-0057). `jr2Setup.createMachine` derives it in ADR-0015's walk, in a fixed order, so
27
+ it is byte-stable for every Turn of the conversation and for every fresh conversation of that Agent. The descriptions
28
+ are the event defs' own and do not vary by state, so a def's description must hold at every writing of it the Agent
29
+ can reach; the load warns where its writings on one path differ (ADR-0015).
30
+ - **An Agent's MCP tools are fixed for the conversation too.** The Harness reads a server's tools at the conversation's
31
+ first Turn, keeps the allowed ones sorted by name, records them with the conversation, and never reads them again for
32
+ it, across a park and a restart
33
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
34
+ tools block is the Working tools, then the MCP tools, then the Menu, and it is the same bytes for every Turn of the
35
+ conversation. Any fixed order keeps the cache. A tool that the server took away stays in the block, and its call fails
36
+ with jr2's text.
30
37
  - **What one Turn may pick is its Allowed picks**: the invoking state's derived set (its own, its ancestors' and its
31
38
  modes' picks — ADR-0015), narrowed to what the live Machine would currently accept **from this Agent**: xstate's
32
39
  selection for an event that carries the Agent's sender, a guard that reads the payload counted as passing, must move
@@ -117,7 +124,8 @@ block was not perfect either: once the model invented a tool it had never been g
117
124
  cross-state `continue`.
118
125
  - **The Machine's whole agent-audience vocabulary as the Menu.** Rejected: it offers picks only another Agent's states
119
126
  handle.
120
- - **Put the Menu tools last.** Rejected: measured, a reordered tools block misses as completely as a changed one.
127
+ - **Narrow the Menu per Turn, with the Menu tools last so that only the tail of the block changes.** Rejected: measured,
128
+ a change at the tail of the tools block misses as completely as one at its head.
121
129
  - **Mask at decode time** (OpenAI `allowed_tools`, logit masking). Deferred: it is provider-specific — Anthropic has no
122
130
  subset form, and changing `tool_choice` there drops the messages cache; vLLM's Chat Completions refuses it. The design
123
131
  must hold without it; where it is cache-safe, it can be added in the presenter.
@@ -141,9 +149,9 @@ block was not perfect either: once the model invented a tool it had never been g
141
149
 
142
150
  ## Consequences
143
151
 
144
- - **One state can allow different picks as its context changes or its active mode changes; the Menu does not change.**
145
- The conformance tier asserts it: two Submissions with different Allowed picks send a byte-identical tools block
146
- (ADR-0027).
152
+ - **One state can allow different picks as its context changes or its active mode changes; the Menu and the MCP tools do
153
+ not change.** The conformance tier asserts it: two Submissions with different Allowed picks send a byte-identical
154
+ tools block (ADR-0027).
147
155
  - **A restatement is appended, never rewritten.** Conformance asserts that each request of a Submission is a byte prefix
148
156
  of the next, with the tools and the system prompt unchanged, and that the restatement sits right after the model
149
157
  answer's last tool result.
@@ -89,8 +89,9 @@ to ask about.
89
89
  ([ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md)) is the run's data, not its Machine's shape: the Chart's
90
90
  text and its hash ride the body's own snapshot, restore admits the text again and compares the hash, and a mismatch or
91
91
  a refusal is drift naming the body slot and the reason, after the stamp check and in the same words.
92
- - **A drifted run is not in `jr2 runs`**, which lists only the live registry. It is reachable through the announce line
93
- and `jr2 status <id>`. If stranded runs become common enough to browse, the listing is where that belongs.
92
+ - **A drifted run is in `jr2 runs --status drifted`** and in the Console's list of ended runs, with its reason
93
+ ([ADR-0009](0009-cli-and-instance-interface.md)), and `jr2 status <id>` reads it whole. It is not in the default
94
+ listing, which is the live runs.
94
95
  - **The digest is only as complete as the walk.** A state running `enqueueActions` can spawn children no static analysis
95
96
  recovers (`machine-doc.ts`'s `opaqueStates`); the opacity is folded into the hash, but what it hides is not. Drift
96
97
  inside such a subtree can go unnoticed.
@@ -39,19 +39,23 @@ built.
39
39
  the Harness has exactly one route to its Menu — the Custodian on `localhost` — and forking that path for one pod buys
40
40
  a divergence in the component this ADR keeps deliberately uniform. The pod is composed by the same function a
41
41
  Sandbox's is ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)), so its Custodian
42
- carries the Instance Harness's model calls the same way. It holds only the model keys of the `"none"` Agents it hosts
43
- — the keys their providers read — and none of the held secrets a `workspace()` lists: a `"none"` Agent cannot execute
44
- code, so it had no way to use the rest. Its held objects are its own (`held-instance.json`,
45
- `jr2-held-secrets-instance`). Its held secrets get secret Stand-ins, as in every Harness pod: `jr2 up` mints them with
46
- the signing key when it writes that pod's Secrets, as it does for the Instance Effector's key
47
- ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). It starts no confined child, so this
48
- is for consistency, not a boundary. Defense-in-depth is the bonus, not the reason.
42
+ carries the Instance Harness's model calls the same way. It holds what the `"none"` Agents it hosts name — the keys
43
+ their providers read, and the secrets of the remote MCP servers they name
44
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)) — and
45
+ none of the held secrets a `workspace()` lists: a `"none"` Agent cannot execute code, so it had no way to use the
46
+ rest. A `"none"` Agent names no stdio MCP server, because the Instance Harness has no Landlock domain to start one in.
47
+ Its held objects are its own (`held-instance.json`, `jr2-held-secrets-instance`). Its held secrets get secret
48
+ Stand-ins, as in every Harness pod: `jr2 up` mints them with the signing key when it writes that pod's Secrets, as it
49
+ does for the Instance Effector's key ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
50
+ It starts no confined child, so this is for consistency, not a boundary. Defense-in-depth is the bonus, not the
51
+ reason.
49
52
  - **The Instance Effector is the second Instance-level pod, beside it**
50
53
  ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), on the same convention: `jr2 up`
51
- converges it when a registered Machine places an Effect there — an `http` Effect — and deletes it otherwise. It runs
52
- the same `jr2-harness` image with `/opt/jr2/bin/effector` as its command, holds no conversation and runs no Agent, and
53
- is a Deployment, not a StatefulSet: it keeps nothing, and a rollout must run the new pod beside the old one. No config
54
- key names, sizes, addresses, or enables it.
54
+ converges it when a registered Machine places an Effect there — an `http`, an `mcp` or a targeted `dispatch` Effect —
55
+ or a Hook needs it (a `mac` verifier, or a kind that declares `posts`), and deletes it otherwise. It runs the same
56
+ `jr2-harness` image with `/opt/jr2/bin/effector` as its command, holds no conversation and runs no Agent, and is a
57
+ Deployment, not a StatefulSet: it keeps nothing, and a rollout must run the new pod beside the old one. No config key
58
+ names, sizes, addresses, or enables it.
55
59
  - **No image config at all.** The Harness image was once configured as `sandbox.image` because the Sandbox pod was the
56
60
  only place a Harness ran — a misnomer once the Instance Harness exists, and config is the wrong seat regardless:
57
61
  `jr2 up` builds and resolves every image it deploys ([ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md)), and
@@ -71,13 +75,14 @@ pointers only; memory snapshots are tied to one template version, so the state w
71
75
  change is a volume the actor reloads on a cold boot. These decisions take that shape.
72
76
 
73
77
  - **A conversation is persisted to a directory, and rebuilt from it on boot.** The Harness writes each conversation as
74
- it goes: jr2's own record — the stream log, the Settlements, the history messages, in the wire's shapes — plus the
75
- engine part, in a folder the engine names (`engine/<name>`), which the record does not read. Today it is jr2's
76
- transcript (ADR-0027): `engine/pi/transcript.jsonl`, the pi-ai messages the model read and each Compaction. Rebuilding
77
- reads jr2's record first; an engine that cannot read the engine part (another engine, another version) continues from
78
- the history messages. The record is the contract; the engine part is not. A Submission in flight at the restart
79
- settles `failed` (the typed error `harness_restarted`, which the Agent's Actor reads as "Turn lost") on the rebuilt
80
- stream, so the Orchestrator reads a Settlement, not a 404, and the conversation's next Turn continues it.
78
+ it goes: jr2's own record — the stream log, the Settlements, the history messages, the conversation's MCP tools, in
79
+ the wire's shapes — plus the engine part, in a folder the engine names (`engine/<name>`), which the record does not
80
+ read. Today it is jr2's transcript (ADR-0027): `engine/pi/transcript.jsonl`, the pi-ai messages the model read and
81
+ each Compaction. Rebuilding reads jr2's record first; an engine that cannot read the engine part (another engine,
82
+ another version) continues from the history messages. The record is the contract; the engine part is not. A Submission
83
+ in flight at the restart settles `failed` (the typed error `harness_restarted`, which the Agent's Actor reads as "Turn
84
+ lost") on the rebuilt stream, so the Orchestrator reads a Settlement, not a 404, and the conversation's next Turn
85
+ continues it.
81
86
  - **A restart while the Agent's state is busy cuts the Turn and is no fault.** When the Agent's state is not at Rest —
82
87
  its pick is outstanding, or work it started in runs below it — or its pick's answer was read and never reached the
83
88
  model, the Agent's Actor raises no `agent.fault` for a restart: the Machine would route it and stop the work the Turn