@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
@@ -6,14 +6,21 @@ A Sandbox pod composes up to four containers around one shared worktree volume (
6
6
  [ADR-0037](0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md)) with jr2's runtime mounted at
7
7
  `/opt/jr2` and the command overridden to start the Harness. The Agent's toolchain lives here because the Working tools
8
8
  (read/write/edit/bash) execute here — the tools the Agent can reach are this image's. It is also where a human
9
- `kubectl exec`s to inspect a run with the agent's exact tools, worktrees, and filesystem.
9
+ inspects a run with the agent's exact tools, worktrees, and filesystem: `jr2 workspace shell` starts its shell through
10
+ the spawner, as an Agent's `bash` starts ([ADR-0009](0009-cli-and-instance-interface.md)), so the shell does not reach
11
+ the control Stand-in. One Harness container serves every workspace-ful Agent of the Sandbox. Each process that the
12
+ Harness starts in `/work` — a Working tool's shell, a test, git, a `command` Effect — runs as a confined child of the
13
+ Harness process, so no code from `/work` reaches the control credential that the Harness process holds
14
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)).
10
15
  - **Custodian container** — jr2-composed sidecar, upstream Envoy configured at provision by the Orchestrator
11
16
  ([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md),
12
17
  [ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): the pod's credential holder. It holds
13
18
  the Sandbox token and the held secrets its pod names — model API keys above all — as files, and carries the Harness's
14
19
  traffic out: its Menu and the ask to the Orchestrator, and its outbound HTTPS. It exists as a separate container
15
20
  precisely _because_ the Working tools give the Agent code execution in the Harness container — the credentials live
16
- where the Agent cannot read them, and the Harness container's env holds only their Stand-ins.
21
+ where the Agent cannot read them, and the Harness container holds only their Stand-ins, which are secret: only the
22
+ Harness process holds them, as files, and a process it confines gets only those that its `workspace()` grants
23
+ (ADR-0059, ADR-0076).
17
24
  - **User Container** — opt-in third seat, composed only when the `workspace()` names its image (a `file:` docker context
18
25
  the Machine ships or a registry ref, the same resolution as the Sandbox Image, no default —
19
26
  [ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)). The authoring surface is that string —
@@ -22,10 +29,11 @@ A Sandbox pod composes up to four containers around one shared worktree volume (
22
29
  Size ([ADR-0060](0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md)): a budget is the pod's business, not
23
30
  the process's, so it puts nothing in the container. Env and ports are not forwarded, because every key jr2 forwarded
24
31
  into the process would be a crack in "jr2 puts nothing in it"; a routable port (the follow-up below) is the known
25
- candidate for the next key. In the pod it is the container named `user` (`kubectl exec -c user`). It runs its **own
26
- entrypoint, untouched**: jr2 injects nothing into its process — no env, no command, no probe — and overrides nothing;
27
- it mounts only what the checkouts need to be checkouts (below). The zero-contract seat, which is exactly why it
28
- exists. Two jobs no other seat can do:
32
+ candidate for the next key. In the pod it is the container named `user`: `jr2 workspace shell <ws> --user` is
33
+ `kubectl exec -c user`, and `jr2 workspace forward <ws> <port> [--stdio]` reaches its sshd or any of its listeners by
34
+ port-forward ([ADR-0009](0009-cli-and-instance-interface.md)). It runs its **own entrypoint, untouched**: jr2 injects
35
+ nothing into its process — no env, no command, no probe — and overrides nothing; it mounts only what the checkouts
36
+ need to be checkouts (below). The zero-contract seat, which is exactly why it exists. Two jobs no other seat can do:
29
37
  - **Unattended services.** A container's one command belongs to the Harness in the primary seat (ADR-0037), so an
30
38
  image's own services — an sshd for managed access, an IDE server, a metrics agent — need a container whose command
31
39
  jr2 deliberately does not own. A system built _on_ jr2 that hands people access to their Sandboxes automates through
@@ -63,8 +71,8 @@ What each container mounts of the credentials, stated once:
63
71
 
64
72
  | Container | Mounts |
65
73
  | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
- | `harness` | `jr2-ca` at `/etc/jr2/ca` (the trust bundles). None of the Custodian's volumes. |
67
- | `custodian` | its values (the Sandbox token, its pod's `jr2-held-secrets-<pod>`, each user `secretKeyRef` its pod names) at `/etc/jr2/custodian/values`; its leaves (`jr2-held-tls`); its pod's config (its own `<sandbox>-custodian`, and `program.lua` from `jr2-held`); `upstream.crt`. |
74
+ | `harness` | `jr2-ca` at `/etc/jr2/ca` (the trust bundles); the Stand-ins — the control one and each held secret's — as files under `/run/jr2/` that only the Harness process reads and no process it confines can (ADR-0059, ADR-0076); no Stand-in is in its env. None of the Custodian's volumes. |
75
+ | `custodian` | its values (the Sandbox's control token and fetch token, its pod's `jr2-held-secrets-<pod>`, each user `secretKeyRef` its pod names) at `/etc/jr2/custodian/values`; its leaves (`jr2-held-tls`); its pod's config (its own `<sandbox>-custodian`, and `program.lua` from `jr2-held`); `upstream.crt`. |
68
76
  | `user` | the checkouts' three volumes, and nothing else. |
69
77
  | `effector` | `/work`, `/repos/<key>` and `/opt/jr2` read-only; at `/run/jr2/secrets` (0400) the secrets its Effects name — its pod's `jr2-effector-<pod>` literals, each user `secretKeyRef` by reference — and at `@git/<name>/` each `git.credentials` entry's push Secret the Sandbox CR names (`gitCredentials`), which the operator projects from that entry's Secret by reference into this container alone (`@` is no character of a secret's name, so no secret's file takes that directory); at `/run/jr2/effector` its key (its own `<sandbox>-effector`) and `effector.json`; an emptyDir at `/run/jr2/scratch` as `$HOME`; at `/etc/jr2/effector-ca`, read-only, the instance's caBundle ConfigMap when the instance ships one (ADR-0020). |
70
78
 
@@ -98,12 +106,13 @@ jr2 sets `runAsUser` nowhere — each image's own `USER` decides its seat's uid
98
106
  and plain POSIX modes would then make the other seat's files read-only. jr2 closes that with two mechanisms it happens
99
107
  to own, unconditionally (both are inert when the uids already match):
100
108
 
101
- - **The pod carries `fsGroup` — the work group, default `2000`.** The default is convention; the one override lives on
102
- the `WorkspaceSpec` (`workGroup?: number`, beside `image` and `user` — pod composition is the spec's business,
103
- ADR-0037), for the image whose session users already hold a gid of their own. Never a config key. Any value is safe
104
- for the jr2 seats: Kubernetes grants the fsGroup as a supplemental group to every container process, so the Harness
105
- writes `/work` whatever the number, and no image's `/etc/group` needs to know it. fsGroup puts a setgid group on
106
- `/work`'s root, and setgid propagates down, so everything either seat creates is group-_owned_ by the work group.
109
+ - **The pod carries `fsGroup` — the work group, default `2000`.** The default is convention; the one override is an
110
+ option of `workspace()` (`workspace(body, { workGroup: 2000 })`, beside `image` and `user` — pod composition is the
111
+ wrapper's business, ADR-0037), for the image whose session users already hold a gid of their own. Never a config key.
112
+ Any value is safe for the jr2 seats: Kubernetes grants the fsGroup as a supplemental group to every container process,
113
+ so the Harness writes `/work` whatever the number, and no image's `/etc/group` needs to know it. fsGroup puts a setgid
114
+ group on `/work`'s root, and setgid propagates down, so everything either seat creates is group-_owned_ by the work
115
+ group.
107
116
  - **The attach stamps a default ACL on every repo root it creates** — `u::rwx,g::rwx,o::r-x`, set by `work-acl`, a
108
117
  static helper on the runtime volume (part of ADR-0037's published `/opt/jr2` surface, static for the same reason `rg`
109
118
  is: it executes on a libc jr2 does not control). A default ACL is inherited by everything created beneath it, and
@@ -116,16 +125,16 @@ to own, unconditionally (both are inert when the uids already match):
116
125
  Group _membership_ is the one thing an image can still owe. `kubectl exec` sessions and a non-root sshd's logins hold
117
126
  the work group automatically — both inherit the container's supplemental groups, where Kubernetes put the fsGroup. A
118
127
  **root** sshd is the exception: the logins it setuids rebuild their groups from `/etc/group`, so that image puts its
119
- session user in the work group itself (`groupadd -g 2000 work && usermod -aG work <user>`) — or the spec points
128
+ session user in the work group itself (`groupadd -g 2000 work && usermod -aG work <user>`) — or the `workspace()` points
120
129
  `workGroup` at a gid its users already hold.
121
130
 
122
131
  **The promise is scoped to repo trees.** An ad-hoc path at `/work`'s top level — either seat's `mkdir /work/scratch` —
123
132
  carries no default ACL, so files there fall back to the writer's umask. The Harness runs at `umask 002` (itself and
124
133
  every tool child) and the attach script opens with the same line, so jr2's own writes are group-writable even there;
125
134
  what a User Container session writes outside a repo tree is its own business. A Workspace with no Repo Slot (ADR-0051)
126
- has no repo tree: its steps share `/work` itself, every Agent and `command` of it in the one Harness container as one
127
- uid, so they need no ACL; a User Container's writes there have this paragraph's limit, until a user of that seat needs
128
- more.
135
+ has no repo tree: its steps share `/work` itself, every Agent and `command` of it at the one uid in the one Harness
136
+ container, so they need no ACL; a User Container's writes there have this paragraph's limit, until a user of that seat
137
+ needs more.
129
138
 
130
139
  ## Constraints on the seat
131
140
 
@@ -134,8 +143,8 @@ The seat is zero-_contract_, not zero-_physics_. What an image brought here must
134
143
  - **The entrypoint must block.** The pod's restart policy restarts an exited container, so a `CMD` that finishes
135
144
  crash-loops. Run a long-lived process — an sshd, an IDE server, `sleep infinity` at minimum.
136
145
  - **The ports of jr2's seats are taken** — one network namespace: `:8080` (the Harness), the Custodian's
137
- `127.0.0.1:8081` (control), `127.0.0.1:15001` (egress) and `:15021` (health), and `:8082` (the Sandbox Effector, in a
138
- pod that has one). Everything else is the image's. A process that binds `:8082` while the Sandbox Effector is down
146
+ `127.0.0.1:8081` (the fetch), `127.0.0.1:15001` (egress) and `:15021` (health), and `:8082` (the Sandbox Effector, in
147
+ a pod that has one). Everything else is the image's. A process that binds `:8082` while the Sandbox Effector is down
139
148
  gets signed requests it can neither forge nor answer (ADR-0068): the Effect fails closed.
140
149
  - **Root is allowed.** The hardened security context (non-root, all capabilities dropped, no escalation) applies to the
141
150
  seats jr2 owns — Harness, Custodian and Sandbox Effector, per container — and deliberately not here: the seat's
@@ -162,7 +171,10 @@ namespace, node, and the worktree volume, and are one trust domain in every isol
162
171
  container edge does provide is **credential visibility** — the asymmetry ADR-0013 builds on: the Agent executes code in
163
172
  the Harness container and none in the others, so a secret that exists only in another container's mount namespace is out
164
173
  of its reach. That is why the Custodian holds the Orchestrator credential and the model keys, and it is what the User
165
- Container offers a human session — with its edges stated plainly:
174
+ Container offers a human session. Inside the Harness container, a second edge holds the one credential the Harness keeps
175
+ itself, the control Stand-in: the Harness process is jr2's code, and each process it starts in `/work` is confined from
176
+ that Stand-in by Landlock (ADR-0076, [ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)). The edges, stated
177
+ plainly:
166
178
 
167
179
  - It holds only for what stays in the private filesystem or session. Anything on `/work` or listening on `localhost` is
168
180
  shared with the Agent — the pod has one network namespace. The Custodian's listeners are on `localhost` too; what they
@@ -206,8 +218,8 @@ Container offers a human session — with its edges stated plainly:
206
218
 
207
219
  - The generic `Sandbox` CRD composes all of this as plain container specs
208
220
  ([ADR-0001](0001-operator-owned-generic-sandbox.md) holds — the operator stays agent-agnostic); the Orchestrator's
209
- `kubectlSandbox` supplies the images. The pod runs two containers, plus `user` when the spec names one and `effector`
210
- when an Effect is placed there.
221
+ `kubectlSandbox` supplies the images. The pod runs two containers, plus `user` when the `workspace()` names one and
222
+ `effector` when an Effect is placed there.
211
223
  - Every container but the Sandbox Effector carries a limit from the Workspace's Size (ADR-0060): the Custodian a fixed
212
224
  kit slice, the User Container its split if stated, the Harness the rest. The Sandbox Effector carries no limit of its
213
225
  own: like a User Container with no split, it shares the pod's Size ceiling — the whole Size, as pod-level resources —
@@ -225,6 +237,8 @@ Container offers a human session — with its edges stated plainly:
225
237
  - The Harness container's `/work` writes being group-writable widens nothing: every process that could abuse group
226
238
  access already runs in the same trust domain, and the work group exists only inside this pod.
227
239
  - **Routable access is a follow-up, deliberately untaken**: the operator's Service serves the Harness at `:8080`, and a
228
- managed system wanting cluster-routed ssh to the User Container needs a declared port there. `kubectl port-forward`
229
- reaches any listener in the pod without one, which covers the human case day one.
240
+ managed system wanting cluster-routed ssh to the User Container needs a declared port there. A person reaches the User
241
+ Container through the kube API: `jr2 workspace shell --user` by exec, and `jr2 workspace forward` by port-forward,
242
+ which reaches any listener in the pod with no Service. `forward --stdio` is an ssh `ProxyCommand`, so
243
+ `ssh <sandbox>.jr2` with agent forwarding reaches the User Container's sshd (ADR-0009).
230
244
  - **GLOSSARY.md**: **User Container** is a glossary term.
@@ -42,6 +42,9 @@ is the only structured channel. Consequences:
42
42
 
43
43
  ## Residual
44
44
 
45
- Unsolicited mid-turn steering remains the hard case (ADR-0002): the menu only opens when the Agent reaches a checkpoint
46
- or a turn boundary. Evidence for the findings above: `poc/actor/POC-5B-FINDINGS.md`, `poc/actor/POC-5C-FINDINGS.md`
47
- (`poc/` is gitignored; conclusions live here).
45
+ jr2's own typed notices now reach a running Agent through the step-boundary read (ADR-0002,
46
+ [ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)); the Menu still opens only when the Agent
47
+ reaches a checkpoint or a turn boundary. Free text from a Caller, or from the Machine, is a Steering message on the same
48
+ channel (ADR-0002, [ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)): the Agent
49
+ hears it once, and it is never a pick or a workflow event. Evidence for the findings above:
50
+ `poc/actor/POC-5B-FINDINGS.md`, `poc/actor/POC-5C-FINDINGS.md` (`poc/` is gitignored; conclusions live here).
@@ -15,7 +15,7 @@ it from the start (the top Machine keeps only counts + ids, children report comp
15
15
  snapshot/restore possible.
16
16
 
17
17
  The store's own durability — what a persist survives, and how long a finished run's row lives — is
18
- [ADR-0065](0065-a-persist-survives-a-crash-and-history-is-a-week.md).
18
+ [ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md).
19
19
 
20
20
  Durable **agent handles** do not ride Machine context at all: the host persists an `iid → admission` ledger beside the
21
21
  snapshot in the same save (ADR-0016). Iids are globally unique, so restore needs no context-tree walking to find them.
@@ -63,12 +63,23 @@ export default defineConfig({
63
63
  ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)): the credentials the Agents use — an
64
64
  Anthropic key, read from `process.env`/`.env`, held by the Custodian and never by the Harness (ADR-0059) — and the
65
65
  ones an Effect names, which only its Effector holds. Where each goes is read, never declared.
66
+ - **`mcp` is the Instance's MCP servers**
67
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)): each
68
+ by a name that the author chooses, remote (`url`, an optional `secret`) or stdio (`command`, `args`), with a
69
+ `timeout`. An Agent and an `mcp` Effect name a server by that name.
70
+ - **`callers`, `hooks` and `cors`** are the Instance's reach for Callers
71
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)): the named Caller tokens
72
+ and the JWT issuers with their scopes, the Hooks with their credentials and the Hook kinds that the config imports,
73
+ and the origins a browser app may call from.
66
74
  - **`registry`** (deployment-varying, resolve from env): absent → images are `kind load`-ed; present → pushed
