@jr2/orchestrator 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +86 -9
  14. package/docs/concepts/architecture.md +41 -28
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +7 -5
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +4 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +50 -22
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +5 -4
  23. package/docs/concepts/pools-and-sources.md +187 -11
  24. package/docs/concepts/secrets-and-trust.md +73 -33
  25. package/docs/concepts/the-menu.md +11 -0
  26. package/docs/concepts/workspaces.md +6 -5
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/guides/add-tools-to-the-sandbox.md +55 -11
  32. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  33. package/docs/guides/answer-a-gate.md +74 -37
  34. package/docs/guides/bind-open-parts.md +24 -8
  35. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  36. package/docs/guides/cancel-a-run.md +26 -16
  37. package/docs/guides/fix-a-failed-converge.md +18 -6
  38. package/docs/guides/give-a-caller-a-scope.md +203 -0
  39. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  40. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  41. package/docs/guides/let-an-agent-write-a-chart.md +9 -6
  42. package/docs/guides/look-inside-a-sandbox.md +101 -31
  43. package/docs/guides/open-the-console.md +63 -14
  44. package/docs/guides/read-a-conversation.md +225 -0
  45. package/docs/guides/run-a-chart-by-name.md +17 -16
  46. package/docs/guides/run-one-worker-per-item.md +15 -9
  47. package/docs/guides/set-an-agents-access-and-dials.md +2 -0
  48. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  49. package/docs/guides/start-a-run-from-a-run.md +18 -16
  50. package/docs/guides/start-a-run.md +24 -18
  51. package/docs/guides/steer-an-agent.md +332 -0
  52. package/docs/guides/use-a-hosted-provider.md +1 -1
  53. package/docs/guides/watch-a-run.md +127 -121
  54. package/docs/guides/write-a-hook-kind.md +238 -0
  55. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  56. package/docs/guides/write-a-workflow.md +17 -7
  57. package/docs/llms.txt +81 -15
  58. package/docs/reference/authoring-surface/agent.md +1 -1
  59. package/docs/reference/authoring-surface/answer.md +1 -1
  60. package/docs/reference/authoring-surface/arg.md +1 -1
  61. package/docs/reference/authoring-surface/basic.md +1 -1
  62. package/docs/reference/authoring-surface/bearer.md +1 -1
  63. package/docs/reference/authoring-surface/branchname.md +1 -1
  64. package/docs/reference/authoring-surface/carry.md +23 -0
  65. package/docs/reference/authoring-surface/chartschema.md +1 -1
  66. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  67. package/docs/reference/authoring-surface/command.md +1 -1
  68. package/docs/reference/authoring-surface/customize.md +4 -1
  69. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  70. package/docs/reference/authoring-surface/defineevent.md +1 -1
  71. package/docs/reference/authoring-surface/dispatch.md +5 -4
  72. package/docs/reference/authoring-surface/fail.md +1 -1
  73. package/docs/reference/authoring-surface/file.md +1 -1
  74. package/docs/reference/authoring-surface/frompick.md +1 -1
  75. package/docs/reference/authoring-surface/gate.md +118 -0
  76. package/docs/reference/authoring-surface/group-agents.md +15 -0
  77. package/docs/reference/authoring-surface/group-charts.md +16 -0
  78. package/docs/reference/authoring-surface/group-composition.md +25 -0
  79. package/docs/reference/authoring-surface/group-config.md +17 -0
  80. package/docs/reference/authoring-surface/group-effects.md +48 -0
  81. package/docs/reference/authoring-surface/group-failure.md +13 -0
  82. package/docs/reference/authoring-surface/group-gates.md +16 -0
  83. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  84. package/docs/reference/authoring-surface/group-steering.md +12 -0
  85. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  86. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  87. package/docs/reference/authoring-surface/http.md +1 -1
  88. package/docs/reference/authoring-surface/isfailure.md +1 -1
  89. package/docs/reference/authoring-surface/isready.md +1 -1
  90. package/docs/reference/authoring-surface/jr2setup.md +13 -3
  91. package/docs/reference/authoring-surface/mcp.md +107 -0
  92. package/docs/reference/authoring-surface/open.md +5 -3
  93. package/docs/reference/authoring-surface/openbody.md +1 -1
  94. package/docs/reference/authoring-surface/paramsof.md +1 -1
  95. package/docs/reference/authoring-surface/pool.md +77 -9
  96. package/docs/reference/authoring-surface/push.md +1 -1
  97. package/docs/reference/authoring-surface/readyof.md +1 -1
  98. package/docs/reference/authoring-surface/reread.md +19 -0
  99. package/docs/reference/authoring-surface/secret.md +1 -1
  100. package/docs/reference/authoring-surface/sentby.md +1 -1
  101. package/docs/reference/authoring-surface/skip.md +22 -0
  102. package/docs/reference/authoring-surface/source.md +56 -1
  103. package/docs/reference/authoring-surface/steer.md +93 -0
  104. package/docs/reference/authoring-surface/text.md +1 -1
  105. package/docs/reference/authoring-surface/type-agentdefinition.md +14 -1
  106. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  107. package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
  108. package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
  109. package/docs/reference/authoring-surface/type-binding.md +1 -1
  110. package/docs/reference/authoring-surface/type-caller.md +40 -0
  111. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  112. package/docs/reference/authoring-surface/type-carry.md +19 -0
  113. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  114. package/docs/reference/authoring-surface/type-commandoutput.md +1 -1
  115. package/docs/reference/authoring-surface/type-customize.md +16 -1
  116. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  117. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  118. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  119. package/docs/reference/authoring-surface/type-dispatchoutput.md +1 -1
  120. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  121. package/docs/reference/authoring-surface/type-framepath.md +1 -1
  122. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  123. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  124. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  125. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  126. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  127. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  128. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  129. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  130. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  131. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  132. package/docs/reference/authoring-surface/type-hostinjectedinput.md +1 -1
  133. package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
  134. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  135. package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
  136. package/docs/reference/authoring-surface/type-lends.md +5 -1
  137. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  138. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  139. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  140. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  141. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  142. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  143. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  144. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  145. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  146. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  147. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  148. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  149. package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
  150. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  151. package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
  152. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  153. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  154. package/docs/reference/authoring-surface/type-reposlot.md +1 -1
  155. package/docs/reference/authoring-surface/type-reread.md +18 -0
  156. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  157. package/docs/reference/authoring-surface/type-sender.md +26 -1
  158. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  159. package/docs/reference/authoring-surface/type-size.md +1 -1
  160. package/docs/reference/authoring-surface/type-skip.md +19 -0
  161. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  162. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  163. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  164. package/docs/reference/authoring-surface/type-sourcespec.md +16 -7
  165. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  166. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  167. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  168. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  169. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  170. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  171. package/docs/reference/authoring-surface/type-workspaced.md +1 -1
  172. package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
  173. package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
  174. package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
  175. package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
  176. package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
  177. package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
  178. package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
  179. package/docs/reference/authoring-surface/workspace.md +1 -1
  180. package/docs/reference/authoring-surface/worktree.md +1 -1
  181. package/docs/reference/authoring-surface.md +88 -167
  182. package/docs/reference/cli.md +413 -74
  183. package/docs/reference/config.md +235 -2
  184. package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
  185. package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
  186. package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
  187. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  188. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  189. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  190. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  191. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +9 -2
  192. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
  193. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  194. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +18 -14
  195. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
  196. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  197. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +32 -18
  198. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
  199. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  200. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +116 -34
  201. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
  202. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  203. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +7 -6
  204. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
  205. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  206. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  207. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -18
  208. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  209. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +4 -3
  210. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  211. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +5 -4
  212. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
  213. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  214. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  215. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  216. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +7 -0
  217. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
  218. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
  219. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
  220. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
  221. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  222. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  223. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +51 -23
  224. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
  225. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
  226. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  227. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +17 -13
  228. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  229. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  230. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
  231. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
  232. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  233. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  234. package/docs/reference/decisions.md +6 -4
  235. package/docs/reference/glossary.md +96 -42
  236. package/docs/reference/hooks.md +1077 -0
  237. package/docs/reference/http-api.md +976 -149
  238. package/docs/why.md +2 -1
  239. package/package.json +10 -3
  240. package/src/actor.ts +36 -10
  241. package/src/agent.ts +50 -0
  242. package/src/assembly-checks.ts +530 -4
  243. package/src/callers.ts +245 -0
  244. package/src/chart.ts +52 -7
  245. package/src/config.ts +550 -1
  246. package/src/conversations.ts +453 -0
  247. package/src/custodian.ts +8 -2
  248. package/src/customize.ts +143 -4
  249. package/src/dispatch-effect.ts +142 -26
  250. package/src/durability.ts +2 -0
  251. package/src/duration.ts +20 -0
  252. package/src/effect-checks.ts +2 -1
  253. package/src/effect.ts +60 -7
  254. package/src/effector-wire.ts +272 -27
  255. package/src/gate-brand.ts +16 -0
  256. package/src/gate.ts +634 -48
  257. package/src/harness-client.ts +10 -4
  258. package/src/held-secrets.ts +72 -23
  259. package/src/holds.ts +2 -2
  260. package/src/hook-kinds/discord.ts +92 -0
  261. package/src/hook-kinds/door.ts +107 -0
  262. package/src/hook-kinds/github.ts +89 -0
  263. package/src/hook-kinds/gitlab.ts +72 -0
  264. package/src/hook-kinds/hook.ts +7 -0
  265. package/src/hook-kinds/index.ts +465 -0
  266. package/src/hook-kinds/slack.ts +151 -0
  267. package/src/hook-kinds/webhook.ts +113 -0
  268. package/src/hooks.ts +506 -0
  269. package/src/http-effect.ts +76 -38
  270. package/src/http.ts +889 -94
  271. package/src/index.ts +16 -5
  272. package/src/instance.ts +196 -26
  273. package/src/internal.ts +22 -3
  274. package/src/machine-doc.ts +26 -1
  275. package/src/mcp-effect.ts +424 -0
  276. package/src/mount-digest.ts +41 -7
  277. package/src/names.ts +64 -2
  278. package/src/notices.ts +138 -10
  279. package/src/open-body.ts +25 -11
  280. package/src/open-effect.ts +17 -4
  281. package/src/open.ts +4 -2
  282. package/src/parts.ts +87 -9
  283. package/src/party.ts +2 -1
  284. package/src/pick.ts +2 -2
  285. package/src/pool.ts +1142 -163
  286. package/src/registration.ts +203 -13
  287. package/src/run-host.ts +1509 -171
  288. package/src/run-list.ts +191 -0
  289. package/src/sandbox-kube.ts +53 -5
  290. package/src/secret-placement.ts +239 -33
  291. package/src/server.ts +53 -4
  292. package/src/setup.ts +72 -32
  293. package/src/snapshot-store.ts +565 -48
  294. package/src/steer.ts +111 -0
  295. package/src/stub-harness.ts +6 -2
  296. package/src/tokens.ts +263 -22
  297. package/src/wire.ts +17 -0
  298. package/src/workspace.ts +50 -6
  299. package/src/worktree.ts +3 -3
  300. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  301. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -181,7 +181,7 @@ jr2 run issues --input '{"workers":2}'
