@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
@@ -30,20 +30,35 @@ whose processes are gone, with no way to know why.
30
30
  workflow prompts you again when something is." The last sentence is safe because the wake asks a model that stopped
31
31
  with nothing allowed when a pick opens ([ADR-0016](0016-agent-turn-mechanics-are-internal.md)). A line that says a
32
32
  Turn ends with exactly one pick would be false for a pick that keeps the Turn open.
33
+ - **An Agent with MCP tools is told that they are not picks.** Its standing part has one more statement: "Your tools
34
+ named `mcp__<server>__<tool>`, where `<server>` is not `jr2`, act on outside services, not on the workflow. A call to
35
+ one is not a pick, and it does not end your Turn." Both tool families share the `mcp__` prefix: the Menu's own tools
36
+ are `mcp__jr2__<event>`, so the statement names `jr2` as the exception. A model that read a call of a service as its
37
+ pick would stop with the workflow's work not done. The statement names no server and no tool, so it is byte-stable; an
38
+ Agent with no MCP tools does not get it. A `"none"` Agent with MCP tools is not told that it acts only by picking from
39
+ its Menu, because its MCP tools act too. The `@model` claim `mcp-then-pick` measures it
40
+ ([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md),
41
+ [ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)).
33
42
  - **The Turn part is written once per Submission, and restated when its Allowed picks change.** When something other
34
43
  than the Agent's own pick changes them inside the Turn, the Harness appends the Allowed picks again at the next step
35
44
  boundary, in a `<jr2-turn>` block of their own: "The workflow's state changed since you last read the Allowed picks.
36
45
  Allowed now: …" ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). It is
37
- appended, never a rewrite of the first, so the cache argument above holds for it too. It carries no notice: notices
38
- are for the next Turn in their scope. It carries a pick's briefing when the restatement opens that pick.
46
+ appended, never a rewrite of the first, so the cache argument above holds for it too. It carries a pick's briefing
47
+ when the restatement opens that pick. The same read at a step boundary returns the conversation's pending notices, and
48
+ the Harness appends them in the same way (below).
39
49
  - **The Turn part comes last, because a model obeys what it read last.** Measured on the home-lab vLLM model: with a
40
50
  prompt that argued for a pick the Turn did not allow, the Allowed picks placed ahead of the prompt were obeyed 0/10
41
51
  (thinking off) and 5/10 (thinking on); placed after it, 10/10 both. Wording made no difference. The author's task
42
52
  still reads first, as it would with no jr2.
43
53
  - **Always on.** No author can turn it off: every fact in it passes the rule above, so removing one can only make the
44
54
  Agent act worse. An `instructions` that repeats a fact is harmless.
45
- - **A notice is delivered once**, on the next admission in its scope that takes it, then cleared. That is the next Turn,
46
- or, for a guard kill that the Agent of an open Turn was not told, that Turn's nudge or wake (below).
55
+ - **A notice is heard once by each member of its audience.** A member takes it at its next admission, or at a step
56
+ boundary of an open Turn: the surface read of the open Turn returns the conversation's pending notices, and the
57
+ Harness appends them, as it appends a changed Allowed-picks list
58
+ ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md), ADR-0002). For a guard
59
+ kill that the Agent of an open Turn was not told, a member also takes it at that Turn's nudge or wake (below). A
60
+ conversation is an Instance ID ([ADR-0057](0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md)): a fresh
61
+ Turn, a runaway reroll and a burned conversation's successor each start a new one.
47
62
  - **Conversation scope** ("this conversation is new; earlier context is gone") goes to that Agent's next Turn. So does
48
63
  a pick's answer that never reached its call
49
64
  ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)): the route left the Agent's state, and
@@ -53,29 +68,43 @@ whose processes are gone, with no way to know why.
53
68
  and that stopped it" (`stopped`), with an earlier link's reply when one came. No pick-answer notice says the work
54
69
  ran to its end. A fresh picking Turn has no such conversation, and its answer is no notice.
55
70
  - **Workspace scope** ("the Sandbox's processes were killed at its memory limit during `coder`'s Turn; `/dev/shm` was
56
- cleared. Anything that was running in the Sandbox at that time is gone.") goes to the next admission of any Agent in
57
- that Workspace that takes it. One kill is one notice, however many Turns saw it. Only a kill read on a stream while
58
- its wait is live names the Turn whose Submission ran. Both forms end with the whole Sandbox, because every process
59
- in it died, a shared Workspace's too, and in the past tense, because the Agent may hear it after processes started
60
- again. A kill read from the pod's list names no Turn and says nothing of when or whose: "the Sandbox's processes
61
- were killed at its memory limit (peak X of Y); /dev/shm was cleared. Anything that was running in the Sandbox at
62
- that time is gone." The list cannot say whose Turn a kill ended: the kill may land after a state exits, or end an
63
- attach, a sync or a command. A kernel kill read from the Sandbox's last end (below) says the same, with the limit
64
- and no peak: "the Sandbox's processes were killed at its memory limit of Y; …". A Menu-only Agent outside a
65
- Workspace receives conversation notices only.
71
+ cleared. Anything that was running in the Sandbox at that time is gone.") goes to an **audience** that the ledger
72
+ fixes when it raises the notice. The rule is: an Agent hears a kill if it was there for it.
73
+ 1. Each conversation admitted in that Workspace before the kill that can still take a Turn: the conversation of an
74
+ open Turn, which hears it at its next step boundary, or a continued conversation, which hears it at its next
75
+ Turn. A fresh Turn that already ended is not in the audience.
76
+ 2. When the kill cut a specific Turn — a kill read on that Turn's stream names it — that Agent's next Turn, fresh or
77
+ continued: the retry of the broken work.
78
+ 3. Minus each conversation whose own `bash` answer returned the kill's verdict (`told`, below). That answer told it
79
+ already.
80
+
81
+ Each member hears the notice once. The notice retires when every member has heard it. A conversation that starts
82
+ after the kill does not hear it, except the retry of (2). So a Pool of coders in one Workspace
83
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)) all hear a kill that cut their work.
84
+ Only a kill read on a stream while its wait is live names the Turn whose Submission ran. Both forms end with the
85
+ whole Sandbox, because every process in it died, a shared Workspace's too, and in the past tense, because the Agent
86
+ may hear it after processes started again. A kill read from the pod's list names no Turn and says nothing of when or
87
+ whose: "the Sandbox's processes were killed at its memory limit (peak X of Y); /dev/shm was cleared. Anything that
88
+ was running in the Sandbox at that time is gone." The list cannot say whose Turn a kill ended: the kill may land
89
+ after a state exits, or end an attach, a sync or a command. A kernel kill read from the Sandbox's last end (below)
90
+ says the same, with the limit and no peak: "the Sandbox's processes were killed at its memory limit of Y; …". A
91
+ Menu-only Agent outside a Workspace receives conversation notices only.
92
+
66
93
  - A notice never replaces a fault. The Machine still receives `agent.fault` and decides (ADR-0016); the notice makes