67
75
  (ADR-0019). **`kitRegistry`** (also env) re-homes the published Kit image refs for self-hosted, air-gapped, or
68
76
  mirror-only clusters ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)).
69
77
  - **The snapshot store is sqlite** on a PVC in the instance's namespace (zero setup); no other store is owed
70
- ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-a-week.md)) — the single-table, single-writer snapshot fits
71
- sqlite, and `replicas: 1` keeps it single-writer.
78
+ ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)) — the single-table, single-writer
79
+ snapshot fits sqlite, and `replicas: 1` keeps it single-writer. The kept conversations are a second sqlite file on the
80
+ same volume ([ADR-0023](0023-the-harness-prints-the-conversation.md)).
81
+ - **`runs`** sets how long jr2 keeps an ended run and the size of the state volume, `{ retain: "30d", volume: "5Gi" }`
82
+ by default (ADR-0065).
72
83
  - Git credentials for private repos: an HTTPS token from `.env`, or a `jr2-git-ssh` Secret holding the key `jr2 up`
73
84
  asked the user to choose — a generated in-cluster deploy keypair (the recommended default), a local key, or one pasted
74
85
  on stdin (ADR-0019, [ADR-0047](0047-the-git-ssh-key-source-is-the-users-choice.md)). Kube target: the current
