@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
@@ -22,24 +22,24 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
22
22
  default: `agent({ model, instructions, workspace?, thinkingLevel?, description? })`.
23
23
 
24
24
  - **The Frame is `prompt` and `cwd`: what this Turn is about and where it works.** Neither identity nor a Dial. A
25
- worktree path is per run by nature — `/work/<slot>/<branch>` exists only once a run has a branch — so no definition
26
- can name it, and a directory is not a property of a persona any more than a prompt is. `cwd` leaves `AgentDefinition`.
27
- The Harness roots the Working tools at the Frame's `cwd`, which rides the admit body beside the prompt and the Dials.
25
+ worktree path is per run by nature — `/work/<slot>/<name>` exists only once a run cuts it — so no definition can name
26
+ it, and a directory is not a property of a persona any more than a prompt is. `cwd` leaves `AgentDefinition`. The
27
+ Harness roots the Working tools at the Frame's `cwd`, which rides the admit body beside the prompt and the Dials.
28
28
 
29
29
  - **The Briefing is not the Frame.** jr2's Turn part
30
30
  ([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)) follows the prompt — the resolved working
31
31
  directory, the Allowed picks and any notices — in a block of its own. The prompt stays the Machine state's words
32
32
  alone; jr2 never writes into it.
33
33
 
34
- - **Under a Workspace, an absent `cwd` resolves to the only Repo Slot's Worktree, and is refused when there is more than
35
- one.** With one slot there is no choice, so no convention is being smuggled in (ADR-0051: the kit gives no slot a
36
- meaning). With two or more, the state must say which — `cwd: context.workspace.repos.target` — and the actor refuses
37
- the Turn before admission, naming the slots and the line to add. The trap is accepted with open eyes: a composer who
38
- adds a second slot on the `customize` line can make a Machine that ran yesterday refuse today, in a file the Machine's
39
- author never sees. The refusal is explicit, at the first Turn, before any pod is spent; `jr2 up` cannot see it because
40
- the invoke's input is a function. A `workspace: "none"` Agent has no Working tools and no `cwd` (ADR-0028); a
41
- stub-Harness run with no Workspace keeps `/work`. `task` always passes its `cwd`, because "the first slot is the one
42
- the coder edits" is its convention and a convention is stated, not defaulted.
34
+ - **Under Repo Slots, `cwd` is required.** A Workspace that only clones has no Worktree for a default to name
35
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), and `/work` is the wrong default: an
36
+ Agent's search hits `default/`, and a relative write lands outside each Worktree, which is the incident above. So the
37
+ state says where — `cwd: "/work/target/fix"` — and the actor refuses the Turn before admission, naming the slots and
38
+ the line to add; `jr2 up` cannot see it because the invoke's input is a function. A Turn that wants `/work` states it.
39
+ A Workspace with no Repo Slot needs no `cwd`: its Turns work in `/work` (ADR-0051). A `workspace: "none"` Agent has no
40
+ Working tools and no `cwd` (ADR-0028); a stub-Harness run with no Workspace keeps `/work`. `task` passes its `cwd` as
41
+ any Machine does: "the first slot is the one the coder edits" is its convention, and a convention is stated, not
42
+ defaulted.
43
43
 
44
44
  - **`continue: true` is the whole continuation surface.** Absent, every invocation is a fresh conversation — jr's lossy
45
45
  handoff, ADR-0016's default, unchanged. Present, the Turn lands on **this Agent's one conversation in this Machine
@@ -52,6 +52,13 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
52
52
  - **Fan-out is safe by construction.** A Pool spawns each worker with the item id as its actor id (pool.ts), so three
53
53
  `task` children each mint their own `…/backlog.worker.<item>/coder`. The packaged Machine never learns it is under a
54
54
  Pool.
