@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.
- package/README.md +20 -2
- package/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- 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
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +5 -3
- package/src/actor.ts +541 -79
- package/src/agent.ts +114 -38
- package/src/ambient.ts +2 -1
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +169 -37
- package/src/charts.ts +34 -6
- package/src/config.ts +430 -178
- package/src/custodian.ts +74 -22
- package/src/customize.ts +188 -46
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect.ts +341 -44
- package/src/effector-wire.ts +160 -11
- package/src/fail.ts +209 -0
- package/src/harness-client.ts +541 -47
- package/src/http-effect.ts +495 -53
- package/src/http.ts +427 -16
- package/src/index.ts +60 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +84 -0
- package/src/machine-doc.ts +21 -3
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/notices.ts +66 -13
- package/src/open-body.ts +159 -23
- package/src/open-effect.ts +81 -8
- package/src/open.ts +59 -6
- package/src/parts.ts +184 -13
- package/src/party.ts +68 -2
- package/src/pick.ts +463 -11
- package/src/pool.ts +247 -36
- package/src/registration.ts +83 -4
- package/src/repos.ts +2 -1
- package/src/run-host.ts +565 -31
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +273 -38
- package/src/sandbox-kube.ts +3 -1
- package/src/secret-placement.ts +52 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +239 -56
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/vocabulary.ts +69 -14
- package/src/wire.ts +90 -16
- package/src/workspace.ts +697 -175
|
@@ -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 in the per-run spec** — what the Sandbox is _made of_ is the
|
|
105
|
+
wrapper's business in the same way its worktrees are, but it is a fact about the MACHINE, not about a run: `jr2 up`
|
|
106
|
+
finds it by walking the registered Machines ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)),
|
|
107
|
+
and a spec is a function of run input that no walk can evaluate. The User Container rides the same rule and the same
|
|
108
|
+
resolution as one more option beside `image` (`user?: string`, [ADR-0005](0005-sandbox-pod-composition.md)). Neither
|
|
109
|
+
is ever persisted: the option rides the Machine (a parts map keyed on `machine.config`, as the vocabulary is) and the
|
|
110
|
+
provisioning state re-reads it at invoke time, so a restore provisions what the Machine carries now — and a resolved
|
|
111
|
+
content-addressed 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.
|