@jr2/orchestrator 0.4.1 → 0.5.1

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 (327) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +442 -0
  10. package/docs/concepts/architecture.md +230 -0
  11. package/docs/concepts/charts.md +143 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +254 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +603 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +289 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +222 -0
  20. package/docs/concepts/the-menu.md +378 -0
  21. package/docs/concepts/workspaces.md +678 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
  27. package/docs/get-started/install.md +126 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +223 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/fix-a-failed-converge.md +361 -0
  34. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  35. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  36. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  37. package/docs/guides/look-inside-a-sandbox.md +174 -0
  38. package/docs/guides/mirror-the-kit-images.md +176 -0
  39. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  40. package/docs/guides/open-the-console.md +91 -0
  41. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  42. package/docs/guides/push-and-sign-a-branch.md +486 -0
  43. package/docs/guides/run-a-chart-by-name.md +270 -0
  44. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  45. package/docs/guides/run-one-worker-per-item.md +321 -0
  46. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  47. package/docs/guides/size-a-workspace.md +204 -0
  48. package/docs/guides/start-a-run-from-a-run.md +237 -0
  49. package/docs/guides/start-a-run.md +207 -0
  50. package/docs/guides/upgrade-the-kit-version.md +240 -0
  51. package/docs/guides/use-a-hosted-provider.md +208 -0
  52. package/docs/guides/use-a-local-model.md +313 -0
  53. package/docs/guides/watch-a-run.md +326 -0
  54. package/docs/guides/write-a-workflow.md +318 -0
  55. package/docs/index.md +44 -0
  56. package/docs/llms.txt +216 -0
  57. package/docs/reference/authoring-surface/agent.md +128 -0
  58. package/docs/reference/authoring-surface/answer.md +140 -0
  59. package/docs/reference/authoring-surface/arg.md +60 -0
  60. package/docs/reference/authoring-surface/basic.md +62 -0
  61. package/docs/reference/authoring-surface/bearer.md +53 -0
  62. package/docs/reference/authoring-surface/chartschema.md +23 -0
  63. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  64. package/docs/reference/authoring-surface/command.md +191 -0
  65. package/docs/reference/authoring-surface/customize.md +64 -0
  66. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  67. package/docs/reference/authoring-surface/defineevent.md +114 -0
  68. package/docs/reference/authoring-surface/dispatch.md +126 -0
  69. package/docs/reference/authoring-surface/fail.md +83 -0
  70. package/docs/reference/authoring-surface/file.md +122 -0
  71. package/docs/reference/authoring-surface/frompick.md +277 -0
  72. package/docs/reference/authoring-surface/http.md +159 -0
  73. package/docs/reference/authoring-surface/isfailure.md +58 -0
  74. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  75. package/docs/reference/authoring-surface/open.md +74 -0
  76. package/docs/reference/authoring-surface/openbody.md +86 -0
  77. package/docs/reference/authoring-surface/paramsof.md +125 -0
  78. package/docs/reference/authoring-surface/pool.md +186 -0
  79. package/docs/reference/authoring-surface/push.md +112 -0
  80. package/docs/reference/authoring-surface/secret.md +118 -0
  81. package/docs/reference/authoring-surface/sentby.md +83 -0
  82. package/docs/reference/authoring-surface/source.md +51 -0
  83. package/docs/reference/authoring-surface/text.md +62 -0
  84. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  85. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  86. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  87. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  88. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  89. package/docs/reference/authoring-surface/type-arg.md +19 -0
  90. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  91. package/docs/reference/authoring-surface/type-binding.md +62 -0
  92. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  93. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  94. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  95. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  96. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  97. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  98. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  99. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  100. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  101. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  102. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  103. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  104. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  105. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  106. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  107. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  108. package/docs/reference/authoring-surface/type-childat.md +12 -0
  109. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  110. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  111. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  112. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  113. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  114. package/docs/reference/authoring-surface/type-customize.md +109 -0
  115. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  116. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  117. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  118. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  119. package/docs/reference/authoring-surface/type-duration.md +15 -0
  120. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  121. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  122. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  124. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  125. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  126. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  127. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  128. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  129. package/docs/reference/authoring-surface/type-failure.md +19 -0
  130. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  131. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  132. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  133. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  134. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  135. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  136. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  137. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  138. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  139. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  140. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  141. package/docs/reference/authoring-surface/type-lends.md +38 -0
  142. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  143. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  144. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  145. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  146. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  147. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  148. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  149. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  150. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  151. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  152. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  153. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  154. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  155. package/docs/reference/authoring-surface/type-openof.md +12 -0
  156. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  157. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  158. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  159. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  160. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  161. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  162. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  163. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  164. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  165. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  166. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  167. package/docs/reference/authoring-surface/type-reached.md +12 -0
  168. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  169. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  170. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  171. package/docs/reference/authoring-surface/type-secret.md +25 -0
  172. package/docs/reference/authoring-surface/type-sender.md +19 -0
  173. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  174. package/docs/reference/authoring-surface/type-size.md +85 -0
  175. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  176. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  177. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  178. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  179. package/docs/reference/authoring-surface/type-text.md +19 -0
  180. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  181. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  182. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  183. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  184. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  185. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  186. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  187. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  188. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  189. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  190. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  191. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  192. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  193. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  194. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  195. package/docs/reference/authoring-surface/workspace.md +259 -0
  196. package/docs/reference/authoring-surface.md +197 -0
  197. package/docs/reference/cli.md +433 -0
  198. package/docs/reference/config.md +756 -0
  199. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  200. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  201. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  202. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  203. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  204. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  205. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  206. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  207. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  208. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  209. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  210. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  211. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  212. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  213. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  214. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  215. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  216. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  217. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  218. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  219. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  220. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  221. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  222. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  223. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  224. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  225. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  226. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  227. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  228. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  229. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  230. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  231. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  232. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  233. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  234. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  235. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  236. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  237. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  238. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  239. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  240. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  241. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  242. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  243. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  244. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  245. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  246. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  247. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  248. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  249. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  250. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  251. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  252. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  253. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  254. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  255. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  256. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  257. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  258. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  259. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  260. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  261. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  262. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  263. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  264. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  265. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  266. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  267. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  268. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  269. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  270. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  271. package/docs/reference/decisions.md +83 -0
  272. package/docs/reference/glossary.md +648 -0
  273. package/docs/reference/http-api.md +1348 -0
  274. package/docs/reference/machines.md +190 -0
  275. package/docs/reference/mechanism-events.md +72 -0
  276. package/docs/why.md +206 -0
  277. package/package.json +5 -3
  278. package/src/actor.ts +541 -79
  279. package/src/agent.ts +114 -38
  280. package/src/ambient.ts +2 -1
  281. package/src/assembly-checks.ts +189 -7
  282. package/src/cause.ts +234 -0
  283. package/src/chart.ts +169 -37
  284. package/src/charts.ts +34 -6
  285. package/src/config.ts +430 -178
  286. package/src/custodian.ts +74 -22
  287. package/src/customize.ts +188 -46
  288. package/src/dispatch-effect.ts +945 -0
  289. package/src/door-compat.ts +217 -0
  290. package/src/durability.ts +11 -4
  291. package/src/effect.ts +341 -44
  292. package/src/effector-wire.ts +160 -11
  293. package/src/fail.ts +209 -0
  294. package/src/harness-client.ts +541 -47
  295. package/src/http-effect.ts +495 -53
  296. package/src/http.ts +427 -16
  297. package/src/index.ts +60 -82
  298. package/src/instance.ts +68 -8
  299. package/src/internal.ts +84 -0
  300. package/src/machine-doc.ts +21 -3
  301. package/src/mechanism-routes.ts +168 -0
  302. package/src/model-keys.generated.ts +53 -0
  303. package/src/model-keys.ts +18 -58
  304. package/src/notices.ts +66 -13
  305. package/src/open-body.ts +159 -23
  306. package/src/open-effect.ts +81 -8
  307. package/src/open.ts +59 -6
  308. package/src/parts.ts +184 -13
  309. package/src/party.ts +68 -2
  310. package/src/pick.ts +463 -11
  311. package/src/pool.ts +247 -36
  312. package/src/registration.ts +83 -4
  313. package/src/repos.ts +2 -1
  314. package/src/run-host.ts +565 -31
  315. package/src/run-output.ts +38 -0
  316. package/src/sandbox-effect.ts +273 -38
  317. package/src/sandbox-kube.ts +3 -1
  318. package/src/secret-placement.ts +52 -17
  319. package/src/server.ts +3 -0
  320. package/src/setup.ts +239 -56
  321. package/src/size.ts +121 -9
  322. package/src/snapshot-store.ts +54 -10
  323. package/src/stub-harness.ts +44 -16
  324. package/src/timers.ts +14 -1
  325. package/src/vocabulary.ts +69 -14
  326. package/src/wire.ts +90 -16
  327. package/src/workspace.ts +697 -175