55
+ - **Each segment of the actor path is encoded, then the segments are joined with `.`.** An actor id can be any text
56
+ (an item id is the source's), and with plain `.` joints the paths `a.b`, `c` and `a`, `b.c` were one conversation. A
57
+ letter, a digit, `-` and `_` stay as they are; each other character becomes `~` and two upper-case hex digits for
58
+ each of its UTF-8 bytes (`F.12` is `F~2E12`). So a segment has no `.` and no `/`, the iid's two joints, and nothing
59
+ that a URL path or a header must escape. The root Machine's path is `root`. Two segments are escaped although each
60
+ of their characters may stay: `root` is `~72oot`, and the empty id is `~`. So no other path is `root` or empty, and
61
+ two paths never have one spelling.
55
62
  - **States of one Machine share by saying `continue`.** States differ by Menu — a coder in `implement` offers
56
63
  `finish`, the same coder in `fix` offers `finish` and `dispute` — and the same conversation is wanted across them. A
57
64
  state that wants a clean context leaves `continue` off. A Turn joins only a conversation that an earlier Turn
@@ -112,11 +119,11 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
112
119
  that is wrong under every `workspace()` is not a default.
113
120
  - **`cwd` as a third Dial.** Rejected on the word: Dials say how hard to run. A directory says where, which is the same
114
121
  kind of fact as the prompt — hence Frame.
115
- - **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`"). The single-slot
116
- default is not the same thing — with one slot there is nothing to privilege.
117
- - **Refuse an absent `cwd` under every Workspace.** The safer twin of the single-slot default: one line per invoke, no
118
- composition trap. Not taken: the trap's failure is explicit and early, and the line is noise for the common
119
- single-repo Machine.
122
+ - **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`").
123
+ - **Default `cwd` to the only Repo Slot's Worktree.** Rejected: a Workspace that only clones has no such Worktree. And a
124
+ refusal under every Workspace with Repo Slots has no composition trap: with a default, a composer who adds a second
125
+ slot on the `customize` line makes a Machine that ran yesterday refuse today, in a file the Machine's author never
126
+ sees. The cost is one line per invoke.
120
127
  - **A caller-chosen conversation key (`conversation: "coder"`).** Rejected: it either restates the structure or
121
128
  contradicts it. Path-scoping the key was proposed and made the key redundant — once the id is
122
129
  `<run>/<machine path>/<agent>`, the author has nothing left to name.
@@ -133,19 +140,19 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
133
140
  - `AgentTurnInput` is `{ prompt, cwd?, continue?, model?, thinkingLevel? }`; `AgentDefinition` loses `cwd`. The Harness
134
141
  admit body gains the Frame's `cwd`; `ResolvedDefinition.cwd` resolves from it, not the definition; the Harness stops
135
142
  validating `description`, which the Orchestrator strips before admission (it is Console material).
136
- - **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case, and it
137
- says `/work` — a stated default. A relative path has no such answer: it would resolve against whatever directory the
138
- Harness process sits in, which is the silent wrong directory this ADR was written for, one level further from the
139
- author. The refusal is a 400 at admission, before a pod is spent.
140
- - `task` passes `cwd: worktreeOf(context).path` and `continue: true`, and drops `generation` (and its `scope`). It keeps
141
- `turns`, reset on `agent.fault`, because re-briefing is prompt policy.
143
+ - **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case, it
144
+ reaches a Harness only where no Repo Slot is, and it says `/work` — a stated default. A relative path has no such
145
+ answer: it would resolve against whatever directory the Harness process sits in, which is the silent wrong directory
146
+ this ADR was written for, one level further from the author. The refusal is a 400 at admission, before a pod is spent.
147
+ - `task` passes the path of the Worktree it cuts as `cwd`, and `continue: true`, and drops `generation` (and its
148
+ `scope`). It keeps `turns`, reset on `agent.fault`, because re-briefing is prompt policy.
142
149
  - The ledger record for a continued conversation carries its epoch; the input mapper reads it at mint time, so the
143
150
  minted id persists with the input and restore re-attaches the same conversation (ADR-0016's mapper rule holds — the
144
151
  fresh suffix is still the random component).
145
152
  - The `@kind` fixtures drop `tools:` and `instanceId:`; the steps compute the continued id from the actor path, as they
146
153
  already computed today's pin.
147
154
  - A regression scenario for the silent success: a `task` whose model uses relative paths must produce its file inside
148
- the worktree, and a multi-slot Machine with no `cwd` must be refused with the slots named.
155
+ the worktree, and a Machine under Repo Slots with no `cwd` must be refused with the slots named.
149
156
  - GLOSSARY.md: **Frame** enters; **Dials** loses `cwd`; **Instance ID** states the `continue` scope and that a faulted
150
157
  conversation is not continued. ADR-0016 (continuation), ADR-0018 (identity line), ADR-0031 (placement scenario),
151
158
  ADR-0035 (reroll gate wording), ADR-0049 (persona hazard), and ADR-0054 (`task`'s continuation) are edited to state
@@ -42,7 +42,7 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
42
42
  bearer. A 401 comes before a 404, so a refused caller does not learn which conversations exist. A refused admission
43
43
  creates no conversation. The Harness requires `JR2_HARNESS_TOKEN_SHA256` at boot and fails loudly without it.
44
44
  - **The echo uses the same bearer.** `JR2_ECHO_TOKEN_SHA256` is removed. The Instance token no longer enters any pod,
45
- which is what [ADR-0032](0032-the-console-unlocks-with-the-instance-token.md) and
45
+ which is what [ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md) and
46
46
  [Secrets and trust](../../concepts/secrets-and-trust.md) say.
47
47
  - **A refused stream is a fault, not a reconnect.** The bearer is derived, so a Harness that refuses it once refuses it
48
48
  on every retry. `wait` settles a 401 or 403 as a `SettlementFault` and does not park the Turn forever.
@@ -52,9 +52,11 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
52
52
  Orchestrator pods (`app: jr2-orchestrator`, `jr2.dev/instance: <name>`). They are converged unconditionally, so a
53
53
  Harness pod is never created before its policy exists. The Instance Effector gets a third of the same form,
54
54
  `jr2-instance-effector`, applied with it in one List (ADR-0068).
55
- - **Starting a run needs the Instance token.** `POST /workflows/:name/runs` moves from `authenticated` to
56
- `instanceOnly`. The scope of a Sandbox token is its own agent surface. The Custodian's route allowlist does not
57
- forward this route, but that is not a control.
55
+ - **Starting a run needs a Caller with `start`.** `POST /workflows/:name/runs` moves from `authenticated` to the guarded
56
+ band, where a Caller's `start` scope opens it
57
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A Sandbox token is no
58
+ Caller: its scope is its own agent surface. The Custodian's route allowlist does not forward this route, but that is
59
+ not a control.
58
60
 
59
61
  ## Considered options
60
62
 
@@ -84,18 +86,24 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
84
86
  transport failure, not a status, because a 401 would mean the bearer stopped it and the policy did not.
85
87
  - **Port-forward and probes are not affected.** `kubectl port-forward` enters the pod's network namespace through the
86
88
  kubelet, and the kubelet's probes come from the node. On kind, neither is filtered by an ingress policy. So the CLI,
87
- the Console, and a human's shell into the User Container ([ADR-0005](0005-sandbox-pod-composition.md)) keep working.
88
- If routable access to the User Container is added later (ADR-0005 leaves it open), it must add its own ingress rule.
89
+ the Console, `jr2 workspace forward` and `jr2 workspace shell --user` into the User Container
90
+ ([ADR-0005](0005-sandbox-pod-composition.md), [ADR-0009](0009-cli-and-instance-interface.md)) keep working: they reach
91
+ the pod by port-forward and exec through the kube API, and no Service routes to the User Container. If routable access
92
+ to the User Container is added later (ADR-0005 leaves it open), it must add its own ingress rule.
89
93
  - **The `@kind` history reads present the bearer.** The steps derive it from the instance Secret's signing key, as the
90
- CLI reads the Instance token: over the kube API, with RBAC as the gate. A human reading a conversation does the same.
94
+ CLI reads the Instance token: over the kube API, with RBAC as the gate. A human reads a conversation from the
95
+ Orchestrator's record with `jr2 logs`, behind `read` ([ADR-0023](0023-the-harness-prints-the-conversation.md)), and
96
+ needs no bearer of a Harness.
91
97
  - **Model keys are not in the Harness container.** `JR2_PROVIDER_API_KEY` and every other held secret live in the
92
98
  Custodian, and the Harness container's env holds their Stand-ins (ADR-0059). `jr2 up` refuses a known model key in
93
99
  `harness.env` or in an `envFrom` Secret.
94
100
  - **Open gaps, recorded here and not fixed:**
95
101
  - **Sandbox egress.** An Agent with bash can reach the internet and send out anything it can read — which no longer
96
- includes a held key: a client that goes around the Custodian carries only Stand-ins. A default-deny egress policy
97
- needs an allowlist: DNS, the Orchestrator, the model provider, and whatever the work needs (package registries, for
98
- example). That needs a config surface and cluster CIDRs, so it is a separate decision.
102
+ includes a held key: a client that goes around the Custodian carries only Stand-ins. One exception: no Harness pod
103
+ reaches a cloud metadata address
104
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). A default-deny egress
105
+ policy needs an allowlist: DNS, the Orchestrator, the model provider, and whatever the work needs (package
106
+ registries, for example). That needs a config surface and cluster CIDRs, so it is a separate decision.
99
107
  - **A held key can be spent.** The Agent can use a key through the Custodian while its pod lives, and a SigV4 or ADC
100
108
  credential cannot be held at all ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
101
109
  - **The Orchestrator's Secret writes are namespace-wide.** Its Role may create and patch every Secret, and may not get
@@ -13,11 +13,24 @@ decision generalizes that container. It holds every credential the Harness in it
13
13
 
14
14
  ## Decision
15
15
 
16
- - **A held secret is a credential the Harness uses and never holds.** The Harness container's env holds its
17
- **Stand-in**, `jr2-held-<NAME>`. The Stand-in is deterministic and public. It means something only to the Custodian,
18
- and only toward a host the secret is bound to. It is unique per name, it is a valid header and env value, and it never
19
- contains `sk-ant-oat` (pi reads such a key as an Anthropic OAuth token). The word is not "placeholder", because that
20
- is the word of an Open part.
16
+ - **A held secret is a credential the Harness uses and never holds.** The Harness process holds its **Stand-in**. It
17
+ means something only to the Custodian, and only toward a host the secret is bound to. It is unique per name, it is a
18
+ valid header and env value, and it never contains `sk-ant-oat` (pi reads such a key as an Anthropic OAuth token). The
19
+ word is not "placeholder", because that is the word of an Open part.
20
+ - **Every Stand-in is secret, except the fetch Stand-in.** The pod's maker mints each Stand-in for its pod with the
21
+ signing key, and no process can guess it: the Orchestrator for a Sandbox, and `jr2 up` for the Instance Harness, as it
22
+ writes that pod's other Secrets (the Instance Effector's key too), and for a check pod
23
+ ([ADR-0019](0019-one-converging-command-against-the-current-context.md)). Only the Harness process holds the
24
+ Stand-ins. It holds each one, the control Stand-in included, as a file under `/run/jr2/` that no process it confines
25
+ can read, and never in the Harness container's env. It gives a model key's Stand-in to the engine in-process. A
26
+ process that the Harness confines gets in its env only the Stand-ins of the secrets that its `workspace()`'s `secrets`
27
+ list grants ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). So the
28
+ Stand-in of a model key is in reach of the Harness process alone, unless the author grants that key. Secrecy matters
29
+ because confinement makes it hold: a Stand-in that Workspace code can guess lets that code spend the secret. A call
30
+ that code in `/work` makes to the model directly is outside the Turn's Settlement, so it escapes the usage that jr2
31
+ records ([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md)) and each
32
+ ceiling on spend. A project whose own code needs a model gets a key that the author grants on purpose. The fetch
33
+ Stand-in stays public (below): it can only ask for a fetch, for its own Sandbox.
21
34
  - **The Custodian holds them.** It is a container in every Harness pod — every Sandbox and the Instance Harness — beside
