@jr2/orchestrator 0.4.1 → 0.6.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 (361) 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 +478 -0
  10. package/docs/concepts/architecture.md +243 -0
  11. package/docs/concepts/charts.md +146 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +257 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +658 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +298 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +306 -0
  20. package/docs/concepts/the-menu.md +381 -0
  21. package/docs/concepts/workspaces.md +756 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
  27. package/docs/get-started/install.md +130 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +308 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +224 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/cut-a-worktree.md +560 -0
  34. package/docs/guides/fix-a-failed-converge.md +361 -0
  35. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  36. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  37. package/docs/guides/let-an-agent-write-a-chart.md +230 -0
  38. package/docs/guides/look-inside-a-sandbox.md +177 -0
  39. package/docs/guides/mirror-the-kit-images.md +176 -0
  40. package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
  41. package/docs/guides/open-the-console.md +91 -0
  42. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  43. package/docs/guides/push-and-sign-a-branch.md +532 -0
  44. package/docs/guides/run-a-chart-by-name.md +270 -0
  45. package/docs/guides/run-a-command-or-call-a-url.md +587 -0
  46. package/docs/guides/run-one-worker-per-item.md +322 -0
  47. package/docs/guides/set-an-agents-access-and-dials.md +420 -0
  48. package/docs/guides/size-a-workspace.md +217 -0
  49. package/docs/guides/start-a-run-from-a-run.md +237 -0
  50. package/docs/guides/start-a-run.md +207 -0
  51. package/docs/guides/upgrade-the-kit-version.md +240 -0
  52. package/docs/guides/use-a-hosted-provider.md +214 -0
  53. package/docs/guides/use-a-local-model.md +313 -0
  54. package/docs/guides/watch-a-run.md +349 -0
  55. package/docs/guides/write-a-workflow.md +318 -0
  56. package/docs/index.md +44 -0
  57. package/docs/llms.txt +237 -0
  58. package/docs/reference/authoring-surface/agent.md +151 -0
  59. package/docs/reference/authoring-surface/answer.md +156 -0
  60. package/docs/reference/authoring-surface/arg.md +82 -0
  61. package/docs/reference/authoring-surface/basic.md +62 -0
  62. package/docs/reference/authoring-surface/bearer.md +53 -0
  63. package/docs/reference/authoring-surface/branchname.md +15 -0
  64. package/docs/reference/authoring-surface/chartschema.md +23 -0
  65. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  66. package/docs/reference/authoring-surface/command.md +206 -0
  67. package/docs/reference/authoring-surface/customize.md +64 -0
  68. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  69. package/docs/reference/authoring-surface/defineevent.md +114 -0
  70. package/docs/reference/authoring-surface/dispatch.md +126 -0
  71. package/docs/reference/authoring-surface/fail.md +96 -0
  72. package/docs/reference/authoring-surface/file.md +142 -0
  73. package/docs/reference/authoring-surface/frompick.md +277 -0
  74. package/docs/reference/authoring-surface/http.md +259 -0
  75. package/docs/reference/authoring-surface/isfailure.md +58 -0
  76. package/docs/reference/authoring-surface/isready.md +14 -0
  77. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  78. package/docs/reference/authoring-surface/open.md +105 -0
  79. package/docs/reference/authoring-surface/openbody.md +86 -0
  80. package/docs/reference/authoring-surface/paramsof.md +143 -0
  81. package/docs/reference/authoring-surface/pool.md +186 -0
  82. package/docs/reference/authoring-surface/push.md +130 -0
  83. package/docs/reference/authoring-surface/readyof.md +21 -0
  84. package/docs/reference/authoring-surface/secret.md +139 -0
  85. package/docs/reference/authoring-surface/sentby.md +83 -0
  86. package/docs/reference/authoring-surface/source.md +51 -0
  87. package/docs/reference/authoring-surface/text.md +62 -0
  88. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  89. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  90. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  91. package/docs/reference/authoring-surface/type-agentslots.md +15 -0
  92. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  93. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  94. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  95. package/docs/reference/authoring-surface/type-arg.md +19 -0
  96. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  97. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  98. package/docs/reference/authoring-surface/type-binding.md +61 -0
  99. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  100. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  101. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  102. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  103. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  104. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  105. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  106. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  107. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  108. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  109. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  110. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  111. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  112. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  113. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  114. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  115. package/docs/reference/authoring-surface/type-childat.md +12 -0
  116. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  117. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  118. package/docs/reference/authoring-surface/type-commandinput.md +20 -0
  119. package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
  120. package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
  121. package/docs/reference/authoring-surface/type-customize.md +111 -0
  122. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  123. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  124. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  125. package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
  126. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  127. package/docs/reference/authoring-surface/type-duration.md +15 -0
  128. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  129. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  130. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  131. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  132. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  133. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  134. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  135. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  136. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  137. package/docs/reference/authoring-surface/type-failure.md +19 -0
  138. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  139. package/docs/reference/authoring-surface/type-filebody.md +37 -0
  140. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  141. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  142. package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
  143. package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
  144. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  145. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  146. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  147. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
  149. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  150. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  151. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  152. package/docs/reference/authoring-surface/type-lends.md +38 -0
  153. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  154. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  155. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  156. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  157. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  158. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
  159. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  160. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  161. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  162. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  163. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  164. package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
  165. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  166. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  167. package/docs/reference/authoring-surface/type-openof.md +12 -0
  168. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  169. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  170. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
  171. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
  172. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  173. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  174. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  175. package/docs/reference/authoring-surface/type-pushinput.md +34 -0
  176. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  177. package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
  178. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  179. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  180. package/docs/reference/authoring-surface/type-reached.md +12 -0
  181. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  182. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  183. package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
  184. package/docs/reference/authoring-surface/type-secret.md +25 -0
  185. package/docs/reference/authoring-surface/type-sender.md +19 -0
  186. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  187. package/docs/reference/authoring-surface/type-size.md +85 -0
  188. package/docs/reference/authoring-surface/type-slothandles.md +24 -0
  189. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  190. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  191. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  192. package/docs/reference/authoring-surface/type-text.md +19 -0
  193. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  194. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  195. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  196. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  197. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  198. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  199. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  200. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  201. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  202. package/docs/reference/authoring-surface/type-workspaced.md +115 -0
  203. package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
  204. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  205. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  206. package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
  207. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  208. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  209. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  210. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  211. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  212. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  213. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  214. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  215. package/docs/reference/authoring-surface/workspace.md +287 -0
  216. package/docs/reference/authoring-surface/worktree.md +71 -0
  217. package/docs/reference/authoring-surface.md +217 -0
  218. package/docs/reference/cli.md +433 -0
  219. package/docs/reference/config.md +765 -0
  220. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
  221. package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
  222. package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
  223. package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
  224. package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
  225. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  226. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  227. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  228. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  229. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  230. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  231. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
  232. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  233. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
  234. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
  235. package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
  236. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  237. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
  238. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  239. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
  240. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  241. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
  242. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
  243. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  244. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  245. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
  246. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
  247. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  248. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  249. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
  250. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  251. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
  252. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  253. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  254. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  255. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  256. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  257. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  258. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  259. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  260. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  261. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  262. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  263. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  264. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  265. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  266. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  267. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  268. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
  269. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
  270. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
  271. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  272. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  273. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  274. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  275. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
  276. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
  277. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  278. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  279. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
  280. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
  281. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  282. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  283. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
  284. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
  285. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
  286. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  287. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
  288. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  289. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  290. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  291. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  292. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  293. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  294. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  295. package/docs/reference/decisions.md +86 -0
  296. package/docs/reference/glossary.md +687 -0
  297. package/docs/reference/http-api.md +1386 -0
  298. package/docs/reference/machines.md +189 -0
  299. package/docs/reference/mechanism-events.md +72 -0
  300. package/docs/why.md +207 -0
  301. package/package.json +5 -3
  302. package/src/actor.ts +694 -106
  303. package/src/agent.ts +114 -38
  304. package/src/ambient.ts +3 -4
  305. package/src/assembly-checks.ts +189 -7
  306. package/src/cause.ts +234 -0
  307. package/src/chart.ts +345 -46
  308. package/src/charts.ts +43 -8
  309. package/src/config.ts +440 -178
  310. package/src/custodian.lua +17 -5
  311. package/src/custodian.ts +325 -99
  312. package/src/customize.ts +199 -49
  313. package/src/dispatch-effect.ts +945 -0
  314. package/src/door-compat.ts +217 -0
  315. package/src/durability.ts +11 -4
  316. package/src/effect-checks.ts +50 -3
  317. package/src/effect.ts +641 -128
  318. package/src/effector-wire.ts +165 -13
  319. package/src/fail.ts +222 -0
  320. package/src/harness-client.ts +587 -50
  321. package/src/harness-env.ts +33 -0
  322. package/src/held-secrets.ts +125 -16
  323. package/src/holds.ts +3 -0
  324. package/src/http-effect.ts +556 -58
  325. package/src/http.ts +480 -30
  326. package/src/index.ts +74 -82
  327. package/src/instance.ts +68 -8
  328. package/src/internal.ts +85 -0
  329. package/src/kube-client.ts +6 -3
  330. package/src/machine-doc.ts +24 -7
  331. package/src/mechanism-routes.ts +168 -0
  332. package/src/model-keys.generated.ts +53 -0
  333. package/src/model-keys.ts +42 -58
  334. package/src/names.ts +19 -6
  335. package/src/notices.ts +483 -41
  336. package/src/open-body.ts +242 -40
  337. package/src/open-effect.ts +81 -8
  338. package/src/open.ts +90 -6
  339. package/src/parts.ts +213 -30
  340. package/src/party.ts +101 -2
  341. package/src/pick.ts +497 -11
  342. package/src/pool.ts +282 -50
  343. package/src/registration.ts +133 -12
  344. package/src/repo-identity.ts +7 -3
  345. package/src/repos.ts +22 -3
  346. package/src/run-host.ts +665 -45
  347. package/src/run-output.ts +38 -0
  348. package/src/sandbox-effect.ts +457 -71
  349. package/src/sandbox-kube.ts +66 -17
  350. package/src/secret-placement.ts +59 -17
  351. package/src/server.ts +3 -0
  352. package/src/setup.ts +325 -62
  353. package/src/size.ts +121 -9
  354. package/src/snapshot-store.ts +54 -10
  355. package/src/stub-harness.ts +44 -16
  356. package/src/timers.ts +14 -1
  357. package/src/tokens.ts +107 -32
  358. package/src/vocabulary.ts +88 -14
  359. package/src/wire.ts +100 -26
  360. package/src/workspace.ts +771 -238
  361. package/src/worktree.ts +249 -0
