@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
@@ -117,7 +117,7 @@ back to `listening`.
117
117
 
118
118
  ## See the runs
119
119
 
120
- [`jr2 runs`](../reference/cli.md#runs) lists the two runs. The run of `triage` has `dispatchedBy` after its id:
120
+ [`jr2 runs`](../reference/cli.md#runs) lists the two runs. The run of `triage` has `dispatchedBy`:
121
121
 
122
122
  ```sh
123
123
  jr2 runs
@@ -126,29 +126,25 @@ jr2 runs
126
126
  ```text
127
127
  …
128
128
  → context kind-jr2 / namespace my-instance
129
- workflows:
130
- intake (machine)
131
- ping (machine)
132
- triage (machine)
133
- [{"runId":"91ca85e1-5092-4e10-9ec6-d14e37811ca1","workflow":"intake","instanceId":"e51d124b-88e5-461c-b464-6a3660ee13dc","status":"active","value":"listening","context":{"started":["45b52048-0848-4015-ad56-6ff8ae33d817"],"request":"The greeting looks wrong sometimes.","last":{"outcome":"started","run":"45b52048-0848-4015-ad56-6ff8ae33d817"}},"children":[],"waiting":[],"held":[],"assembly":"3229103a2952","dispatched":[{"slot":"triage","run":"45b52048-0848-4015-ad56-6ff8ae33d817","at":"2026-10-24T06:20:43.465Z"}]},{"runId":"45b52048-0848-4015-ad56-6ff8ae33d817","dispatchedBy":{"run":"91ca85e1-5092-4e10-9ec6-d14e37811ca1","slot":"triage","effect":"08423831-751f-41e5-9d93-1fa063faaf61","depth":1},"workflow":"triage","instanceId":"a2ad6e54-6a52-45c2-81f4-89f408a27d1f","status":"active","value":"triaging","context":{"request":"The greeting looks wrong sometimes.","exchange":[]},"children":[],"waiting":[],"held":[],"assembly":"3229103a2952"}]
129
+ [{"runId":"e8a74de7-f3f3-4621-876e-e5f117af5fff","workflow":"triage","status":"active","startedBy":{"kind":"run","run":"f45bed48-9950-4e63-9cda-4df368e9a62f","slot":"triage"},"startedAt":"2026-11-06T19:23:24.220Z","assembly":"19d0bf0cbe10","openGates":0,"dispatchedBy":{"run":"f45bed48-9950-4e63-9cda-4df368e9a62f","slot":"triage","effect":"c63bce4d-e353-4fee-9035-6b13dfac4917","depth":1}},{"runId":"f45bed48-9950-4e63-9cda-4df368e9a62f","workflow":"intake","status":"active","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"startedAt":"2026-11-06T19:23:21.744Z","assembly":"19d0bf0cbe10","openGates":1}]
134
130
  ```
135
131
 
136
- The first line, `…`, is the line `→ .env:`, with the keys that the CLI took from `.env`. The lines on stderr list the
137
- Workflows of the Instance. In `dispatchedBy`, `run` is the run that started it, `slot` is the slot of the Effect, and
138
- `effect` is the id of that Effect in the record of the run that started it. jr2 uses it as the key of the start, so that
139
- a start that jr2 sends again starts no second run. `depth` is the number of runs before it in the chain. `dispatched` of
140
- `intake` lists each run that it started. Your ids, your time and your hash are different.
132
+ The first line, `…`, is the line `→ .env:`, with the keys that the CLI took from `.env`. In `dispatchedBy`, `run` is the
133
+ run that started it, `slot` is the slot of the Effect, and `effect` is the id of that Effect in the record of the run
134
+ that started it. jr2 uses it as the key of the start, so that a start that jr2 sends again starts no second run. `depth`
135
+ is the number of runs before it in the chain. The list gives the newest start first, so `triage` comes before `intake`.
136
+ `jr2 status` of `intake` shows `dispatched`: each run that it started. Your ids, your time and your hash are different.
141
137
 
142
138
  [`jr2 status`](../reference/cli.md#status) prints the link in both directions, on stderr. For the run of `intake`:
143
139
 
144
140
  ```text
145
- dispatched: 45b52048-0848-4015-ad56-6ff8ae33d817 (triage)
141
+ dispatched: e8a74de7-f3f3-4621-876e-e5f117af5fff (triage)
146
142
  ```
147
143
 
148
144
  For the run of `triage`:
149
145
 
150
146
  ```text
151
- dispatched by: 91ca85e1-5092-4e10-9ec6-d14e37811ca1 (triage), depth 1
147
+ dispatched by: f45bed48-9950-4e63-9cda-4df368e9a62f (triage), depth 1
152
148
  ```
153
149
 
154
150
  The two runs are independent. A `CANCEL` of `intake` does not cancel the run of `triage`. Answer the `clarify` Gate of
@@ -174,7 +170,9 @@ To start a run on the Orchestrator of a different [Instance](../reference/glossa
174
170
  definition: `target: { url: "https://ops.example.com", token: secret("ops-instance") }`.
175
171
 
176
172
  - `url` is the origin of the other Orchestrator: the scheme, the host and the port. It has no path.
177
- - `token` names a secret that holds the Instance token of the other Instance. Put the secret in the
173
+ - `token` names a secret that holds the token of a [Caller](../reference/glossary.md#caller) of the other Instance, with
174
+ the `start` scope for the Workflow, and also `read` when `wait` is `true`. A named Caller token of the other Instance
175
+ that has only these scopes is the safest ([Give a Caller a scope](give-a-caller-a-scope.md)). Put the secret in the
178
176
  [`secrets`](../reference/config.md#secrets) list of `jr2.config.ts`, as for an `http` Effect. See
179
177
  [Run a command or call a URL with an Effect](run-a-command-or-call-a-url.md).
180
178
  - The Effect runs in the [Instance Effector](../reference/glossary.md#instance-effector), which holds the token.
@@ -201,7 +199,7 @@ the token together:
201
199
  - The `url` of a target is an origin. The refusal starts with
202
200
  `dispatch("<name>", { target }): "<url>" is not an origin — write the scheme and the host, with a port if it is not the scheme's, and no path, query or userinfo`.
203
201
  - The `token` of a target is a secret:
204
- `dispatch("<name>", { target }): the token is a secret("<name>") holding the target's Instance token (ADR-0071)`
202
+ `dispatch("<name>", { target }): the token is a secret("<name>") holding a Caller token of the target with start for the Workflow (ADR-0071)`
205
203
  - An Open target is the whole target, not its secret:
206
204
  ``dispatch("<name>", { target }): secret(open) in a target — the target is the Open part, not its secret: leave the whole target Open (`target: open`) and let the composer bind its url and token together (ADR-0071)``
207
205
  - With no target, the Assembly must have the Workflow. `jr2 up` stops:
@@ -214,11 +212,15 @@ the token together:
214
212
  `warning: workflow "<workflow>" dispatches "<wrapper>/<name>": the Assembly has no Chart file workflows/<wrapper>/<name>.chart.json`.
215
213
  - For a target, each problem that `jr2 up` finds is a warning. For example, when the other Orchestrator does not answer:
216
214
  `warning: dispatch "<name>" at <url>: no answer from this host (<error>) — the other Instance may be down, or reached only from inside its cluster; while no door answers, the dispatch settles "unreachable" (ADR-0071)`
215
+ - When the input does not agree with the `input` schema of the slot, the invoke ends with an error, and no run starts.
216
+ The error starts with `dispatch "<slot>": its input does not match the slot's input schema —`. Before that, jr2 asks
217
+ for a run under the key of the start, with no target and with one. When a run has the key, the Effect gives that run,
218
+ and does not read the input. With a target, jr2 sends the key and no input, so the other Orchestrator starts nothing.
217
219
  - When the Orchestrator starts no run, the Effect ends with `refused`, not with an error. `report` holds the lines of
218
220
  the refusal, and `status` is its status code:
219
221
  - `400`: the Workflow does not take the input.
220
222
  - `404`: the current Assembly has no Workflow of that name.
221
- - `409`: the named Chart cannot run now, or a run of a different Workflow has the key of this start.
223
+ - `409`: the named Chart cannot run now.
222
224
  - `413`: the start is larger than the Effector reads.
223
225
  - `422`: the door refuses the Chart in the input.
224
226
  - `0`: the Effector refused the start before it sent it.
@@ -29,9 +29,9 @@ jr2 run ping --input '{"message":"hi"}'
29
29
 
30
30
  ```text
31
31
  → context <context> / namespace <namespace>
32
- run e3972b4c-eef9-4e7d-9068-3556332d2ba6 (ping) — attached
32
+ run 17b959ed-5200-4d98-adae-403dd706277b (ping) — attached
33
33
  → done "done"
34
- {"runId":"e3972b4c-eef9-4e7d-9068-3556332d2ba6","workflow":"ping","instanceId":"8e13475a-d325-45d4-bc64-0627086e97ba","status":"done","value":"done","context":{"message":"hi","reply":"pong: hi"},"children":[],"waiting":[],"held":[],"assembly":"16f4181d70d9"}
34
+ {"runId":"17b959ed-5200-4d98-adae-403dd706277b","workflow":"ping","instanceId":"32e6e954-8cc5-4b52-b6df-6c9ef6533192","startedAt":"2026-11-06T19:16:12.219Z","status":"done","value":"done","context":{"message":"hi","reply":"pong: hi"},"children":[],"waiting":[],"held":[],"assembly":"50e6f81e3bfb","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}
35
35
  ```
36
36
 
37
37
  Your run id, your instance id and your `assembly` hash are different. When your Instance folder has a `.env` file, the
@@ -47,7 +47,7 @@ output starts with one more line, `→ .env:`, with the keys that the CLI took f
47
47
  - `jr2 run` stays attached until the run ends. A run that waits at a [Gate](../reference/glossary.md#gate) does not end,
48
48
  so `jr2 run` waits too. To answer the Gate, use a second terminal. See [Answer a Gate](answer-a-gate.md).
49
49
  - When you stop `jr2 run`, for example with Ctrl-C, the run continues in the Orchestrator. To attach again, run
50
- `jr2 logs -f <run id>`.
50
+ `jr2 watch <run id>`.
51
51
  - `jr2 run` exits with `0` only when the run ends with the status `done`. It exits with `1` for each other end:
52
52
  `failed`, `error`, `cancelled`, `lost` and `drifted`, and also when the run stops before it ends. For a run that ends
53
53
  `failed`, the line `failed: <reason>` on stderr gives the reason.
@@ -65,15 +65,15 @@ jr2 run ping --input '{"message":"hi"}' --detach
65
65
  {"runId":"fc8b82d1-bd73-498d-9057-ef13cf962240"}
66
66
  ```
67
67
 
68
- Use the run id, or its first 4 characters or more, with `jr2 status`, `jr2 logs` and `jr2 send`.
68
+ Use the run id, or its first 4 characters or more, with `jr2 status`, `jr2 watch`, `jr2 logs` and `jr2 send`.
69
69
 
70
70
  ## Start a run from the Console
71
71
 
72
- You need the Instance token to start a run from the Console. [Open the Console](open-the-console.md) tells how to open
73
- it and how to get the token.
72
+ You need a credential with the `start` scope for the Workflow, such as the Instance token, to start a run from the
73
+ Console. [Open the Console](open-the-console.md) tells how to open it, how to get the token and how to log in.
74
74
 
75
75
  1. Open the Console.
76
- 2. Type the Instance token in the `instance token` box in the nav.
76
+ 2. Type the token in the `Caller token` box in the nav, or click `log in`.
77
77
  3. Push Enter. The badge beside the box shows `…`, then `live`.
78
78
  4. In the Fleet rail on the left, find the Workflow. Click `start` beside its name.
79
79
  5. Fill in the form. When the Machine declares its input, the form has one field for each property of the input. A field
@@ -94,7 +94,10 @@ cannot start one.
94
94
  ## Start a run with the HTTP API
95
95
 
96
96
  Send [`POST /workflows/:name/runs`](../reference/http-api.md#post-workflows-name-runs) to the Orchestrator, with the
97
- Instance token. The body is the run input. A script, a webhook handler or a CI job can start a run this way.
97
+ token of a [Caller](../reference/glossary.md#caller) that has the `start` scope for the Workflow. The body is the run
98
+ input. A script, a webhook handler or a CI job can start a run this way. A CI job uses a named Caller token, not the
99
+ Instance token: [Give a Caller a scope](give-a-caller-a-scope.md) makes it. The example below uses the Instance token,
100
+ because it can do everything.
98
101
 
99
102
  `jr2 up` makes the Service `jr2-orchestrator` on port 4000, with no address outside the cluster. Thus a caller outside
100
103
  the cluster uses a port forward, or an address that you make for the Service.
@@ -144,13 +147,14 @@ curl -s http://localhost:4000/workflows/ping -H "Accept: application/json"
144
147
  ```
145
148
 
146
149
  [`GET /workflows`](../reference/http-api.md#get-workflows) gives all the Workflows, each with where it comes from, and
147
- the named Charts of each Chart wrapper. It also needs no token. [`jr2 runs`](../reference/cli.md#runs) prints the same
148
- list on stderr.
150
+ the named Charts of each Chart wrapper. It also needs no token. [`jr2 workflows`](../reference/cli.md#workflows) prints
151
+ the same list.
149
152
 
150
153
  ### Use the CLI without the Instance folder
151
154
 
152
155
  The CLI can also speak to an Orchestrator at a URL. Then it does not look for the Instance folder or for a kubectl
153
- context. Give the URL in `--url`, or in `JR2_URL`. Give the Instance token in `JR2_TOKEN`:
156
+ context. Give the URL in `--url`, or in `JR2_URL`. Give a credential in `JR2_TOKEN`: the Instance token, or the token of
157
+ a [Caller](../reference/glossary.md#caller) with the `start` scope ([Give a Caller a scope](give-a-caller-a-scope.md)):
154
158
 
155
159
  ```sh
156
160
  JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":"hi"}' --detach
@@ -177,10 +181,12 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
177
181
  `the body must be a JSON object`.
178
182
  - `jr2 run` starts no run when `--input` is not valid JSON. It prints `error: <message>`, where `<message>` is the
179
183
  message of the JSON parser, and exits with `1`.
180
- - The Orchestrator refuses a start without the Instance token, with the status code `401`: `unauthorized`.
184
+ - The Orchestrator refuses a start with no valid credential, with the status code `401`: `unauthorized`.
181
185
  - The Orchestrator refuses a start with the token of a [Sandbox](../reference/glossary.md#sandbox), with the status code
182
- `403`: `run state and control need the Instance token — a Sandbox token has neither`. Thus an
183
- [Agent](../reference/glossary.md#agent) cannot start a run.
186
+ `403`: `this route needs a Caller, and a Sandbox token is not one: no Agent reaches run state, run control or a Gate`.
187
+ Thus an [Agent](../reference/glossary.md#agent) cannot start a run.
188
+ - The Orchestrator refuses a start from a Caller that does not have the `start` scope for the Workflow, with the status
189
+ code `403`: `the Caller <via>/<name> needs the "start" scope for Workflow "<workflow>" (ADR-0078)`
184
190
  - `jr2 run` outside an Instance folder, with no `--url` and no `JR2_URL`, starts no run:
185
191
  `error: not inside a jr2 instance — no jr2.config.ts found walking up from cwd`
186
192
  - `jr2 run` starts no run when the namespace holds no Instance:
@@ -189,9 +195,9 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
189
195
  ``error: no kube context — `kind create cluster` (or point kubectl at one), then `jr2 up` ``
190
196
  - When the connection to the Orchestrator ends before the run ends, for example when the Orchestrator stops, `jr2 run`
191
197
  prints `error: <message>` and exits with `1`. The run continues when the Orchestrator starts again. To attach again,
192
- run `jr2 logs -f <run id>`.
198
+ run `jr2 watch <run id>`.
193
199
  - When the Orchestrator holds the run but did not continue it, `jr2 run` exits with `1`:
194
- ``run <run id> is parked: stopped before it settled — `jr2 logs -f <run id>` follows it once restored``
200
+ ``run <run id> is parked: stopped before it settled — `jr2 watch <run id>` follows it once restored``
195
201
 
196
202
  ## Read next
197
203
 
@@ -201,7 +207,7 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 run ping --input '{"message":
201
207
  Orchestrator.
202
208
  - [ADR-0033](../reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md): the input that a Machine
203
209
  declares.
204
- - [ADR-0058](../reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md): why only the Instance token
205
- starts a run.
210
+ - [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): the
211
+ Callers, and the `start` scope.
206
212
  - [Answer a Gate](answer-a-gate.md)
207
213
  - [Watch a run](watch-a-run.md)
@@ -0,0 +1,332 @@
1
+ ---
2
+ title: Steer an Agent
3
+ description: Send free text to one Agent of a live run, from the CLI, from HTTP, or from the Machine itself.
4
+ ---
5
+
6
+ # Steer an Agent
7
+
8
+ Send free text to one [Agent](../reference/glossary.md#agent) of a live run, from the CLI, from HTTP, or from the
9
+ [Machine](../reference/glossary.md#machine) itself.
10
+
11
+ A [Steering message](../reference/glossary.md#steering-message) is not a pick and not an event. No guard reads it, and
12
+ no [Gate](../reference/glossary.md#gate) takes it. The Agent hears it one time, and nothing interrupts the Agent. Then
13
+ the Agent decides what to do, and it acts on the run only with its picks. To move the run, answer a Gate:
14
+ [Answer a Gate](answer-a-gate.md).
15
+
16
+ The examples on this page use the [Workflow](../reference/glossary.md#workflow) `notes`. A writer writes release notes,
17
+ and a reviewer reads them. When the reviewer sends a note, the Machine relays it to the writer as a Steering message.
18
+ Save the file as `workflows/notes.ts` in your [Instance](../reference/glossary.md#instance). Then run `jr2 up`:
19
+
20
+ **Anthropic**
21
+
22
+ ```ts
23
+ import { assign } from "xstate";
24
+ import { z } from "zod";
25
+ import { agent, defineEvent, fail, jr2Setup, steer } from "@jr2/orchestrator";
26
+
27
+ // The writer's pick: the draft is ready for review.
28
+ const done = defineEvent({
29
+ name: "done",
30
+ description: "The draft is ready. Give the whole draft.",
31
+ input: z.object({ draft: z.string().min(1) }),
32
+ });
33
+ // The reviewer's two picks.
34
+ const note = defineEvent({
35
+ name: "note",
36
+ description: "Send the draft back to the writer, with what to change.",
37
+ input: z.object({ text: z.string().min(1) }),
38
+ });
39
+ const approve = defineEvent({
40
+ name: "approve",
41
+ description: "The draft is good.",
42
+ input: z.object({}),
43
+ });
44
+
45
+ type Context = { version: string; draft?: string; fault?: string };
46
+
47
+ export const machine = jr2Setup({
48
+ types: {} as { input: { version: string }; context: Context },
49
+ events: [done, note, approve],
50
+ actors: {
51
+ writer: agent({
52
+ model: "anthropic/claude-haiku-5-5",
53
+ instructions: "You write short release notes.",
54
+ workspace: "none",
55
+ }),
56
+ // A Caller cannot steer the reviewer. The Machine can.
57
+ reviewer: agent({
58
+ model: "anthropic/claude-haiku-5-5",
59
+ instructions: "You review release notes for clear and correct words.",
60
+ workspace: "none",
61
+ steerable: false,
62
+ }),
63
+ fail: fail(),
64
+ },
65
+ }).createMachine({
66
+ id: "notes",
67
+ input: z.object({ version: z.string() }),
68
+ context: ({ input }) => ({ version: input.version }),
69
+ initial: "writing",
70
+ // A Turn that fails in either state ends the run `failed`, with the reason of the Turn.
71
+ on: { "agent.fault": { target: ".failed", actions: assign({ fault: ({ event }) => event.reason }) } },
72
+ states: {
73
+ writing: {
74
+ invoke: {
75
+ src: "writer",
76
+ // The writer continues its conversation, so it keeps its draft from Turn to Turn.
77
+ input: ({ context }) => ({ prompt: `Write the release notes for ${context.version}.`, continue: true }),
78
+ },
79
+ on: { done: { target: "reviewing", actions: assign({ draft: ({ event }) => event.draft }) } },
80
+ },
81
+ reviewing: {
82
+ invoke: {
83
+ src: "reviewer",
84
+ input: ({ context }) => ({ prompt: `Review these release notes:\n\n${context.draft ?? ""}` }),
85
+ },
86
+ on: {
87
+ // The reviewer's note goes to the writer's next Turn as a Steering message. `from` names the
88
+ // reviewer, so the writer reads it as information from a peer, not as an instruction.
89
+ note: {
90
+ target: "writing",
91
+ actions: steer("writer", ({ event }) => event.text, { from: ({ event }) => event.sender }),
92
+ },
93
+ approve: "published",
94
+ },
95
+ },
96
+ published: { type: "final" },
97
+ failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
98
+ },
99
+ });
100
+ ```
101
+
102
+ **Local**
103
+
104
+ ```ts
105
+ import { assign } from "xstate";
106
+ import { z } from "zod";
107
+ import { agent, defineEvent, fail, jr2Setup, steer } from "@jr2/orchestrator";
108
+
109
+ // The writer's pick: the draft is ready for review.
110
+ const done = defineEvent({
111
+ name: "done",
112
+ description: "The draft is ready. Give the whole draft.",
113
+ input: z.object({ draft: z.string().min(1) }),
114
+ });
115
+ // The reviewer's two picks.
116
+ const note = defineEvent({
117
+ name: "note",
118
+ description: "Send the draft back to the writer, with what to change.",
119
+ input: z.object({ text: z.string().min(1) }),
120
+ });
121
+ const approve = defineEvent({
122
+ name: "approve",
123
+ description: "The draft is good.",
124
+ input: z.object({}),
125
+ });
126
+
127
+ type Context = { version: string; draft?: string; fault?: string };
128
+
129
+ export const machine = jr2Setup({
130
+ types: {} as { input: { version: string }; context: Context },
131
+ events: [done, note, approve],
132
+ actors: {
133
+ writer: agent({
134
+ model: "local/qwen3.6-35b-a3b",
135
+ instructions: "You write short release notes.",
136
+ workspace: "none",
137
+ }),
138
+ // A Caller cannot steer the reviewer. The Machine can.
139
+ reviewer: agent({
140
+ model: "local/qwen3.6-35b-a3b",
141
+ instructions: "You review release notes for clear and correct words.",
142
+ workspace: "none",
143
+ steerable: false,
144
+ }),
145
+ fail: fail(),
146
+ },
147
+ }).createMachine({
148
+ id: "notes",
149
+ input: z.object({ version: z.string() }),
150
+ context: ({ input }) => ({ version: input.version }),
151
+ initial: "writing",
152
+ // A Turn that fails in either state ends the run `failed`, with the reason of the Turn.
153
+ on: { "agent.fault": { target: ".failed", actions: assign({ fault: ({ event }) => event.reason }) } },
154
+ states: {
155
+ writing: {
156
+ invoke: {
157
+ src: "writer",
158
+ // The writer continues its conversation, so it keeps its draft from Turn to Turn.
159
+ input: ({ context }) => ({ prompt: `Write the release notes for ${context.version}.`, continue: true }),
160
+ },
161
+ on: { done: { target: "reviewing", actions: assign({ draft: ({ event }) => event.draft }) } },
162
+ },
163
+ reviewing: {
164
+ invoke: {
165
+ src: "reviewer",
166
+ input: ({ context }) => ({ prompt: `Review these release notes:\n\n${context.draft ?? ""}` }),
167
+ },
168
+ on: {
169
+ // The reviewer's note goes to the writer's next Turn as a Steering message. `from` names the
170
+ // reviewer, so the writer reads it as information from a peer, not as an instruction.
171
+ note: {
172
+ target: "writing",
173
+ actions: steer("writer", ({ event }) => event.text, { from: ({ event }) => event.sender }),
174
+ },
175
+ approve: "published",
176
+ },
177
+ },
178
+ published: { type: "final" },
179
+ failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
180
+ },
181
+ });
182
+ ```
183
+
184
+ The Agents use the model of the tab.
185
+
186
+ In the commands, replace these values:
187
+
188
+ - `<namespace>`: the namespace of your Instance. It is the [`name`](../reference/config.md#name) in `jr2.config.ts`.
189
+ When `jr2.config.ts` sets no `name`, it is the name of the Instance folder.
190
+ - `<run id>`: the run id, or its first 4 characters or more. `jr2 run` prints the run id in the line
191
+ `run <run id> (<workflow>) — attached`.
192
+
193
+ Give each `jr2` command in the Instance folder.
194
+
195
+ ## Start a run
196
+
197
+ ```sh
198
+ jr2 run notes --input '{"version":"1.4.0"}'
199
+ ```
200
+
201
+ ```text
202
+ → context <context> / namespace <namespace>
203
+ run 7058fde8-4c32-4513-82f0-e6c095d8707b (notes) — attached
204
+ → active "writing"
205
+ → active "writing"
206
+ ```
207
+
208
+ Your run id is different. Give the commands in the next steps in a second terminal.
209
+
210
+ ## Find the Agent slot
211
+
212
+ A message goes to an Agent slot: the path of the Machine and the name of the Agent. The Agents of the root Machine have
213
+ the path `root`. An Agent in a child Machine has the path of that Machine, such as `review/reviewer`. Give
214
+ [`jr2 steer`](../reference/cli.md#steer) the run id alone to list the slots:
215
+
216
+ ```sh
217
+ jr2 steer 7058
218
+ ```
219
+
220
+ ```text
221
+ → context <context> / namespace <namespace>
222
+ [{"agent":"root/writer","open":true,"steerable":true},{"agent":"root/reviewer","open":false,"steerable":false}]
223
+ ```
224
+
225
+ - `open` is `true` when a [Turn](../reference/glossary.md#turn) of the slot is open now.
226
+ - `steerable` is `false` when the definition of the Agent says `steerable: false`. A Caller cannot steer it.
227
+
228
+ The list has each Agent of each Machine instance that is live now, also an Agent with no open Turn.
229
+
230
+ ## Send the message
231
+
232
+ ```sh
233
+ jr2 steer 7058 root/writer "Put the breaking change first."
234
+ ```
235
+
236
+ ```text
237
+ → context <context> / namespace <namespace>
238
+ sent a Steering message to root/writer on 7058fde8-4c32-4513-82f0-e6c095d8707b: its open Turn hears it at its next step boundary, or its next Turn hears it if this Turn ends first
239
+ ```
240
+
241
+ Then one of these things occurs:
242
+
243
+ - When a Turn of the slot is open, the Harness adds the message to the Turn before the next model request.
244
+ - When the Turn ends with a pick before its next model request, the message waits for the next Turn of the slot.
245
+ - When no Turn of the slot is open, the message waits. The next Turn of the slot in the same Machine instance hears it,
246
+ and `jr2 steer` prints `its next Turn hears it`.
247
+ - When that Machine instance ends first, the message is not delivered.
248
+
249
+ ## Read what became of the message
250
+
251
+ [`jr2 status`](../reference/cli.md#status) prints the record of the messages of the run in its `steering` field. Each
252
+ entry has the slot, the author, the text, the time, and the `outcome`: `held` while it waits, `heard` when a Turn heard
253
+ it, or `not-delivered` when its Machine instance ended first.
254
+
255
+ ```sh
256
+ jr2 status 7058
257
+ ```
258
+
259
+ ```text
260
+ → context <context> / namespace <namespace>
261
+ {"runId":"7058fde8-4c32-4513-82f0-e6c095d8707b","workflow":"notes",…,"steering":[{"id":"c46a31f0-db82-4d80-b7f1-8908c2d9b121","agent":"root/writer","author":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"text":"Put the breaking change first.","at":"2026-11-06T20:56:33.005Z","outcome":"heard"},{"id":"f788b1f6-2b93-44be-a54e-e91f83685493","agent":"root/writer","author":{"kind":"machine","from":{"party":"agent","id":"reviewer"}},"text":"The release notes are well-structured, but I suggest a change for clarity in the **Notifications** section:\n\n\"Real-time notifications are now **persisted** across device logins.\"\n\n\"Persisted\" is technical jargon referring to data storage. If the intended meaning is that notifications appear on different devices, \"**synchronized**\" (or \"synced\") is the standard and clearer term for users.\n\nSuggested edit: \"Real-time notifications are now **synchronized** across devices.\"","at":"2026-11-06T20:57:35.442Z","outcome":"heard"}],…}
262
+ started by: token/instance on behalf of kubernetes-admin
263
+ ```
264
+
265
+ The model reads the message after the notices of the Turn and before its Allowed picks. The Harness tells the model that
266
+ the message is an instruction from outside the run, and gives your name as a [Caller](../reference/glossary.md#caller).
267
+
268
+ ## Send the message from the Console
269
+
270
+ With a credential that has the `steer` scope for the Workflow, the [Console](../reference/glossary.md#console) shows a
271
+ box beside the conversation in the bottom drawer. Select the run, and click the tab of the Agent slot. Type the message
272
+ in the box under `steer <slot>`, and click `send`. The box shows `<slot> cannot be steered now` when the definition of
273
+ the Agent says `steerable: false`, or when the Machine instance of the slot ended. The conversation shows the message
274
+ where the model read it. [Open the Console](open-the-console.md#read-a-conversation) tells how to open the drawer.
275
+
276
+ ## Send the message from HTTP
277
+
278
+ A client that is not the CLI sends the message to
279
+ [`POST /runs/:runId/steering`](../reference/http-api.md#post-runs-runid-steering), with the credential of a Caller that
280
+ has the `steer` scope for the Workflow of the run. [Give a Caller a scope](give-a-caller-a-scope.md) makes such a token.
281
+ The path takes the full run id, not a prefix.
282
+ [`GET /runs/:runId/agents`](../reference/http-api.md#get-runs-runid-agents) lists the slots. This example reaches the
283
+ Orchestrator through the port forward in [Open the Console](open-the-console.md):
284
+
285
+ ```sh
286
+ curl -X POST http://localhost:4000/runs/<run id>/steering \
287
+ -H "authorization: Bearer <token>" \
288
+ -d '{"agent":"root/writer","text":"Put the breaking change first."}'
289
+ ```
290
+
291
+ ```text
292
+ {"id":"29e54d63-e858-4249-a789-f7e5506f433a","agent":"root/writer","open":true}
293
+ ```
294
+
295
+ The status is 202.
296
+
297
+ ## Steer from the Machine
298
+
299
+ The Machine sends a Steering message with the [`steer`](../reference/authoring-surface/steer.md) action, in a
300
+ transition. In `notes.ts`, the `note` pick of the reviewer sends the writer back to `writing`, and `steer` gives the
301
+ reviewer's text to the writer's next Turn.
302
+
303
+ `from` names the [Sender](../reference/glossary.md#sender) of the event: here, the reviewer. Then the writer reads the
304
+ text as information from a peer, not as an instruction. When `from` is a Gate, the writer reads the text as what the
305
+ Gate received, not as an order: the workflow did not write it. A `steer` with no `from` is the text of the workflow
306
+ itself. Agents tell each other things only in this way: jr2 gives an Agent no tool to message another Agent.
307
+
308
+ The Machine can steer an Agent whose definition says `steerable: false`. That refuses Callers only.
309
+
310
+ ## Rules
311
+
312
+ - A message is at most 16 KiB of UTF-8. `POST /runs/:runId/steering` refuses a longer text with the status 413:
313
+ `the text is longer than 16384 bytes (16 KiB)`. The `steer` action cuts a longer text and adds
314
+ `[jr2 cut this message at 16 KiB]`.
315
+ - A body with no `agent`, or no `text`, gets the status 400.
316
+ - A slot that no live Machine instance of the run holds gets the status 409, with the slots of the run in `agents`:
317
+ `no live Machine instance of run "<run id>" holds the Agent slot "<agent>" (its slots: <slots>)`. A run that has ended
318
+ gets 409 too: `run "<run id>" is not live: no Agent of it can hear a message`.
319
+ - A slot whose Agent says `steerable: false` gets the status 403:
320
+ `the Agent slot "<agent>" is not steerable: its definition says steerable: false`.
321
+ - A Caller with no `steer` scope for the Workflow of the run gets the status 403:
322
+ `the Caller <via>/<name> needs the "steer" scope for Workflow "<workflow>" (ADR-0078)`.
323
+ - An unknown run gets the status 404: `no run "<run id>"`.
324
+ - The record keeps the newest 100 messages of a run, and each message that waits.
325
+
326
+ ## Read next
327
+
328
+ - [Agents and Turns](../concepts/agents-and-turns.md#steering-messages)
329
+ - [`jr2 steer`](../reference/cli.md#steer) and [`steer`](../reference/authoring-surface/steer.md)
330
+ - [ADR-0002](../reference/decisions/0002-duplex-streaming-actor.md#steering-messages): Steering messages
331
+ - [ADR-0062](../reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md): one ledger for notices
332
+ and Steering messages
@@ -186,10 +186,16 @@ custom provider, with `harness.provider` and `apiKey`. The guide
186
186
  - `jr2 up` refuses a gateway address that is not `https://`:
187
187
  `harness.catalog.<id>.baseUrl must be an https:// URL — the Custodian carries TLS only (ADR-0059). Read it from .env: catalog: { anthropic: { baseUrl: process.env.LITELLM_URL ?? "" } }`.
188
188
  - `jr2 up` warns about a key that no pod gets:
189
- `warning: secrets[<i>] (<NAME>) is named by nothing; goes nowhere — no workspace() lists it, no Agent's provider reads it, and no Effect names it (ADR-0059, ADR-0068)`.
189
+ `warning: secrets[<i>] (<NAME>) is named by nothing; goes nowhere — no workspace() lists it, no Agent's provider reads it, and no Effect or Hook names it (ADR-0059, ADR-0068)`.
190
190
  - A credential that signs each request, such as `AWS_ACCESS_KEY_ID`, cannot be a Held secret. `jr2 up` warns when
191
191
  `harness.env` sets one. Send the calls through a gateway that holds the credential, and hold the key of the gateway:
192
192
  `warning: harness.env sets <NAME>: a signing credential cannot be held (a Stand-in cannot be swapped into a signature), so the Agent can read it — route the provider through a gateway that holds it, and hold the gateway's key instead (ADR-0059)`.
193
+ - The Harness process reads the file that a credential-file name in `harness.env` names, such as `AWS_CONFIG_FILE` or
194
+ `GOOGLE_APPLICATION_CREDENTIALS`. `jr2 up` refuses a path in a folder that the Agent's code writes, or a path that is
195
+ not absolute:
196
+ `harness.env[<i>] sets <NAME> to "<path>", which is in <folder>, a folder the Agent's code writes: the Harness process reads that file, and code that writes it acts outside the Agent's confinement (ADR-0076). Point it at a file of the image that the Sandbox's user cannot write.`
197
+ For another path, it warns:
198
+ `warning: harness.env sets <NAME>: the Harness process reads "<path>", so the Sandbox's user must not be able to write it — a folder of the image that the user owns is one the Agent's code writes (ADR-0076)`.
193
199
  - The Harness refuses a model that is not in the catalog when it admits the Turn:
194
200
  `harness admission failed (400): agent "<agent>": model "<provider>/<modelId>" resolves to nothing — not in pi's catalog, and no custom provider "<provider>" is configured (ADR-0018)`.
195
201