@@ -0,0 +1,227 @@
1
+ # A Repo is a slot on the `workspace()` and a cache on the node
2
+
3
+ ADR-0049 moved Agents and the Sandbox Image into the Machine and left one dependency outside it: the Repo, catalogued in
4
+ `jr2.config.ts` and named by string from a `workspace()` spec, with the name typed back through a `Register` interface
5
+ (ADR-0050). The grill of 2026-09-13 found two faults. A packaged Machine still could not say which repository it works
6
+ on, nor could a consumer bind one, except by the consumer's config holding the right NAME and the door threading it —
7
+ the same encapsulation gap ADR-0049 closed for Agents. And the catalog's shape was dictated by storage: one read-only
8
+ volume of `default/` checkouts, which on a multi-node cluster pins every Sandbox to the node holding it. The two are one
9
+ decision: **what a Machine says about a repository, and what the cluster does with that.**
10
+
11
+ ## Decision
12
+
13
+ - **A Repo is a slot on the `workspace()`**, beside `image` and `user`, keyed by the Machine's own word for it:
14
+
15
+ ```ts
16
+ export const codeReview = workspace(body, {
17
+ input,
18
+ image: import.meta.resolve("./image"),
19
+ repos: {
20
+ target: open, // the consumer's — must be bound
21
+ docs: { url: "https://github.com/acme/handbook.git", ref: "v3" }, // the package's own
22
+ },
23
+ spec: ({ input }) => ({ branch: input.branch }),
24
+ });
25
+ ```
26
+
27
+ A slot is in one of three states, and the walk tells them apart without evaluating anything: **bound** (a url, or
28
+ `{ url, ref? }`), **open** (the `open` sentinel — someone downstream binds it), or **per-run** (a mapper over the
29
+ wrapper's door: `feature: ({ input }) => ({ url: input.feature.repo, ref: input.feature.baseRef })`). `ref` is the
30
+ base the branch worktree is cut from; absent, the repository's own default branch. The spec no longer carries repos —
31
+ it is `{ branch, workGroup?, reviewSha? }`, or `{ workGroup? }` for a Workspace with no slot (below). The body's
32
+ handles are keyed by slot (`workspace.repos.target`), and so are the in-pod paths (`/work/<slot>/<branch>`): a slot
33
+ key is collision-free by construction and the same in every Instance that consumes the package, so a prompt can name a
34
+ path and be right everywhere.
35
+
36
+ - **The handles are the slot map and the branch — no slot is the `workdir`.** `workspace.repos` keeps the slots in
37
+ declaration order, and jr2 reads nothing into that order. Which checkout an Agent works in is a fact about that
38
+ Agent's Turn, not about the Workspace: a body with a `backend` and a `frontend` slot frames one Agent with the first
39
+ path and another with the second, and either may read the other's tree. A body that names no slot may give the order a
40
+ meaning of its own and document it (`task`: the first slot is the one the coder edits). The order is the kit's promise
41
+ — a slot key starts with a letter, because JS reads an integer-like key first wherever it was written — and the
42
+ meaning is the Machine's. Rejected: a `workdir` handle, the first declared slot's worktree — it made a singular
43
+ "primary repo" a property of every Workspace, which is wrong for a body whose Agents work in different slots or in
44
+ more than one, and it hid a rule in key order that the body never chose.
45
+
46
+ - **A Machine whose body names no slot declares the whole map Open: `repos: open`.** A body that enumerates whatever is
47
+ attached — `@jr2/machines`'s `task` — knows nothing a slot name could say, and a `target: open` on it would claim "one
48
+ repository" where the body claims nothing. So the map is the Open part, one level up from a slot: the composer names
49
+ every slot with `customize`, in the order the Machine documents. The body's handles are typed `Workspaced<…, string>`,
50
+ which is its statement that it names no slot; `workspace()` refuses a body that names one under an Open map, since the
51
+ composer may never write that word. The walk reports an Open map as one Open part with no slot, and `jr2 up` refuses
52
+ it by the same route, with `<slot>` left in the line for the composer to fill. Rejected: `customize` adding keys to a
53
+ declared map — a slot a Machine never declared is a compile error on purpose (a typo cannot mint a clone), and a body
54
+ that named `target` could say nothing about a `reference` it never knew of; the map-level Open says what the body
55
+ actually knows. Rejected: a second Machine of the consumer's own, wrapping the exported body with more slots —
56
+ composition is the right act for a different BODY, but the same body with more checkouts beside it is a binding, and
57
+ `customize` is where bindings go.
58
+
59
+ - **A Workspace with no Repo Slot leaves `repos` out.** A Workspace is a Sandbox bound to one unit of work, and a
60
+ repository is something it may attach, not what makes it one. Some work has no repository: fetch data, write a report,
61
+ have a second Agent edit it, upload it. Its steps share `/work`, as the steps of one GitHub Actions job share that
62
+ job's folder; two Workspaces are two jobs and share nothing. So `workspace(body, { input })` with no `repos` is a
63
+ Workspace with no Repo Slot: the attach is skipped (there is nothing to clone, and the pod's `fsGroup` already makes
64
+ `/work` the work group's), the body's handles are `{ repos: {} }` with no branch, typed `Workspaced<…, never>`, and a
65
+ Turn that frames no `cwd` and a `command` both work in `/work`. The spec has nothing to cut a branch in: `spec` is
66
+ optional, gives only `workGroup`, and a `branch` or a `reviewSha` in it is refused — at the type, and when the run
67
+ enters, since a value jr2 silently ignores is the failure the refusals exist for. `repos: {}` is refused with the
68
+ pointer to leaving it out, so "none" has one spelling and an empty map never reads as one somebody forgot to fill. An
69
+ Open map still asks `customize` for at least one slot: Open says the composer names the slots, and a body that
70
+ enumerates its checkouts (`task`) has nothing to do with none. Two Effects need a slot and are refused there, by
71
+ `jr2 up` and at invoke, with one reason: a `push` publishes a slot's branch, and a `command` with an Effect secret
72
+ runs against one commit of it (ADR-0067). A file-body `http` reads `/work` and stays. `/work` is an `emptyDir`, so a
73
+ Lost Sandbox takes everything with it — with a repository, pushed commits outlive the pod; here only what an Effect
74
+ sent out does, and the body hears `workspace.lost` as any body does. Storage that outlives the pod, the job's
75
+ "artifacts", is a decision of its own. Rejected: a placeholder repository bound to satisfy the rule — a clone nobody
76
+ uses, a branch that means nothing, a Worktree that becomes every Agent's default `cwd`, and a cache entry to warm.
77
+ Rejected: a new term for the repository-less Sandbox — the Glossary's Workspace never named a repository, so none was
78
+ needed.
79
+
80
+ - **The consumer binds with `customize`**, in the same call as Agents and the image, nested through `actors` like every
81
+ other part: `customize(codeReview, { repos: { target: "git@github.com:ourorg/app.git" } })`. Any of the three forms is
82
+ accepted, so a consumer can also bind a mapper over the child's door. A slot the Machine does not declare is a compile
83
+ error (a phantom on the wrapper type, read through `pool()`'s `worker` and `workspace()`'s `body` as the image is). A
84
+ registered Machine with an open slot nobody bound is refused by `jr2 up`'s walk, before it builds anything, naming the
85
+ Machine, the slot, and the `customize` line that fixes it. That is the one check that is converge-time and not
86
+ compile-time: a `workflows/` export has no type to hang it on (ADR-0050).
87
+ - **The url is the identity.** There is no repo name. `jr2.config.ts` loses `repos`; `Register`, `RepoName`,
88
+ `repoNames()`, and the `const` on `defineConfig` retire. ADR-0050's rule holds vacuously: the string a Machine writes
89
+ is the thing, not a reference into someone else's file. Two spellings of one repository (`https://`, `git@…:`,
90
+ `ssh://`, with or without `.git`) resolve to one identity — host plus path, scheme and user dropped — and therefore
91
+ one cache. A door that offers a menu of repositories is the Instance's own `z.enum` of urls.
92
+ - **The cluster keeps a bare cache per node, not a volume.** Each repository is a `Repo` custom resource the operator
93
+ reconciles (spec: url, refresh interval; status: per node — synced, generation, last error). A cache agent runs as a
94
+ DaemonSet per Instance over its Sandbox nodes (ADR-0052) with a hostPath directory: it clones a repository onto its
95
+ node the first time a Sandbox there needs it, fetches in place on the interval and on demand before an attach, and
96
+ pins gc exactly as ADR-0004 requires. The Sandbox CR names the repositories it needs by identity; the operator mounts
97
+ each node cache read-only, sets a soft node affinity toward nodes whose status holds them, and reports `Ready` only
98
+ once every one is present and has been fetched since the CR asked. The agent works per Repo, four at once on a node (a
99
+ kit value; a fetch is network and CPU beside the Sandboxes it serves), so one Repo's cold clone never holds another's
100
+ on-demand fetch; every cache speaks git protocol v2, so a fetch advertises only the refs it asks for; and the refresh
101
+ interval carries ±20% jitter, like the Lease, so R Repos on N nodes do not hit a remote on one beat (scaling review
102
+ R13, 2026-09-30). The agent also consolidates a cache's packs without deleting an object (ADR-0004). The
103
+ Orchestrator's attach then runs unchanged: `git clone --shared` off the mount, worktree beside it. A Sandbox that
104
+ lands on a cold node pays one clone there, once — the same economics as an image pull, and the reason a Sandbox can
105
+ run on any node.
106
+ - **The Orchestrator creates `Repo` CRs; it does not sync them.** At boot it walks its registered Machines and creates
107
+ one CR per bound identity, so a statically known repository is KNOWN before a run can ask: the cache agent on every
108
+ node probes it (`git ls-remote`) as soon as the CR exists, so a wrong url or a credential that does not reach shows in
109
+ `jr2 status` right after converge, not at the first run. Known is not warm: the clone happens on a node the first time
110
+ a Sandbox there needs the repository, never on every node at boot — cloning a large repository onto every node that
111
+ may never run a Sandbox for it is the cost the image-pull economics above exist to avoid. A per-run url creates its CR
112
+ at first attach, and every later attach anywhere finds it. The boot restates a bound CR's url and `secretRef` at every
113
+ deploy; an attach restates the `secretRef` of a CR nothing binds, since no boot will — so a `git.credentials` entry
114
+ fixed after a failed clone reaches the cache at the next run either way. Only an attach moves the eviction clock: a
115
+ boot is not an attach.
116
+ - **Credentials are matched by prefix and carried by the CR, the Argo and Flux shape.** `jr2.config.ts` holds
117
+ `git.credentials`, a list of `{ match, token?, sshKey? }`: `match` is a prefix on the identity (`github.com/ourorg/`,
118
+ or `*`), `token` names an env var whose value `jr2 up` writes into that entry's own clone Secret,
119
+ `jr2-git-<hash of match>` (labelled, and deleted when the entry no longer has one; an unset variable writes none),
120
+ listing the Secrets it wrote in `git-clone.json` in `jr2-held`, names only; `sshKey` names a Secret holding a deploy
121
+ key; the url's scheme picks which field applies, and the longest match wins. When the Orchestrator creates a `Repo` CR
122
+ it resolves the entry and writes a `secretRef`, the name of a Secret `jr2 up` wrote or the user made — a token entry
123
+ whose clone Secret `git-clone.json` does not list gets none, and the clone is anonymous; the cache agent reads only
124
+ that Secret, and the operator matches nothing. The Orchestrator writes and reads no git credential (ADR-0068). The
125
+ Secret uses Flux's key names (`username`/`password`; `identity`, `identity.pub`, `known_hosts`), so a Flux or Argo
126
+ user reuses the Secret they have. **The list is also the fence.** A per-run url — a run input, a ticket field — is
127
+ otherwise a way to spend the cluster's credential against any host: one that matches no entry is refused at attach,
128
+ naming the list. A static binding is code the Instance typechecked and deployed, so it is admitted without a match and
129
+ cloned anonymously; a private one then fails at the cache with git's error. `jr2 init` scaffolds one wildcard entry —
130
+ `{ match: "*", token: "JR2_GIT_TOKEN", sshKey: "jr2-git-ssh" }`, today's two implicit defaults made visible, with a
131
+ comment saying to narrow it before anything untrusted can start a run, and that it also pushes: a `push()` whose Repo
132
+ Slot url it matches, a per-run url included, spends the same token or key — so the open fence is in the user's file
133
+ and commit, never implied.
134
+ - **An entry may carry a push credential and a signing key** (ADR-0067), checked by shape when the config loads:
135
+ `push?: { sshKey } | { token }`, the credential a `push()` to a Binding under the entry spends — absent, the clone
136
+ credential pushes, and an entry with no credential admits a clone, never a push; a token goes Basic over https through
137
+ a credential helper only, which answers for the Binding's own host alone, and the push follows no redirect, so the
138
+ token never reaches an origin outside the entry. `sign?: { sshKey, name, email } | { gpgKey, name, email }`, the key a
139
+ `push({ sign: true })` signs with and the committer it signs as — explicit, so an auth key is never reused as a
140
+ signing key; an ssh key is the default format, a GPG key is a Secret with `private.asc` and no passphrase. `name` and
141
+ `email` are required: the Effector re-creates the commits, and a forge verifies a signature only when the committer
142
+ email belongs to the account that holds the key. The author line stays the Agent's. `committer?: { name, email }`, who
143
+ an unsigned `push({ squash: true })` is committed by, at the Effector's clock (ADR-0067): a squash is a new commit,
144
+ and without `sign` no signer names its committer; `jr2 up` refuses an unsigned squash whose entry has neither `sign`
145
+ nor `committer`.
146
+ - **The pod's maker places the push credential, by reference, when the url is known.** When a registered Machine pushes,
147
+ `jr2 up` writes each entry's push and sign credentials into that entry's own Secret, `jr2-git-push-<hash of match>`
148
+ (labelled, and deleted when the entry no longer has one), never per Workspace: the token from `.env` in Flux's key
149
+ names (`username`, `password`); the keys of the Secret its ssh key names, copied as data and never decoded
150
+ (`identity`, `identity.pub`, `known_hosts`); the signing key as `sign.identity` or `sign.private.asc`; and
151
+ `entry.json`, what the entry is (its match, what it pushes with, the committer it signs as). An entry with no `push`
152
+ field pushes with its clone credentials, each over its own scheme, so the scaffold's wildcard holds both. An entry
153
+ with nothing it can place — its token unset, its key's Secret absent — gets no Secret, and `jr2 up` says why. It
154
+ writes `git-push.json` in `jr2-held` beside them: which entries have a Secret and what each can spend, names only. At
155
+ provision the Orchestrator matches each pushed slot's url — static, per-run or `customize()`-bound, one path — against
156
+ the fence (longest prefix, the attach match), and the Sandbox CR carries the matched entry's Secret name
157
+ (`gitCredentials`, the `secretRef` shape); the operator projects it into the Sandbox Effector alone (ADR-0005). The
158
+ Orchestrator reads no value. The Effector holds only the entries named there and matches a push's url as the fence
159
+ does, longest match first, and refuses a url outside the entry.
160
+ - **A push is refused with a fix that works, at `jr2 up` for a static Binding and at provision for any url.** One rule
161
+ (`pushRefusal`), and the fix each refusal names is one the same rule then accepts: a url no entry matches (the entry
162
+ to add, its `match` the url's host and directories, so always a prefix of the url); a local path (git's local
163
+ transport runs that repository's config and hooks) or `git://`; an `http://` url — a push credential goes over https
164
+ or ssh only: a token over http is the token in the clear, and no ssh key is spent over http — told the url its entry
165
+ can push to (https for a token, ssh for an ssh key) or, when no entry or no credential is there, the https url and the
166
+ token to add; an entry that holds no credential the url's scheme can spend (a token over https, an ssh key over ssh):
167
+ the clone field to add when it holds a clone credential and no `push` field, since a `push` field would replace that
168
+ credential, the `push` field when it holds none, and the other scheme's url when its `push` field already names the
169
+ other credential, since a `push` field names one; and `sign: true` on an entry with no `sign` field (the field to
170
+ add). `jr2 up` reads a static Binding's entry as written, then refuses an https Binding whose entry's token variable
171
+ is unset. A key's Secret, or a signing key's, that is absent at `jr2 up` is a warning, not a refusal: it may be
172
+ written after (ADR-0047's prompt, a human); the entry's Secret is written without that key, and the provision refuses
173
+ the push that needs it, naming the Secret to create and `jr2 up` to run again. The provision reads the same entry as
174
+ `jr2 up` placed it, so a per-run url is refused at attach for the same reasons, as a url outside the fence is, before
175
+ anything is written. Accepted costs: `jr2 up`'s report is exact for a static Binding and says "the entry its url
176
+ matches" for a per-run slot; a wildcard entry gives a per-run push the catch-all credential, the fence's existing
177
+ choice.
178
+ - **Freshness degrades, absence does not.** A fetch that fails on a warm cache lets the attach proceed on the objects it
179
+ has, announced as stale with git's own error. A stale attach is stale until the next fetch anyone inside the pod runs:
180
+ the worktree's `origin` asks the cache, and the cache asks the remote (ADR-0053) — no container the Agent executes in
181
+ holds a credential (ADR-0005), and the same failure lets that fetch fall through to the cache's objects with a warning
182
+ on stderr. A clone that fails on a cold node fails that provision pointedly, naming the repository and the error, and
183
+ the CR status carries it for `jr2 status` (ADR-0048's pattern).
184
+ - **Eviction is reachability plus age.** `jr2 gc` deletes a `Repo` CR that no Machine of a held Assembly binds (the
185
+ current one's, and every one a live run holds,
186
+ [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)) and no run has attached
187
+ within its TTL; the cache agent removes the node copy on CR deletion, once no pod on that node mounts it.
188
+
189
+ ## Considered options
190
+
191
+ - **Image volumes** (each repository generation packed as an OCI artifact, mounted by kubelet): gives per-node cache,
192
+ scheduling, and GC for free, and immutability makes the gc invariant structural. Rejected: a refresh is a new layer
193
+ per fetch, so packs accumulate until a repack, and a repack is a full re-upload and a full re-pull on every node — the
194
+ re-clone cost a mutable bare repo never pays. Immutability bought only what gc pinning already buys.
195
+ - **A per-repo PersistentVolume.** Rejected: an RWO volume still binds to one node at a time; it changes nothing about
196
+ pinning, only the number of volumes.
197
+ - **Keep the shared PVC, require RWX storage for multi-node.** Rejected: scales the storage, not the fetch, and makes
198
+ the kit's first cluster requirement a storage class most small clusters lack.
199
+ - **A Flux-style `GitRepository` serving an artifact over HTTP.** Rejected: an artifact download is a clone per Sandbox
200
+ — the cost the cache exists to avoid.
201
+ - **Urls kept in the spec, not the slot** (the shape before this decision, with a url in place of a name). Rejected: the
202
+ walk cannot see inside the spec, so a bound repository would be invisible to `jr2 up` and to prewarm, and `customize`
203
+ would have nothing to bind.
204
+
205
+ ## Consequences
206
+
207
+ - The operator becomes git-aware, which ADR-0001 kept it from being: the `Repo` CRD and the cache agent are its. The
208
+ Sandbox CRD stays free of git semantics beyond a list of identities; clone and worktree remain the Orchestrator's
209
+ post-`Ready` step (ADR-0004).
210
+ - `workspace()`'s `repos` names at least one slot, or is `open` whole — in which case the `customize` that names the map
211
+ is held to the same rule — or is left out, for a Workspace with no Repo Slot; a Machine that composes no Sandbox has
212
+ no repos to customize, and the data-plane switch ("does this Instance need Sandboxes") is "a registered Machine
213
+ composes a Sandbox", read off the walk, not "config has repos". That switch decides what `jr2 up` DEPLOYS for
214
+ Sandboxes, the cache agent and the Sandbox Images; a deployed Orchestrator wires its kube client whatever its current
215
+ Assembly composes, because a publish can bring the first `workspace()` without a roll
216
+ ([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)).
217
+ - Every slot gets a branch worktree cut at the run's branch, a reference checkout included. A slot has no read-only
218
+ mode; that is a decision of its own if it is ever wanted.
219
+ - The Console's start form loses the catalog dropdown: a per-run repository is a url field unless the Machine's door
220
+ enumerates its own.
221
+ - The volume paths change: the in-pod source mount is `/repos/<key>` where `<key>` is derived from the identity, and the
222
+ pod-local layout is `/work/<slot>/{default,<branch>}`. Existing checkouts on a pre-0051 PVC are not migrated.
223
+ - ADR-0047's deploy-key prompt reads ssh urls off the walk. A per-run ssh url first seen at attach was never prompted
224
+ for; its first attach fails with the key hint, as any unregistered key does.
225
+ - A `customize` of a per-run slot with a static url is legal and makes it bound; a Machine that wants a slot to stay the
226
+ run's declares it per-run and the consumer leaves it alone. Which slots a consumer may fix is the package author's
227
+ call, expressed by the slot's state.
@@ -0,0 +1,81 @@
1
+ # A Sandbox lands where an ordinary pod lands, and the cache follows it
2
+
3
+ [ADR-0051](0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md) made a Sandbox schedulable on any node and
4
+ put a Repo cache agent on every node as a DaemonSet that tolerates every taint. The grill of 2026-09-13 found the two
5
+ halves disagreeing about which nodes matter: the Sandbox pod carries no tolerations and no selector, so it lands only
6
+ where an ordinary pod lands, while the cache agent probes and clones on control-plane and GPU nodes no Sandbox can reach
7
+ — and a `present` cache there scores a node the scheduler cannot use. Meanwhile
8
+ [ADR-0045](0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md) read a third set (every node not
9
+ cordoned) to choose build platforms, and nothing in `jr2.config.ts` could say which nodes an Instance's Sandboxes may
10
+ use. **Which nodes are an Instance's Sandbox nodes, and who follows that set** is one decision.
11
+
12
+ ## Decision
13
+
14
+ - **A Sandbox node is where an ordinary pod lands. There is no jr2 node label.** A node is a Sandbox node when it is not
15
+ cordoned, matches the Instance's `sandbox.nodeSelector`, and carries no taint the Instance's `sandbox.tolerations` do
16
+ not tolerate. With no config that is exactly the default scheduler's answer: taints already express "not for ordinary
17
+ work" for every workload on the cluster, so jr2 reads them instead of asking for a second, jr2-only opt-in that every
18
+ kind cluster and every fresh pool would have to perform before `jr2 up` could work.
19
+ - **The override is Instance-level and raw Kubernetes**: `sandbox: { nodeSelector?, tolerations? }` in `jr2.config.ts`,
20
+ pod-spec shapes verbatim, the stance `harness.env` takes for `EnvVar`. A node label or taint is a deployment fact a
21
+ Machine cannot know (ADR-0050), so it lives in the file that holds deployment facts. No `affinity` key: the operator
22
+ owns the Sandbox's affinity (the soft preference toward nodes holding its Repo caches), and a user preference beside
23
+ it would need a merge rule for a case no cluster has asked for. A requirement (`nodeSelector`) never conflicts with a
24
+ preference, so the two sit side by side untouched.
25
+ - **One predicate, three readers.** The Sandbox pod, the cache-agent DaemonSet, and `jr2 up`'s summary all use the same
26
+ `nodeSelector` and `tolerations`. The DaemonSet loses its blanket `operator: Exists` toleration: one cache agent per
27
+ Sandbox node, so a cache is only ever present where a Sandbox can land, and the affinity it feeds only ever names a
28
+ usable node. The build set (ADR-0045) is the union of the nodes an ordinary pod lands on — the Orchestrator
29
+ Deployment's own placement — and the Sandbox nodes, intersected with the supported set as before.
30
+ - **The Sandbox CR carries the placement.** `Sandbox.spec` gains `nodeSelector` and `tolerations` beside `resources`,
31
+ `initContainers`, and `volumes`; the Orchestrator writes them from its baked config at provision and the operator
32
+ copies them onto the pod, merging nothing. The operator stays config-blind (ADR-0001), and every Instance's placement
33
+ is its own: the CR is namespaced, the DaemonSet is per Instance with a per-namespace hostPath, and the shared operator
34
+ holds no placement state. Version skew is ADR-0019's contract: an older operator would place a pod without the new
35
+ fields, and the `jr2 up` that introduces them is the one that upgrades the operator.
36
+ - **An empty set warns; it never refuses.** `jr2 up` prints the Sandbox nodes it sees, and when there are none names
37
+ each node with the taint or selector that excluded it and the `sandbox.*` line that would admit it — then converges.
38
+ The set moves: a pool autoscales from zero, a node is cordoned mid-converge, a GPU box joins tomorrow. A refusal would
39
+ turn `jr2 up` into a point-in-time check of a moving set. The run-time truth is the operator's: a `Pending` Sandbox
40
+ reports the scheduler's own `FailedScheduling` message through its condition, so `jr2 status` shows the taint or
41
+ selector while the run waits (ADR-0048's pattern).
42
+ - **A Machine says nothing about placement — for now.** A packaged `workspace()` that needs a GPU cannot write the
43
+ consumer's label (the encapsulation gap ADR-0049/0051 closed for Agents and Repos). The extension, when a Machine
44
+ needs it, is a named class: `workspace(body, { node: "gpu" })` and
45
+ `sandbox: { classes: { gpu: { nodeSelector, tolerations } } }`, refused by `jr2 up`'s walk when unmapped — the Repo
46
+ Slot pattern. `sandbox.nodeSelector` and `sandbox.tolerations` are that shape's default class, so the extension adds
47
+ `classes` beside them and renames nothing; and because placement rides the CR, a per-Machine class needs no new
48
+ plumbing. A class is placement only: how big a Sandbox is belongs to the Machine, not the cluster, and rides the
49
+ `workspace()` as its Size ([ADR-0060](0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md)).
50
+ `sandbox.resources` sits beside `nodeSelector` and `tolerations` as the Instance's default Size, for a Workspace that
51
+ states none.
52
+
53
+ ## Considered options
54
+
55
+ - **A jr2 label** (`jr2.dev/sandbox=true` on nodes that opt in). Rejected: every cluster pays a labeling step before the
56
+ first `jr2 up`, a single-node kind cluster included, and a labeled node is a candidate for every Sandbox — a GPU node
57
+ labeled for one Machine hosts all of them. Taints already say what the label would, for every workload at once.
58
+ - **A jr2 vocabulary** (`sandbox.nodes: ["node-4"]`, or `pool: "workers"`). Rejected: simpler to type and wrong on the
59
+ first autoscaled pool, where node names are not stable and "pool" is a cloud provider's word with a different label on
60
+ each.
61
+ - **The operator reads placement from a per-Instance object** (a ConfigMap, or the DaemonSet's own template) instead of
62
+ the CR. Rejected: a second read per reconcile and a cross-object dependency the CR does not declare, for no gain — the
63
+ CR already carries everything else the pod needs.
64
+ - **Refuse `jr2 up` on an empty set**, the ADR-0051 stance for an unbound slot. Rejected: a slot is a static fact of the
65
+ code; the node set is a fact of the cluster right now.
66
+ - **Keep the DaemonSet on every node.** Rejected: a cache on a node no Sandbox reaches is a clone nobody reads, a probe
67
+ that fails on a control-plane node's egress rules, and an affinity term the scheduler cannot honor.
68
+
69
+ ## Consequences
70
+
71
+ - ADR-0045 is rewritten in place: "schedulable nodes" is the union above, and the `platforms` escape hatch's "polluted
72
+ set" case narrows to an untainted pool of a foreign arch — a tainted GPU pool is no longer counted.
73
+ - ADR-0051 is rewritten in place: the cache agent is a DaemonSet over the Sandbox nodes, not every node.
74
+ - The DaemonSet controller auto-tolerates `node.kubernetes.io/unschedulable`, so a cache agent also sits on a cordoned
75
+ Sandbox node. Harmless: the scheduler ignores the soft affinity toward a node it cannot use.
76
+ - A Sandbox node of a NEW architecture that joins after a converge meets a Sandbox Image the cluster never built for it
77
+ and fails with `exec format error` until the next `jr2 up` — as any node did before this ADR; `platforms` is the
78
+ answer for a pool that autoscales from zero.
79
+ - The Orchestrator and operator Deployments keep their own placement; `sandbox.*` names Sandboxes only.
80
+ - A `NoExecute` taint an Instance tolerates on the Sandbox pod is tolerated verbatim: jr2 adds no `tolerationSeconds`,
81
+ so a Sandbox stays through the taint for as long as its Lease is renewed (ADR-0021).
@@ -0,0 +1,111 @@
1
+ # A fetch inside the pod asks the node cache, and the cache asks the remote
2
+
3
+ [ADR-0051](0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md) made the node cache the only thing that
4
+ fetches from a remote, and left one consequence standing: stale stays stale from inside the pod until the interval or
5
+ the next attach. The only ask a cache agent understood was a pod's birth — `asked` is the newest pod's creation time,
6
+ and a Sandbox is `Ready` once its caches were fetched after its CR was created. Nothing after birth could ask: no CLI,
7
+ no route out of the pod, no body primitive. The grill of 2026-09-13 took the concrete case — a human pushes a fix to
8
+ `master`, the coder at `request_changes` must rebase onto it now, and the human in the User Container of the same pod
9
+ wants the same commit — and found that a five-minute interval is not a freshness model, it is the absence of one. **How
10
+ a fetch inside a pod reaches the remote, and who may ask** is one decision.
11
+
12
+ ## Decision
13
+
14
+ - **`origin`'s fetch url is a command, not a path.** The attach sets it to git's built-in `ext::` transport, an absolute
15
+ program on the runtime volume with the Repo's identity as its argument:
16
+
17
+ ```
18
+ ext::/opt/jr2/bin/jr2-upload-pack %S github.com/acme/app
19
+ ```
20
+
21
+ Git runs the program and bridges stdio, so the program owes no remote-helper protocol: it asks for a fetch, waits for
22
+ the landing, then execs `git upload-pack /repos/<key>` and gets out of the way. Git speaks its ordinary protocol and
23
+ prints its ordinary output; refs update once, at the end. What the caller sees is a fetch on a slow handshake, never
24
+ an early return and never a partial state. `git fetch`, `git pull`, `git ls-remote origin`, and `git fetch --dry-run`
25
+ all pass through it, so every fetch inside a pod is a fetch of the remote's now. `git archive --remote=origin` is the
26
+ one other read git asks a remote for, and the program serves it the same way — the same ask, then `git upload-archive`
27
+ against the same cache — because the path url served it and a decision about fetching may not quietly take it away.
28
+ Nothing else in git talks to a remote, so `status`, `log`, and `rebase` stay local. The push url is unchanged: the
29
+ Binding's own spelling, the caller's own credential, never the Agent's (ADR-0005).
30
+
31
+ - **The ask rides the Sandbox CR and reaches the agent through the pod.** The program asks the Custodian on `localhost`
32
+ (`POST /fetch`, with the Sandbox token's Stand-in — a constant, because the User Container gets no env); the Custodian
33
+ puts the token it holds in place of the Stand-in and asks the Orchestrator for THIS pod's Sandbox
34
+ (`POST /sandboxes/<name>/fetch` — the program cannot name another,
35
+ [ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)); the Orchestrator patches one
36
+ annotation on the Sandbox CR, per Repo key, timestamp value — the Lease's shape, an annotation the Orchestrator
37
+ writes. The operator copies it onto the pod as it copies everything else the pod needs from the CR, and the cache
38
+ agent's `asked` becomes the later of the pod's creation and its ask for that key. Demand stays read off pods alone.
39
+ The mark is a coalescer: however many fetches are in flight before one lands, the remote is fetched once, and a fetch
40
+ that started before the ask does not satisfy it — the agent stamps `lastFetched` with the attempt's start, which is
41
+ what makes that true.
42
+ - **The landing is reported on the Sandbox, standing and per key.** The verdict the operator already computes on the way
43
+ to `Ready` — fresh, stale with git's error, or still cloning, per Repo, against the CR's creation — becomes a standing
44
+ entry on the Sandbox status computed against the later of creation and the ask. `Ready` itself stays as it was:
45
+ sticky, once per pod life. The Orchestrator's route waits on that entry — a Sandbox watch event, never a poll
46
+ ([ADR-0063](0063-the-orchestrator-watches-the-cluster.md)) — then answers the Custodian, which answers the program.
47
+ One wait, and it is the wait an attach already makes.
48
+ - **The scope is the Sandbox token's scope, and no seat gains a credential.** A Sandbox may ask for the caches it mounts
49
+ and nothing else; the remote is reached by the cache agent with the Repo CR's `secretRef`, the credential that cloned
50
+ the cache. The pod holds none before and none after. The Menu is untouched — a fetch is not something the Agent
51
+ _says_, it is something it _does_ — and no Machine declares anything: the ability is ambient in every Workspace pod,
52
+ the way Working tools are, and absent on the Instance Harness, which mounts no Repo.
53
+ - **Freshness degrades, absence does not, and the caller is told.** A remote fetch that fails or outruns the on-demand
54
+ budget lets the program fall through to the objects the cache holds — ADR-0051's stance for attach — and writes one
55
+ line to stderr, which git passes through verbatim:
56
+ `warning: jr2: remote fetch failed (<git's error>); serving the cache as of <lastFetched>`. The budget is the cache
57
+ agent's one on-demand number; the program's wait is that plus watch slack, and nothing else owns a timeout.
58
+ - **Every seat that holds the checkouts holds the program.** The fetch url lives in the shared `default/.git/config`, so
59
+ a seat without the program has checkouts whose `git fetch` dies. That is ADR-0005's own argument for `/repos` — half
60
+ of one exception, not a second — applied once more: the pod's `/opt/jr2` volume is mounted read-only into the User
61
+ Container beside `/repos`. `ext::` names the program by absolute path, so the seat needs no PATH and jr2 injects no
62
+ env; the program is a static binary, the `work-acl` rule (ADR-0037), because it executes on a libc jr2 does not
63
+ control. A human in the User Container gets the same `git fetch` as the Agent, with no credential of their own; the
64
+ human `exec`'d into the Harness container already had it.
65
+ - **Names.** `fetch` is what an Agent, a human, and a cache do; `refresh` is what the interval does and stays on
66
+ `refreshInterval`; an _ask_ is the mark, the word the agent and the operator already use. The url carries the Repo's
67
+ identity, never the cache key — a key is a derived directory name, never chosen by a human (ADR-0004), and not the
68
+ name a human should read in `git remote -v`.
69
+ - **What stays.** An attach still fetches before it runs, because a creation is an ask. The interval still runs, so a
70
+ cold attach and `jr2 status` stay honest without anyone asking. There is no CLI verb: the human who wants the fetch is
71
+ in the pod, where `git fetch` is the verb, and the mark makes a `jr2` verb additive if a caller outside the pod ever
72
+ needs one.
73
+
74
+ ## Considered options
75
+
76
+ - **The run asks** — a body primitive before a Turn, or on `request_changes` re-entry, the way attach asks on entry.
77
+ Rejected as the only path: it covers what the author foresaw and nothing an Agent discovers mid-Turn. Not foreclosed:
78
+ the mark is the substrate, and a body primitive that sets it is additive.
79
+ - **A Menu pick** (`need_fresh`, declared with `defineEvent`, a `fetching` state the pick transitions to). Rejected: a
80
+ Turn boundary per fetch, an author opt-in per state, and a transition for something that moves the Machine nowhere. A
81
+ fetch is not part of a Machine's Vocabulary.
82
+ - **A named Menu tool** (`mcp__jr2__fetch` beside the Menu's events). Rejected: the model must learn a tool and remember
83
+ to call it before the verb it already knows, and a human at a shell gets nothing from it.
84
+ - **Shorten the interval.** Rejected: polling per node per Repo, and the latency is still the interval.
85
+ - **The ask on the Repo CR.** Rejected: every node holding the cache fetches, and the wait must then name a node.
86
+ - **The cache agent reads Sandbox CRs.** Rejected: two sources of demand where one exists, and a new RBAC grant for the
87
+ DaemonSet, to save one copy the operator makes for every other field.
88
+ - **A `jr2::` remote helper found on PATH.** Rejected: PATH is env, the User Container gets none (ADR-0005), and an owed
89
+ image line where `ext::` costs nothing but a longer line in `git remote -v`.
90
+ - **The human's credentialed fetch pushed into the cache.** Rejected: the cache is read-only in the pod by design, the
91
+ structural half of the gc invariant (ADR-0004). And moot: with this decision the human's own credential is not needed
92
+ for a fetch at all.
93
+ - **Fail the fetch when the remote fails.** Rejected: attach chose availability (ADR-0051), and the Agent's `git fetch`
94
+ would become less reliable than today, where it always succeeds against the cache.
95
+
96
+ ## Consequences
97
+
98
+ - ADR-0051 is rewritten in place: "stale stays stale from inside the pod" is retired; a stale attach is stale until the
99
+ next fetch anyone inside the pod runs. ADR-0005 is rewritten in place: "`git fetch origin` reads the node cache" is
100
+ now "asks the node cache", the User Container mounts `/opt/jr2` read-only beside `/repos`, and "nothing injected"
101
+ narrows to the seat's process — no env, no command, no probe. ADR-0004's refresh section gains the in-pod ask.
102
+ - The runtime volume carries a second static binary, and so a jr2 program is present in every seat of every Workspace
103
+ pod, User Container included. It is the natural home for in-pod jr2 functionality that does not yet exist.
104
+ - A fetch inside a pod is a remote round trip through the node agent, seconds rather than milliseconds. Every git
105
+ command that fetches pays it; none that does not.
106
+ - The Sandbox status grows a standing per-key Repo entry; the Custodian carries one loopback route for it; the
107
+ Orchestrator gains a Sandbox-token route. No new credential, no new watch in the cache agent, no git knowledge in the
108
+ operator beyond the timestamp comparison it already makes.
109
+ - The rewind gotcha vanishes: a human who fetched around the cache with their own credential advanced a remote-tracking
110
+ ref the Agent's next cache fetch forced back. Nobody fetches around the cache now.
111
+ - The Harness `bash` tool's own timeout still bounds a fetch the way it bounds any slow command.
@@ -0,0 +1,126 @@
1
+ # The kit ships Machines, and a shipped Machine leaves its parts Open
2
+
3
+ The kit's only worked Machines lived in `examples/`: two model Instances, one of them (`coding`) four workflows deep,
4
+ written against surfaces three ADRs old, runnable by nobody but the author, and installable by no one — an example is
5
+ copied, never imported. ADR-0049 had already settled that "a package exports a Machine, nothing beside it" and ADR-0019
6
+ that "reusable work is published as npm packages", yet the kit itself shipped none, so the packaged-Machine story had no
7
+ first instance to hold it honest. The grill of 2026-09-14 replaced the examples with a package, and in doing so found
8
+ the one part ADR-0049/0051 left a package unable to leave unsaid: **the model.**
9
+
10
+ ## Decision
11
+
12
+ - **The kit ships Machines as `@jr2/machines`.** A fourth published package beside
13
+ `@jr2/{cli,orchestrator,agent-protocol}` (ADR-0043), on the same release train, exporting named Machines from its
14
+ index: `import { task } from "@jr2/machines"`. It ships Machines and never Workflows — a Workflow is the name an
15
+ Instance registers a Machine under, and only the Instance's `workflows/` file can do that (GLOSSARY.md, ADR-0009).
16
+ Rejected: `@jr2/workflows` — it names exactly the thing the kit does not ship, and `workflow` is on Machine's
17
+ avoid-list for the reason ADR-0049/0050 lean on the distinction.
18
+ - **`@jr2/machines` authors against the consumer's `@jr2/orchestrator`: peer dependencies, no regular ones.** Every part
19
+ a Machine carries is keyed in maps `@jr2/orchestrator` holds (vocabulary.ts, parts.ts) and in `xstate`'s own `provide`
20
+ and `isMachine`; a second copy of either is a Machine whose Menus are empty and whose Gates accept nothing, with no
21
+ error anywhere. `peerDependencies: { "@jr2/orchestrator": <exact kit version>, xstate, zod }` states the fact and is
22
+ the shape a user's own Machine package copies. Rejected: regular deps at the exact version — dedupe makes it work by
23
+ the accident of equal versions and installs two copies silently on skew.
24
+ - **A shipped Machine leaves its parts Open, and Open widens from the Repo Slot to the Agent's model.** ADR-0051's
25
+ `open` sentinel — "a consumer's to bind, with `customize`" — applies unchanged to the one part a package cannot
26
+ honestly fill: `agent({ model: open, instructions })`. The `jr2 up` walk reports an Open model beside Open Repo Slots
27
+ and refuses the converge naming the `customize` line that binds it; the Agent actor refuses to admit a Turn whose
28
+ definition is still Open, as the second fence. `AgentDefinition` stays the wire type with `model: string` — a Symbol
29
+ does not ride a Turn — and `agent()` takes a declaration whose `model` is `string | typeof open`. The sentinel becomes
30
+ `Symbol.for("jr2.open")`. Rejected: a stock default model — the kit picks a vendor in a published package, and a user
31
+ who never reads the file spends money on a model they did not choose, silently, which is the failure class `jr2 up`
32
+ exists to make loud. Rejected: a Machine factory `task({ model })` — ADR-0049 settled that a package exports a Machine
33
+ and retunes with `customize`, and a factory reopens that.
34
+ - **Open widens to an Effect's url, argv and secret, bound once.** A package cannot know the host its `http` Effect
35
+ calls, the program its `command` runs
36
+ ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)), or the secret its
37
+ composer spends, so it may leave each Open: `http({ method, url: open, auth, input, request })`,
38
+ `http({ method: "PUT", url: open, body: file(path) })`, `command(open, { env, pin })`, and `secret(open)` in place of
39
+ a secret's name. The url, the argv and the secret's name are the Open parts of an Effect that exist; everything else
40
+ the definition says is the package's as written — the method, the headers, the auth and the secrets they name, the
41
+ input, `request`, the env, `pin` — and is checked when the Machine is built. `secret(open)` is taken by `bearer`,
42
+ `basic` (the user or the password), a header's value, and a command's env, as its value or its `.path`; in a url it is
43
+ refused when the Machine is built ("leave the whole url Open"), since a url that carries a secret is left Open whole
44
+ (below). An Effect takes one Open secret: a second `secret(open)` in one definition is refused when the Machine is
45
+ built ("an Effect takes one Open secret"); the one `secret(open)` may stand in several places, a value and its
46
+ `.path`. An Effect that names an Open secret is placed as one that names a secret is — a credentialed command in the
47
+ Sandbox Effector, an `http` where its body places it — before the name is known. The composer binds the parts with
48
+ `customize(machine, { effects: { <slot>: { url } } })`, `{ argv }` or `{ secret }`, and an Effect with an Open url or
49
+ argv beside an Open secret takes one object with both keys (`{ url, secret }`, `{ argv, secret }`): a url is a string
50
+ or a `text` with a secret in its path, an argv is the usual list, and a secret is a name that obeys `secret()`'s own
51
+ rule — it is the secret's file in the Effector's mount (ADR-0068). The type of `effects` is the intersection of the
52
+ Open parts' bindings, so a binding that leaves one of them unbound is a compile error, as an unbound url is; at run
53
+ time, a binding that leaves one unbound gives an Effect still Open in it, which a later `customize()` may bind. Each
54
+ is bound ONCE, to a value: never a mapper over the door, so a host is fixed in the definition or bound before any run,
55
+ and no run chooses one, nor a secret. Binding builds the Effect again with its constructor, with the bound name in
56
+ every place `secret(open)` stood, so a bound part meets every rule the constructor checks — a literal host, no secret
57
+ in a query, a non-empty argv, a secret's name — and the bound Effect is the one the same definition written with that
58
+ name gives; `jr2 up` reads it as written: the placement, the secrets it names, and the Effector's fence
59
+ ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)) are the bound ones, and the bound secret
60
+ is placed only in that Effect's Effector, as a named secret is. Only an Open part is offered: a part the author fixed
61
+ is no key of `effects` and is refused at the call, and a part bound once is fixed from then on, so a second composer
62
+ cannot move it. `effects` reaches through `workspace()`'s body, `pool()`'s worker and a composed Machine's `actors`,
63
+ as `agents` does, typed off the slot's logic. An Open argv's slots are the composer's, so the load walk counts every
64
+ pick param as read by it when the Machine is built, and `customize()` runs the walk's pick-param check again once the
65
+ argv is bound — a param the bound argv does not read is refused there, and a warning it gives for the bound argv is
66
+ the bound Machine's, as it is the same Machine's written with that argv: the bound Machine gets a config of its own,
67
+ which carries those warnings ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so a copy
68
+ `provide()` makes of it — a run's — keeps them, and the Open Machine keeps its own. The compiler's check does not
69
+ reach a bound argv: an Open argv's input is one string per key, so nothing at build or at `customize()` checks that
70
+ the invoke supplies each slot the bound argv names, and a slot it does not supply faults when the Effect starts — its
71
+ state's `onError`, with nothing run. The `jr2 up` walk reports an Open Effect beside Open Repo Slots and Agents and
72
+ refuses the converge naming the `effects` line — `{ effects: { <slot>: { secret: "<name>" } } }` for an Open secret,
73
+ one object with both keys for an Effect with two Open parts; the Effect refuses to run while Open, as the second fence
74
+ — its state's `onError`, with nothing sent and no ledger row. The Open part is the whole url, the whole argv, or the
75
+ secret's name.
76
+ - **The per-run Dial is the escape hatch, not the default.** A shipped Machine's door may carry `model` and
77
+ `thinkingLevel` (ADR-0018's two Dials) and fold them into each invoke's input; the bound definition is what `jr2 up`
78
+ preflights, the door value overrides it for one run's Turns. Nothing in jr2 changes for this — it is the Machine's own
79
+ four lines.
80
+ - **The first Machine is `task`: one prompt, one Workspace, one human says done.** Door
81
+ `{ prompt, branch?, model?, thinkingLevel? }`, branch defaulting to `jr2/task-<instance id>`, where `<instance id>` is
82
+ the seed Instance ID of the run (`instanceId` in `jr2 status`), not the run id; the Repo Slots Open as a map
83
+ (`repos: open`, ADR-0051) — the consumer names them, and the order is `task`'s own convention, since jr2 gives no slot
84
+ a meaning: the first is where the coder works, any others are checkouts the first Turn frames for the coder to read;
85
+ one Agent `coder` with an Open model. `working` invokes `coder`; its `finish { summary }` parks at the `review` Gate
86
+ (meta: summary, branch, worktree), as does a terminal `agent.fault` (meta: reason). `approve` reaches the final state
87
+ and tears the Workspace down; `request_changes { notes }` **continues the same conversation** (`continue: true`,
88
+ ADR-0057) — one human steering one Agent wants the Agent to remember what it did, unlike ADR-0049's coder⇄reviewer
89
+ handoff, which is lossy on purpose. No round cap: the human is the cap. Rejected: a per-run Repo on the door — a
90
+ packaged door cannot enumerate the Instance's repos, and "a Workflow is a name" reads best when the name means "a
91
+ prompt against THIS repo". Rejected: a named `target: open` slot — the body reads the first checkout the handles
92
+ carry, never `repos.target`, so the name claimed a shape the body did not have and shut out the one thing a task
93
+ commonly wants beside its repository: another checkout to read.
94
+ - **The Machine does not push.** ADR-0005/0053: the Agent holds no credential and the push url is the caller's own
95
+ spelling with the caller's own credential. The Gate park is the inspection window — exec in, review, push, then
96
+ `approve`, and unpushed commits go with the pod, as ADR-0012 always said. A credentialed push out of the pod is a
97
+ decision of its own, not this one.
98
+ - **`examples/` retires; `templates/default` is the one Instance in the checkout.** The scaffold's model moves from
99
+ `examples/starter` to `templates/default` — the folder documents what an Instance looks like and is what `jr2 init`
100
+ renders, kept byte-equal to the CLI's inline templates by the existing drift test, since an installed CLI cannot read
101
+ a repo-root folder (ADR-0043). `templates/` is plural because a later `jr2 init --template <name>` is the obvious next
102
+ step. The scaffold stays `ping`-only — the `@dist` tier's contract is a fresh instance that runs before any provider
103
+ or repo exists — and `ping.ts`'s prose shows the `@jr2/machines` move as the next step instead of an inline `agent()`
104
+ sketch. `examples/coding` is deleted whole, `jr.ts` included;
105
+ [An Agent in a Sandbox](../../get-started/an-agent-in-a-sandbox.md) shows `task` in its place.
106
+
107
+ ## Consequences
108
+
109
+ - **Every Machine in `@jr2/machines` ships with a `@kind` scenario.** These are for end users, and the only proof a
110
+ shipped Machine works in jr2 is the stock Harness driving it in a real Sandbox. `features/kind-instance` registers
111
+ each one the way a consumer would — `customize(task, { repos, agents })` over the tier's fixture repo and scripted
112
+ model — and the scenario walks its Gates. The socket-free mechanics test beside the Machine covers the default
113
+ `pnpm -r test` gate; it does not stand in for the scenario.
114
+ - `@jr2/machines` joins the `PUBLIC` list in `publish.test.ts` and the guard loop in `scripts/dist-publish.sh`; the
115
+ `@dist` tier publishes it to verdaccio, so its pack and `files:` list execute there even though no `@dist` scenario
116
+ runs it.
117
+ - GLOSSARY.md gains **Open** as a term, and the Workflow entry says the kit ships Machines. Eleven earlier ADRs cite
118
+ `examples/`; the citations are retargeted in place.
119
+ - A `customize` that binds a model is now the common consumer line, not the exotic one; `jr2 up`'s refusal for an Open
120
+ Agent prints it, as it does for an Open Repo Slot and an Open Effect (its `effects` line).
121
+ - No Machine in `@jr2/machines` leaves an Effect Open yet; `task` carries no Effect.
122
+ - Two copies of one Machine spend two teams' secrets: each composer binds the Open secret, or a url that carries one in
123
+ its path, with its own. A labelled form for two Open secrets in one Effect can be added later without changing the
124
+ unlabelled one.
125
+ - The `triaged-task` mechanics test went with `examples/coding`, and the cross-Machine conversation pin it exercised
126
+ went with ADR-0057; the `@kind` `consulted`/`advised` fixtures hold Instance Harness placement.