@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
@@ -0,0 +1,203 @@
1
+ ---
2
+ title: Give a Caller a scope
3
+ description: Make a named token for a CI job or a bridge, give it only the scopes that it needs, and rotate it.
4
+ ---
5
+
6
+ # Give a Caller a scope
7
+
8
+ Give a CI job, a bridge or a script a token of its own, with only the scopes that it needs.
9
+
10
+ A [Caller](../reference/glossary.md#caller) is a client that acts on the Instance from outside its
11
+ [Machines](../reference/glossary.md#machine). The CLI on your kubectl context is the Caller `token/instance`, and it can
12
+ do everything. A named Caller token can do less. Declare each one in
13
+ [`callers.tokens`](../reference/config.md#callers-tokens) of `jr2.config.ts`, with its scopes. This example declares
14
+ three. It needs the [Workflow](../reference/glossary.md#workflow) `release` with its
15
+ [Gate](../reference/glossary.md#gate) `review`: save `workflows/release.ts` from [Answer a Gate](answer-a-gate.md) in
16
+ your Instance first. `jr2 up` refuses a `{ workflow, gates }` item for a Workflow that the Instance does not have (see
17
+ [Rules](#rules)).
18
+
19
+ ```ts
20
+ import { defineConfig } from "@jr2/orchestrator";
21
+
22
+ export default defineConfig({
23
+ callers: {
24
+ tokens: {
25
+ // A CI job: it starts runs of release and follows them, and does nothing else.
26
+ ci: { may: { start: ["release"], read: ["release"] } },
27
+ // A bridge to a chat service: it reads each run and answers the Gates of any Workflow.
28
+ "chat-bridge": { may: { read: "all", answer: "all" } },
29
+ // A release manager: it answers only the Gate "review" of release, and reads the runs of release.
30
+ "release-manager": { may: { read: ["release"], answer: [{ workflow: "release", gates: ["review"] }] } },
31
+ },
32
+ },
33
+ });
34
+ ```
35
+
36
+ Each scope is `"all"`, or a list of the names of the Workflows that it opens:
37
+
38
+ | Scope | What the Caller can do |
39
+ | -------- | ----------------------------------------------------------------------------------- |
40
+ | `start` | Start a run. |
41
+ | `read` | Read the full state of a run, follow it, list the runs, and read its conversations. |
42
+ | `answer` | Answer a Gate, and read the open Gates. |
43
+ | `cancel` | Cancel a run. |
44
+ | `steer` | Send a [Steering message](../reference/glossary.md#steering-message) to an Agent. |
45
+
46
+ The list of `answer` can also hold an item that names single Gates of a Workflow: `{ workflow, gates }`. A Caller with
47
+ `answer: ["triage", { workflow: "release", gates: ["review"] }]` answers each Gate of `triage`, and only the Gate
48
+ `review` of `release`. The name of a Gate is its invoke id: the `id` of its `invoke`, or else the path of its state,
49
+ such as `review` or `review.open`. `jr2 up` refuses a name that the Machines of the Workflow do not have, and a name
50
+ that two Machines of the Workflow have. It cannot check the Gates of a runtime [Chart](../reference/glossary.md#chart),
51
+ so for a [Chart wrapper](../reference/glossary.md#chart-wrapper) it warns and does not refuse.
52
+ [`jr2 gates`](../reference/cli.md#gates) and `GET /runs/:runId` show only the Gates that the Caller can answer. A value
53
+ of the `groups` map of an issuer takes the same item. Only `answer` takes it.
54
+
55
+ In the example, `release-manager` answers the Gate `review` of `release`, and no other Gate. When `release` has a second
56
+ Gate, such as a Gate for a security check, a different Caller answers it. `jr2 gates` with the token of
57
+ `release-manager` lists only the `review` Gates.
58
+
59
+ A scope that the entry does not give opens nothing. A scope does not limit what the run does: a run that a Caller starts
60
+ can start a run of a different Workflow with a [`dispatch`](../reference/authoring-surface/dispatch.md) Effect.
61
+
62
+ ## Make the tokens
63
+
64
+ Run `jr2 up`. It makes the value of each new name one time, and keeps it in a Secret of its own:
65
+
66
+ ```sh
67
+ jr2 up
68
+ ```
69
+
70
+ ```text
71
+ …
72
+ caller token "chat-bridge": made — `jr2 caller token chat-bridge` prints it, and it works when `jr2 up` says converged
73
+ caller token "ci": made — `jr2 caller token ci` prints it, and it works when `jr2 up` says converged
74
+ caller token "release-manager": made — `jr2 caller token release-manager` prints it, and it works when `jr2 up` says converged
75
+ …
76
+ ```
77
+
78
+ The [Orchestrator](../reference/glossary.md#orchestrator) gets only the SHA-256 digest of each value. A value is `jr2c_`
79
+ and 43 more characters.
80
+
81
+ ## Give the token to the Caller
82
+
83
+ Print the value with [`jr2 caller token`](../reference/cli.md#caller-token). It reads the Secret over the Kubernetes
84
+ API, so your Kubernetes RBAC decides who can read it:
85
+
86
+ ```sh
87
+ jr2 caller token ci
88
+ ```
89
+
90
+ ```text
91
+ → context <context> / namespace <namespace>
92
+ jr2c_<value>
93
+ ```
94
+
95
+ Only the value goes to stdout. Put it in the secret store of the CI job, as `JR2_TOKEN`. Keep it as you keep a password.
96
+
97
+ ## Check what the token can do
98
+
99
+ The Caller reaches the Orchestrator at a URL. In a new terminal, forward a port to the Orchestrator. Keep this command
100
+ open:
101
+
102
+ ```sh
103
+ kubectl -n <namespace> port-forward svc/jr2-orchestrator 4000:4000
104
+ ```
105
+
106
+ Run [`jr2 whoami`](../reference/cli.md#whoami) with the token:
107
+
108
+ ```sh
109
+ JR2_URL=http://localhost:4000 JR2_TOKEN=$(jr2 caller token ci) jr2 whoami
110
+ ```
111
+
112
+ ```text
113
+ → http://localhost:4000
114
+ {"via":"token","name":"ci","may":{"start":["release"],"read":["release"]}}
115
+ ```
116
+
117
+ The CI job runs `jr2 run`, `jr2 status` and `jr2 watch` with the same two variables, as
118
+ [Start a run](start-a-run.md#use-the-cli-without-the-instance-folder) shows. The run records its Caller in `startedBy`,
119
+ and `jr2 status` prints it on stderr as `started by: token/ci`.
120
+
121
+ ## Rotate a token
122
+
123
+ Run [`jr2 caller rotate`](../reference/cli.md#caller-rotate). It makes a new value, and prints no value:
124
+
125
+ ```sh
126
+ jr2 caller rotate ci
127
+ ```
128
+
129
+ ```text
130
+ → context <context> / namespace <namespace>
131
+ caller token "ci": waiting for the kubelet to deliver jr2-caller-digests to the Orchestrator
132
+ caller token "ci": rotated — the old value no longer works; `jr2 caller token ci` prints the new one
133
+ ```
134
+
135
+ Give the new value to the Caller. No pod restarts.
136
+
137
+ ## Remove a token
138
+
139
+ Remove its entry from `callers.tokens`, and run `jr2 up`. It deletes the Secret of the name, and the value stops
140
+ working:
141
+
142
+ ```text
143
+ …
144
+ caller token "chat-bridge": deleted — callers.tokens no longer declares it
145
+ …
146
+ ```
147
+
148
+ ## Rules
149
+
150
+ - The config load refuses a name that is not a DNS label of 40 characters or fewer:
151
+ `<file> at callers.tokens.<name>: a token name is lowercase letters, digits and -, at most 40, starts with a letter and ends with a letter or digit — <shape>`.
152
+ `<shape>` is the shape of the `callers` block.
153
+ - The config load refuses the name `instance`, which is the name of the Instance token:
154
+ `<file> at callers.tokens.instance: the name instance is the Instance token's — <shape>`
155
+ - The config load refuses a scope that is not one of the five, and a list with no Workflow:
156
+ `<file> at callers.tokens.<name>.may<path>: <problem> — <shape>`
157
+ - The config load refuses an item `{ workflow, gates }` in a scope other than `answer`. `<problem>` is then:
158
+ `only "answer" can name single Gates; "<scope>" names whole Workflows, because it acts on a whole run`
159
+ - `jr2 up` and `jr2 check` check each `{ workflow, gates }` item of `callers.tokens` and of the `groups` map of each
160
+ issuer. Each line starts with the place of the item: `callers.tokens.<name>.may.answer` for a token, or
161
+ `callers.issuers.<index>.groups.<group>.answer` for an issuer. The lines below show the token form.
162
+ - They refuse a Gate name that the Machines of the Workflow do not have. The line lists the Gates that the Workflow has,
163
+ as `its Gates are "<gate>", "<gate>"`, or `it has no Gate` when it has none:
164
+ `callers.tokens.<name>.may.answer: Workflow "<workflow>" has no Gate "<gate>" — a Gate's name is its invoke id: the invoke id that the author wrote, or else the key path of its gated state; its Gates are "<gate>", "<gate>" (ADR-0011, ADR-0078)`
165
+ - They refuse a Gate name that two Machines of the Workflow have:
166
+ `callers.tokens.<name>.may.answer: Gate "<gate>" of Workflow "<workflow>" is in more than one Machine (<machines>), so the one name would open each of those Gates: give one of them an invoke id of its own (ADR-0078)`
167
+ - They refuse an item for a Workflow that the [Assembly](../reference/glossary.md#assembly) does not register:
168
+ `callers.tokens.<name>.may.answer: { workflow: "<workflow>", gates } names the Gates of a Workflow that this Assembly does not register, so jr2 cannot check them: name a registered Workflow or a Chart file (ADR-0078)`
169
+ - For a Chart wrapper that takes runtime Charts, a Gate name that jr2 cannot find is a warning, not a refusal. The line
170
+ ends with `; a runtime Chart of it may have one, which jr2 up cannot check`.
171
+ - An item for `<wrapper>/<name>` that is no Chart file of the Assembly is a warning when the wrapper takes runtime
172
+ Charts:
173
+ `callers.tokens.<name>.may.answer: { workflow: "<wrapper>/<name>", gates } names a Chart that is no Chart file of this Assembly; a runtime Chart's Gates cannot be checked at jr2 up (ADR-0070, ADR-0078)`
174
+ - When the wrapper takes no runtime Chart, the same item is a refusal, and the line ends with
175
+ `, and "<wrapper>" takes no runtime Chart`.
176
+ - The Orchestrator refuses a value that it does not know, such as a value from before a rotation, with the status code
177
+ `401`: `unauthorized`. `jr2 caller rotate` returns when the Orchestrator has read the new digest, so the old value no
178
+ longer works then.
179
+ - The Orchestrator refuses a request that needs a scope that the token does not have for the Workflow, with the status
180
+ code `403`: `the Caller token/<name> needs the "<scope>" scope for Workflow "<workflow>" (ADR-0078)`
181
+ - It refuses an answer to a Gate that an `answer` item with `gates` does not name, with the status code `403`:
182
+ `the Caller token/<name> needs the "answer" scope for Gate "<gate>" of Workflow "<workflow>" (ADR-0078)`. The body
183
+ also holds `scope`, `workflow` and `gate`.
184
+ - It refuses a request that needs a scope that the token has for no Workflow, with the status code `403`:
185
+ `the Caller token/<name> has no "<scope>" scope for any Workflow (ADR-0078)`
186
+ - It refuses a route that no scope opens, such as a runtime Chart, with the status code `403`:
187
+ `this route needs the Instance token: no scope opens it, so the Caller token/<name> cannot`
188
+ - `jr2 caller token` prints nothing on stdout and exits with `1` when the Secret is not there:
189
+ ``error: no Caller token "<name>" here (context <context>, namespace <namespace>) — declare it in callers.tokens of jr2.config.ts, then `jr2 up` makes it``
190
+ - `jr2 caller rotate` changes nothing and exits with `1` when the Secret is not there:
191
+ ``error: no Caller token "<name>" in namespace "<namespace>" — declare it in callers.tokens of jr2.config.ts and run `jr2 up` (tokens here: <names>)``.
192
+ The part `(tokens here: <names>)` shows only when the namespace has a token.
193
+ - `jr2 caller token` and `jr2 caller rotate` take `-n` and `--context`, and not `--url`: the Orchestrator holds no
194
+ value.
195
+
196
+ ## Read next
197
+
198
+ - [Callers and Hooks](../concepts/callers-and-hooks.md)
199
+ - [Secrets and trust](../concepts/secrets-and-trust.md)
200
+ - [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): the
201
+ Callers, the scopes, and the named tokens.
202
+ - [ADR-0014](../reference/decisions/0014-observation-is-open-run-state-is-not.md): which scope opens which route.
203
+ - [Start a run](start-a-run.md)
@@ -0,0 +1,422 @@
1
+ ---
2
+ title: Give an Agent MCP tools
3
+ description: Declare MCP servers in jr2.config.ts, and give an Agent the tools that it can call on each server.
4
+ ---
5
+
6
+ # Give an Agent MCP tools
7
+
8
+ Declare the MCP servers of your [Instance](../reference/glossary.md#instance) in `jr2.config.ts`, and give an
9
+ [Agent](../reference/glossary.md#agent) the tools that it can call on each server.
10
+
11
+ An MCP server gives tools for a service, such as a chat, a ticket tracker or a forge. The tools that an Agent gets from
12
+ the servers that its definition names are its [MCP tools](../reference/glossary.md#mcp-tools). The model calls them in
13
+ its [Turn](../reference/glossary.md#turn), as it calls its [Working tools](../reference/glossary.md#working-tools). A
14
+ call of an MCP tool is not a pick: the Turn goes on after it. When a call needs the approval of a person, the
15
+ [Machine](../reference/glossary.md#machine) makes it.
16
+ [Call an MCP tool from a Machine](call-an-mcp-tool-from-a-machine.md) tells how.
17
+
18
+ In this [Workflow](../reference/glossary.md#workflow), a Menu-only Agent reads the stand-up channel of two chat
19
+ workspaces, Acme and a partner. Then it posts one summary to the Acme workspace, and picks `posted`. Each workspace has
20
+ its own server entry and its own token. Save the file as `workflows/standup.ts` in your Instance:
21
+
22
+ **Anthropic**
23
+
24
+ ```ts
25
+ import { assign } from "xstate";
26
+ import { z } from "zod";
27
+ import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
28
+
29
+ // The Agent's one pick. Each MCP tool call before it is not a pick, and the Turn goes on.
30
+ const posted = defineEvent({
31
+ name: "posted",
32
+ description: "Call this after you posted the summary. Give the link to your message.",
33
+ input: z.object({ link: z.string().describe("The link to the message that you posted.") }),
34
+ });
35
+
36
+ const door = z.object({ day: z.string().describe("The day of the stand-up, such as 2026-11-04.") });
37
+
38
+ type Input = z.infer<typeof door>;
39
+ type Ctx = Input & { link?: string; fault?: string };
40
+
41
+ export const machine = jr2Setup({
42
+ types: {} as { context: Ctx; input: Input; output: { link: string } },
43
+ events: [posted],
44
+ actors: {
45
+ reporter: agent({
46
+ model: "anthropic/claude-haiku-5-5",
47
+ instructions: "You read what two teams said at their stand-up, and you post one short summary for both teams.",
48
+ // No Sandbox and no Working tools. Its Turns run on the Instance Harness.
49
+ workspace: "none",
50
+ // The servers of the mcp block of jr2.config.ts, by name, each with the tools that the Agent can call.
51
+ mcp: [
52
+ { server: "slack-acme", tools: ["read_channel", "post_message"] },
53
+ // Only read: the Agent cannot post to the partner's workspace.
54
+ { server: "slack-partner", tools: ["read_channel"] },
55
+ ],
56
+ }),
57
+ fail: fail(),
58
+ },
59
+ }).createMachine({
60
+ id: "standup",
61
+ input: door,
62
+ context: ({ input }) => ({ day: input.day }),
63
+ initial: "summarizing",
64
+ states: {
65
+ summarizing: {
66
+ invoke: {
67
+ src: "reporter",
68
+ input: ({ context }) => ({
69
+ prompt: `Read #standup in the Acme and the partner workspaces for ${context.day}. Post one summary to #standup of Acme.`,
70
+ }),
71
+ },
72
+ on: {
73
+ posted: { target: "done", actions: assign({ link: ({ event }) => event.link }) },
74
+ // A server that does not answer at the first Turn fails the Turn. The reason names the server.
75
+ "agent.fault": { target: "failed", actions: assign({ fault: ({ event }) => event.reason }) },
76
+ },
77
+ },
78
+ done: { type: "final" },
79
+ // Not a final state: the run ends `failed`, with the reason of the Turn.
80
+ failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
81
+ },
82
+ output: ({ context }) => ({ link: context.link ?? "" }),
83
+ });
84
+ ```
85
+
86
+ **Local**
87
+
88
+ ```ts
89
+ import { assign } from "xstate";
90
+ import { z } from "zod";
91
+ import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
92
+
93
+ // The Agent's one pick. Each MCP tool call before it is not a pick, and the Turn goes on.
94
+ const posted = defineEvent({
95
+ name: "posted",
96
+ description: "Call this after you posted the summary. Give the link to your message.",
97
+ input: z.object({ link: z.string().describe("The link to the message that you posted.") }),
98
+ });
99
+
100
+ const door = z.object({ day: z.string().describe("The day of the stand-up, such as 2026-11-04.") });
101
+
102
+ type Input = z.infer<typeof door>;
103
+ type Ctx = Input & { link?: string; fault?: string };
104
+
105
+ export const machine = jr2Setup({
106
+ types: {} as { context: Ctx; input: Input; output: { link: string } },
107
+ events: [posted],
108
+ actors: {
109
+ reporter: agent({
110
+ model: "local/qwen3.6-35b-a3b",
111
+ instructions: "You read what two teams said at their stand-up, and you post one short summary for both teams.",
112
+ // No Sandbox and no Working tools. Its Turns run on the Instance Harness.
113
+ workspace: "none",
114
+ // The servers of the mcp block of jr2.config.ts, by name, each with the tools that the Agent can call.
115
+ mcp: [
116
+ { server: "slack-acme", tools: ["read_channel", "post_message"] },
117
+ // Only read: the Agent cannot post to the partner's workspace.
118
+ { server: "slack-partner", tools: ["read_channel"] },
119
+ ],
120
+ }),
121
+ fail: fail(),
122
+ },
123
+ }).createMachine({
124
+ id: "standup",
125
+ input: door,
126
+ context: ({ input }) => ({ day: input.day }),
127
+ initial: "summarizing",
128
+ states: {
129
+ summarizing: {
130
+ invoke: {
131
+ src: "reporter",
132
+ input: ({ context }) => ({
133
+ prompt: `Read #standup in the Acme and the partner workspaces for ${context.day}. Post one summary to #standup of Acme.`,
134
+ }),
135
+ },
136
+ on: {
137
+ posted: { target: "done", actions: assign({ link: ({ event }) => event.link }) },
138
+ // A server that does not answer at the first Turn fails the Turn. The reason names the server.
139
+ "agent.fault": { target: "failed", actions: assign({ fault: ({ event }) => event.reason }) },
140
+ },
141
+ },
142
+ done: { type: "final" },
143
+ // Not a final state: the run ends `failed`, with the reason of the Turn.
144
+ failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
145
+ },
146
+ output: ({ context }) => ({ link: context.link ?? "" }),
147
+ });
148
+ ```
149
+
150
+ The Agent uses the model of the tab. Add the two servers and their tokens to `jr2.config.ts`, beside the fields and the
151
+ entries that are there:
152
+
153
+ ```ts
154
+ import { defineConfig } from "@jr2/orchestrator";
155
+
156
+ export default defineConfig({
157
+ // The two tokens. The values come from .env.
158
+ secrets: [
159
+ { name: "SLACK_ACME_TOKEN", value: process.env.SLACK_ACME_TOKEN },
160
+ { name: "SLACK_PARTNER_TOKEN", value: process.env.SLACK_PARTNER_TOKEN },
161
+ ],
162
+ // Two servers of one service, each with its own token. The names are yours.
163
+ mcp: {
164
+ "slack-acme": { url: "https://mcp.slack.example/mcp", secret: "SLACK_ACME_TOKEN" },
165
+ "slack-partner": { url: "https://mcp.slack.example/mcp", secret: "SLACK_PARTNER_TOKEN", timeout: "90s" },
166
+ },
167
+ });
168
+ ```
169
+
170
+ `https://mcp.slack.example/mcp` is an example. Write the URL of the MCP endpoint of your service. Then put the two
171
+ tokens in `.env`:
172
+
173
+ ```sh
174
+ SLACK_ACME_TOKEN=<acme token>
175
+ SLACK_PARTNER_TOKEN=<partner token>
176
+ ```
177
+
178
+ `<acme token>` and `<partner token>` are the tokens that the service gave you for each workspace. Run
179
+ [`jr2 up`](../reference/cli.md#up). Then start a run:
180
+
181
+ ```sh
182
+ jr2 run standup --input '{"day":"2026-11-04"}'
183
+ ```
184
+
185
+ ## Declare the servers
186
+
187
+ The [`mcp`](../reference/config.md#mcp) block of `jr2.config.ts` gives each server a name of yours. An Agent and an
188
+ [Effect](../reference/glossary.md#effect) name a server by this name, and never by its URL or its token. A name has
189
+ lowercase letters, digits and `-`, and it starts with a letter. `jr2` is not a server name, because the tools of the
190
+ [Menu](../reference/glossary.md#menu) have the names `mcp__jr2__<event>`.
191
+
192
+ A server is one of two kinds:
193
+
194
+ - **A remote server**, `{ url, secret?, timeout? }`. The Harness speaks to it over Streamable HTTP. `secret` is the name
195
+ of an entry of the [`secrets`](../reference/config.md#secrets) list: the bearer token of the server. With a `secret`,
196
+ the URL is `https://`. With no `secret`, it can be `http://`.
197
+ - **A stdio server**, `{ command, args?, timeout? }`. It is a program of the
198
+ [Sandbox Image](../reference/glossary.md#sandbox-image). See [A stdio server](#a-stdio-server).
199
+
200
+ `timeout` is the longest time of one call, such as `"90s"` or `"5m"`. The default is 60 seconds. After it, jr2 cancels
201
+ the call, and the model reads that the server did not answer. The server can still do the work of the call.
202
+
203
+ The token of a remote server is a [Held secret](../reference/glossary.md#held-secret). The Harness sends
204
+ `Authorization: Bearer <Stand-in>`, and the [Custodian](../reference/glossary.md#custodian) puts the token in place of
205
+ the [Stand-in](../reference/glossary.md#stand-in). The model and the
206
+ [Workspace code](../reference/glossary.md#workspace-code) never read the token. With no `hosts`, `headers` or `paths` on
207
+ the entry of the `secrets` list, jr2 binds the token to the host of the server's URL, in the header `authorization`.
208
+ When you set them, they must let the Stand-in through to the URL.
209
+
210
+ ## Name the servers in the Agent
211
+
212
+ The `mcp` field of the definition is a list. Each entry names one server, and `tools` is the allowlist for a remote
213
+ server:
214
+
215
+ - `tools: ["read_channel", "post_message"]`: the Agent gets these tools of the server, and no other tool.
216
+ - `tools: "*"`: the Agent gets each tool of the server.
217
+
218
+ A stdio server takes no `tools`. The Agent gets each tool of the server.
219
+
220
+ The model reads each tool as `mcp__<server>__<tool>`, such as `mcp__slack-acme__post_message`. That name has only
221
+ letters, digits, `_` and `-`, and at most 128 characters. When `"*"` gives a tool whose name breaks this rule, the
222
+ Harness does not give the tool, and it writes one line in its log that names it.
223
+
224
+ An Agent can name a server only one time. A Menu-only Agent can name remote servers only. Its Turns run on the
225
+ [Instance Harness](../reference/glossary.md#instance-harness), where no Workspace code runs.
226
+
227
+ ## When the allowlist is a guarantee
228
+
229
+ The allowlist guards the token of the server. For a remote server, it is a guarantee: the Harness gives the model only
230
+ those tools, and it runs only those tools. The Harness holds the Stand-in of the token in a file that no Workspace code
231
+ can read. Thus only a call of the Harness gets the token from the Custodian.
232
+
233
+ Workspace code in the Agent's [Sandbox](../reference/glossary.md#sandbox) can spend the token in three cases. In each
234
+ case, jr2 takes only `tools: "*"`:
235
+
236
+ - The [`workspace()`](../reference/authoring-surface/workspace.md) of the Sandbox lists the secret under `secrets`.
237
+ - A `command` Effect of that Workspace names the secret in its `env`.
238
+ - An `http` Effect of that Workspace sends a file of a Worktree with the secret.
239
+
240
+ In these cases, a file that the Agent wrote can call each tool of the server with the token. Thus a list would not be a
241
+ limit, and `jr2 up` refuses it. A Menu-only Agent keeps its list, because its Turns run on the Instance Harness.
242
+
243
+ The scopes of the token are the outer limit. The allowlist makes what the Agent can do with a token smaller, and it
244
+ never makes it larger. Give the server a token with the smallest scopes that the Agent needs. jr2 cannot see one token
245
+ under two secret names. When you do that, a list on one name does not limit what the other name can do.
246
+
247
+ ## Two servers of one service
248
+
249
+ In the example, `slack-acme` and `slack-partner` have the same URL, and each has its own token. Each Agent that names
250
+ one of them gets its own tools: `mcp__slack-acme__read_channel` and `mcp__slack-partner__read_channel` are two tools.
251
+
252
+ The two tokens go to one host in one header. The Custodian chooses the token by the Stand-in that the request carries.
253
+ Thus each call goes out with the token of its own server.
254
+
255
+ ## A stdio server
256
+
257
+ A stdio server is a program in the Sandbox Image. Name its `command` and its `args` in the `mcp` block. The Harness
258
+ starts the program for each conversation of an Agent that names it, at the first Turn. The program runs until the
259
+ conversation ends, or until the Harness stops. Two Agents never share one process. When the process stops, its call
260
+ fails, and the next call starts the program again.
261
+
262
+ The program is Workspace code. The Harness starts it through the spawner, inside the Landlock confinement of the
263
+ Sandbox, in the directory of the Turn. Its env has only the Stand-ins that the `workspace()` grants, so a stdio server
264
+ has no `secret` of its own. [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md) adds a stdio server to the
265
+ Sandbox Image.
266
+
267
+ ## The tools of a conversation
268
+
269
+ `jr2 up` asks each remote server for its tools before it converges, from the check pod and through its Custodian. Each
270
+ name in an allowlist must be a tool of the server:
271
+
272
+ ```text
273
+ …
274
+ mcp: listing the tools of "slack-acme" at <your MCP URL> on the Instance Harness, through a Custodian that holds SLACK_ACME_TOKEN, with its Stand-in
275
+ mcp: listing the tools of "slack-partner" at <your MCP URL> on the Instance Harness, through a Custodian that holds SLACK_PARTNER_TOKEN, with its Stand-in
276
+ provider: the check pod has up to 5 min to start, then up to 6 min for its model and 1 min for each of its 2 MCP servers
277
+ …
278
+ mcp: "slack-acme" lists 2 tool(s); every allowlisted name is there
279
+ mcp: "slack-partner" lists 2 tool(s); every allowlisted name is there
280
+ …
281
+ ```
282
+
283
+ `<your MCP URL>` is the URL of your server. The check pod has the placement of the pods that run the Agent: here, the
284
+ Instance Harness. The number in `lists 2 tool(s)` is the number of tools of the server, and yours can be different.
285
+ `jr2 up` stops when a name is not there, and when the server refuses the token. It only warns when the server does not
286
+ answer. `--skip-provider-preflight` skips the check of the tools and the check of the models. `jr2 up` does not check a
287
+ stdio server.
288
+
289
+ The Harness reads the tools of each server at the first Turn of a conversation. It keeps the allowed tools, sorted by
290
+ name, and it records them with the conversation. Each later Turn of the conversation gets the same tools, also after a
291
+ park and after a restart of the Harness. A change in the tools block makes the model provider read the full conversation
292
+ again, so the Harness does not read the list again. When the server removes a tool, a call of it fails with the text
293
+ `<server> no longer has the tool <tool>`. A new conversation gets the new list.
294
+
295
+ The tools block has the Working tools first, then the MCP tools, then the Menu. The
296
+ [Briefing](../reference/glossary.md#briefing) of an Agent with MCP tools tells the model that a call of an MCP tool is
297
+ not a pick and does not end its Turn (see [The Briefing](../concepts/agents-and-turns.md#the-briefing)).
298
+
299
+ The printed conversation shows a call of an MCP tool as `[<server>/<tool>] <input>`, such as
300
+ `[slack-acme/post_message] {"channel":"C024","text":"…"}`. A pick prints by the name of its event, so the two never look
301
+ the same. The Harness does not print the result of an MCP tool. [Watch a run](watch-a-run.md) gives each line.
302
+
303
+ ## What the model reads
304
+
305
+ - The text of the result. A result with `structuredContent` and no text gives the JSON as text.
306
+ - The images of the result, such as a screenshot. A model that reads no image gets a line of text in place of each
307
+ image. An image larger than about 5 MiB is one line of jr2:
308
+ `[jr2: the result has an image that is too large or not base64, which the model does not read]`.
309
+ - One line of jr2 for each audio or resource in the result:
310
+ `[jr2: the result has <kind>, which the model does not read]`.
311
+ - A result with `isError` as an error.
312
+ - At most 64 KiB of a result. At the cut, the model reads `[jr2 cut this result at 64 KiB]`.
313
+ - When a call passes the `timeout` of its server:
314
+ `<server> did not answer the call of <tool> in <seconds> s, so jr2 cancelled the call. The server can still do the work that the call asked for`
315
+ - When the server asks for input during a call:
316
+ `<server> asked for input during the call of <tool>, and no one can answer it: an Agent's MCP call cannot ask for input. The call failed`
317
+ - When a stdio server stops during a call: `<server> stopped while it ran <tool>. The next call starts it again`
318
+
319
+ When a server does not answer at the first Turn of a conversation, the Turn fails before the model reads anything. The
320
+ Machine gets [`agent.fault`](../reference/mechanism-events.md#agent-fault), and its `reason` is
321
+ `<server> did not answer: <reason>`. In the example, `agent.fault` goes to `failed`, and the run ends `failed` with that
322
+ reason. At a later call, the model reads the same text, and the Turn goes on.
323
+
324
+ A result comes from outside the Instance. It can hold text that tries to give the model orders, as a web page can. The
325
+ allowlist limits what the Agent can do with the token. The Machine limits what the run does.
326
+
327
+ ## Bind the servers of a packaged Machine
328
+
329
+ A Machine from a package names the servers that its Agents need. When your `mcp` block has other names, bind the Agent
330
+ to your servers with [`customize()`](../reference/authoring-surface/customize.md), on the line that registers the
331
+ Machine. For example, `customize(standup, { agents: { reporter: { mcp: [{ server: "chat-main", tools: "*" }] } } })`
332
+ gives the `reporter` each tool of your server `chat-main`.
333
+
334
+ The list replaces the whole `mcp` list of the Agent. [Bind a shipped Machine's Open parts](bind-open-parts.md) tells how
335
+ to bind the parts of a Machine that you get from a package.
336
+
337
+ ## Rules
338
+
339
+ When the config loads, it refuses:
340
+
341
+ - the name `jr2`:
342
+ `<file> at mcp.jr2: the server name jr2 is reserved — the Menu's tools are named mcp__jr2__<event>; give the server another name (ADR-0079)`
343
+ - a name with other characters:
344
+ `<file> at mcp.<name>: a server name is lowercase letters, digits and -, and starts with a letter ([a-z][a-z0-9-]*) — it is the <server> of the tool names mcp__<server>__<tool> (ADR-0079)`
345
+ - a `secret` that the `secrets` list does not have:
346
+ `<file> at mcp.<name>.secret: "<NAME>" is not an entry of secrets — add it: secrets: [{ name: "<NAME>", value: process.env.<NAME> }] (ADR-0079)`
347
+ - a `timeout` that is not a positive duration:
348
+ `<file> at mcp.<name>.timeout: a timeout is a positive duration, such as "90s", "5m" or "1h" — an entry is <name>: { url, secret?, timeout? } for a remote server, or <name>: { command, args?, timeout? } for a stdio server (ADR-0079)`
349
+ - an entry that is not one of the two kinds, such as one with a `url` and a `command`:
350
+ `<file> at mcp.<name>: Unrecognized key: "<key>" — an entry is <name>: { url, secret?, timeout? } for a remote server, or <name>: { command, args?, timeout? } for a stdio server (ADR-0079)`
351
+
352
+ `jr2 up` refuses a URL:
353
+
354
+ - that is not `http://` or `https://`, such as an empty URL from a variable that `.env` does not set:
355
+ `refusing: mcp.<name>.url "<url>" is not an http:// or https:// URL — an unset .env variable reads as empty; set it, such as url: process.env.<NAME>_URL (ADR-0079)`
356
+ - with a user name or a password:
357
+ `refusing: mcp.<name>.url carries userinfo — put the credential in secret, never in the url (ADR-0079)`
358
+ - with `http://`, for a server with a `secret`:
359
+ `refusing: mcp.<name>.url must be an https:// URL — the server has a secret, <NAME>, and the Custodian carries TLS only (ADR-0059, ADR-0079)`
360
+
361
+ `jr2 up` refuses a binding of a server's secret that does not let its Stand-in through to the URL. `<i>` is the place of
362
+ the entry in the `secrets` list:
363
+
364
+ - `secrets[<i>].headers leaves out "authorization", where the Harness sends the Stand-in of <NAME> to mcp.<name>.url — each call of that server would arrive with no credential. Add it, or drop headers to take that default (ADR-0059, ADR-0079)`
365
+ - `secrets[<i>].hosts leaves out <host>, the host of mcp.<name>.url, whose secret <NAME> is — each call of that server would arrive with the Stand-in and fail with 401. Add it, or drop hosts to take that default (ADR-0059, ADR-0079)`
366
+ - `secrets[<i>].paths does not cover <path>, the path of mcp.<name>.url, whose secret <NAME> is — each call of that server would be refused with 403. Add "<path>", or drop paths (ADR-0059, ADR-0079)`
367
+
368
+ `jr2 up` refuses an Agent's `mcp` list, and prints the line to write. Each refusal starts with
369
+ `refusing: workflow "<workflow>": Agent "<slot>"`, then:
370
+
371
+ - a server that the `mcp` block does not declare:
372
+ `names the MCP server "<server>", which the mcp block declares "<names>" — bind the Agent to a declared server where the Machine is registered: export const machine = customize(<import>, { agents: { <slot>: { mcp: [<entries>] } } }); or declare it in jr2.config.ts: mcp: { "<server>": { url: "https://<host>/mcp", secret: "<SECRET>" } } (ADR-0079)`
373
+ - a remote server with no `tools`:
374
+ `names the remote MCP server "<server>" with no tools — a remote server needs its allowlist: { server: "<server>", tools: ["<tool>"] }, or tools: "*" for every tool (ADR-0079)`
375
+ - an empty list:
376
+ `gives MCP server "<server>" an empty tools list, which offers no tool — name the tools, or drop the entry (ADR-0079)`
377
+ - a list for a stdio server:
378
+ `gives a tools list for the stdio MCP server "<server>" — a stdio server takes none: the Agent gets every tool, because the server runs as Workspace code, and a list would guard nothing. Write { server: "<server>" } (ADR-0079)`
379
+ - a stdio server on a Menu-only Agent:
380
+ `has workspace: "none" and names the stdio MCP server "<server>" — a stdio server runs as Workspace code, inside a Sandbox's confinement, and a "none" Agent's Turns run on the Instance Harness, which has none: name a remote server, or give the Agent a Workspace (ADR-0079, ADR-0031)`
381
+ - a tool whose name breaks the rule:
382
+ `lists the tool "<tool>" of MCP server "<server>": the model would read it as "mcp__<server>__<tool>", and a tool name is [A-Za-z0-9_-], at most 128 characters — a model provider refuses any other (ADR-0079)`
383
+ - a tool that the list names two times:
384
+ `lists the tool "<tool>" of MCP server "<server>" twice — a model provider refuses a tools block that has one name two times: list each tool once (ADR-0079)`
385
+ - a server that the list names two times: `names the MCP server "<server>" twice — one entry for each server (ADR-0079)`
386
+ - a list where Workspace code in the Sandbox can spend the secret. `<how>` is one of
387
+ `its workspace() lists it under secrets`, `the command Effect "<effect>" names it` and
388
+ `the http Effect "<effect>" sends a Worktree file with it`:
389
+ `gives a tools list for MCP server "<server>", and Workspace code in its Sandbox can spend that server's secret, <NAME>: <how>. A list would be a hint, not a limit — write { server: "<server>", tools: "*" }, or keep <NAME> out of that Workspace (ADR-0079, ADR-0028)`
390
+
391
+ For a child Machine, `(on the Machine composed as "<slot>")` comes after the Agent's slot. The publish door of the
392
+ Orchestrator runs the same check of the lists. Thus a publish that did not go through `jr2 up` is refused too, and the
393
+ Orchestrator keeps the Assembly that it has.
394
+
395
+ `jr2 up` checks the tools of each remote server from the check pod. It stops with an error for:
396
+
397
+ - a name in an allowlist that the server does not list:
398
+ `error: mcp: workflow "<workflow>": Agent "<slot>" allowlists the tool "<tool>", which "<server>" does not list — it lists: <tools> (ADR-0079)`
399
+ - a server that refuses the request with 401 or 403:
400
+ `error: mcp: "<server>" at <url> refused the request from a pod on <where> (HTTP <status>) — a wrong or expired value of <NAME>, or a binding of it (hosts, headers, paths) that does not let its Stand-in through to the url; every call of an Agent that names it would fail (ADR-0079)`
401
+
402
+ It warns about a server that does not answer:
403
+ `warning: mcp: "<server>" at <url> did not answer from a pod on <where> (<reason>) — its tool names are not checked; a Turn of an Agent that names it fails if it does not answer then (ADR-0079)`.
404
+ With `--skip-provider-preflight`, it warns:
405
+ `warning: provider: preflight skipped (--skip-provider-preflight) — these models were not checked: <models>; these MCP servers' tool lists were not checked: <servers>`.
406
+
407
+ ## Read next
408
+
409
+ - [Agents and Turns](../concepts/agents-and-turns.md#the-mcp-tools): the MCP tools beside the Working tools and the
410
+ Menu.
411
+ - [Secrets and trust](../concepts/secrets-and-trust.md): who holds the token of a server.
412
+ - [Call an MCP tool from a Machine](call-an-mcp-tool-from-a-machine.md): a call that a person approves first.
413
+ - [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md): your own tool, in the Sandbox Image.
414
+ - Reference: [`mcp`](../reference/config.md#mcp),
415
+ [`AgentMcpServer`](../reference/authoring-surface/type-agentmcpserver.md) and
416
+ [`AgentDefinition`](../reference/authoring-surface/type-agentdefinition.md).
417
+ - [ADR-0079](../reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md):
418
+ an Agent holds MCP tools, a Machine calls them as an Effect, and the Machine is the approval.
419
+ - [ADR-0028](../reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md): the
420
+ allowlist is a guarantee about the credential.
421
+ - [ADR-0059](../reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md): the Custodian
422
+ holds the keys.