22
35
  the Harness, and it holds the held secrets its own pod names (below). The Agent executes no code in it, and
23
36
  `shareProcessNamespace` stays off ([ADR-0005](0005-sandbox-pod-composition.md)). So the Agent cannot read its env, its
@@ -25,16 +38,25 @@ decision generalizes that container. It holds every credential the Harness in it
25
38
  `@jr2/orchestrator`). The pod's maker writes Envoy's whole configuration — the Orchestrator a Sandbox's at provision,
26
39
  `jr2 up` the Instance Harness's — from the manifest `jr2 up` declares (see "Objects, per pod"). There is no admin
27
40
  listener and no xDS, so nothing in the pod can change the configuration or read it back.
28
- - **The Custodian replaces the Adapter.** The `@jr2/adapter` package and the `jr2-adapter` image are deleted. The
29
- Sandbox token is a held secret of the kit's own: the Harness container holds
30
- `JR2_SANDBOX_TOKEN=jr2-held-JR2_SANDBOX_TOKEN`, and the Custodian holds the token as a file from the `<sandbox>-token`
31
- Secret (the Instance Harness: the `JR2_INSTANCE_HARNESS_TOKEN` key of `jr2-instance`). No other container mounts it.
32
- - **The Harness reads its Menu itself.** There is no MCP. For each Submission, `menu.ts` reads
33
- `GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian on
34
- `127.0.0.1:8081`, with the Stand-in as the bearer. `menu-tools.ts` gives pi each accepted event as a tool. The tool
35
- names stay `mcp__jr2__<event>`, because shipped instructions and the printer depend on them; the name is no longer a
36
- transport. `menu-tools.ts` adds the prefix, and `menu.ts` keeps the bare name, because an MCP client adds
37
- `mcp__<server>__` itself. The Harness keeps what the Adapter did: a turn that is over is never prompted
41
+ - **The Custodian replaces the Adapter.** The `@jr2/adapter` package and the `jr2-adapter` image are deleted. A
42
+ Sandbox's two tokens are held secrets of the kit's own (ADR-0013): the Custodian holds the control token and the fetch
43
+ token as files from the `<sandbox>-token` Secret (the Instance Harness: its control token, from
44
+ `jr2-instance-harness-token`, which `jr2 up` mints as that pod's maker; it serves no fetch). No other container mounts
45
+ the tokens, and the real tokens never leave the Custodian. The same Secret holds the pod's Stand-ins, which the
46
+ Harness container mounts as files; so it is never `jr2-instance`, which holds the Instance token and the signing key.
47
+ - The **control Stand-in** is secret, as every Stand-in but one. The Harness process alone holds it, as a file under
48
+ `/run/jr2/` that no process it confines can read, and no `secrets` list can grant it: it is in no child's env
49
+ (ADR-0076). The Custodian puts the control token in its place on the agent routes. There is one control Stand-in for
50
+ each Sandbox, whatever the count of its Agents.
51
+ - The **fetch Stand-in** is public, `jr2-held-JR2_SANDBOX_TOKEN`. `jr2-upload-pack` holds it as a constant. The
52
+ Custodian puts the fetch token in its place on the fetch route alone, and refuses it on every agent route.
53
+ - **The Harness reads its Menu itself.** No MCP carries the Menu. For each Submission, `menu.ts` reads
54
+ `GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian's control
55
+ socket, `/run/jr2/custodian/control.sock`, with the control Stand-in as the bearer and the slot it states for the
56
+ conversation (ADR-0076). `menu-tools.ts` gives pi each accepted event as a tool. The tool names stay
57
+ `mcp__jr2__<event>`, because shipped instructions and the printer depend on them; the name is no longer a transport.
58
+ `menu-tools.ts` adds the prefix, and `menu.ts` keeps the bare name, because an MCP client adds `mcp__<server>__`
59
+ itself. The Harness keeps what the Adapter did: a turn that is over is never prompted
38
60
  ([ADR-0026](0026-a-turn-that-is-over-is-never-prompted.md)), a receipt is prose