@@ -79,28 +90,42 @@ export default defineConfig({
79
90
  The kit publishes to npm under `@jr2/*`; stock Agent definitions ship in **`@jr2/agents`** (coder, reviewer, …), and a
80
91
  package may ship whole Machines with their Agents and image inside (ADR-0049). Because a definition is plain data
81
92
  (ADR-0018), composition needs no API: a Machine carries `agent(coder)`, a workflows file exports a packaged Machine
82
- as-is or through `customize(machine, { agents: { coder: { model: "…" } } })`. Adding tools/skills waits on the
83
- definition contract growing that seat (ADR-0018).
93
+ as-is or through `customize(machine, { agents: { coder: { model: "…" } } })`. An Agent's tools beyond its own come from
94
+ the MCP servers that it names, and the same `customize()` rebinds them
95
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). Adding
96
+ skills waits on the definition contract growing that seat (ADR-0018).
84
97
 
85
98
  ## Orchestrator HTTP API (hono, run-addressed-by-id)
86
99
 
87
100
  Push + control + observe only — the **pull** path needs no HTTP (the Orchestrator's Source pulls, ADR-0017). Auth bands
88
101
  per ADR-0014: structure + observation open; the Agent surface takes the Sandbox token; run state, control, and gates
89
- take the Instance token.
102
+ take a Caller with the scope in brackets (ADR-0078); `[instance]` is the Instance token alone.
90
103
 
91
104
  ```
92
105
  GET /workflows # list registered workflows [open]
93
106
  GET /workflows/:name/machine /viz/* # machine structure + visualizer [open]
94
107
  GET /workflows/:name/runs[, /:runId/events] # observation projection (ADR-0014) [open]
108
+ GET /workflows/:name/runs/:runId/machine # the run's own Machine, Charts compiled (ADR-0069) [open]
95
109
  GET /workflows/:name/events # SSE: the whole workflow, level-triggered (ADR-0022) [open]
96
- POST /workflows/:name/runs # start a run (push work); body = input → { runId } [instance]
97
- GET /runs GET /runs/:runId # live list; durable run status (read-through) [instance]
98
- GET /runs/resolve?prefix=<p> # run ids sharing a prefix, live + settled [instance]
99
- GET /runs/:runId/events # SSE: status replay + live deltas [instance]
100
- POST /runs/:runId/events # run-level infra interrupt: CANCEL [instance]
101
- POST /runs/:runId/gates/:gate/events # deliver a workflow event to an open gate [instance]
110
+ POST /workflows/:name/runs # start a run (push work); body = input → { runId } [start]
111
+ # a standing Workflow: resume a paused one [?drop-held=true] (ADR-0017)
112
+ GET /runs # run summaries; ?status ?workflow ?since ?cursor [read]
113
+ GET /runs/:runId # durable run status (read-through) [read]
114
+ GET /runs/resolve?prefix=<p> # run ids sharing a prefix, live + settled [any scope]
115
+ GET /runs/:runId/events # SSE: status replay + live deltas [read]
116
+ GET /runs/:runId/conversations # the run's kept conversations (ADR-0023) [read]
117
+ GET /runs/:runId/conversations/:iid[?after=<seq>] /events # entries; SSE follow [read]
118
+ GET /gates # open Gates the Caller may answer; ?workflow [answer]
119
+ POST /runs/:runId/events # run-level infra interrupt: CANCEL [cancel]
120
+ POST /runs/:runId/gates/:gate/events # deliver a workflow event to an open gate [answer]
121
+ POST /runs/:runId/steering # a Steering message to an Agent slot (ADR-0002) [steer]
122
+ GET /runs/:runId/agents # the slot list, one row per Agent slot [steer]
123
+ GET /whoami # the request's Caller record (ADR-0078) [any Caller]
124
+ POST /hooks/:name # a Hook's delivery (ADR-0011) [the Hook's verifier]
125
+ GET /hooks/:name # the Hook's log, newest first (ADR-0011) [instance]
102
126
  GET /repos # per-Repo node-cache sync state (ADR-0048) [instance]
103
127
  GET /agents/:iid/surface POST /agents/:iid/events # the Menu, via the Custodian [sandbox]
128
+ POST /agents/:iid/conversation # a batch of the record's entries (ADR-0023) [sandbox]
104
129
  POST /sandboxes/:name/fetch # the ask (ADR-0053) [sandbox]
105
130
  GET /healthz GET /readyz
106
131
  ```
@@ -112,11 +137,11 @@ ADR-0047's "register the key, the cache agent retries" points at. Instance band,
112
137
  carries git's error text, exactly the class of author/deployment detail the open projections strip (ADR-0014).
113
138
 
114
139
  Gates are the human/webhook/CI seam (ADR-0011): a gated state registers `{ gate, accepts, meta }`; `GET /runs/:runId`
115
- lists the open gates (with schemas + `meta`), and the gate POST validates the body against the named event schema and
116
- delivers it into that state. Per-gate addressing exists because concurrent children park concurrently — a run-level
117
- events POST is ambiguous. `CANCEL` is the one reserved run-level event; everything else is workflow vocabulary. It
118
- **ends** the run — its Agents' turns end with it and it does not restore
119
- ([ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)).
140
+ lists the open gates (with schemas + `meta`), `GET /gates` lists the open Gates of every live run that the Caller may
141
+ answer, and the gate POST validates the body against the named event schema and delivers it into that state. Per-gate
142
+ addressing exists because concurrent children park concurrently — a run-level events POST is ambiguous. `CANCEL` is the
143
+ one reserved run-level event; everything else is workflow vocabulary. It **ends** the run — its Agents' turns end with
144
+ it and it does not restore ([ADR-0025](0025-cancel-ends-a-run-stop-parks-it.md)).
120
145
 
121
146
  ## The `jr2` CLI
122
147
 
@@ -137,25 +162,78 @@ jr2 version [--local] [--json] # this jr2, the global that handed off, the I
137
162
 
138
163
  # runs / workflows (wrap the HTTP API)
139
164
  jr2 run <workflow> [--input <json>] [--detach]
140
- jr2 runs jr2 status [runId|abbrev] jr2 logs <runId|abbrev> [-f] # bare `status` = the instance's repos (ADR-0048)
165
+ jr2 run <workflow> [--drop-held] # resume a paused standing Pool (ADR-0017)
166
+ jr2 runs [--status <s>] [--workflow <w>] [--since <d>] # live runs by default; `--status ended|all|<list>`
167
+ jr2 workflows [--json] # the Workflows a run can start, with their origins and Charts (ADR-0070)
168
+ jr2 status [runId|abbrev] # bare `status` = the instance's repos (ADR-0048)
169
+ jr2 watch <runId|abbrev> [--json] # the run's status feed, followed to its end
170
+ jr2 logs <runId|abbrev> [agent] [-f] # the run's conversations, printed as ADR-0023 prints them
171
+ jr2 gates [--workflow <w>] # the open Gates this Caller may answer, oldest first
141
172
  jr2 send <runId|abbrev> --event CANCEL
142
173
  jr2 send <runId|abbrev> --gate <gate> --event <name> [--input <json>]
143
-
144
- # workspaces (kubectl-style, over the operator's Sandbox CRs) — decided, not yet in the binary
145
- jr2 ls # list workspaces + run + status + endpoint
146
- jr2 ssh <workspace> # exec into the Sandbox's harness container (ADR-0037)
147
- jr2 logs <workspace> jr2 rm <workspace>
174
+ jr2 steer <runId|abbrev> [<agent> "<text>"] # a Steering message; the slots alone (ADR-0002)
175
+
176
+ # Callers and Hooks (ADR-0078, ADR-0011)
177
+ jr2 whoami # the Caller this credential is
178
+ jr2 caller token <name> # print a named Caller token's value
179
+ jr2 caller rotate <name> # make a new value; the old one stops working
180
+ jr2 hooks <name> # the Hook's log of deliveries
181
+
182
+ # workspaces (kubectl-style, over the operator's Sandbox CRs; kube credentials only)
183
+ jr2 workspace ls [run] # the Workspaces, with run, path, status and age
184
+ jr2 workspace shell <ws> [--user] [-- <cmd>] # a shell in the Workspace, as its Agents work (--user: the User Container)
185
+ jr2 workspace forward <ws> <port> [--stdio] # a local port to a port in the pod (--stdio: an ssh ProxyCommand)
186
+ jr2 workspace rm <ws> # remove a Sandbox that no live run holds
187
+
188
+ # shell completion
189
+ jr2 completion bash|zsh|fish # the completion script, generated from the command table
148
190
  ```
149
191
 
150
- `jr2 ssh` is `kubectl exec -c harness` into the Sandbox Image
151
- ([ADR-0037](0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md)) — the inspect seat: the agent's own
152
- tools, worktrees, and filesystem. The optional User Container ([ADR-0005](0005-sandbox-pod-composition.md)) is reached
153
- by its own front door (its sshd, or `kubectl exec -c user`), not by this verb — its point is sessions and services the
154
- agent's container must not host.
192
+ **The workspace verbs read the cluster, not the Orchestrator.** They use the kube credentials of the current context and
193
+ no Caller scope, so they work when the Orchestrator is down, and kube RBAC is the gate. A Workspace's handle is
194
+ `<run-prefix>/<workspace path>`: a prefix of its run's id and its path in the run's Machine, read off the CR's label
195
+ `jr2.dev/run` and its annotation `jr2.dev/workspace-path` ([ADR-0012](0012-workspace-wrapper-machine.md)). The CLI lists
196
+ the instance's CRs and compares on the client, because a label selector cannot match a prefix. `workspace ls` prints the
197
+ exact path. The CR's name is also a handle, and a handle that ends in `.jr2` is the CR's name with that suffix, for the
198
+ ssh config below. A handle that matches more than one Workspace prints the candidates and fails, as a run prefix does.
199
+
200
+ - **`workspace shell`** asks the Harness to start a shell through its spawner, so the shell is a process of the Agents'
201
+ Landlock domain, with its env, the Workspace's grants and its cwd
202
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). It can signal and trace
203
+ the Agents' processes, as a later `bash` call can. So the shell has the agent's own tools, worktrees and filesystem
204
+ ([ADR-0037](0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md)), and it never reaches the control
205
+ Stand-in or any Stand-in that the Workspace does not grant. `-- <cmd>` runs one command in place of a shell. `--user`
206
+ is `kubectl exec -c user` into the optional User Container ([ADR-0005](0005-sandbox-pod-composition.md)), where jr2
207
+ starts nothing of its own.
208
+ - **`workspace forward`** is a port-forward to the Sandbox pod: a local port to `<port>` in the pod's network namespace,
209
+ which reaches a server the Agent started or the User Container's sshd. It prints `127.0.0.1:<local port>` and holds
210
+ the forward until Ctrl-C. With `--stdio` it carries the connection on its stdin and stdout, so it is an ssh
211
+ `ProxyCommand`: `Host *.jr2` with `ProxyCommand jr2 workspace forward %h 22 --stdio -n <namespace>`, where the host is
212
+ the CR's name with `.jr2` after it. ssh runs the command in any folder, and with `-n` the verb needs no instance
213
+ folder. There is no routable Service for the User Container; the forward reaches it through the kube API.
214
+ - **`workspace rm`** deletes a Sandbox that no live run holds: an ended run's Sandbox that waits for its idle timeout,
215
+ or one that a fault left as evidence (ADR-0021). It refuses the Sandbox of a live run and says "cancel the run first".
216
+ It tells from the cluster alone, with no Orchestrator, by the hold mark that the Lease writes on the CR (ADR-0021):
217
+ `jr2.dev/hold` is `held` from the Lease's first write, and the Lease sets it to `released` when it stops for the
218
+ body's final state, a fault, a cancel or a parent that leaves the Workspace's state, but not while a live Workspace of
219
+ the run holds the same name (a parent that invokes the same Machine again after a defect). A restore that ends a run
220
+ `drifted` or `lost` without hosting it marks each of its CRs `released`. A park and a process shutdown write nothing,
221
+ so the CR of a run that an Orchestrator crash left stays `held`. `rm` removes a CR that is `released`, and a CR whose
222
+ `jr2.dev/keepalive` is older than the CR's idle timeout, which the operator reaps then in any case. It refuses every
223
+ other CR, a CR with no mark included. It measures the keepalive's age on the API server's clock (the `Date` header of
224
+ its answer), not on the local clock, so a fast laptop clock cannot make a live Sandbox look free. There is no
225
+ `--force`: the run owns its Workspace's life (ADR-0012).
226
+
227
+ Pod logs stay `kubectl logs`. The conversations of a Workspace's Agents are `jr2 logs <run> [agent]`, which reads the
228
+ Orchestrator's record and works after the Sandbox is gone (ADR-0023).
229
+
230
+ **`jr2 completion`** prints the script for bash, zsh or fish, made from the command table, so a verb or a flag cannot
231
+ complete that the CLI does not take. It completes dynamic values too: Workspace handles (from the CR labels and the
232
+ `jr2.dev/workspace-path` annotation), live run ids, Workflow names, and the Agent slots of a run. A lookup that takes
233
+ more than 2 s offers nothing, so a slow cluster never holds the shell.
155
234
 
156
235
  The CLI is the everyday surface; HTTP is the machine-to-machine one. The workspace verbs make the orchestrator feel like
157
- `kubectl` for agents: the run↔workspace link rides on the CR's labels (`jr2.dev/run`, `jr2.dev/workflow`), so `jr2 ls`
158
- can group without the orchestrator being reachable. CLI and hono app sit on one shared API client.
236
+ `kubectl` for agents. CLI and hono app sit on one shared API client.
159
237
 
160
238
  A planned `jr2 build` (build + push + render manifests, no apply — the pure-GitOps CI verb) is deferred; `jr2 up` in CI
161
239
  covers the interim (ADR-0019).
@@ -165,9 +243,10 @@ covers the interim (ADR-0019).
165
243
  **Instance addressing.** The CLI finds its instance by walking up from cwd to the directory containing `jr2.config.ts` —
166
244
  the root marker. The _deployment_ is addressed by the current kube context + the instance's namespace: run-verbs
167
245
  port-forward the Orchestrator Service for the duration of the command and read the Instance token from its in-cluster
168
- Secret (kube RBAC is the gate). `--url` / `JR2_URL` (+ token env) overrides both — the ingress-exposed/remote-caller
169
- case — and skips the folder walk entirely. Every run-verb prints the context it targets on stderr, so ambient-context
170
- drift is visible (ADR-0019).
246
+ Secret (kube RBAC is the gate); its Caller is `token/instance`, on behalf of the kube user (ADR-0078). `--url` /
247
+ `JR2_URL` (+ `JR2_TOKEN`, any Caller's credential) overrides both — the ingress-exposed/remote-caller case — and skips
248
+ the folder walk entirely. Every run-verb prints the context it targets on stderr, so ambient-context drift is visible
249
+ (ADR-0019).
171
250
 
172
251
  **The command table.** Each verb is an entry in `packages/cli/src/command-table.ts`: its arguments, its flags, the env
173
252
  vars it reads, what it prints, and its ADRs, as data. The verb's `parseArgs` options come from its entry, and so does
@@ -178,12 +257,18 @@ usage line. The CLI Reference of the docs is generated from the same table
178
257
 
179
258
  **Two output classes (amended 2026-09-20).** The stream rule below — JSON result on stdout, activity on stderr — was
180
259
  written for `jr2 run`, whose reason is the pipe: `jr2 run … | jq` must yield the result alone. It generalises to every
181
- **result verb** — `run`, `runs`, `status <runId>`, `logs`, `kit push` — because their result is small and the stderr
182
- lines beside it are advisory. It does not generalise to a **report verb** — `version`, and `status` with no run id —
183
- whose output is a diagnosis a human reads whole: dual-printing there shows a JSON blob and a partial table, and the
184
- readable form exists nowhere. So a report verb prints its human table on **stdout**, and `--json` swaps the table for
185
- the one object. No `isatty` sniffing: output that changes with how it is invoked is a debugging trap of its own. `init`,
186
- `check`, `up`, `down`, `gc` and `send` are neither class — activity only, no result.
260
+ **result verb** — `run`, `runs`, `status <runId>`, `gates`, `kit push` — because their result is small and the stderr
261
+ lines beside it are advisory. It does not generalise to a **report verb** — `version`, `status` with no run id, `watch`,
262
+ `logs` and `workspace ls` — whose output a human reads whole: dual-printing there shows a JSON blob and a partial table,
263
+ and the readable form exists nowhere. So a report verb prints its human table or lines on **stdout**, and `--json` swaps
264
+ them for the JSON (for `watch`, the feed's JSON frames; `logs` has no `--json`). No `isatty` sniffing: output that
265
+ changes with how it is invoked is a debugging trap of its own. `init`, `check`, `up`, `down`, `gc`, `send`,
266
+ `caller rotate` and `workspace rm` are neither class — activity only, no result. `workspace shell` and
267
+ `workspace forward --stdio` give the terminal to the session or the connection. `whoami` is a result verb. A **value
268
+ verb** — `caller token`, `completion`, `workspace forward` (its local address) — prints one value alone on stdout, not
269
+ JSON, and activity on stderr, because its one use is `JR2_TOKEN=$(jr2 caller token ci)`, and a JSON string there needs
270
+ `jq -r` for nothing. `caller rotate` prints no value: the new one is a `caller token` away, and a value is printed only
271
+ when it is asked for.
187
272
 
188
273
  **`jr2 version` (amended 2026-09-20).** The report you paste into a bug: what is _here_ and what is _deployed_, side by
189
274
  side, so the gap between the halves is the answer ("you edited the pin and never ran `jr2 up`"). The local half: `cli`
@@ -225,13 +310,28 @@ orchestrator**, not a temporary per-invocation runtime — a jr2 run is durable
225
310
  outliving the CLI call. `--detach` starts the run, prints its `runId`, returns.
226
311
 
227
312
  **Attach / detach / re-attach.** A run lives server-side, so attaching = opening `GET /runs/:runId/events` and detaching
228
- = closing it (Ctrl-C); neither affects the run. `jr2 logs <runId> -f` re-attaches to any running run. On attach, the SSE
229
- **replays the run's current status immediately** (so a parked run shows where it is) before streaming live deltas; it
230
- carries `status` events (auto, per transition) and `emit` events (the workflow author's `emit({…})`).
313
+ = closing it (Ctrl-C); neither affects the run. `jr2 watch <runId>` re-attaches to any run: it prints the lines that
314
+ `jr2 run` prints while it is attached, follows the run to its end, and for an ended run prints its last status once. It
315
+ exits 0 when it followed the feed to its end, for a failed or a parked run too: its exit code is the watch's, and
316
+ `jr2 run`'s is the run's result. On attach, the SSE **replays the run's current status immediately** (so a parked run
317
+ shows where it is) before streaming live deltas; it carries `status` events (auto, per transition) and `emit` events
318
+ (the workflow author's `emit({…})`).
231
319
 
232
320
  **Terminal runs stay readable.** The final snapshot persists to the store before the run drops from the live registry,
233
321
  so `status` / `GET /runs/:runId` **read through to the store** — a completed run reports its terminal status/context
234
- instead of `404`. (`GET /runs` stays live-only; history via `?all` is deferred.)
322
+ instead of `404`.
323
+
324
+ **`GET /runs` lists history.** Its filters are `status` (`live` by default; `ended`, `all`, or a list of statuses),
325
+ `workflow` and `since` (a duration back from now, such as `7d`, or an ISO-8601 time). It lists the newest start first, a
326
+ page at a time: `limit` (50 by default, at most 500) and `cursor`, and the answer is `{ runs, next? }`. An item is a
327
+ summary, not the whole `RunStatus`:
328
+ `{ runId, workflow, status, reason?, startedBy?, startedAt, endedAt?, assembly?, openGates, placing?, dispatchedBy? }`,
329
+ where `placing` names the child paths of the Workspaces that wait for capacity
330
+ ([ADR-0064](0064-a-workspace-waits-for-capacity.md)) and `dispatchedBy` names the run that started this one
331
+ ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), because `jr2 runs` prints both. A `drifted` run is
332
+ listed, with its reason (ADR-0030). A `live` row whose restore a boot deferred or that failed is listed as `active`,
333
+ with the reason `waiting for restore`. A run is listed while its row is kept (`runs.retain`, ADR-0065). `jr2 runs` takes
334
+ the same filters as `--status`, `--workflow` and `--since`.
235
335
 
236
336
  **Run ids abbreviate to a unique prefix.** A run id is a bare uuid, so every id-taking verb takes a git-style short
237
337
  form: `jr2 status 1a2b3c4d`. **Prefix only** — not fuzzy, not suffix — which keeps it an indexed range scan and keeps
@@ -250,10 +350,10 @@ returns silently for an unknown run, so an unresolved CANCEL used to report succ
250
350
  The candidate set is the ids that **exist**, not the ones that read: `lost` runs are included, because dropping them
251
351
  would let a prefix they share with a live run resolve silently to the live one. The set unions the live registry with
252
352
  the store — neither alone is complete, since `persist()` is microtask-scheduled (a just-started run is live before it is
253
- stored) and a settled run is stored but not live. Note the deliberate tension with `GET /runs`'s deferred `?all`: this
254
- route does reveal that settled run **ids** exist to an Instance-token holder. It answers ids only — no status, no
255
- context, no gates — to keep that widening as small as the feature allows, and it is Instance-only rather than open,
256
- since prefix probing on an open route would be a run-id enumeration oracle.
353
+ stored) and a settled run is stored but not live. It answers ids only — no status, no context, no gates — to a Caller
354
+ with any scope for their Workflow, where `GET /runs` needs `read`, and it is guarded rather than open, since prefix
355
+ probing on an open route would be a run-id enumeration oracle. Any scope opens it, for the Workflows of that scope,
356
+ because a Caller that may only cancel or steer also addresses a run by a prefix.
257
357
 
258
358
  **`jr2 init` (v1).** Scaffolds the minimum runnable instance: `jr2.config.ts` (root marker), `package.json` (deps on
259
359
  `@jr2/*` + xstate), one starter `workflows/<name>.ts`, `images/default/Dockerfile` (the Sandbox Image every Workspace
@@ -266,10 +366,11 @@ lockfile is what picks one ([ADR-0043](0043-the-kit-is-tested-as-installed-a-loc
266
366
 
267
367
  ## Consequences
268
368
 
269
- - The instance-facing packages (`@jr2/cli`, `@jr2/orchestrator`, `@jr2/agent-protocol`) publish to npm; `@jr2/harness`
270
- ships inside a Kit image, never via npm
369
+ - The instance-facing packages (`@jr2/cli`, `@jr2/orchestrator`, `@jr2/agent-protocol`, `@jr2/machines`) publish to npm;
370
+ `@jr2/harness` ships inside a Kit image, never via npm
271
371
  ([ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md)). The CLI ships as the `jr2`
272
372
  bin (`npx jr2`).
273
- - Workspaces are a first-class CLI resource backed by the operator's `Sandbox` CRs, label-linked to their runs.
373
+ - Workspaces are a first-class CLI resource backed by the operator's `Sandbox` CRs, label-linked to their runs and their
374
+ paths, and reached with kube credentials alone.
274
375
  - Code is never loaded from a third party at run time (ADR-0008, ADR-0069): discovery is over the instance's own
275
376
  Assembly, which only `jr2 up` publishes. A run may bind a Machine's Open body with a Chart, which is data (ADR-0070).
@@ -81,14 +81,16 @@ Cucumber.js**, living in a top-level `./features/` workspace package (`@jr2/e2e`
81
81
  one is the tier watching the fix rather than merely enjoying it: the retry is absorbed by design, so without it a
82
82
  cluster drifting toward the 90s window would keep passing — slightly slower, silently — until the day it did not.
83
83
  - **An opt-in `@console` tier for the Console's UX** (added with
84
- [ADR-0032](0032-the-console-unlocks-with-the-instance-token.md)). The Console's risky behavior is interaction —
84
+ [ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)). The Console's risky behavior is interaction —
85
85
  token-mode switching, the frame-triggered gate inbox, selection vs. folding — which no reducer test sees and no CLI
86
86
  step drives. These scenarios hold a **Playwright page inside ordinary Cucumber steps** (Playwright the _library_, not
87
87
  the `@playwright/test` runner): the browser is a second driver of the same black box, reusing the per-scenario
88
88
  orchestrator fixture unchanged, and a scenario may drive both (the CLI starts a run; the browser sees it park). Tagged
89
89
  `@console` and **excluded from the default profile** exactly as `@kind` is — the everyday suite must not require a
90
- Chromium install — but they need no docker: a host-booted orchestrator serves the Console fine. Scope discipline: they
91
- assert what a user does and sees, never pixels or layout; what the page _believes_ stays in `console-store.test.ts`.
90
+ Chromium install — but they need no docker: a host-booted orchestrator serves the Console fine. The one exception is
91
+ the login, which needs an identity provider: its scenario runs Dex in a container and is also tagged `@dex`. The
92
+ `console` profile runs it; a host with no docker runs `--tags "@console and not @dex"`. Scope discipline: they assert
93
+ what a user does and sees, never pixels or layout; what the page _believes_ stays in `console-store.test.ts`.
92
94
  - **Harness conformance, in `@jr2/harness`, in the default gate**
93
95
  ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)). The three tiers above all reach the
94
96
  Harness through the **stub Harness**, which is a hand-written model of the _wire_ — it holds no conversation, so it