67
94
  sure the next Agent is not working blind.
68
- - **The Orchestrator is the only keeper of notices**: pending notices are plain data in the run's persisted state
69
- (ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has two paths,
70
- under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults on it, and
71
- the fault raises the notice with the Turn's Agent. A kill while no Turn runs faults nothing, so before each admission
72
- into a Workspace the Agent actor also reads the container's last end from the Sandbox watch, which the operator
73
- publishes: a last end `OOMKilled` raises the notice, which names no Turn. The ledger's `seen` makes the two paths one
74
- notice. An actor whose own watch saw the restart reads the last end only when it re-admits the Turn after the cut.
75
- When the Turn faults, the fault's notice names the Turn's Agent. A watch that has not listed yet says nothing, and the
76
- next admission reads again: the last end stays until the container ends again. A nudge and a wake take no kernel kill;
77
- it stays for the next Turn. The re-admission after a cut takes it, as it takes every notice in scope that is not a
78
- guard kill. The Harness reports a guard kill (ADR-0061) on two paths, under one id that the Harness gives the kill:
95
+
96
+ - **The Orchestrator is the only keeper of notices and Steering messages**: pending ones are plain data in the run's
97
+ persisted state (ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has
98
+ two paths, under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults
99
+ on it, and the fault raises the notice with the Turn's Agent. A kill while no Turn runs faults nothing, so before each
100
+ admission into a Workspace the Agent actor also reads the container's last end from the Sandbox watch, which the
101
+ operator publishes: a last end `OOMKilled` raises the notice, which names no Turn. The ledger's `seen` makes the two
102
+ paths one notice. An actor whose own watch saw the restart reads the last end only when it re-admits the Turn after
103
+ the cut. When the Turn faults, the fault's notice names the Turn's Agent. A watch that has not listed yet says
104
+ nothing, and the next admission reads again: the last end stays until the container ends again. A nudge and a wake
105
+ take no kernel kill; it stays for the next Turn. The re-admission after a cut takes it, as it takes every notice in
106
+ scope that is not a guard kill. The Harness reports a guard kill (ADR-0061) on two paths, under one id that the
107
+ Harness gives the kill:
79
108
  - **On a stream.** While a Submission runs, the kill is an event on that conversation's updates stream. The Agent
80
109
  actor reads a stream while it waits on the Submission. It stops when the Agent's state exits, before its abort
81
110
  reaches the Harness. So a stream does not always reach a reader: a kill in that window is on a stream that no one
@@ -89,13 +118,15 @@ whose processes are gone, with no way to know why.
89
118
  the only limit. A read that never left the Orchestrator, or a draining Harness's 503, is sent again in that time. A
90
119
  read that fails or times out is none, and the next admission reads again: the list keeps the kill. The Orchestrator
91
120
  logs one line when the reads from a Harness start to fail. It logs no line when a read works again, and it logs the
92
- failure again only when a read fails after a read that worked. The Agent's uid can write the file, as it can the
93
- conversation records: it can change only what its own Workspace is told. So a `peak` or a `limit` that is not a size
94
- in the guard's form, or is long, is left out and logged, on the Harness's read of the file and on the Orchestrator's
95
- read of the list and of a stream. The kill is kept, so a real kill is never lost, and its notice says no sizes: "the
96
- Sandbox's processes were killed at its memory limit; /dev/shm was cleared. …". Only a kill with no id or no time, or
97
- one outside the bounds below, is dropped. A stream event from an older Harness has no id; its source is then its
98
- place on the stream, exactly as the Orchestrator raised it before.
121
+ failure again only when a read fails after a read that worked. The file is beside the Harness's conversations, which
122
+ no confined child can reach
123
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The bounds stay as a
124
+ second layer: a `peak` or a `limit` that is not a size in the guard's form, or is long, is left out and logged, on
125
+ the Harness's read of the file and on the Orchestrator's read of the list and of a stream. The kill is kept, so a
126
+ real kill is never lost, and its notice says no sizes: "the Sandbox's processes were killed at its memory limit;
127
+ /dev/shm was cleared. …". Only a kill with no id or no time, or one outside the bounds below, is dropped. A stream
128
+ event from an older Harness has no id; its source is then its place on the stream, exactly as the Orchestrator
129
+ raised it before.
99
130
 
100
131
  Both paths raise the notice with the same source, the Workspace and the kill's id. The ledger keeps every source it
101
132
  raised (`seen`) in the run's persisted state. So a kill read on both paths, or on two streams, is one notice. Each
@@ -107,71 +138,107 @@ whose processes are gone, with no way to know why.
107
138
  restart of the Orchestrator raises no kill twice. A restart of the Harness container keeps the file, which is on the
108
139
  pod's emptyDir with its conversations (ADR-0031). A lost pod takes the file with it, but then the Workspace is lost
109
140
  (ADR-0021). The list keeps the newest 16 kills, each with at most 32 conversations in `told`, and the Orchestrator
110
- reads at most 256 KiB of it. The Agent's uid can write the file, so the two sides hold the same bounds: an `id` and an
111
- `at` of at most 64, an `agent` and an `instanceId` of at most 128, and an `at` that parses as a time. A kill outside
112
- them is dropped, and a `told` entry outside them is left out. The Harness counts the bytes of the JSON text, and the
113
- Orchestrator counts characters, which are never more, so the Harness keeps no kill that the Orchestrator drops. The
114
- Harness reads at most the last 256 KiB of the file. With these bounds the largest list is 149,259 bytes. When more
115
- come between two admissions, the oldest is lost, and a newer kill tells the same thing. When more than 16 come in one
116
- Turn, the Turn's mark leaves the list too, and every kill in the list then counts as after the mark; a nudge or a wake
117
- of that Turn may then carry a kill from before it. The Instance Harness keeps no kill, because it holds no Workspace.
141
+ reads at most 256 KiB of it. The two sides hold the same bounds: an `id` and an `at` of at most 64, an `agent` and an
142
+ `instanceId` of at most 128, and an `at` that parses as a time. A kill outside them is dropped, and a `told` entry
143
+ outside them is left out. The Harness counts the bytes of the JSON text, and the Orchestrator counts characters, which
144
+ are never more, so the Harness keeps no kill that the Orchestrator drops. The Harness reads at most the last 256 KiB
145
+ of the file. With these bounds the largest list is 149,259 bytes. When more come between two admissions, the oldest is
146
+ lost, and a newer kill tells the same thing. When more than 16 come in one Turn, the Turn's mark leaves the list too,
147
+ and every kill in the list then counts as after the mark; a nudge or a wake of that Turn may then carry a kill from
148
+ before it. The Instance Harness keeps no kill, because it holds no Workspace.
118
149
 
119
150
  A notice counts as delivered when its admission is ledgered, so a restart before that point delivers it again and a
120
- restart after it does not. Delivered is not yet heard: if the Orchestrator ends the Turn before the Harness reads its
121
- surface, the Harness never prompts it (ADR-0026), so the Orchestrator raises that admission's notices again. This
122
- holds for a nudge and a wake too: each one must have its surface read again before its notices count as heard. An
123
- admission that fails leaves the earlier admission's notices heard. A nudge and a wake re-prompt the Turn within
124
- itself, so they take no notice of the kind a Turn takes. They take only the guard kills of the list that come after
125
- the newest kill whose `told` names their conversation. That `bash` answer told the Agent that all its processes died,
126
- so it covers every kill up to it. They also take no kill from before the Turn started. Such a kill would tell the
127
- Agent that processes it started in this Turn died. The Turn's first read of the list sets its mark: the id of the
128
- newest kill it saw, or "none". The mark is ledgered with every admission of the Turn, and a restore reads it back, so
129
- a nudge or a wake takes only kills after it. Only when the first read failed is the Turn's start the mark, ledgered
130
- the same way, with one second allowed for a pod clock that runs behind. A read that failed is "could not tell", never
131
- an empty list, so it sets no mark. The kills they do not take stay pending for the next Turn. The re-admission after a
132
- Harness restart cut the Turn continues the same Turn, so it takes every notice in scope except the guard kills that
133
- this rule leaves out. When its read could not tell, it takes no guard kill at all, as a nudge then takes none: it
134
- cannot know which kills its own `bash` reported. The notices that the Turn's admissions carried since the Harness last
135
- read the Turn's surface are pending again first, each with the source it was raised with, and this re-admission
136
- carries them whatever else its scope leaves out. A notice with no source merges only into a pending one with no
137
- source, so an equal notice of another kill never takes its place. When such an unread admission carried the Turn's
138
- task, the task follows the re-admit text. When the rebuilt Harness lost the conversation, nothing of the Turn is left:
139
- every notice that the Turn's admissions carried is pending again whatever the surface reads said, the re-admission
140
- takes every notice in scope, as a Turn's admission does, and the Turn's task follows the re-admit text. It says once
141
- that the conversation is new. A restore drops the prompt from a Turn's input, so the task rides the ledgered admission
142
- too; a lost conversation with no task to send faults the Turn instead. Whether the Harness still holds the
143
- conversation is asked again for up to 30 seconds while the new container does not listen yet or drains, and only then
144
- is the answer taken as "held". The continue path asks the same way, so a continued Turn after a restart can wait up to
145
- 30 seconds before its admission. A Harness in CrashLoopBackOff can take longer than that: the answer is then "held",
146
- and it can be wrong. So every admission to a conversation that the Harness was taken to keep says so with
147
- `kept: true`: the re-admission after a cut, a continued Turn, and a nudge or a wake. A Harness that keeps no such
148
- conversation refuses it with 404 and `kept: false` in the body, and seats none. The Orchestrator takes only that 404
149
- as the refusal; another 404 is a failed admission. Without the refusal the Harness would seat a new conversation, and
150
- a model on an empty context would read "continue your task" with no task. On the refusal the Turn takes the path of a
151
- lost conversation. The re-admission is admitted as a Turn, with the task after the re-admit text and the notice that
152
- the conversation is new, or faults when it has no task to send. A continued Turn is told that its conversation is new,
153
- and is admitted again. A nudge or a wake is admitted again as a Turn: its prompt is the task alone, because nothing
154
- was cut, with the notice that the conversation is new and every notice in scope. Every notice that the Turn's
155
- admissions carried is pending again first, as for a lost conversation after a cut. With no task to send, the Turn
156
- faults. The Instance Harness's restart is on no watch, so this refusal is the only way a nudge or a wake learns of it.
157
- What the Turn's admissions carried, and whether the Harness read their surface, is kept in the Orchestrator's memory
158
- alone: after a restore of the Orchestrator before a surface read, nothing is raised again. A fresh Turn's "this
159
- conversation is new", which rides an admission alone, is carried again by the next re-admission when the Harness has
160
- not read the one before.
151
+ restart after it does not. A notice at a step boundary has no admission. The surface read of the open Turn reserves it
152
+ for that member, and it counts as delivered, and heard, when the Harness acknowledges the append. When the Turn ends
153
+ with no acknowledgment, the reservation is released, and the notice is pending again for that member. A reservation
154
+ lives in the Orchestrator's memory only, so a restart forgets it: the notice is pending again, and the next read or
155
+ admission carries it. So a conversation can hear a notice twice after a crash, as for an admission. Delivered is not
156
+ yet heard: if the Orchestrator ends the Turn before the Harness reads its surface, the Harness never prompts it
157
+ (ADR-0026), so the Orchestrator raises that admission's notices again. This holds for a nudge and a wake too: each one
158
+ must have its surface read again before its notices count as heard. An admission that fails leaves the earlier
159
+ admission's notices heard. A nudge and a wake re-prompt the Turn within itself. Of the notices, they take only the
160
+ guard kills of the list that come after the newest kill whose `told` names their conversation. That `bash` answer told
161
+ the Agent that all its processes died, so it covers every kill up to it. They also take no kill from before the Turn
162
+ started. Such a kill would tell the Agent that processes it started in this Turn died. The Turn's first read of the
163
+ list sets its mark: the id of the newest kill it saw, or "none". The mark is ledgered with every admission of the
164
+ Turn, and a restore reads it back, so a nudge or a wake takes only kills after it. Only when the first read failed is
165
+ the Turn's start the mark, ledgered the same way, with one second allowed for a pod clock that runs behind. A read
166
+ that failed is "could not tell", never an empty list, so it sets no mark. The kills they do not take stay pending for
167
+ the next Turn. The re-admission after a Harness restart cut the Turn continues the same Turn, so it takes every notice
168
+ in scope except the guard kills that this rule leaves out. When its read could not tell, it takes no guard kill at
169
+ all, as a nudge then takes none: it cannot know which kills its own `bash` reported. The notices that the Turn's
170
+ admissions carried since the Harness last read the Turn's surface are pending again first, each with the source it was
171
+ raised with, and this re-admission carries them whatever else its scope leaves out. A notice with no source merges
172
+ only into a pending one with no source, so an equal notice of another kill never takes its place. When such an unread
173
+ admission carried the Turn's task, the task follows the re-admit text. When the rebuilt Harness lost the conversation,
174
+ nothing of the Turn is left: every notice that the Turn's admissions carried is pending again whatever the surface
175
+ reads said, the re-admission takes every notice in scope, as a Turn's admission does, and the Turn's task follows the
176
+ re-admit text. It says once that the conversation is new. A restore drops the prompt from a Turn's input, so the task
177
+ rides the ledgered admission too; a lost conversation with no task to send faults the Turn instead. Whether the
178
+ Harness still holds the conversation is asked again for up to 30 seconds while the new container does not listen yet
179
+ or drains, and only then is the answer taken as "held". The continue path asks the same way, so a continued Turn after
180
+ a restart can wait up to 30 seconds before its admission. A Harness in CrashLoopBackOff can take longer than that: the
181
+ answer is then "held", and it can be wrong. So every admission to a conversation that the Harness was taken to keep
182
+ says so with `kept: true`: the re-admission after a cut, a continued Turn, and a nudge or a wake. A Harness that keeps
183
+ no such conversation refuses it with 404 and `kept: false` in the body, and seats none. The Orchestrator takes only
184
+ that 404 as the refusal; another 404 is a failed admission. Without the refusal the Harness would seat a new
185
+ conversation, and a model on an empty context would read "continue your task" with no task. On the refusal the Turn
186
+ takes the path of a lost conversation. The re-admission is admitted as a Turn, with the task after the re-admit text
187
+ and the notice that the conversation is new, or faults when it has no task to send. A continued Turn is told that its
188
+ conversation is new, and is admitted again. A nudge or a wake is admitted again as a Turn: its prompt is the task
189
+ alone, because nothing was cut, with the notice that the conversation is new and every notice in scope. Every notice
190
+ that the Turn's admissions carried is pending again first, as for a lost conversation after a cut. With no task to
191
+ send, the Turn faults. The Instance Harness's restart is on no watch, so this refusal is the only way a nudge or a
192
+ wake learns of it. What the Turn's admissions carried, and whether the Harness read their surface, is kept in the
193
+ Orchestrator's memory alone: after a restore of the Orchestrator before a surface read, nothing is raised again. A
194
+ fresh Turn's "this conversation is new", which rides an admission alone, is carried again by the next re-admission
195
+ when the Harness has not read the one before.
161
196
 
162
- A continued Turn of the same conversation is told, as a notice, a kill that its own `bash` call answered in an earlier
163
- Turn. The Agent reads that kill twice, which is true and costs little. The `told` rule could later extend to the first
164
- admission of a continued Turn too; it does not now, because a fresh Turn's conversation holds no earlier answer, and
165
- the rule would then need the conversation in the ledger's scope.
197
+ A conversation whose own `bash` call answered a kill is not in that kill's audience, so it does not hear the kill
198
+ again as a notice, at its next Turn or at a step boundary.
166
199
 
200
+ - **One ledger carries notices and Steering messages.** A Steering message ([ADR-0002](0002-duplex-streaming-actor.md))
201
+ is not a notice: a notice is a fact that jr2 knows and that has no author, and a Steering message is an utterance with
202
+ an author, which the model weighs by who wrote it. So the two are siblings on one transport. Each entry of the ledger
203
+ is an **envelope** and a **body**. The envelope holds what the ledger delivers by: the address, the source, the
204
+ audience, and what was reserved and heard. The body is a Notice, or a Steering message with its text and its author:
205
+ `{ kind: "caller", via, name, onBehalfOf? }` or `{ kind: "machine", from? }`, where `from` is the Sender whose words
206
+ the Machine relays. The rules above, heard once, reserved at a step boundary and delivered when the append is
207
+ acknowledged, hold for both.
208
+ - **The address is a tagged value.** `{ conversation }` and `{ workspace }` are the two kinds that notices use, and
209
+ `{ slot }` (an Agent slot of one Machine instance of the run) is the kind that a Steering message uses. A new kind
210
+ is an addition, and no kind changes the meaning of another's fields.
211
+ - **A slot's audience is a future member.** A Workspace notice fixes its audience when the ledger raises it. A message
212
+ to a slot goes to the slot's open Turn, or, when there is none, to the slot's next Turn in the same Machine
213
+ instance, whose conversation does not exist yet at the raise. It retires, recorded "not delivered", when that
214
+ Machine instance ends.
215
+ - **The Harness frames a Steering message by its author.** A Caller's message is an instruction from outside the run,
216
+ and the frame names the Caller. A message that the Machine relays `from` another Agent is information from a peer.
217
+ The frame says that the workflow passes it on but did not write it, and a closing line after the text says that it
218
+ is not an instruction and does not change the Agent's task: the Agent does what the workflow asked. The closing line
219
+ is read last, after the peer's text (ADR-0029). In `peer-not-order`, a frame that said only "not an order" read 5 of
220
+ 10, because a model that obeyed read "the workflow relays" as the workflow's own word. A frame that said, ahead of
221
+ the text only, that it was "that Agent's word, not the workflow's", read 17 of 20, and the closing line read 20 of
222
+ 20 (ADR-0066). A message that the Machine relays `from` a Gate is what the Gate received, and the frame says that it
223
+ is not an order either. A Machine's message with no `from` is the workflow's own text. The text is quoted line by
224
+ line, so the model sees where it starts and ends, and it follows the notices in the Turn part, ahead of the Allowed
225
+ picks. A name in the frame (a Caller's name, its `onBehalfOf`, a peer's or a Gate's id) is a code span that its own
226
+ text cannot close, on one line: a name comes from outside jr2 and can hold a backtick or a line break. A message
227
+ whose author the Harness does not know is dropped, never framed as another author's.
228
+ - **The two bodies ride the wire apart.** The admit body carries `notices` and `steering`, and the surface read
229
+ `notices: [{ id, notice }]` and `steering: [{ id, message }]`; one acknowledgment,
230
+ `POST /agents/:iid/notices { heard }`, hears both by their ledger ids. A nudge and a wake take notices by their
231
+ sources, so they carry no message: the surface read that starts their Submission does.
167
232
  - **The Harness writes every word.** The admit body carries typed notices
168
233
  (`notices: [{ kind: "memory-limit", scope, agent?, limit?, peak? } | { kind: "conversation-new", reason } | { kind: "pick-answer", pick, cut, answer?, stopped? } …]`,
169
- where `peak` comes only with `limit`), and the Harness renders both parts. An outstanding pick's answer is data too —
170
- the last reply since the pick, a party's event with its payload or a child's output (ADR-0024) — and its words ("Its
171
- answer: `approve` from Gate `review`, with {…}", "No reply came back for it.") are the Harness's, in the receipt and
172
- in the notice alike; "it moved the workflow, and nothing ran" is a guard straight back's receipt alone. The standing
173
- part needs no wire: the Harness already holds its limits (cgroup and Downward API env), the definition's `workspace`
174
- access, and the Menu. The wording lives in one place, and Harness conformance checks it (ADR-0027).
234
+ where `peak` comes only with `limit`) and Steering messages with their authors, and the Harness renders both parts and
235
+ frames each message. The text of a Steering message is the author's; the frame around it is the Harness's. An
236
+ outstanding pick's answer is data too — the last reply since the pick, a party's event with its payload or a child's
237
+ output (ADR-0024) — and its words ("Its answer: `approve` from Gate `review`, with {…}", "No reply came back for it.")
238
+ are the Harness's, in the receipt and in the notice alike; "it moved the workflow, and nothing ran" is a guard
239
+ straight back's receipt alone. The standing part needs no wire: the Harness already holds its limits (cgroup and
240
+ Downward API env), the definition's `workspace` access, and the Menu. The wording lives in one place, and Harness
241
+ conformance checks it (ADR-0027).
175
242
 
176
243
  ## Considered options
177
244
 
@@ -197,6 +264,9 @@ whose processes are gone, with no way to know why.
197
264
  does not.
198
265
  - **Read the kill on the Sandbox watch.** Rejected: the operator publishes what the kubelet says about the container,
199
266
  and a guard kill does not end the container. The Harness has no Kubernetes credentials to publish it.
267
+ - **A Steering message as a kind of notice.** Rejected: a notice is a fact with no author, and the Harness words it. A
268
+ Steering message is an author's text, and the model must weigh a Caller's instruction and a peer's word differently.
269
+ Folded into a notice, the author and that difference have no place.
200
270
  - **The Harness words the kill into the next Turn itself.** Rejected: a second keeper of notices, outside the run's
201
271
  persisted state, which cannot know when an admission is ledgered or heard.
202
272
 
@@ -209,4 +279,8 @@ whose processes are gone, with no way to know why.
209
279
  - The Briefing is how a Turn learns its Allowed picks: the tools block is the whole Menu, fixed for a conversation so it
210
280
  does not break the cache
211
281
  ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)).