@@ -0,0 +1,138 @@
1
+ # A turn compacts its own context mid-flight
2
+
3
+ The retired flue runtime compacted a conversation automatically; the
4
+ [ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md) rewrite dropped it without recording the
5
+ loss. `packages/orchestrator/src/config.ts` kept declaring `contextWindow` for a reader that no longer existed. This ADR
6
+ is the unrecorded regression, decided.
7
+
8
+ The failure is live, not projected. Run `588b0f5e` (`task-with-review`, a real bug hunt on a 131k window) investigated
9
+ for 161 tool calls at ~800 tokens each, filled the window, and ended mid-sentence with no menu pick. The no-signal
10
+ nudges then continued the SAME conversation with zero headroom: nudge 1 produced 0 tokens, nudge 2 produced one token,
11
+ the budget went dry, and the run took the terminal `agent.fault` to a humanReview park. Once a context is full every
12
+ conversation-continuing recovery is structurally futile — [ADR-0016](0016-agent-turn-mechanics-are-internal.md)'s nudge
13
+ ladder cannot work there. The endpoint clamped its output rather than erroring, so the observable shape is a truncated
14
+ `stopReason`, not a 400.
15
+
16
+ **The window filled MID-turn**, and that decides the seat. jr2's shape is one long agentic turn per Submission
17
+ ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)), so context grows in the middle of a
18
+ Turn, not between Turns. pi-agent-core 1.x has no Compaction and keeps no conversation on disk, so jr2 owns both: the
19
+ engine part ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md), `engine/pi/transcript.jsonl`) and the cut
20
+ over it. pi's loop gives the seat: its `prepareRequest` hook runs before each provider request, and the context it
21
+ returns is the context of that request and of the rest of the run.
22
+
23
+ **Compaction** enters the vocabulary as the cut itself, not the act of summarizing: what the model still sees, replaced
24
+ by a summary plus a retained tail.
25
+
26
+ ## Decision
27
+
28
+ - **The seat is the engine's request hook, and the write goes to the engine part.** The hook is `beforeRequest` at the
29
+ engine seam (ADR-0027), which the pi adapter runs from pi's `prepareRequest`. It fires before every provider request,
30
+ the first one included, with the run's own abort signal. The engine part is complete at that moment: the Harness
31
+ appends each message the loop completes before the loop goes on, and pi has appended the pending messages (the prompt,
32
+ a steered message) before it calls the hook. The hook estimates the context tokens of the engine part against the
33
+ model's `contextWindow`; over the threshold it prepares the cut, asks for the summary, appends the compaction entry to
34
+ the engine part, and returns the rebuilt context, so the request that noticed is already compacted. Firing on a turn's
35
+ first request is what rescues the nudge ladder: a nudge is a fresh admission on the same conversation (`actor.ts`), so
36
+ it compacts before it asks. `settleAbandonedMessage` runs in the seam's `convertToLlm` hook, over each array that is
37
+ sent — including a rebuilt one, since the engine part stores an aborted assistant message verbatim.
38
+ - **The algorithm is jr2's, ported from pi-agent-core 0.82.1.** Where the cut goes, which messages the summary covers,
39
+ the split turn, the file lists, and every text the summarizer reads (its system prompt, the summary and update
40
+ prompts, the serialized conversation) are pi 0.82.1's, byte for byte; replay fixtures from pi 0.82.1's own code pin
41
+ them. The engine part marks where each Submission starts, and the cut reads that mark as pi 0.82.1 read the tool-set
42
+ entry it wrote at the same place: a cut at a Submission's prompt is a split turn, whose prefix is the Submission
43
+ before it.
44
+ - **The thresholds are jr2-owned, derived, and have no author surface.** `reserve = max(16384, model.maxTokens)`, with
45
+ flue's small-window floor (`reserve × 2 ≥ contextWindow → max(1024, contextWindow / 3)`); `keepRecentTokens` 20000.
46
+ The derivation is deliberate: the threshold is checked BEFORE a request that may emit up to `maxTokens`, so a reserve
47
+ smaller than one full response does not reserve anything. pi's flat 16384 and flue's `min(20000, maxTokens)` — which
48
+ shrinks the reserve as the model gains room — are both wrong for the live rig's 32768. The values and the algorithm
49
+ are jr2's, in `compaction.ts` beside the derivation that reads them; pi has none — so a pin bump cannot move when
50
+ compaction fires, and the shipped arithmetic is what the unit tier proves; `TurnDeps` carries test seams only, exactly
51
+ like `stepBudget`. A model with no declared `contextWindow` resolves to 0 and **compaction is off** — said out loud
52
+ here, because silently inert is how the original regression hid.
53
+ - **`contextWindow` is not a compaction knob.** It sits beside `maxTokens` in the provider spec as a fact about an
54
+ endpoint jr2 cannot enumerate. Compaction reads it. It stops being inert without becoming an author surface, and no
55
+ new config field appears.
56
+ - **The summarizer is the Turn's own model and the Turn's own thinking level.** The model because a Dial says how hard
57
+ to run this Turn ([ADR-0018](0018-instance-agents-are-definitions-jr2-assembles-the-harness.md)) and mechanism
58
+ inherits; a separate compaction model would need its own provider entry, its own boot validation, and its own
59
+ unresolvable-model failure at the moment context is already full. The thinking level because overriding it would be
60
+ half of that same argument, and no evidence says otherwise yet. **The summarizer carries the run's abort signal** —
61
+ the hook's own — so a sweep does not wait on a summary jr2 no longer wants
62
+ ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)'s promotion ordering). **jr2's summary call
63
+ states its retry budget (2) and calls pi-ai's `retryAssistantCall` itself**: the Turn's own stream `maxRetries` never
64
+ reaches this call. A compaction that fails after those retries settles the Submission `failed` as an ADR-0016
65
+ **infra** fault — no new fault class: a legible compaction failure beats walking into an overflow that surfaces as
66
+ truncation. The failure words stay pi 0.82.1's (`Summarization failed: …`), and jr2 owns them now.
67
+ - **The retained tail is NOT stored on the compaction entry** — the index of its first kept entry (`firstKept`) alone;
68
+ that is the engine part's format. An entry that carried its tail would stop the branch walk AT that entry, so the next
69
+ cut could not find its first kept entry on the walk and would neither summarize nor retain the previous cut's tail: up
70
+ to `keepRecentTokens` of the MOST RECENT work, deleted at every cut past the first. With `firstKept` alone the walk
71
+ stops there, the rebuilt context is `[summary, ...tail, ...after]`, and the next cut can reach the tail and summarize
72
+ it. jr2's mid-turn regime cuts repeatedly on one conversation, so this matters at every cut past the first.
73
+ - **Visible in the pod log, invisible on the wire.** The printer gets one line
74
+ ([ADR-0023](0023-the-harness-prints-the-conversation.md)) — a reader of 161 tool calls needs the point where the agent
75
+ stopped being able to see the first 120, or an agent that forgets what it read at step 20 reads as a defect. Nothing
76
+ else: no `StreamEvent` type (the durable stream's one consumer is `wait`, and a compaction settles nothing), no
77
+ history entry, nothing on the ADR-0014 open band. The asymmetry is the point — mechanics narrate, they do not surface.
78
+ jr2's history is unaffected by construction: it records what was said, while compaction changes only what the model
79
+ sees, and the engine part's compaction entry is the durable record of the cut.
80
+ - **ADR-0035's runaway counters do not reset on compaction, and the compaction call is not a step.** The watch counts
81
+ tool calls per Submission; compaction is a fact about tokens. A turn that compacts and continues is still spending its
82
+ one step budget.
83
+ - **A tripped watch ends the run at the step boundary (the seam's `endsRun`, which the pi adapter runs from pi's
84
+ `finishTurn`), so no request follows and no cut is taken.** A cut there would run a full summarization on a full
85
+ window for a request that is never sent: real tokens, an unbounded wait against the very liveness ADR-0035 exists to
86
+ guarantee, and a `[compacted]` line that makes a killed turn read as a healthy one. The hook also asks for no summary
87
+ when the run's signal has aborted or the watch has tripped.
88
+ - **The claims are proven in the ADR-0027 conformance suite**, where a scripted provider fabricates usage to cross the
89
+ threshold without burning a real window; it runs in the default `pnpm -r test` gate.
90
+
91
+ ## Considered options
92
+
93
+ - **Threshold at turn boundaries plus overflow-retry only** (flue's exact split: `checkCompaction` after each completed
94
+ assistant message, and one compact-and-retry when a turn actually overflows). Rejected: it would not have saved run
95
+ `588b0f5e`, whose window filled at step ~161 of a single turn. It rescues the nudges after the failure, not the
96
+ failure.
97
+ - **A pure transform in the request hook, never touching the engine part.** Rejected: no compaction record for the
98
+ printer or the engine part, and the summary would have to be cached jr2-side anyway to avoid an LLM call per request —
99
+ which is the compaction entry reimplemented worse.
100
+ - **Wait for pi to own Compaction.** Rejected on ADR-0027's grounds: pi 0.82 had the arithmetic and called it from no
101
+ loop, pi 1.0 removed it from pi-agent-core, and the pin is exact.
102
+ - **A compaction Dial, or a definition override** (flue accepted `reserveTokens`/`keepRecentTokens`/`model` per agent).
103
+ Rejected: compaction is neither identity nor how-hard-to-run, so it is not a Dial (ADR-0018), and ADR-0016 puts
104
+ budgets in jr2's hands. Greenfield: no workflow has needed one.
105
+ - **A headroom check before each no-signal nudge** (fault fast when the window is full). Rejected: it needs a new wire
106
+ field carrying token counts to a consumer that would use it for one branch, to guard a state compaction now prevents.
107
+ The nudge's own first request compacts.
108
+
109
+ ## Consequences
110
+
111
+ - `contextWindow` in the instance spec becomes live for the first time since ADR-0027.
112
+ - **ADR-0035's step budget of 256 is now the only bound on a productive turn.** Its justification weakened: it was set
113
+ above an honest 161-step bug hunt that exhausted its context, and context exhaustion was the real ceiling. The number
114
+ does not move here — there is no data from the far side of compaction — but the next reader raising it should know the
115
+ backing changed.
116
+ - Compaction can make an agent re-read what it already read. That is duplicated work, not a runaway: K counts
117
+ CONSECUTIVE identical calls, and a re-read after a summary is one call, not four. The two guards do not fight; a
118
+ compacted turn is just less efficient than its transcript suggests.
119
+ - Inheriting the Turn's thinking level carries a known risk, recorded rather than designed against: the Harness budgets
120
+ the summary at `min(0.8 × reserve, model.maxTokens)` (no `maxTokens` cap when the model states none) and the model
121
+ spends reasoning tokens from that same budget, so a high-effort turn on a small `maxTokens` can truncate its own
122
+ summary. The trigger is named; if it is observed, the fix is a stated divergence, not a knob.
123
+ - ADR-0016's no-signal ladder assumed a turn that ends with room left. That was silently false at a full window and is
124
+ now true by construction — so a nudge ladder observed dying with a full context again is evidence compaction failed,
125
+ not evidence the ladder needs a gate.
126
+ - The degenerate edges resolve the same way: a tool result larger than the reserve leaves nothing worth cutting, and a
127
+ `continue: true` invocation can arrive on a conversation already at the ceiling. Both compact, and if the context is
128
+ still over, the turn fails as infra.
129
+ - A cut does not lower the reading that triggered it: the estimate reads the last valid assistant usage, and a cut
130
+ RETAINS recent assistant messages, so the number survives the cut. When the request after a cut ends `error` or
131
+ `aborted` it contributes no usage of its own and the next request would cut again on a context nothing has grown — a
132
+ second summary over work the first already covered. A cut therefore requires assistant usage recorded AFTER the newest
133
+ compaction entry.
134
+ - **The pi surface is the `prepareRequest` hook, behind the engine seam (`pi-engine.ts`), and pi-ai's message types.**
135
+ The hook's rule that its context "replaces the runtime values for this and later requests" is documented in pi's
136
+ types, not promised as a caller contract. The algorithm, the context rule and the branch walk are jr2's code, so a pin
137
+ bump cannot move them. The conformance suite is still the canary (ADR-0027): it asserts the mid-turn cut and the
138
+ SECOND cut's retention, not just the threshold arithmetic.
@@ -0,0 +1,196 @@
1
+ # A Sandbox Image is built or brought; jr2 injects the Harness at pod time
2
+
3
+ Working tools execute in the Harness container
4
+ ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md),
5
+ [ADR-0028](0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md)), so the tools an Agent can reach
6
+ are whatever that image carries — a toolchain is the one part of a Sandbox a user genuinely owns. Users own toolchains
7
+ in two shapes: a Dockerfile the instance iterates on locally, and an image already baked, versioned, and hosted on their
8
+ own registry. jr2's runtime must live in the same filesystem either way, and the constraint that decides the mechanism
9
+ is ownership: the user's image must stay the user's — zero jr2 layers, zero rebuild, zero opinions about its `USER`,
10
+ `HOME`, or entrypoint. Any build-time modification makes "bring your own image" impossible (jr2 becomes the owner of
11
+ every build) and promotes incidental image properties into contract items.
12
+
13
+ ## Decision
14
+
15
+ - **Two origins, one meaning.** A built Sandbox Image is a docker context the Machine names —
16
+ `workspace(body, { image: import.meta.resolve("./image") })`, a `file:` URL to the folder, which a package ships
17
+ beside its Machine and `jr2 up` finds by walking the registered Machines
18
+ ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md); the first cut discovered
19
+ `images/<name>/Dockerfile` by dirname, a second named it from config). The build context is **that directory**, so the
20
+ content hash covers exactly what the build can see and a workflow edit invalidates no image; there is no cross-image
21
+ `FROM`. `jr2 up` builds it, content-tags it, and delivers it
22
+ ([ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md)). **Or a registry ref**: a `workspace()`'s `image` may name
23
+ an image jr2 never builds — baked and hosted by the user, pulled by the cluster, its tag discipline its owner's. The
24
+ two are distinguishable by shape (a `file:` scheme is a context; anything else is a ref). Resolution is: the wrapper's
25
+ `image` → the Instance's `images/default` → stock `jr2-harness:<kitversion>`. The middle step is a **path convention,
26
+ not discovery**: `jr2 init` scaffolds `images/default/Dockerfile`, `jr2 up` builds that one folder when it exists, and
27
+ a `workspace()` that names nothing gets it — so a local Machine never has to spell
28
+ `import.meta.resolve("../images/default")`. A `file:` URL that names no folder fails at `jr2 up`; a ref is the
29
+ cluster's to pull.
30
+ - **The Harness arrives by volume, never by build.** Every Sandbox is composed with an `/opt/jr2` volume that is an
31
+ **`image` volume of the kit's harness image** (`subPath: opt/jr2`, read-only in every container that mounts it), and
32
+ the primary container's **command** is overridden to start the Harness. The kubelet mounts the image's own layers, so
33
+ nothing is copied: a pod start costs what a warm image costs, whatever else starts on the node, and every Sandbox on a
34
+ node reads one set of runtime pages. The ref, pull policy, and pull secrets are the harness image's as the cluster
35
+ already pulls it — the volume adds nothing to deliver. The image the pod runs is the user's, byte-for-byte: no
36
+ appended layers, no rewritten Dockerfile, no jr2 knowledge inside it. Its own `USER` and `HOME` are respected — the
37
+ human who execs in lands in the environment the image's author built, dotfiles included; its `ENTRYPOINT`/`CMD` simply
38
+ do not run, because a container has one command and the Harness must own it (its death must be the container's death —
39
+ the Ready probe and restart semantics are the operator's contract at `:8080`). A **built** image that declares no user
40
+ runs as uid 1000 with `HOME=/home/jr2` on an emptyDir as the fallback — jr2 learns "declares none" for free at build
41
+ time (`docker inspect`) and records it beside the ref. A **brought** ref is never inspected — that is the point of
42
+ refs — so the fallback cannot apply: a ref must declare a numeric non-root `USER` itself, and one that would run as
43
+ root fails at provision with an error that names that line (never as the kubelet's silent
44
+ `CreateContainerConfigError`). A process the image _wants_ running is not lost — it has its own seat, the User
45
+ Container ([ADR-0005](0005-sandbox-pod-composition.md)).
46
+ - **PATH is appended at the process level, never prepended.** The Harness exports `PATH="${PATH}:/opt/jr2/bin"` for
47
+ itself, and Working tools spawn without an env override (`execFile(file, args, { cwd, signal })`), so children inherit
48
+ it: the image's `node`, `rg`, and toolchain win where present and jr2's are the fallback; prepending would silently
49
+ shadow a pinned toolchain inside the user's own image. The same process-level discipline carries `umask 002`: the
50
+ Harness sets it at startup so even what it writes _outside_ a repo tree stays group-writable for the pod's work group.
51
+ Inside the repo trees the umask stops mattering — the attach stamps a default ACL on each repo root and POSIX ignores
52
+ the umask where one exists ([ADR-0005](0005-sandbox-pod-composition.md)); this is the defence-in-depth layer, inert
53
+ when no other uid ever writes.
54
+ - **git's system level is jr2's default identity, at the lowest rung.** An image with no git identity made the Agent's
55
+ first `git commit` fail ("Author identity unknown"), and a model then spent its Turn on `git config`; real Haiku 5.5
56
+ runs lost trials to it. The image is the author's, and the author may set git config at any level, so jr2's identity
57
+ must never override one. The runtime volume carries `/opt/jr2/etc/gitconfig`: `[user] name = jr2 Agent`,
58
+ `email = agent@jr2.invalid`, then `[include] path = /etc/gitconfig`. At startup the Harness sets `GIT_CONFIG_SYSTEM`
59
+ to it, at the process level like PATH, so every git the Harness container runs reads it — the Agent's `bash`, a
60
+ `command` Effect there, and the attach (which commits nothing, so the identity is unused). The include reads the
61
+ image's own system file after jr2's lines, so the image's values win key by key, and every higher level wins anyway:
62
+ global (`~/.gitconfig`, XDG), repo-local, `-c`, and `GIT_AUTHOR_*`/`GIT_COMMITTER_*`. The Harness sets nothing when:
63
+ - the image sets `GIT_CONFIG_SYSTEM` or `GIT_CONFIG_NOSYSTEM` (any value): it made its own choice about git's system
64
+ level, and a file jr2 put in front of the image's would replace a value the image set. Including the image's file in
65
+ place of `/etc/gitconfig` was rejected: the file is static on a read-only volume, and to name the image's file it
66
+ must be written at boot into a writable directory the image owns;
67
+ - the image's git reads another system file than `/etc/gitconfig`, or the Harness cannot tell which. A git built with
68
+ another prefix (a source build in `/usr/local`, Homebrew, conda) reads `<prefix>/etc/gitconfig`, which jr2's file
69
+ does not include, so its `safe.directory`, credential helper and identity would be lost. git 2.42 and later name the
70
+ file (`git var GIT_CONFIG_SYSTEM`); for an older git the Harness reads `git --exec-path`, and only
71
+ `/usr/lib/git-core` or `/usr/libexec/git-core` (prefix `/usr`, which reads `/etc/gitconfig`) passes. No git on PATH
72
+ gets nothing too. Such an image gets no default identity: that is the limit, and it sets its own;
73
+ - the file is not on the volume: git reads a missing system file as empty, and the image's `/etc/gitconfig` would be
74
+ lost.
75
+
76
+ Five limits are known and accepted. A git older than 2.32 does not read `GIT_CONFIG_SYSTEM`: it is safe (it reads
77
+ `/etc/gitconfig` as before) and gets no default identity. "The image's values win" holds for the config git uses (a
78
+ commit, `git config --get`), not for a `--system` read: `git config --system` does not follow the include, so
79
+ `--system --get user.name` answers `jr2 Agent` where the image's `/etc/gitconfig` names another, and `--system --list`
80
+ omits the image's values. An image that sets `user.useConfigOnly = true` and no identity no longer stops a commit,
81
+ because jr2's file supplies one; such an image sets `GIT_CONFIG_SYSTEM=/etc/gitconfig` itself, and jr2 then adds
82
+ nothing. With the variable set, `git config --system <key> <value>` writes jr2's file on the read-only volume and
83
+ fails (exit 4); without it, an Agent that runs as root wrote `/etc/gitconfig`. A `--global` or repo-local write works
84
+ as before. A human's `kubectl exec` shell does not get the variable, as it does not get the PATH append. jr2's other
85
+ git is out of scope: the sync runs under `GIT_CONFIG_NOSYSTEM` (ADR-0067); the Sandbox Effector is another container
86
+ with its own committer rules (ADR-0068), and a credentialed `command` Effect runs there with `PATH`, `HOME` and
87
+ `TMPDIR` alone, so it gets no default identity.
88
+
89
+ - **The contract is the floor, and the preflight proves it.** What is inherent to "jr2's runtime in your filesystem" and
90
+ nothing more: a **glibc** base no older than the one jr2's node was built against (musl is out entirely), **`git`** on
91
+ the system PATH (relocating git means `/usr/lib/git-core`, templates, `git-remote-https` → curl + openssl + CA store —
92
+ a rabbit hole, and the agent wants the git you chose), a writable `HOME` for the image's user (the attach script
93
+ writes `$HOME/.gitconfig`, and any real toolchain needs `~/.npm`, `~/.cargo`, `~/.cache`), and `/opt/jr2`, `/work`,
94
+ `:8080` unclaimed. jr2 vendors the rest: **ripgrep** as its official static-musl binary and node's
95
+ `libstdc++.so.6`/`libgcc_s.so.1` into `/opt/jr2/lib`. The probe is one command —
96
+ `git config --global safe.directory "*" && /opt/jr2/bin/node -e "" && rg --version` — and it runs **only where the
97
+ seat is known: as an init step in the user's own image**, `/opt/jr2` mounted, before the Harness starts. It cannot run
98
+ at converge: the floor is a Harness-seat obligation, a built context may equally be destined for the User Container
99
+ seat — which owes no floor at all (ADR-0005) — and which seat a directory serves is workflow-internal and statically
100
+ unrecoverable (ADR-0031, the same line that puts unknown names at provision). So a broken toolchain image surfaces at
101
+ its first provision, in the `preflight` init container's log, with an error that names the fix (because "node did not
102
+ execute" is not actionable) — never as a mid-turn tool failure, and never as a converge refusal for a contract the
103
+ image was not under.
104
+ - **`workspace()` names the image as a STATIC OPTION, never from run input** — what the Sandbox is _made of_ is the
105
+ wrapper's business in the same way its clones are, and it is a fact about the MACHINE, not about a run: `jr2 up` finds
106
+ it by walking the registered Machines ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), and no
107
+ walk can evaluate a function of run input. The User Container rides the same rule and the same resolution as one more
108
+ option beside `image` (`user?: string`, [ADR-0005](0005-sandbox-pod-composition.md)). Neither is ever persisted: the
109
+ option rides the Machine (a parts map keyed on `machine.config`, as the vocabulary is) and the provisioning state
110
+ re-reads it at invoke time, so a restore provisions what the Machine carries now — and a resolved content-addressed
111
+ tag, which would outlive the image it names, never leaves the port's side of the seam at all.
112
+ - **A Menu-only Agent never gets a Sandbox Image.** `workspace: "none"` withholds the entire Working toolset (ADR-0028),
113
+ so there is no tooling to carry; the Instance Harness (ADR-0031) runs the stock image permanently. The feature has
114
+ exactly one seat, and it is the Workspace.
115
+
116
+ ## Considered options
117
+
118
+ - **A build-time wrap** — jr2 builds a kit-owned stage `FROM` the user's image (`COPY /opt/jr2`, `USER 1000`,
119
+ `HOME=/home/jr2`, `CMD` = the Harness). Rejected on four counts. It makes jr2 the owner of every Sandbox Image build,
120
+ so an image baked and hosted by the user cannot exist — the registry-ref origin is unimplementable. It promotes
121
+ non-inherent properties into contract (`USER 1000` forced, `HOME=/home/jr2` forced, "your image must not set
122
+ `ENTRYPOINT`") where the inherent floor is only glibc + git + a writable home. It couples every Sandbox Image tag to
123
+ the resolved harness ref (the wrap is `COPY --from=<harness>`), so a kit source edit re-tags, rebuilds, and
124
+ re-delivers every user image on the cluster. And it needs an intermediate tag between the user's build and the wrap
125
+ build — a mutable shared name that serializes concurrent converges of one checkout, a problem space that exists only
126
+ because the wrap does.
127
+ - **The user layers on jr2** (`FROM jr2-harness:<kitversion>`). Simplest, and the contracts come inherited. Rejected: it
128
+ pins every instance to jr2's debian/node base forever — you could never start from `golang:1.23` or a company base
129
+ image, which is the entire feature.
130
+ - **jr2 installs the missing floor packages into the image.** Rejected: package-manager sniffing
131
+ (`apt`/`apk`/`dnf`/`zypper`/`pacman`) is a fragile shell ladder, distroless and scratch have no shell at all, the
132
+ `apk` branch is pointless because musl breaks node regardless, and reaching the network to mutate someone's image
133
+ behind their back fails in exactly the proxied enterprise environments where it is least debuggable. Vendoring covers
134
+ the same ground without any of it.
135
+ - **A published `jr2-base:<kitversion>` for the scaffold to start from.** Rejected: a fourth image on the release train
136
+ that re-couples the user's Dockerfile to a jr2 tag. It would also absorb a future contract change silently, when a
137
+ contract change should be loud in every instance.
138
+ - **Cross-container tool execution** — the Harness in its own kit-owned container, Working tools exec'd into a sibling
139
+ running the user's image, making that image truly zero-contract. Rejected: it requires a cross-container execution
140
+ transport jr2 does not have, bought only to delete a floor (glibc + git) that most real images already clear by
141
+ accident.
142
+ - **Keeping the image's own `CMD` as the container's main process** (the Harness as a second process, or supervised by
143
+ the user's entrypoint). Rejected: the container's lifecycle must be the Harness's — a pod whose main process is the
144
+ user's entrypoint keeps "Running" through a Harness death, which makes the operator's Ready probe and restart
145
+ semantics lies. Unattended services belong in the User Container (ADR-0005), whose command jr2 deliberately does not
146
+ own.
147
+ - **An init container copies the runtime into an emptyDir** (`init-copy`, the first mechanism). Rejected on measurement
148
+ (2026-09-27, home-lab Sandbox node, 4 cores): the copy is 272 MB in 18k files per pod, so its cost grows with how many
149
+ pods start on the node together — 3s for one pod, a 37s median for 20 at once and 46s for 40, where the `image` volume
150
+ took a 3.0s median for the same 20. It also charged 272 MB of ephemeral storage to every pod and gave every pod its
151
+ own copy of the node binary's pages. The copy existed because image volumes were not yet on by default.
152
+ - **The copy as a fallback below the floor**, chosen by server version at provision. Rejected: two pod shapes and two
153
+ mechanisms to test in every tier, kept alive for clusters no Instance runs on.
154
+ - **A runtime-only Kit image** (`FROM scratch`, `/opt/jr2` alone) as the volume's source. Rejected: it pulls 272 MB
155
+ instead of the harness image's 625 MB on a cold node, but adds a third Kit image to build, push, mirror, and sweep
156
+ ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md) cut the kit to two). The unused 353 MB
157
+ costs one pull per node per kit version, and pre-pull removes it from the start path.
158
+ - **The Agent definition or an `agentRun` dial names the image.** Rejected on both doctrine and physics: available
159
+ tooling is what an Agent may _do_ (ADR-0028 territory, identity — only `model` and `thinkingLevel` are dials,
160
+ ADR-0018), and a Turn cannot change the image of a pod that already exists. A definition-level _assertion_ ("this
161
+ Agent requires image X, fail at admission if its Workspace provisioned another") stays available later.
162
+
163
+ ## Consequences
164
+
165
+ - **The runtime's version rides the volume, not the image.** A kit edit moves the harness image's own tag and re-images
166
+ _future_ pods without touching any Sandbox Image tag — the only way a registry-ref image could ever follow a kit
167
+ update. Live Sandboxes keep the runtime they started with, the same create-if-absent stance ADR-0038 takes for images.
168
+ - **A registry ref sits outside the build-and-label world**: never built, never labeled, never swept
169
+ ([ADR-0039](0039-image-garbage-collects-by-reachability.md) — what jr2 did not stamp, jr2 does not touch). Its pull is
170
+ the cluster's own; a mutable ref is its owner's stale-image risk, named here, not solved.
171
+ - **Concurrent converges of one checkout share no mutable image name** — a Sandbox Image build is one `docker build`
172
+ straight to its content tag, with no intermediate tag at all. The `@kind` tier's `--parallel` rides on this
173
+ ([ADR-0010](0010-bdd-acceptance-tests.md)).
174
+ - **A human exec-ing the Harness container inherits the agent's model credentials.** The person exec-ing is the instance
175
+ owner, so this is an accepted trade — and the User Container (ADR-0005) or `kubectl debug` with a plain image is the
176
+ creds-free shell in the same pod when it matters.
177
+ - **A glibc floor.** Bases older than the one jr2's node was built against fail the preflight, and alpine/musl is out
178
+ entirely. Named in the error — at converge for a built image, at provision for a ref — not discovered in a pod log.
179
+ - **The stock Harness image is also the injection source**: the `image` volume mounts its `/opt/jr2`, and it carries the
180
+ vendoring step (static ripgrep per `TARGETARCH`, the two relocated C++ runtime libraries). It also carries the static
181
+ Effector binary at `/opt/jr2/bin/effector`, built from the top-level `effector/` Go module: a container becomes an
182
+ Effector by running it as its command, in whatever image it runs
183
+ ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). Static, so it owes no glibc floor.
184
+ - **A Kubernetes floor: 1.35**, where image volumes are on by default (GA in 1.36). `jr2 up` reads the server version
185
+ and refuses below it, naming the version found and the floor
186
+ ([ADR-0046](0046-a-converge-failure-carries-its-diagnosis.md)). The container runtime must support image volumes too
187
+ (containerd ≥ 2.1, CRI-O ≥ 1.31); jr2 cannot see the runtime cheaply, so an older one fails at the first provision, in
188
+ the pod's own events.
189
+ - **The mode is what every uid gets.** An image volume keeps the layers as built — owned by root, modes as the build
190
+ left them — and the Sandbox Image may run any uid. The harness image's `/opt/jr2` must therefore be readable by every
191
+ uid, and executable where the build made it so; a build-time check holds it there, and what the build copies from a
192
+ checkout (whose modes are the host's umask) is copied `a+rX`. (The copy restated this with a `chmod`.)
193
+ - **`images/` is instance-local by design.** A built Sandbox Image travels as a folder to copy; a shared one travels as
194
+ a registry ref — which is now a first-class origin, so sharing needs no further mechanism.
195
+ - **GLOSSARY.md**: **Sandbox Image** covers both origins and carries no part of jr2; **User Container** is defined by
196
+ ADR-0005.
@@ -0,0 +1,170 @@
1
+ # `jr2 up` builds every image it deploys, except a pinned one
2
+
3
+ [ADR-0019](0019-one-converging-command-against-the-current-context.md) promised one converging command, but `jr2 up`
4
+ builds exactly one image — the instance's. The Harness and operator images come from `just` recipes at **mutable tags**
5
+ (`jr2-harness:local`), pointed at by `images` overrides in `jr2.config.ts`. Nothing can detect that a mutable tag moved,
6
+ so editing `packages/harness/src` and running `jr2 up` reports convergence onto pods running last week's code — the
7
+ failure the Instance's own artifact already fixed for itself by hashing the materialized bundle rather than the source
8
+ folder (then its image; now its Assembly,
9
+ [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)).
10
+ [ADR-0037](0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md) would add a second class of
11
+ build-it-yourself-first image on top of that.
12
+
13
+ ## Decision
14
+
15
+ - **Every image `jr2 up` deploys, `jr2 up` builds — when its source is visible.** The CLI detects a kit checkout by
16
+ resolving from its own module URL and requiring _both_ `deploy/harness/Dockerfile` and `packages/harness/package.json`
17
+ naming `@jr2/harness`. In a checkout it builds the Harness and operator images; installed from npm those paths do not
18
+ resolve, so a real instance takes the published-`<kitversion>` path and never needs docker for kit images. **The
19
+ checkout is the signal** — no flag, no config key, no env. A CLI loaded from under `node_modules` is never a checkout,
20
+ even when a checkout contains it: an instance inside the repository that installs `@jr2/*` from npm takes its pinned
21
+ version's published images, never a build of HEAD beside an Orchestrator at the pin. The `just` recipes survive as
22
+ shortcuts for building one image without a converge, never as prerequisites. A registry-ref Sandbox Image (ADR-0037)
23
+ is the one deployed image whose source is nobody's here: never built, labeled, or delivered by jr2 — the cluster pulls
24
+ it, and its tag discipline is its owner's.
25
+ - **A Pinned image is deployed and never built.** The Custodian runs upstream Envoy
26
+ ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): jr2 writes its configuration and
27
+ none of its code, so there is no source to build from and a jr2-built Envoy would be a fork to maintain. The kit names
28
+ it by tag AND digest (`CUSTODIAN_IMAGE` in `@jr2/orchestrator`), so a kit version deploys exactly one set of bytes in
29
+ both worlds — checkout and installed alike — and `imagePullPolicy: IfNotPresent` is correct for the same reason a
30
+ content-addressed tag is. `jr2 up` resolves its ref into the image map (the `custodian` key) — re-homed onto
31
+ `kitRegistry` when one is set ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)) — and
32
+ delivers nothing: a node pulls it. It is not a Kit image (GLOSSARY.md: "Pinned image"), and moving the pin is a kit
33
+ change.
34
+ - **Every tag is a content address.** Orchestrator, Sandbox, Harness, operator — each addressed by its own inputs and
35
+ its platform set, the platform as a visible tag suffix
36
+ ([ADR-0045](0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md)). Three things follow:
37
+ `imagePullPolicy: IfNotPresent` becomes _correct_ rather than lucky (a unique tag per content means "present" implies
38
+ "current"), which is what makes kind and a real cluster behave identically instead of needing `Never` on one and
39
+ `Always` on the other; a kit source edit moves its own image's tag with no bookkeeping; and skipping is exact.
40
+ - **Over-hash deliberately.** A kit image is hashed over its whole source directory, tests included, not over the exact
41
+ file list its Dockerfile copies. Deriving the list by hand means a new `COPY` silently desynchronizes it, which is the
42
+ invisible-stale-image bug being deleted; a needless rebuild in kit dev costs cached-layer seconds. **A Sandbox Image's
43
+ hash covers its build-context directory alone** — the Harness rides the pod's `/opt/jr2` volume (ADR-0037), so a kit
44
+ edit moves the harness image's own tag and touches no Sandbox Image tag. That hash is also the map KEY the deployed
45
+ Orchestrator looks the ref up by, which is what lets a Machine name its context by `file:` URL and be understood on
46
+ both sides without a path table ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)).
47
+ - **A staged tree records nothing about where or when it was staged.** The Assembly's hash addresses the staged tree
48
+ ([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)), so anything in that tree
49
+ that names its own scratch directory — or the minute it was written — makes one hash name many Assemblies, and
50
+ "present implies current" stops being true for the one artifact every Instance publishes. `pnpm deploy` writes both:
51
+ each `.bin` shim bakes the staging path into `NODE_PATH`, and `node_modules/.modules.yaml` is nothing but a record of
52
+ where and when. So the tree is **sealed** before it is hashed — the `.bin` shims are deleted, since the Orchestrator
53
+ runs no bin from an Assembly and a path that names the Assembly's own directory cannot be rewritten to one that
54
+ contains its own hash; `.modules.yaml` is deleted with them. (**Amended by
55
+ [ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md)**: the bundle is `pnpm deploy`'s
56
+ only for a workspace-member instance; a standalone one is a staged copy plus a frozen lockfile install, whose shims
57
+ name no absolute path — but whose pnpm branch leaves a second where-and-when record,
58
+ `node_modules/.pnpm-workspace-state.json`, deleted with `.modules.yaml`.) A non-UTF-8 file holding the path is a loud
59
+ failure, never a blind rewrite. **Nothing is excluded from the hash any more.** The old exclude set named exactly the
60
+ files that varied, so the tag stood still while the bytes moved and the mechanism that should have exposed the drift
61
+ was the one hiding it; with an empty set the failure inverts — a bundle that ever varies again re-tags on every
62
+ converge, in the open, where a rebuild-and-reload every single time is impossible to miss.
63
+ - **One transport branch for all of them**, the one the Sandbox Images use: `registry` configured → push; kind context →
64
+ `kind load`; neither → fail loudly naming `registry`. `jr2 up` records the converged name→ref map as an annotation on
65
+ the Orchestrator Deployment and diffs it, so a steady-state converge spends a directory walk and no docker at all.
66
+ **Amended by [ADR-0041](0041-a-build-the-host-already-holds-is-not-spent-again.md)**: when the record is silent (a
67
+ fresh namespace), the host daemon's own labeled listing answers the _build_ question — the seal below is what made
68
+ "present implies current" true enough to ask it — while the record keeps answering the delivery one.
69
+ - **The resolved name→ref map reaches the Orchestrator as a ConfigMap, read per provision — never as Deployment env.**
70
+ Env is a pod-template change, so every Dockerfile edit would roll the Orchestrator and put every live run through
71
+ snapshot restore ([ADR-0007](0007-durable-machine-state.md)) for a change that affects only _future_ Sandboxes. The
72
+ map is data consulted when creating a pod, not configuration defining the process. The cost is a stale-read window of
73
+ one kubelet propagation after `jr2 up`, and that two workspaces provisioned seconds apart can straddle a change —
74
+ which was already true across a roll.
75
+ - **The `images` config block is deleted outright — no key, no env escape hatch.** Its per-image entries were kit-dev
76
+ overrides that auto-build now covers; the User Container's image is a static `workspace()` option the Machine carries,
77
+ not config (ADR-0005, [ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so no `user` entry
78
+ belongs here either. Nobody should be able to run a patched Harness against a real cluster: that is ADR-0027's "no
79
+ eject hatch" enforced rather than merely stated.
80
+ - **`jr2 up` reports live workspaces on an older image; it never re-images one.** Provision is create-if-absent, so a
81
+ running Sandbox keeps the image its CR was created with — the only safe behavior, since replacing the pod takes the
82
+ worktrees and unpushed commits with it, which is precisely the Continuity break
83
+ [ADR-0021](0021-workspace-continuity-is-a-lease-that-answers-back.md) exists to report. So the converge lists them
84
+ (`kubectl get sandboxes`, no new state) and stops: _"2 running workspaces keep `…:9c1e02`; new workspaces use
85
+ `:4a77b1`; delete these runs to re-image."_
86
+ - **`jr2 down` prunes this instance's images from kind nodes by default** — **superseded by
87
+ [ADR-0039](0039-image-garbage-collects-by-reachability.md)**, which replaces name-scoped, down-only pruning with a
88
+ label-scoped reachability sweep at both `up` and `down`, host daemon included. Kept as written for the record: scoped
89
+ to EXACT repo names: `jr2-instance-<name>:*` plus, per discovered `images/<x>/`, `jr2-sandbox-<name>-<x>:*` and its
90
+ `-base` intermediate (matched after stripping containerd's `docker.io/library/` namespace, which `kind load`
91
+ normalizes local tags into — and nothing else, so a registry-pushed tag keeps its host and stays unmatched). Exact
92
+ names, never one open-ended `jr2-sandbox-<name>-` prefix, which also matches instance `<name>-extra`'s images on a
93
+ shared node. Removal is planned **per image id**: `crictl rmi` cannot untag — it resolves any tag to the id and takes
94
+ the whole image, every tag with it — so an id is removed only when every tag on it is this instance's, and an id
95
+ sharing tags with anyone else (two instances whose image inputs are byte-identical produce one id) is kept whole and
96
+ reported. Content addressing means ten Dockerfile iterations leave ten full images in the node's containerd, invisible
97
+ to `kubectl` and on the developer's own disk. **Kit images are never pruned** — they are shared by every instance on
98
+ the cluster — and neither are registry-pushed tags. The accepted remainder: deleting an `images/<x>/` folder orphans
99
+ that image's already-loaded tags (nothing derives their names any more); they go with the cluster, or by hand.
100
+ - **No `workspace()`, no Sandbox Image builds.** A registered Machine that composes a Sandbox is the data-plane switch,
101
+ read off the walk (ADR-0051): a workspace-less instance has no Sandboxes, so it must not pay a docker build for a
102
+ scaffolded `images/default/` it can never use.
103
+
104
+ ## Considered options
105
+
106
+ - **An env escape hatch for kit image refs** (`JR2_HARNESS_IMAGE`, …), kept for the `@kind` tier, which pins
107
+ `jr2-harness-dev:local`. Rejected once the stub was read properly: `deploy/harness-dev/` is a whole alternate Harness
108
+ — the stub plus a hand-rolled MCP client — written when "flue's real Harness image is not part of this repo," which
109
+ ADR-0027 made false. The substitution belongs at the **provider**, not the image: `harness.provider` already accepts
110
+ any OpenAI-compatible `baseUrl`, so pointing `@kind` at a scripted model endpoint runs the **stock** Harness and
111
+ removes the last consumer of image substitution.
112
+ - **The ref map as Deployment env** (symmetric with the harness config the Harness containers read from theirs).
113
+ Attractive because a run's Sandbox image becomes a deterministic function of the Orchestrator generation instead of a
114
+ read-at-a-moment. Rejected on the roll: bouncing every run in flight because someone added a CLI to a Dockerfile is
115
+ the wrong trade.
116
+ - **A `--reimage` flag** that deletes and re-provisions live Sandboxes. Rejected: it destroys unpushed work, and the run
117
+ already has a designed path for losing its Sandbox (`workspace.lost`, ADR-0021) that the workflow's policy drives —
118
+ not the CLI.
119
+ - **Hashing only the files each Dockerfile copies**, or hashing the whole kit tree via git. The first desynchronizes
120
+ silently; the second rebuilds every image on any edit anywhere. Per-image directory hashing sits between them and errs
121
+ toward rebuilding.
122
+ - **pnpm's hoisted node linker** (`deploy --node-linker=hoisted`), which emits no absolute path at all and so needs no
123
+ seal. Verified to work and to leave the artifact all but identical (60 MB vs 62 MB, 3998 vs 4029 files). Rejected at
124
+ first on exposure, not on merit: `pnpm deploy` calls itself experimental and jr2 already pinned `--legacy`, so a third
125
+ deviation that changes the `node_modules` layout bought nothing the seal did not. Adopted with
126
+ [ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md): the Assembly must be flat
127
+ so the Kit closure strips as top-level entries and no pnpm store name reaches the archive, so both bundle paths
128
+ install hoisted now. The seal keeps its deletions; it rewrites nothing.
129
+ - **A `.dockerignore` derived from the exclude set.** Rejected: it deletes the shims, and pnpm's shims `exec` through a
130
+ relative `$basedir` — they WORK in the container. An Instance whose dependency ships a CLI would lose it. The four
131
+ varying files are content the image should keep, holding a path that is simply wrong.
132
+ - **Freezing the staging path** (a stable scratch dir instead of `mkdtemp`). Rejected twice over: it freezes the wrong
133
+ path rather than fixing it, and it makes the bundle a function of `TMPDIR` and the instance's location — the same
134
+ inputs would yield different bytes in CI than on a laptop, which is the property this decision exists to establish. It
135
+ also collides when one Instance folder is converged concurrently.
136
+ - **`pnpm deploy` inside the Dockerfile**, so the bundle is materialized at `/instance` and no host path can leak.
137
+ Rejected for now: it needs the whole monorepo plus the pnpm store in the build context (or network during the build
138
+ for an installed Instance), and it moves the hash off the materialized bundle — the ground ADR-0019 chose
139
+ deliberately, because hashing guessed inputs instead is how `jr2 up` came to skip builds it needed.
140
+ - **Opt-in `--prune-images`.** Rejected: `down` is already the destructive, always-confirms command, and abandoned
141
+ images are discovered at 100% disk rather than at the moment one would think to pass a flag.
142
+
143
+ ## Consequences
144
+
145
+ - **The `@kind` tier is rewritten, not renamed.** `deploy/harness-dev/` dies; the scripted persona's behavior moves from
146
+ MCP client calls to OpenAI-format `tool_calls` on a fake provider (in-cluster, or host-side with a LAN `baseUrl` — the
147
+ config already notes a LAN address works on kind). The tier gains real-Harness coverage and becomes a **second pi
148
+ canary** beside the conformance suite (ADR-0027), so a pi bump can now break it too.
149
+ - `stub-harness.ts` keeps its job — the socket-free tier reaches it by explicit `endpoint` (ADR-0031). Only the
150
+ containerized stub is retired.
151
+ - **`jr2 up` in a kit checkout now needs docker for kit images**, including a Go build for the operator. Hash-skip means
152
+ that is a first-converge cost, not a per-converge one.
153
+ - **An air-gapped or mirror-only cluster still cannot pull published kit images.** The answer is a registry _prefix_ for
154
+ kit refs, not per-image overrides — a different mechanism, deliberately deferred while nothing is published.
155
+ (**Resolved by [ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)**: the prefix is
156
+ `kitRegistry`, re-homing refs from the canonical `ghcr.io/snapwich` home, fed by a deliberate instance-less mirror.)
157
+ - **The seal deletes garbage at its source.** Before it, every converge produced a new artifact under an unchanged
158
+ address, so each one orphaned a whole Orchestrator image of the Instance — 465 MB on the `@kind` instance — which
159
+ ADR-0039's sweep then collected. That was garbage produced by the builder on every run, not by iteration, and the
160
+ collector was doing work that should never have existed. The same rule now keeps a no-op converge from publishing an
161
+ Assembly: an unchanged tree has an unchanged hash, and the Orchestrator already holds it.
162
+ - **`jr2 up` leans on `pnpm deploy`, which pnpm still labels experimental**, at `--legacy` and hoisted. The seal does
163
+ not deepen that exposure, but the assumption that staging is otherwise deterministic is now held by a test that stages
164
+ one Instance into two directories and compares hashes, rather than by trust. A pnpm upgrade that bakes a path
165
+ somewhere new fails that test instead of silently shipping two images under one tag. (**Amended by
166
+ [ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md)**: only a workspace-member
167
+ instance leans on it. A standalone instance invokes whichever package manager wrote its lockfile, and `pnpm deploy` is
168
+ never reached.)
169
+ - **`just` recipes stop being load-bearing**, and the `images:` line in `features/kind-instance/jr2.config.ts` is
170
+ deleted with the block.