@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
@@ -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.
@@ -49,21 +49,21 @@ jr2 up — instance "my-instance" → context kind-jr2 / namespace my-instance
49
49
  typecheck: tsc --noEmit (the instance's own compiler)
50
50
  agents: (none) (carried by 1 workflow machine(s))
51
51
  assembly: staging
52
- assembly: 0adf5bff234d (kit v<version>; 1 workflow(s): ping)
52
+ assembly: 0756fbb530cb (kit v<version>; 1 workflow(s): ping)
53
53
  …
54
54
  sandbox images: skipped (no registered Machine composes a Sandbox)
55
55
  …
56
56
  orchestrator: applying (image ghcr.io/snapwich/jr2-orchestrator:<version>)
57
57
  orchestrator: waiting for rollout
58
58
  orchestrator: verified — 1 pod(s) running ghcr.io/snapwich/jr2-orchestrator:<version>
59
- instance effector: none (no Effect is placed there)
60
- assembly: publishing 0adf5bff234d (current is ba71fa27f37f)
61
- assembly: 0adf5bff234d is current — workflows: ping
62
- swept nothing — every labeled image is named by a live root
59
+ instance effector: none (no Effect is placed there, and no Hook needs it)
60
+ assembly: publishing 0756fbb530cb (current is 50e6f81e3bfb)
61
+ assembly: 0756fbb530cb is current — workflows: ping
62
+ …
63
63
  converged — `jr2 run <workflow>` when ready
64
64
  ```
65
65
 
66
- `0adf5bff234d` is the hash of the new [Assembly](../reference/glossary.md#assembly), and `ba71fa27f37f` is the hash of
66
+ `0756fbb530cb` is the hash of the new [Assembly](../reference/glossary.md#assembly), and `50e6f81e3bfb` is the hash of
67
67
  the Assembly that was current. Your hashes can be different. `<version>` is the
68
68
  [Kit version](../reference/glossary.md#kit-version). The hash changed, because the Workflow changed. Thus `jr2 up`
69
69
  published the new Assembly to the [Orchestrator](../reference/glossary.md#orchestrator). The image of the Orchestrator
@@ -84,9 +84,9 @@ jr2 run ping --input '{"message":"hi"}'
84
84
 
85
85
  ```text
86
86
  → context kind-jr2 / namespace my-instance
87
- run 7df09f94-d26d-44c3-ba1e-0279f16ccacb (ping) — attached
87
+ run 8b6c68a2-2ab6-498b-9cc5-bb722d71140f (ping) — attached
88
88
  → done "done"
89
- {"runId":"7df09f94-d26d-44c3-ba1e-0279f16ccacb","workflow":"ping","instanceId":"b2ec6504-553e-495a-a8be-62715c6d8595","status":"done","value":"done","context":{"message":"hi","reply":"pong, again: hi"},"children":[],"waiting":[],"held":[],"assembly":"0adf5bff234d"}
89
+ {"runId":"8b6c68a2-2ab6-498b-9cc5-bb722d71140f","workflow":"ping","instanceId":"919c7165-d20e-472b-b889-1ba4209a488b","startedAt":"2026-11-06T19:17:06.158Z","status":"done","value":"done","context":{"message":"hi","reply":"pong, again: hi"},"children":[],"waiting":[],"held":[],"assembly":"0756fbb530cb","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}
90
90
  ```
91
91
 
92
92
  Your run id and your instance id are different. The `reply` comes from the new Machine, and `assembly` is the hash of
@@ -112,8 +112,9 @@ A second `jr2 up` with no change builds nothing and publishes nothing:
112
112
 
113
113
  ```text
114
114
  …
115
- assembly: 0adf5bff234d (current — publish skipped)
115
+ assembly: 0756fbb530cb (current — publish skipped)
116
116
  swept nothing — every labeled image is named by a live root
117
+ …
117
118
  converged — `jr2 run <workflow>` when ready
118
119
  ```
119
120
 
@@ -158,15 +158,18 @@ jr2 run plan --input '{"task":"Write the release notes for the greet CLI, and as
158
158
  Open the run in the [Console](../reference/glossary.md#console). After the planner picks `run_chart`, the Console draws
159
159
  the Chart inside `running`. The Chart of each run is different, because the planner writes it.
160
160
 
161
- When the door refuses a Chart, the answer to the pick holds the report. The log of the
162
- [Instance Harness](../reference/glossary.md#instance-harness) shows each answer. Replace `<namespace>` with the
163
- namespace of your Instance, and `<run id>` with the full run id. The log holds the lines of each run of a Menu-only
164
- Agent, and the first `grep` keeps the lines of your run:
161
+ When the door refuses a Chart, the answer to the pick holds the report. The conversation of the planner shows each
162
+ answer. [`jr2 logs`](../reference/cli.md#logs) prints it. Replace `<run id>` with the run id, or its first 4 characters
163
+ or more:
165
164
 
166
165
  ```sh
167
- kubectl -n <namespace> logs statefulset/jr2-instance-harness -c harness | grep -F '[<run id>/' | grep -F '[run_chart answer]'
166
+ jr2 logs <run id> planner | grep -F '[run_chart answer]'
168
167
  ```
169
168
 
169
+ [Read a conversation](read-a-conversation.md) tells more. The log of the Instance Harness has the same lines, for each
170
+ run of a Menu-only Agent:
171
+ `kubectl -n <namespace> logs statefulset/jr2-instance-harness -c harness | grep -F '[<run id>/'`, with the full run id.
172
+
170
173
  For example, in one run with the model of each tab, the first Chart of the planner gave a prompt to the Gate. The door
171
174
  refused it:
172
175
 
@@ -185,7 +188,7 @@ it. Then the Chart ends, and the run ends with the status `done`.
185
188
 
186
189
  - [`openBody()`](../reference/authoring-surface/openbody.md) refuses a declaration that is not correct, with one line
187
190
  for each problem. The refusal starts with `openBody: `, then each problem, separated by `; `. For example:
188
- - `lends.<kind>: an Open body lends agents, effects, machines, guards, actions, delays, gate and fail only`
191
+ - `lends.<kind>: an Open body lends agents, effects, machines, gates, guards, actions, delays, gate and fail only`
189
192
  - `allows.workspace: "<value>" is not a Workspace access (write, read, none)`
190
193
  - `ceilings.<ceiling>: missing; an Open body states every ceiling`
191
194
  - `ceilings.<ceiling>: <value> is not a positive integer`