181
181
  ```text
182
182
  …
183
183
  → context kind-jr2 / namespace my-instance
184
- run e1effd33-9bf6-4137-ae70-3aa504910784 (issues) — attached
184
+ run 7c248a11-445a-4123-8ce2-834cb57706d7 (issues) — attached
185
185
  → active "discovering"
186
186
  → active "discovering"
187
187
  → active "discovering"
@@ -206,7 +206,8 @@ jr2 status <run id>
206
206
  ```text
207
207
  …
208
208
  → context kind-jr2 / namespace my-instance
209
- {"runId":"e1effd33-9bf6-4137-ae70-3aa504910784","workflow":"issues",…,"gates":[{"gate":"source",…},{"gate":"issue-3.body.review",…},{"gate":"issue-2.body.review",…}]}
209
+ {"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues",…,"gates":[{"gate":"source",…},{"gate":"issue-3.body.review",…},{"gate":"issue-2.body.review",…}]}
210
+ …
210
211
  ```
211
212
 
212
213
  In the JSON that it prints, `gates` holds each open Gate of the run:
@@ -234,7 +235,7 @@ jr2 send <run id> --gate issue-2.body.review --event approve
234
235
  ```text
235
236
  …
236
237
  → context kind-jr2 / namespace my-instance
237
- delivered approve to gate "issue-2.body.review" on e1effd33-9bf6-4137-ae70-3aa504910784
238
+ delivered approve to gate "issue-2.body.review" on 7c248a11-445a-4123-8ce2-834cb57706d7
238
239
  ```
239
240
 
240
241
  The Pool then calls `next` again, and starts a worker for the next issue.
@@ -255,12 +256,18 @@ jr2 send <run id> --gate source --event issues_changed
255
256
  ```text