282
+ - A Workspace notice reaches each conversation that was there for the kill, and the retry of the Turn that it cut; an
283
+ open Turn hears it at its next step boundary, not only at its next admission (ADR-0002,
284
+ [ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). So a memory kill reaches
285
+ every coder of a Pool that shares one Workspace (ADR-0075).
212
286
  - GLOSSARY.md gains **Briefing**.
@@ -52,10 +52,9 @@ Decisions since then need pod facts at once: the scheduler's reason for a Pendin
52
52
  merge patch per Workspace every 5 minutes, ±20% jitter so Leases never renew in lockstep after a restart. It no longer
53
53
  carries detection.
54
54
  - **The attach is a Harness call, not an exec.** The Harness runs the attach — ACL stamp, `git clone --shared` from the
55
- node cache, remotes, `git worktree add` — on `POST /attach { slots, branch }`, authenticated like every Harness call
56
- (ADR-0058). No stream per Workspace crosses the API server, and **`pods/exec` leaves the Orchestrator's RBAC**. It
57
- also binds work late: a pod already running receives its Repos and branch, the shape standby Sandboxes and Agent
58
- Substrate need.
55
+ node cache, remotes — on `POST /attach { slots }`, authenticated like every Harness call (ADR-0058). No stream per
56
+ Workspace crosses the API server, and **`pods/exec` leaves the Orchestrator's RBAC**. It also binds work late: a pod
57
+ already running receives its Repos, the shape standby Sandboxes and Agent Substrate need.
59
58
 
60
59
  ## Considered options
61
60
 
@@ -70,8 +69,8 @@ Decisions since then need pod facts at once: the scheduler's reason for a Pendin
70
69
  - **A jr2 WebSocket API.** Nothing to add: a Kubernetes watch is already a pushed stream of events.
71
70
  - **Keep the attach as exec through the new client.** The exec protocol is the hardest part of any Kubernetes client, it
72
71
  keeps one stream per Workspace through the API server, and it keeps `pods/exec`.
73
- - **Attach from an init container.** Declarative, but it fixes the Repos and branch at pod creation, which rules out
74
- late binding, and it must wait on a cache that exists only after scheduling.
72
+ - **Attach from an init container.** Declarative, but it fixes the Repos at pod creation, which rules out late binding,
73
+ and it must wait on a cache that exists only after scheduling.
75
74
 
76
75
  ## Consequences
77
76
 
@@ -79,5 +78,6 @@ Decisions since then need pod facts at once: the scheduler's reason for a Pendin
79
78
  the watch and the Harness attach. ADR-0001: the operator publishes pod facts on the Sandbox's status, and writes
80
79
  status only on change — every write is now a watch event at the Orchestrator.
81
80
  - The Harness gains a route that is not about a conversation. It is jr2's own work in the container ADR-0005 gives it.
82
- - The CLI keeps `kubectl` on the user's machine (`jr2 up`, `jr2 ssh`, ADR-0009); only the Orchestrator stops using it.
81
+ - The CLI keeps `kubectl` on the user's machine (`jr2 up`, the `jr2 workspace` verbs, ADR-0009); only the Orchestrator
82
+ stops using it.
83
83
  - Tests fake the client with a small HTTP server instead of a `kubectl` stub.
@@ -37,8 +37,8 @@ Workspaces for the Instance (R4), and concurrent Turns per model provider (R5)?
37
37
  delete does not touch a name that a live Workspace of the run holds then: a parent that re-enters the state, or
38
38
  invokes the same Machine again, gives its new Workspace the same invoke id and so the same name, and that Workspace
39
39
  has placed it again ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)). A