39
61
  ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md),
40
62
  [ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), the surface read retries
@@ -42,32 +64,44 @@ decision generalizes that container. It holds every credential the Harness in it
42
64
  ([ADR-0042](0042-ready-is-not-routable-an-admission-retries-its-connection.md)), and a `poll` event refuses the turn
43
65
  (`deferred` is retired, ADR-0006). The event's own JSON Schema is the tool's parameters, so the model sees the
44
66
  contract the Orchestrator validates against.
45
- - **The control listener forwards four routes and no other.** `127.0.0.1:8081`, plain HTTP, to the Orchestrator's
46
- Service (`jr2-orchestrator.<ns>.svc:4000`, written into the pod's bootstrap by whoever makes the pod — see "Objects,
47
- per pod"):
67
+ - **The control listener forwards six routes and no other.** Plain HTTP, to the Orchestrator's Service
68
+ (`jr2-orchestrator.<ns>.svc:4000`, written into the pod's bootstrap by whoever makes the pod — see "Objects, per
69
+ pod"):
48
70
 
49
71
  ```
50
72
  GET /agents/<iid>/surface → the same path the Menu
51
73
  POST /agents/<iid>/events → the same path a pick
52
74
  GET /agents/<iid>/deliveries/<id> → the same path an outstanding pick's answer (ADR-0024)
75
+ POST /agents/<iid>/notices → the same path a step boundary's notices heard (ADR-0062, ADR-0076)
76
+ POST /agents/<iid>/conversation → the same path a batch of the conversation's entries (ADR-0023)
53
77
  POST /fetch → /sandboxes/<this pod's Sandbox>/fetch the ask (ADR-0053)
54
78
  anything else → 404, from the Custodian
55
79
  ```
56
80
 
57
- A read — the surface, a delivery's poll — is retried on any reset; a pick and the ask only on a reset before they were
58
- sent, so one the Orchestrator may have read is not sent twice. A poll holds up to about 25 s, far below the listener's
59
- 3600 s idle limit, so an outstanding pick never rides one long request (ADR-0002).
81
+ The five agent routes are on a Unix socket, `/run/jr2/custodian/control.sock`, in a folder that the Harness and the
82
+ Custodian share and that no confined program can write (ADR-0076). The fetch is on `127.0.0.1:8081`, because
83
+ `jr2-upload-pack` runs in the domain, and the fetch Stand-in is public. Each listener answers 404 to the other's
84
+ routes. Landlock does not limit a connect to a named socket, so a confined program can connect to the control socket.
85
+ It holds no control Stand-in, so the Custodian refuses each agent route that it asks for there, with the fetch
86
+ Stand-in or with none, and the control socket answers 404 to the fetch route.
60
87
 
61
- On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token and removes every
62
- other credential header. A request without the Stand-in gets 403. The ask's path comes from the Custodian
63
- (`JR2_SANDBOX` in its env, set per pod); on the Instance Harness there is none, and the ask gets 404. The route
64
- allowlist is the second layer. The control stays in the Orchestrator: the token's scope, the live registration, and
65
- the event's name and payload are judged there, as before (ADR-0013).
88
+ A read — the surface, a delivery's poll — an acknowledgment of notices, which marks them heard however often it lands,
89
+ and a batch of the conversation's entries, whose `seq`s make a second send add nothing (ADR-0023), are retried on any
90
+ reset; a pick and the ask only on a reset before they were sent, so one the Orchestrator may have read is not sent
91
+ twice. A poll holds up to about 25 s, far below the listener's 3600 s idle limit, so an outstanding pick never rides
92
+ one long request (ADR-0002).
93
+
94
+ On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token of that route — the
95
+ control token on an agent route, the fetch token on the fetch route — and removes every other credential header. A
96
+ request without the Stand-in of its route gets 403. The ask's path comes from the Custodian (`JR2_SANDBOX` in its env,
97
+ set per pod); on the Instance Harness there is none, and the ask gets 404. The route allowlist is the second layer.
98
+ The control stays in the Orchestrator: the token's scope, the live registration, and the event's name and payload are
99
+ judged there, as before (ADR-0013). The Orchestrator also accepts each token on its own routes only, and checks the
100
+ slot that the Harness states against the registration's (ADR-0076).
66
101
 
67
102
  - **Model keys go the same way, over HTTPS.** When its pod holds a held secret, the Harness container also gets:
68
103
 
69
104
  ```sh
70
- <NAME>=jr2-held-<NAME> one per held secret of the pod
71
105
  HTTPS_PROXY / https_proxy=http://127.0.0.1:15001
72
106
  NO_PROXY / no_proxy=localhost,127.0.0.1,::1
73
107
  NODE_USE_ENV_PROXY=1
@@ -75,11 +109,11 @@ decision generalizes that container. It holds every credential the Harness in it
75
109
  SSL_CERT_FILE / REQUESTS_CA_BUNDLE / GIT_SSL_CAINFO=/etc/jr2/ca/bundle.crt
76
110
  ```
77
111
 
78
- These ride `env`, after `harness.env` and before the wire's digest. In Kubernetes `env` wins over `envFrom`, so a
79
- Stand-in overrides a same-named key of an `envFrom` Secret, even one that changes later. There is no `HTTP_PROXY`: the
80
- Custodian carries TLS only, and plain HTTP (the Custodian's control listener, an in-cluster seed repository) goes
81
- direct. `NO_PROXY` is loopback only, so an in-cluster gateway that a secret is bound to still goes through the
82
- Custodian.
112
+ These ride `env`, after `harness.env` and before the wire's digest. This is the Harness container's env, and it holds
113
+ no Stand-in: the Stand-ins are files (above). A process that the Harness confines gets an explicit env instead, which
114
+ holds only the Stand-ins that its `workspace()` grants (ADR-0076). There is no `HTTP_PROXY`: the Custodian carries TLS
115
+ only, and plain HTTP (the Custodian's control listener, an in-cluster seed repository) goes direct. `NO_PROXY` is
116
+ loopback only, so an in-cluster gateway that a secret is bound to still goes through the Custodian.
83
117
 
84
118
  - **The egress listener: CONNECT only.** `127.0.0.1:15001`. The CONNECT target decides everything (lowercased, without a
85
119
  trailing dot) — never the SNI, never the `Host` header. A target that a held secret is bound to goes to an internal
@@ -92,7 +126,10 @@ decision generalizes that container. It holds every credential the Harness in it
92
126
  `paths` gives 403. It is checked after Envoy removes dot segments and reads `\` as `/` and `..;` as `..`, and the
93
127
  upstream gets that path. So IIS and Tomcat cannot read a different path. A credential header sent twice gives 400.
94
128
  - The swap: in each header the secret names, the Stand-in alone, or one auth scheme and the Stand-in
95
- (`Bearer <Stand-in>`), becomes the value. Nothing else is rewritten.
129
+ (`Bearer <Stand-in>`), becomes the value. Nothing else is rewritten. The Stand-in chooses the secret: when several
130
+ secrets are bound to one target and one header, the header gets the value of the secret whose Stand-in it carries.
131
+ So two MCP servers of one service, with two tokens, can be in one pod
132
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)).
96
133
  - Every credential header that is not the product of a swap is removed. A request with no swap gets 403 and never
97
134
  leaves the pod: `jr2 custodian: <target> needs the stand-in of <NAME> in <headers>`.
98
135
  - Envoy opens its own TLS to the target, verifies the chain against `upstream.crt` and the name against the target's
@@ -143,10 +180,11 @@ decision generalizes that container. It holds every credential the Harness in it
143
180
  (`program.lua`); each pod's own `custodian.lua` is a generated `HELD` table — names, headers, paths and targets, never
144
181
  a value — that ends with `dofile` of the program, so the program runs after the table it reads. At start the program
145
182
  reads each value from `/etc/jr2/custodian/values/<NAME>`, takes off one trailing `\n` or `\r\n`, and stops the
146
- start-up with a line that names the secret when a value is missing, empty, or has a byte outside `0x20`–`0x7E`.
147
- Envoy's `credential_injector` is not used: it is alpha, it breaks on a secret file that ends in a newline (1.39.1),
148
- and it replaces a whole header, where the swap must see the Stand-in. Values never enter a ConfigMap. Envoy runs one
149
- worker, with hot restart off, and flushes its access log twice a second.
183
+ start-up with a line that names the secret when a value is missing, empty, or has a byte outside `0x20`–`0x7E`. Each
184
+ Stand-in but the fetch Stand-in is a value too: the Custodian reads it from its values, never from the table. Envoy's
185
+ `credential_injector` is not used: it is alpha, it breaks on a secret file that ends in a newline (1.39.1), and it
186
+ replaces a whole header, where the swap must see the Stand-in. Values never enter a ConfigMap. Envoy runs one worker,
187
+ with hot restart off, and flushes its access log twice a second.
150
188
  - **The log is a contract.** One line per decision on stdout, with the prefix `jr2.custodian` and `key=value` pairs:
151
189
  `ready secrets=… hosts=N` (at the first probe), `action=intercept target=… secret=… method=… status=…`,
152
190
  `action=tunnel target=…`, `action=refuse target=… reason=…`. Never a header value, a body, a path, a query, a value or
@@ -170,51 +208,59 @@ decision generalizes that container. It holds every credential the Harness in it
170
208
  and `harness.catalog` (a catalog provider id → `{ baseUrl }`). The list serves both sides
171
209
  ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). `harness.provider.apiKey` is an entry
172
210
  too, implicitly: `JR2_PROVIDER_API_KEY`, bound to its `baseUrl`'s host. `loadConfig` checks the shape in both worlds.
173
- - **Naming is placement: a pod gets the held secrets something in it names.** Placement is read, never declared
174
- (`secretPlacementsOf`), and an entry's `hosts`, `headers` and `paths` are its binding — where the Custodian may put
175
- the value — never its placement:
211
+ - **Naming is placement: a pod gets the held secrets something in it names, and a Custodian holds what its pod's Agents
212
+ name.** Placement is read, never declared (`secretPlacementsOf`), and an entry's `hosts`, `headers` and `paths` are
213
+ its binding — where the Custodian may put the value — never its placement:
176
214
 
177
- | Pod | The held secrets its Custodian holds |
178
- | --------------------- | ------------------------------------------------------------------------------------------ |
179
- | A Workspace's Sandbox | the names its `workspace()` lists under `secrets: [...]`, and the model keys of its Agents |
180
- | The Instance Harness | the model keys of the `"none"` Agents it hosts, and nothing else |
215
+ | Pod | The held secrets its Custodian holds |
216
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
217
+ | A Workspace's Sandbox | the names its `workspace()` lists under `secrets: [...]`, the model keys of its Agents, and the secrets of the MCP servers they name |
218
+ | The Instance Harness | the model keys of the `"none"` Agents it hosts, and the secrets of the MCP servers they name |
181
219
 
182
220
  A model key is claimed by the PROVIDER: an Agent names its model, and jr2 knows which keys that provider reads
183
221
  (`MODEL_KEYS`; the custom provider's id for `JR2_PROVIDER_API_KEY`). An Agent's Turns run in the Workspace below which
184
222
  it is declared, down to the next `workspace()`, unless it is `"none"`, whose Turns run at the Instance Harness
185
- ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). A `"none"` Agent cannot execute code, so the
186
- Instance Harness loses nothing by holding only model keys. Listing a model key under a `workspace()`'s `secrets` is
187
- allowed, and `jr2 up` reports it as redundant. A name a `workspace()` lists that the list does not hold is refused,
188
- with the line to add. An entry nothing names — no `workspace()`, no provider, no Effect — goes nowhere, and `jr2 up`
189
- warns. `jr2 up` reports per pod:
223
+ ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). An MCP server's secret is claimed by the Agent
224
+ that names the server in its `mcp` list (ADR-0079), and held in the pod where that Agent's Turns run. The rule is the
225
+ same for every pod: a `"none"` Agent cannot execute code, so the Instance Harness holds only what its Agents name, and
226
+ only the Harness process spends it. Listing a model key under a `workspace()`'s `secrets` is allowed, and `jr2 up`
227
+ reports it as redundant. A name a `workspace()` lists that the list does not hold is refused, with the line to add. An
228
+ entry nothing names — no `workspace()`, no provider, no Effect, no MCP server that an Agent or an Effect names — goes
229
+ nowhere, and `jr2 up` warns. `jr2 up` reports per pod:
190
230
  `Workspace of workflow "coding": Custodian holds ANTHROPIC_API_KEY (provider), JIRA_TOKEN`.
191
231
 
192
232
  `jr2 up` alone checks the claimed entries (`heldSecretsOf`), host-side, because `.env` values are absent in-cluster.
193
233
  An entry no pod claims is not checked as a held secret:
194
234
 
195
- | # | Refused |
196
- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
197
- | R1 | A known model key (`MODEL_KEYS`) in `harness.env`, or as a key of a Secret or ConfigMap `harness.envFrom` names. |
198
- | R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
199
- | R3 | One name held twice, or held and set by `harness.env`. |
200
- | R4 | Not exactly one of `value`/`valueFrom`; an empty value; a byte outside `0x20`–`0x7E`; leading or trailing space. |
201
- | R5 | A claimed entry with no `hosts` where no default exists; a wildcard, userinfo, a non-`https` scheme, a bare-host path, a bad port; two secrets on one host in one header. |
202
- | R6 | A `headers` entry that is not a lowercase token, or that frames or routes the request; a duplicate. |
203
- | R7 | `provider.apiKey` with a non-`https` `baseUrl`; a non-`https` catalog `baseUrl`. |
204
- | R8 | A catalog key that is not a known provider, or that is the custom provider's id. |
205
- | R9 | Explicit `hosts` on a known model key that leave out where a carried Agent's provider sends its calls. |
206
- | R10 | A `secretKeyRef` whose Secret or key does not exist. |
207
- | R11 | While a secret is held: `harness.env` sets a proxy or trust variable. |
208
- | R12 | A `paths` entry that does not start with `/`, or holds `..`, `;`, `\`, `%2e`, `%2f` or `%5c`. |
209
- | R13 | A held value that also reaches the Harness: its Secret loaded by `harness.envFrom`, its key read by a `harness.env` `secretKeyRef`, or its literal set by `harness.env`. A kit Secret (`jr2-instance`, `jr2-held-*`, `jr2-instance-effector`, `jr2-effector-*`, `<sandbox>-effector`, `jr2-git-*`) named by either. |
235
+ | # | Refused |
236
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237
+ | R1 | A known model key (`MODEL_KEYS`) in `harness.env`, or as a key of a Secret or ConfigMap `harness.envFrom` names. |
238
+ | R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
239
+ | R3 | One name held twice, or held and set by `harness.env`. |
240
+ | R4 | Not exactly one of `value`/`valueFrom`; an empty value; a byte outside `0x20`–`0x7E`; leading or trailing space. |
241
+ | R5 | A claimed entry with no `hosts` where no default exists; a wildcard, userinfo, a non-`https` scheme, a bare-host path, a bad port. |
242
+ | R6 | A `headers` entry that is not a lowercase token, or that frames or routes the request; a duplicate; explicit `headers` on an MCP server's secret that leave out `authorization`, where the Harness sends its Stand-in. |
243
+ | R7 | `provider.apiKey` with a non-`https` `baseUrl`; a non-`https` catalog `baseUrl`; an MCP server with a `secret` and a non-`https` `url`. |
244
+ | R8 | A catalog key that is not a known provider, or that is the custom provider's id. |
245
+ | R9 | Explicit `hosts` on a known model key that leave out where a carried Agent's provider sends its calls, or on an MCP server's secret that leave out the host of the `url` of a server that names it. |
246
+ | R10 | A `secretKeyRef` whose Secret or key does not exist. |
247
+ | R11 | While a secret is held: `harness.env` sets a proxy or trust variable. |
248
+ | R12 | A `paths` entry that does not start with `/`, or holds `..`, `;`, `\`, `%2e`, `%2f` or `%5c`; explicit `paths` on an MCP server's secret that do not cover the path of the server's `url`. |
249
+ | R13 | A held value that also reaches the Harness: its Secret loaded by `harness.envFrom`, its key read by a `harness.env` `secretKeyRef`, or its literal set by `harness.env`. A kit Secret (`jr2-instance`, `jr2-held-*`, `jr2-instance-effector`, `jr2-effector-*`, `<sandbox>-effector`, `jr2-git-*`, and each pod's credentials, `jr2-instance-harness-token` and `<sandbox>-token`) named by either. |
250
+ | R14 | A held secret's name that is also a key of a Secret that `harness.envFrom` names. The message names the Secret, the key and the held secret. |
251
+
252
+ R14 exists because the Stand-ins are files, not env: no Stand-in hides such a key, so its real value would reach the
253
+ Harness's env, and the Harness uses a held secret but never holds it. `up` already reads each `envFrom` Secret to
254
+ check that it exists (ADR-0019).
210
255
 
211
256
  Each message names the config path, never a value, and says what to write instead. A SigV4 or ADC credential in
212
257
  `harness.env` is warned about, not refused. A known model key with no `hosts` is bound to the host its provider's
213
258
  calls go to: the host of `harness.catalog.<id>.baseUrl` when set, else pi's catalog host. The key and the calls then
214
- read one config and cannot drift. `MODEL_KEYS` is pi's data at the pin, written as plain data by `just model-keys` (a
215
- script in `@jr2/harness`) at each pi bump, so the Orchestrator has no pi dependency. A test in `@jr2/harness` checks
216
- the table against pi, and checks that the file is the script's exact text. So a pi bump that moves a provider, or a
217
- hand edit of the file, fails the default gate until the recipe runs.
259
+ read one config and cannot drift. An MCP server's secret with no `hosts` is bound to the host of the `url` of each
260
+ server that names it, in the header `authorization`, in the same way. `MODEL_KEYS` is pi's data at the pin, written as
261
+ plain data by `just model-keys` (a script in `@jr2/harness`) at each pi bump, so the Orchestrator has no pi
262
+ dependency. A test in `@jr2/harness` checks the table against pi, and checks that the file is the script's exact text.
263
+ So a pi bump that moves a provider, or a hand edit of the file, fails the default gate until the recipe runs.
218
264
 
219
265
  - **A catalog provider can move to a gateway.** pi has no env var for a catalog provider's base URL. It hands the
220
266
  provider the model it is given. So `resolveModel` returns a copy of the catalog model with `baseUrl` replaced, for
@@ -309,16 +355,18 @@ decision generalizes that container. It holds every credential the Harness in it
309
355
 
310
356
  ## Consequences
311
357
 
312
- - **What this closes, and what it does not.** The Agent cannot take a model key or the Sandbox token out of the pod. It
313
- can still **spend** a key through the Custodian while its pod lives, on any path `paths` allows. A key with admin
314
- rights can mint a new key, and a minted key is one the Agent can take away: use a key with no admin rights, and narrow
315
- `paths`. The User Container shares the pod's loopback, so a human in it can spend a key the same way. That human
316
- cannot read one either.
358
+ - **What this closes, and what it does not.** The Agent cannot take a model key or a Sandbox's token out of the pod. The
359
+ Harness spends the model keys for the Turns. Workspace code can still **spend** a key through the Custodian while its
360
+ pod lives, on any path `paths` allows, when the `workspace()` grants that key. A key with admin rights can mint a new
361
+ key, and a minted key is one the Agent can take away: use a key with no admin rights, and narrow `paths`. The User
362
+ Container shares the pod's loopback, so a human in it can spend a key the same way. That human cannot read one either.
317
363
  - **Response echo.** Responses pass unchanged. An upstream that echoes the full credential in a response hands it to the
318
364
  Agent. Providers mask keys in errors; jr2 does not scrub responses.
319
365
  - **Bypass.** A client that ignores `HTTPS_PROXY` goes direct and sends the Stand-in, which is worth nothing. It is not
320
- blocked: egress control stays the cluster's decision (ADR-0058's open egress gap). The same holds for the control
321
- plane: a caller that skips the Custodian and sends the Stand-in to the Orchestrator gets 401 on every route
366
+ blocked: egress control stays the cluster's decision (ADR-0058's open egress gap), except for the cloud metadata
367
+ addresses, which no Harness pod reaches
368
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The same holds for the
369
+ control plane: a caller that skips the Custodian and sends the Stand-in to the Orchestrator gets 401 on every route
322
370
  (`auth.test.ts`).
323
371
  - **Clients that do not trust the jr2 CA** (Java, a pinned client) fail TLS to a bound host. They fail closed and send
324
372
  nothing. Unbound hosts still work for them.
@@ -338,24 +386,27 @@ decision generalizes that container. It holds every credential the Harness in it
338
386
  - **Rotation of a value** reaches a live Sandbox only when its pod is replaced.
339
387
  - **The walk reads definitions, not Turns.** A pod's model keys come from the models its Agents' definitions name. A
340
388
  Turn whose invocation overrides the model with another provider's (`AgentTurnInput.model`) finds no key for that
341
- provider in its pod: the Harness container gets a Stand-in only for the names in its own pod's manifest, so that
389
+ provider in its pod: the Harness process holds a Stand-in only for the names in its own pod's manifest, so that
342
390
  provider's key variable is not set at all. The call then has no key (a catalog provider) or sends the custom
343
391
  provider's placeholder `unused`, and it fails. This holds in a Workspace and in the Instance Harness alike. The fix is
344
392
  a definition that names the provider; in a Workspace, the key under the `workspace()`'s `secrets` also works. The
345
- Instance Harness has no such list: its pod holds only the model keys of the `"none"` Agents it hosts.
393
+ Instance Harness has no such list: its pod holds only what the `"none"` Agents it hosts name.
346
394
  - **TLS-only bindings.** A key for a plain-HTTP endpoint is refused (R7). A LAN `llama-server --api-key` needs TLS and
347
395
  its CA in `caBundle`, or no key.
348
396
  - **No proxy chaining.** The Custodian cannot forward to a corporate proxy. R11 says so rather than fail silently.
349
- - **Two Harness env shapes.** With no held secret in it, a pod gets the Custodian's address and the token's Stand-in and
350
- no proxy, and its HTTPS goes direct. The Custodian runs in both shapes, because it carries the Menu.
351
- - **The dial guard adds no confinement while egress is open.** The Agent can dial those addresses itself. The guard
352
- matters when a cluster closes egress to everything but the Custodian: then the Custodian is not a way around it.
397
+ - **Two Harness env shapes.** With no held secret in it, a pod gets the Custodian's address and no proxy, and its HTTPS
398
+ goes direct. The Harness process holds the control Stand-in in both shapes. The Custodian runs in both shapes, because
399
+ it carries the Menu.
400
+ - **The dial guard adds no confinement while egress is open.** The Agent can dial those addresses itself, except the
401
+ cloud metadata addresses, which jr2's egress policy closes on a CNI that enforces it (ADR-0076). The guard matters
402
+ when a cluster closes egress to everything but the Custodian: then the Custodian is not a way around it.
353
403
  - **One container holds two kinds of credential.** A fault in Envoy's TLS or HTTP parsing is a fault in the container
354
- that holds both the Sandbox token and the model keys. The branch with a separate Adapter keeps them apart.
355
- - **The Harness container now speaks the Orchestrator's two agent routes.** The Agent could always make those calls (the
356
- Adapter would forward any MCP call it asked for); now the Harness makes them itself. Nothing is judged in the pod.
357
- - **`jr2-upload-pack` carries the Stand-in as a constant.** The User Container gets no env (ADR-0005), and asks like any
358
- other seat. A test pins the Go literal to `standIn("JR2_SANDBOX_TOKEN")`.
404
+ that holds both the Sandbox's tokens and the model keys. The branch with a separate Adapter keeps them apart.
405
+ - **The Harness process speaks the Orchestrator's agent routes itself.** Nothing is judged in the pod. No process that
406
+ the Harness confines holds the control Stand-in, so none can make those calls (ADR-0076).
407
+ - **`jr2-upload-pack` carries the fetch Stand-in as a constant.** The User Container gets no env (ADR-0005), and asks
408
+ like any other seat. A test pins the Go literal to `standIn("JR2_SANDBOX_TOKEN")`. The Custodian swaps it on the fetch
409
+ route alone, so the constant reaches no agent route.
359
410
  - **The Custodian's image is a Pinned image.** It is deployed and never built, which
360
411
  [ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md) now states. `kitRegistry` re-homes it and `jr2 kit push`
361
412
  mirrors it ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)). The image sweep never