256
257
  …
257
258
  → context kind-jr2 / namespace my-instance
258
- delivered issues_changed to gate "source" on e1effd33-9bf6-4137-ae70-3aa504910784
259
+ delivered issues_changed to gate "source" on 7c248a11-445a-4123-8ce2-834cb57706d7
259
260
  ```
260
261
 
261
- Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A webhook can send the same
262
+ Without the event, the Pool calls `next` again when a worker finishes, or after `pollEvery`. A script can send the same
262
263
  event with [`POST /runs/:runId/gates/:gate/events`](../reference/http-api.md#post-runs-runid-gates-gate-events).
263
264
 
265
+ A [Hook](../reference/glossary.md#hook) can tell the Pool too. Give the Source a `hooks` handler for the forge's event,
266
+ for example `github` and `issues.labeled`, that gives [`reread()`](../reference/authoring-surface/reread.md), or
267
+ [`skip(reason)`](../reference/authoring-surface/skip.md) for a label that is not `jr2`. Then each delivery of the Hook
268
+ makes the Pool call `next` at once. [Pools and Sources](../concepts/pools-and-sources.md#a-hook-reaches-a-source) tells
269
+ how.
270
+
264
271
  ## The end of the run
265
272
 
266
273
  When the last worker finishes and no issue is ready, the run ends. In the first terminal, `jr2 run` prints the last
@@ -270,7 +277,7 @@ state of the Pool, then the status of the run as JSON:
270
277
  …
271
278
  → active "discovering"
272
279
  → done "drained"
273
- {"runId":"e1effd33-9bf6-4137-ae70-3aa504910784","workflow":"issues","instanceId":"db9712c8-7c19-4d18-8fb1-8c01d326808f","status":"done","value":"drained","context":{"runInput":{"workers":2,"instanceId":"db9712c8-7c19-4d18-8fb1-8c01d326808f"},"cap":2,"active":[],"items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{},"status":"drained"},"children":[],"waiting":[],"held":[],"assembly":"ca3b0e760c85","output":{"status":"drained","items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{}}}
280
+ {"runId":"7c248a11-445a-4123-8ce2-834cb57706d7","workflow":"issues","instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4","startedAt":"2026-11-06T19:20:02.886Z","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"status":"done","value":"drained","context":{"runInput":{"workers":2,"instanceId":"1d3090c8-645b-4e22-b343-68d548aab5f4"},"cap":2,"active":[],"items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{},"held":[],"ready":[],"claimed":{},"again":[],"latch":false,"taken":3,"carried":[],"refused":[],"status":"drained"},"children":[],"waiting":[],"held":[],"answers":[{"gate":"issue-2.body.review","type":"approve","at":"2026-11-06T19:21:49.638Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"source","type":"issues_changed","at":"2026-11-06T19:21:57.842Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-3.body.review","type":"approve","at":"2026-11-06T19:22:03.493Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}},{"gate":"issue-1.body.review","type":"approve","at":"2026-11-06T19:22:33.752Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}],"assembly":"f5bb024883b4","output":{"status":"drained","items":{"issue-2":{"branch":"jr2/issue-2"},"issue-3":{"branch":"jr2/issue-3"},"issue-1":{"branch":"jr2/issue-1"}},"failures":{}},"worktrees":[{"slot":"app","path":"/work/app/task","branch":"jr2/issue-3"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-2"},{"slot":"app","path":"/work/app/task","branch":"jr2/issue-1"}]}
274
281
  ```