40
- Sandbox that was placed and then faulted keeps ADR-0021's rule: it stays inspectable (logs, `jr2 ssh`) and ages out at
41
- the idle timeout.
40
+ Sandbox that was placed and then faulted keeps ADR-0021's rule: it stays inspectable (`kubectl logs`,
41
+ `jr2 workspace shell`) and ages out at the idle timeout.
42
42
  - **jr2 caps nothing of its own.** Not live Workspaces (R4), not concurrent Turns per provider (R5). See the options.
43
43
  - **A provider's refusal is named.** A Turn that ends because the Harness's provider retries ran out has a fault reason
44
44
  with the fixed prefix `provider limit`, as a memory kill has `memory limit` (ADR-0061), so `jr2 status` and the
@@ -0,0 +1,109 @@
1
+ # A persist survives a crash, and history is kept for a set time
2
+
3
+ Every transition of a run persists its whole snapshot (ADR-0007). The scaling review (R6) read the store and listed its
4
+ costs: `JSON.stringify` of the whole run, a synchronous upsert with an fsync on the event loop, a Pool's run as one blob
5
+ holding every worker, terminal rows never deleted, and `restore()` parsing every row before the server listens. It also
6
+ found the words "Postgres opt-in" in GLOSSARY.md, ADR-0008 and ADR-0009, with no such store in the code. Measured on the
7
+ home cluster's chaos Instance (2026-09-30, `local-path` volume): a persist costs **4.5 ms, whatever the blob's size** (1
8
+ KB or 100 KB), because the cost is the fsync, not the JSON; with the write-ahead log and `synchronous=NORMAL` the same
9
+ upserts cost 0.04 ms (1 KB) and 0.18 ms (100 KB). A run's blob is about 1 KB; a Pool of 40 workers is about 100 KB.
10
+
11
+ ## Decision
12
+
13
+ - **A persist survives a process crash, not a power loss.** The store runs sqlite in WAL mode with `synchronous=NORMAL`:
14
+ a write is durable once the OS holds it, and sqlite fsyncs only at a checkpoint. A node's power loss can lose the
15
+ transitions since the last checkpoint; the file stays consistent, and `restore()` treats the older snapshot as it
16
+ treats any restart — reconcile against the live world, re-attach, or `lost`. The window in which a crash re-POSTs a
17
+ prompt (ADR-0007's at-most-once sliver) widens from one microtask to one checkpoint, and only on a power loss, which
18
+ also ends every Sandbox on that node.
19
+ - **The write stays synchronous and on the event loop.** At 0.2 ms for a Pool's blob there is nothing to move off the
20
+ loop, and the ordering matters: the row is on disk before the Machine's next side effect runs, which is what lets an
21
+ epoch bump (ADR-0057), an admission (ADR-0016) or an Effect's intent row (ADR-0067) be saved "at the write" — the
22
+ intent row before its call leaves. A worker thread, an asynchronous driver or a network store would land the row after
23
+ the next `invoke` fires, on every persist.
24
+ - **One blob per run stays.** The snapshot is a tree that xstate restores from its root, and one row keeps it atomic —
25
+ with the ledgers beside it (admissions, epochs, notices, timers, outstanding picks, Effects); a Pool's cost at 40
26
+ workers is 0.2 ms, so its workers do not become rows of their own. The Effect ledger keeps a row for every Effect the
27
+ run started (ADR-0067), so a Machine that loops through an Effect grows its blob by one row per pass: id, kind and
28
+ outcome. The output's tail is dropped once the save that carries the Effect's transition is on disk; a push row keeps
29
+ its published commit, its source, its `squashed` and its sync's answer whole — every later push of its Repo Slot sends
30
+ them as one of its pairs — and its bundle only while it is its Repo Slot's newest push and its sync did not finish —
31
+ one bundle per Repo Slot at most. An outstanding pick's hold is the one place tails accumulate: its `chain` keeps
32
+ every reply since the pick whole until the pick is answered, then it is dropped (ADR-0067).
33
+ - **`restore()` reads live rows only.** The store answers `WHERE status = 'live'`; terminal rows are not parsed at boot.
34
+ - **A finished run's row lives for `runs.retain`, 30 days by default.** `runs: { retain: "30d", volume: "5Gi" }` in
35
+ `jr2.config.ts` sets it. `retain` is a duration of at least `1h`, with no upper limit, and the config load refuses a
36
+ shorter one. It covers what jr2 keeps of an ended run: its row, its kept conversations
37
+ ([ADR-0023](0023-the-harness-prints-the-conversation.md)) and the hook log (below). Every non-live status — `done`,
38
+ `failed`, `error`, `cancelled`, `lost`, `drifted` — ages from its terminal write, and a sweep at boot and every hour
39
+ deletes what is older, with the run's conversations. With no current Assembly (the Kit bump window,
40
+ [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)), no `runs.retain` is
41
+ bound, so this sweep waits for the first publish; the collection of Assemblies still runs. A `live` row is never
42
+ swept: a run parked on a Gate for a month stays and is restored. Nor is the run that the head of a standing Workflow
43
+ names ([ADR-0017](0017-pool-over-a-source-port.md)): a paused Pool keeps its held items in that run's blob for as long
44
+ as it is paused. The setting exists because a history surface uses it: `GET /runs` lists ended runs,
45
+ `jr2 runs --status` and the Console show them ([ADR-0009](0009-cli-and-instance-interface.md)), and `jr2 logs` reads
46
+ an ended run's conversations. jr2 is still not the record of a run's results (a Machine sends its output where it
47
+ belongs — a PR, a Source); the history is for a person who reads what a run did and why. A swept id frees its
48
+ abbreviations (ADR-0009).
49
+ - **The state volume has a size, and a guard keeps it from filling.** `runs.volume` is the size of the Orchestrator's
50
+ state volume claim, `5Gi` by default. It holds `state.db`, `conversations.db`, the Assemblies and the Charts. A claim
51
+ grows only where its StorageClass allows expansion, and Kubernetes never makes one smaller. So `jr2 up` refuses a
52
+ `runs.volume` that the config sets and that is smaller than the claim, or larger on a StorageClass that does not allow
53
+ expansion (kind's local-path class does not), and names the class; the claim keeps its size. When the config does not
54
+ set `runs.volume` and a claim exists, `jr2 up` keeps the claim's size, and prints one line that says so when that size
55
+ is not the default, so an Instance whose claim is older than the default is not refused for a value that no one wrote.
56
+ The guard measures the bytes of the files on the volume (both databases with their `-wal` files, the Assemblies and
57
+ the Charts) against the smaller of the claim's size and the size that `statfs` gives, because a hostPath class such as
58
+ kind's reports the node's disk and not the claim. The Orchestrator does not read the claim: `jr2 up` gives it the size
59
+ that it applied, the kept size of a claim that no config names too, in the Deployment's env. It looks at each sweep
60
+ and every minute. When the volume is more than 80% full by that measure, the sweep deletes the kept conversations of
61
+ ended runs, the oldest run first, until the volume is below 80% again. The run rows stay, and each run whose
62
+ conversations went is marked `conversation swept: no space`, which `jr2 logs` and the Console show in place of the
63
+ conversation. A conversation of a live run is never swept for space. If the volume is still more than 95% full when no
64
+ conversation of an ended run is left, the Orchestrator keeps no more entries and marks each open conversation cut, as
65
+ at its cap ([ADR-0023](0023-the-harness-prints-the-conversation.md)), so the conversations never fill the volume.
66
+ - **The hook log keeps each Hook's delivery for `runs.retain`**, as a row of its own: the Hook, the delivery id, the
67
+ body's digest and the outcome ([ADR-0011](0011-workflow-defined-events.md)). The repeat check reads it, so a repeat is
68
+ found while the row is kept; a body caught and sent again with a new id after that passes the check, and a handler
69
+ that compares the commit limits what it can do. A `slack` or `discord` delivery carries a signed timestamp, and jr2
70
+ refuses one more than 300 seconds from its clock, so that replay fails for them
71
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A run's blob keeps its
72
+ own `(delivery, gate)` pairs. A standing Pool's run is handed over before its Effect ledger makes its blob large:
73
+ after 500 dispatches or one day ([ADR-0017](0017-pool-over-a-source-port.md)).
74
+ - **The store is sqlite on the Instance's volume. No Postgres is owed.** The `SnapshotStore` interface stays as the
75
+ seam, with one implementation. `DATABASE_URL` is not a thing jr2 reads.
76
+
77
+ ## Considered options
78
+
79
+ - **Keep `synchronous=FULL`, move the write to a worker thread.** Rejected: the thread hides 4.5 ms from the loop but
80
+ does not make the disk faster, so writes queue under load and the loop no longer knows how far behind the store is —
81
+ and it opens the ordering gap on every write, which NORMAL opens only on a power loss.
82
+ - **Batch persists in a window.** Rejected: one persist per macrostep already coalesces a transition's events, and with
83
+ the fsync gone there is nothing left to amortize.
84
+ - **A row per worker.** Rejected: it splits an atomic tree across rows (a worker's row saved, the Pool's not), makes
85
+ restore reassemble what xstate restores whole, and buys nothing measurable at 0.2 ms per blob.
86
+ - **A fixed retention, like the Sandbox idle timeout.** Rejected: a list of ended runs and their kept conversations are
87
+ a history surface, and how long a team reads its history is the team's decision, not jr2's.
88
+ - **A count cap per workflow.** Rejected: a count cap does not bound a busy workflow's disk and punishes a quiet one.
89
+ The space guard bounds the disk, and it deletes only the conversations, which are the largest part; run rows stay.
90
+ - **An upper limit on `retain`.** Rejected: how long a team reads its history is the team's decision, and the space
91
+ guard bounds the largest part, the conversations. What `retain` leaves to grow is the run rows and the hook log, which
92
+ grow with the count of runs and deliveries; a team that keeps a long history of a busy Instance sets a larger
93
+ `runs.volume`. A limit would refuse that team and bound nothing that the volume's size does not.
94
+ - **Build the Postgres store.** Rejected: the only thing it buys is an Orchestrator pod that reschedules without its
95
+ volume, which is a storage-class question (a network RWO volume follows the pod) and belongs to the Orchestrator pod's
96
+ own decision (R10), not to the store. Its cost is a dependency the owner operates, a second store in every test tier,
97
+ and the asynchronous write above.
98
+
99
+ ## Consequences
100
+
101
+ - ADR-0008 and ADR-0009 lose "Postgres opt-in" and `DATABASE_URL`; ADR-0008's Orchestrator persists its snapshots "to
102
+ sqlite on its volume". ADR-0007 points here for the store's durability.
103
+ - The Instance's volume gains `state.db-wal` and `state.db-shm` beside `state.db`, and `conversations.db` with its own
104
+ `-wal` and `-shm` (ADR-0023).
105
+ - A `drifted` run (ADR-0030) is swept `runs.retain` after the deploy that changed its Machine, not after it ended; that
106
+ is the time a human has to read it.
107
+ - An idempotency key of a Pool item or a `dispatch` lives as long as its run's row
108
+ ([ADR-0017](0017-pool-over-a-source-port.md), [ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), so
109
+ `runs.retain` is also how long a repeated key finds its run.