275
282
 
276
283
  In the JSON, `context.items` holds the output of each worker that finished, by item id. `context.failures` holds the
@@ -306,9 +313,8 @@ In the JSON, `context.items` holds the output of each worker that finished, by i
306
313
  starts with ``task: no `branch` on the door and no Instance ID to derive one from``.
307
314
  - Without `cap`, one worker runs at a time. Each `task` worker has its own
308
315
  [Workspace](../reference/glossary.md#workspace). Thus the run has `cap` Sandboxes or fewer at one time.
309
- - The Pool takes the `wake` event only while it waits: workers run, and no item is ready. At other times,
310
- [`jr2 send`](../reference/cli.md#send) refuses it:
311
- `error: gate "source" is open, and the run does not take "<event>" now: no writing of it would move the run with this payload — not delivered (takes now: nothing)`.
316
+ - The Pool takes the `wake` event at any time while the run is open. While it waits, it calls `next` at once. While
317
+ `next` runs, it calls `next` once more after that call, so that the change is not lost.
312
318
  - `onDrained` takes only `"final"`: the run ends.
313
319
 
314
320
  ## Read next
@@ -360,6 +360,8 @@ Dial. Thus:
360
360
  - `jr2 up` gives each pod the model key of the providers that the definitions of its Agents name. For an Agent in a
361
361
  Sandbox, use a Dial model of a provider that a definition in that Sandbox names. For a Menu-only Agent, use a provider
362
362
  that a Menu-only definition of the Instance names.
363
+ - In the same way, `jr2 up` gives each pod the secret of each MCP server that the definitions of its Agents name. The
364
+ [MCP tools](../reference/glossary.md#mcp-tools) of an Agent come from its definition only. A Turn cannot change them.
363
365
 
364
366
  ## Continue a conversation
365
367
 
@@ -0,0 +1,290 @@
1
+ ---
2
+ title: Start a run for each alert
3
+ description: Make a standing Pool that starts one run for each alert that an outside service sends to a Hook.
4
+ ---
5
+
6
+ # Start a run for each alert
7
+
8
+ Start one run of a [Workflow](../reference/glossary.md#workflow) for each alert that an alert service sends to a
9
+ [Hook](../reference/glossary.md#hook).
10
+
11
+ The example has three Workflows and one Hook. The alert service sends each new issue to the Hook `sentry`. The Workflow
12
+ `alerts` is a standing [Pool](../reference/glossary.md#pool) of runs: its push Source holds each alert, and the Pool
13
+ starts one run of `triage` for each alert, with at most 8 live at one time. A `triage` run waits until a person takes
14
+ the alert or dismisses it.
15
+
16
+ `@jr2/orchestrator/hooks/webhook` has `webhook()`, which makes the kind of a service that jr2 ships no kind for. It is
17
+ part of `@jr2/orchestrator`, so your [Instance](../reference/glossary.md#instance) installs nothing more.
18
+
19
+ ## Write the kind of the service
20
+
21
+ The alert service signs the raw body with a shared secret, and puts the HMAC-SHA256 in hex in the header
22
+ `sentry-hook-signature`. The header `sentry-hook-resource` and the field `action` give the event key, such as
23
+ `issue.created`. The header `request-id` is the id of the delivery. Save the kind as `hooks/sentry.ts`, beside
24
+ `workflows/`, because it is not a Workflow:
25
+
26
+ ```ts
27
+ import { z } from "zod";
28
+ import { webhook } from "@jr2/orchestrator/hooks/webhook";
29
+
30
+ // An alert service that jr2 ships no kind for. The service signs the raw body with a shared secret
31
+ // (sha256, hex) in the header sentry-hook-signature. The event key is the resource and its action,
32
+ // such as issue.created. The module is in `hooks/`, not in `workflows/`, because it is not a Workflow.
33
+ export const sentry = webhook({
34
+ id: "sentry",
35
+ events: {
36
+ "issue.created": z.looseObject({
37
+ data: z.looseObject({
38
+ issue: z.looseObject({ id: z.string(), title: z.string(), level: z.string() }),
39
+ }),
40
+ }),
41
+ },
42
+ verify: { mac: { header: "sentry-hook-signature" } },
43
+ key: { name: { header: "sentry-hook-resource" }, action: { json: "action" } },
44
+ delivery: { header: "request-id" },
45
+ });
46
+
47
+ declare module "@jr2/orchestrator/hooks" {
48
+ interface HookKinds {
49
+ sentry: typeof sentry;
50
+ }
51
+ }
52
+ ```
53
+
54
+ The `declare module` block adds the kind to the types of the handlers. A handler for an event key that the kind does not
55
+ give does not compile.
56
+
57
+ ## Write the Workflow of one alert
58
+
59
+ Save `workflows/triage.ts`. Its door takes the issue id and the title of one alert:
60
+
61
+ ```ts
62
+ import { assign } from "xstate";
63
+ import { z } from "zod";
64
+ import { defineEvent, jr2Setup } from "@jr2/orchestrator";
65
+
66
+ // The two answers of the person on call.
67
+ const take = defineEvent({
68
+ name: "take",
69
+ description: "I take this alert.",
70
+ input: z.object({ who: z.string().min(1) }),
71
+ });
72
+ const dismiss = defineEvent({
73
+ name: "dismiss",
74
+ description: "This alert needs no work. Say why.",
75
+ input: z.object({ reason: z.string().min(1) }),
76
+ });
77
+
78
+ type Context = { issue: string; title: string; who?: string };
79
+
80
+ // One run for each alert. The run waits until a person takes the alert or dismisses it.
81
+ export const machine = jr2Setup({
82
+ types: {} as { input: { issue: string; title: string }; context: Context },
83
+ events: [take, dismiss],
84
+ }).createMachine({
85
+ id: "triage",
86
+ // The door: the alerts Pool sends this input for each alert.
87
+ input: z.object({ issue: z.string(), title: z.string() }),
88
+ context: ({ input }) => ({ issue: input.issue, title: input.title }),
89
+ initial: "waiting",
90
+ states: {
91
+ waiting: {
92
+ invoke: {
93
+ src: "gate",
94
+ input: ({ context }) => ({ meta: { issue: context.issue, title: context.title } }),
95
+ },
96
+ on: {
97
+ take: { target: "taken", actions: assign({ who: ({ event }) => event.who }) },
98
+ dismiss: "dismissed",
99
+ },
100
+ },
101
+ taken: { type: "final" },
102
+ dismissed: { type: "final" },
103
+ },
104
+ });
105
+ ```
106
+
107
+ ## Write the standing Pool
108
+
109
+ Save `workflows/alerts.ts`:
110
+
111
+ ```ts
112
+ import { z } from "zod";
113
+ import { carry, dispatch, pool, skip, source } from "@jr2/orchestrator";
114
+ import type {} from "../hooks/sentry.ts";
115
+
116
+ // The door of the triage Workflow: each alert is one run of it.
117
+ const triageDoor = z.object({ issue: z.string(), title: z.string() });
118
+
119
+ // A push Source: each Hook delivery carries an alert, and the Pool holds it until a slot is free.
120
+ const alerts = source({
121
+ item: z.object({ issue: z.string(), title: z.string(), level: z.string() }),
122
+ hooks: {
123
+ sentry: {
124
+ "issue.created": ({ payload }) => {
125
+ const issue = payload.data.issue;
126
+ if (issue.level !== "error" && issue.level !== "fatal") return skip(`level is ${issue.level}`);
127
+ return carry({ issue: issue.id, title: issue.title, level: issue.level });
128
+ },
129
+ },
130
+ },
131
+ });
132
+
133
+ // A standing Pool of runs: one triage run for each alert, at most 8 live at once.
134
+ export const machine = pool(dispatch("triage", { input: triageDoor, wait: true }), {
135
+ id: "alerts",
136
+ standing: true,
137
+ source: alerts,
138
+ itemId: (alert) => `sentry-${alert.issue}`,
139
+ cap: 8,
140
+ itemInput: (alert) => ({ issue: alert.issue, title: alert.title }),
141
+ });
142
+ ```
143
+
144
+ - The Source has `item` and `hooks`, and no read: it is a push Source. Its handler gives `carry(item)` for an alert of
145
+ the level `error` or `fatal`, and `skip(reason)` for the other levels.
146
+ - The worker is `dispatch("triage", { input, wait: true })`, so each alert is a run of its own of `triage`.
147
+ - `itemId` is the key of the run. A second delivery of the same alert finds the run of the first one, and starts no
148
+ second run.
149
+ - `standing: true`: jr2 starts the run of the Pool, and keeps one live run of it.
150
+
151
+ ## Declare the Hook
152
+
153
+ Add the Hook to `jr2.config.ts`, and its secret to the `secrets` list:
154
+
155
+ ```ts
156
+ import { defineConfig, secret } from "@jr2/orchestrator";
157
+ import { sentry } from "./hooks/sentry.ts";
158
+
159
+ export default defineConfig({
160
+ // The Hook's secret is an entry of the list. jr2 up places SENTRY_HOOK in the Instance Effector.
161
+ secrets: [{ name: "SENTRY_HOOK", value: process.env.SENTRY_HOOK }],
162
+ hooks: {
163
+ // POST /hooks/sentry: the alert service signs each delivery with SENTRY_HOOK. The Hook brings
164
+ // items to the Pool of the alerts Workflow, through its source Gate, and to nothing more.
165
+ sentry: sentry({ secret: secret("SENTRY_HOOK"), may: { answer: ["alerts"] } }),
166
+ },
167
+ });
168
+ ```
169
+
170
+ `may.answer` names `alerts`, because the Hook brings items to the Pool through the Pool's `source` Gate. Put the secret
171
+ in `.env`:
172
+
173
+ ```sh
174
+ SENTRY_HOOK=<secret>
175
+ ```
176
+
177
+ `<secret>` is a long random value. Give the alert service the same value.
178
+
179
+ ## Converge
180
+
181
+ ```sh
182
+ jr2 up
183
+ ```
184
+
185
+ ```text
186
+ …
187
+ standing: sentry → alerts → triage
188
+ …
189
+ secret SENTRY_HOOK: an Effect secret in the Instance Effector (hook sentry)
190
+ the Instance Effector: holds SENTRY_HOOK
191
+ …
192
+ standing alerts → triage: live, run 0d08bbbc-d34e-47fb-9b4b-8e788e328eb4, 0 held items
193
+ …
194
+ ```
195
+
196
+ `jr2 up` puts the secret in the [Instance Effector](../reference/glossary.md#instance-effector) only, which checks each
197
+ signature. jr2 starts the run of `alerts` at this publish. Your run id is different.
198
+
199
+ ## Set up the alert service
200
+
201
+ The address of the Hook is `POST /hooks/sentry` on the Orchestrator. `jr2 up` makes the Service `jr2-orchestrator` on
202
+ port 4000, with no address outside the cluster. Give the Service an address that the alert service can reach, such as an
203
+ ingress. Then give the service the URL `https://<host>/hooks/sentry` and the secret.
204
+
205
+ ## Send a test delivery
206
+
207
+ You can sign a delivery yourself. Forward a port to the Orchestrator, and keep this command open:
208
+
209
+ ```sh
210
+ kubectl -n <namespace> port-forward svc/jr2-orchestrator 4000:4000
211
+ ```
212
+
213
+ In a second terminal, sign a body with the secret and send it:
214
+
215
+ ```sh
216
+ BODY='{"action":"created","data":{"issue":{"id":"4505","title":"TypeError in checkout","level":"error"}}}'
217
+ SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SENTRY_HOOK" | sed 's/^.* //')
218
+ curl -s -X POST http://localhost:4000/hooks/sentry \
219
+ -H "Content-Type: application/json" \
220
+ -H "sentry-hook-resource: issue" \
221
+ -H "request-id: test-1" \
222
+ -H "sentry-hook-signature: $SIG" \
223
+ -d "$BODY"
224
+ ```
225
+
226
+ ```text
227
+ {"delivery":"test-1","seen":false,"delivered":[],"refused":[],"ignored":0,"sources":[{"pool":"0d08bbbc-d34e-47fb-9b4b-8e788e328eb4","workflow":"alerts","source":"source","verdict":"held","key":"sentry-4505"}]}
228
+ ```
229
+
230
+ The Pool holds the alert, and starts a run of `triage` for it when a slot is free. Send the same command again. The
231
+ answer has `"seen":true`, because the Hook got the id `test-1` before, and the Pool starts no second run.
232
+
233
+ ## Follow the alerts
234
+
235
+ [`jr2 runs`](../reference/cli.md#runs) lists the run of `triage` for the alert.
236
+ [`jr2 status <run id>`](../reference/cli.md#status) of that run prints, in its `dispatched by:` line, the run of the
237
+ Pool, the key `sentry-4505`, and the Hook delivery that carried the alert. The person on call answers its Gate:
238
+
239
+ ```sh
240
+ jr2 send <run id> --gate waiting --event take --input '{"who":"<you>"}'
241
+ ```
242
+
243
+ `<run id>` is the id of the `triage` run, and `<you>` is your name. To see what the door of the Hook did with each
244
+ delivery that jr2 keeps (`runs.retain`, 30 days by default), print its log:
245
+
246
+ ```sh
247
+ jr2 hooks sentry
248
+ ```
249
+
250
+ To stop the alerts for a time, cancel the run of `alerts`. A cancel of a standing Pool is a pause: the Hook's answer has
251
+ the verdict `paused`, and the Pool holds nothing. [`jr2 run alerts`](../reference/cli.md#run) resumes it.
252
+
253
+ ## Rules
254
+
255
+ - `pool()` refuses a Pool of runs that does not stand:
256
+ ``pool("<id>"): a Pool of runs stands: give it `standing: true`. A Pool of runs that a Caller starts and that ends is not offered: its itemId is a key that lives for runs.retain past the run, so a second batch in that time does no work (ADR-0017)``
257
+ - `pool()` refuses a `dispatch` slot without `wait: true`:
258
+ ``pool("<id>"): its dispatch slot for "<workflow>" has no `wait: true` — with `started` alone, the Pool cannot know when a run ends, and `cap` counts nothing (ADR-0017)``
259
+ - `pool()` refuses a `dispatch` slot with a `target`:
260
+ ``pool("<id>"): its dispatch slot for "<workflow>" has a `target` — a Pool of runs on another Orchestrator is not designed: remove the target (ADR-0017)``
261
+ - `pool()` refuses a Pool of runs without `cap`:
262
+ ``pool("<id>"): a Pool of runs states its `cap`, a whole number above 0: the most live runs at once (ADR-0017)``
263
+ - `pool()` refuses a push Source on a Pool that does not stand:
264
+ `pool("<id>"): a push Source needs a standing Pool of runs: a Pool that ends when it drains would end at once (ADR-0017)`
265
+ - `jr2 up` refuses a standing Pool that is not the top Machine of its Workflow:
266
+ `refusing: workflow "<workflow>": machine "<machine>" composes the standing Pool "<id>" — a standing Pool is the top Machine of its Workflow, because jr2 keeps one run of each standing Workflow: move the Pool to a file of its own in workflows/ (ADR-0017)`
267
+ - `jr2 up` warns when two standing Pools dispatch to one Workflow:
268
+ ``warning: the standing Pools "<pool>" and "<pool>" each dispatch to "<workflow>": `cap` is for each Pool, so one item that two Sources carry can take a slot in each, and the caps add (ADR-0017)``
269
+ - The Orchestrator refuses `jr2 run` of a standing Workflow whose run is live, with the status code `409`:
270
+ ``"<workflow>" is a standing Workflow, and its run <run id> is live: jr2 keeps its run, and `jr2 run` only resumes a paused one (ADR-0017)``
271
+ - The Orchestrator refuses a `dispatch` to a standing Workflow:
272
+ `"<workflow>" is a standing Workflow: jr2 starts its run, and a dispatch cannot (ADR-0017)`
273
+ - When the Pool holds `hold` items (1000 by default), the door answers 429 with the verdict `hold-full`, and the service
274
+ can send the delivery again.
275
+ - While the Pool waits for the back-off of a hand-over, it has no live run, and the door answers 503.
276
+ - A Hook whose `may.answer` does not name the Workflow of the Pool brings it nothing.
277
+
278
+ ## Read next
279
+
280
+ - [Pools and Sources](../concepts/pools-and-sources.md)
281
+ - [Callers and Hooks](../concepts/callers-and-hooks.md)
282
+ - [Write a Hook kind](write-a-hook-kind.md), for a service that `webhook()` cannot read
283
+ - [Start a run from a run](start-a-run-from-a-run.md)
284
+ - Reference: [`pool()`](../reference/authoring-surface/pool.md), [`source()`](../reference/authoring-surface/source.md),
285
+ [`carry()`](../reference/authoring-surface/carry.md), [`skip()`](../reference/authoring-surface/skip.md),
286
+ [`dispatch()`](../reference/authoring-surface/dispatch.md) and [`webhook()`](../reference/hooks.md#webhook)
287
+ - [ADR-0017](../reference/decisions/0017-pool-over-a-source-port.md): a standing Pool, and a Hook that brings items to
288
+ it
289
+ - [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): a
290
+ Hook, and `webhook()`
@@ -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)