@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.
- 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 +478 -0
- package/docs/concepts/architecture.md +243 -0
- package/docs/concepts/charts.md +146 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +257 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +658 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +298 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +306 -0
- package/docs/concepts/the-menu.md +381 -0
- package/docs/concepts/workspaces.md +756 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +383 -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 +993 -0
- package/docs/get-started/install.md +130 -0
- package/docs/guides/add-tools-to-the-sandbox.md +308 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +224 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/cut-a-worktree.md +560 -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 +230 -0
- package/docs/guides/look-inside-a-sandbox.md +177 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -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 +532 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +587 -0
- package/docs/guides/run-one-worker-per-item.md +322 -0
- package/docs/guides/set-an-agents-access-and-dials.md +420 -0
- package/docs/guides/size-a-workspace.md +217 -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 +214 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +349 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +237 -0
- package/docs/reference/authoring-surface/agent.md +151 -0
- package/docs/reference/authoring-surface/answer.md +156 -0
- package/docs/reference/authoring-surface/arg.md +82 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/branchname.md +15 -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 +206 -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 +96 -0
- package/docs/reference/authoring-surface/file.md +142 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +259 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +105 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +143 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +130 -0
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +139 -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 +15 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -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-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +61 -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 +20 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
- package/docs/reference/authoring-surface/type-customize.md +111 -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 +72 -0
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -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 +37 -0
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -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 +95 -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-lateroutcome.md +21 -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-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -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 +22 -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 +26 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +34 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +149 -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 +84 -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 +24 -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 +115 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +22 -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 +27 -0
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +287 -0
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +217 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +765 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +50 -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 +175 -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 +271 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -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 +287 -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 +101 -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 +175 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -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 +245 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -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 +193 -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 +126 -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 +238 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -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 +108 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -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 +239 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -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 +552 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -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 +334 -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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +86 -0
- package/docs/reference/glossary.md +687 -0
- package/docs/reference/http-api.md +1386 -0
- package/docs/reference/machines.md +189 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +207 -0
- package/package.json +5 -3
- package/src/actor.ts +694 -106
- package/src/agent.ts +114 -38
- package/src/ambient.ts +3 -4
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +345 -46
- package/src/charts.ts +43 -8
- package/src/config.ts +440 -178
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +325 -99
- package/src/customize.ts +199 -49
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +641 -128
- package/src/effector-wire.ts +165 -13
- package/src/fail.ts +222 -0
- package/src/harness-client.ts +587 -50
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +556 -58
- package/src/http.ts +480 -30
- package/src/index.ts +74 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +85 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +24 -7
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +42 -58
- package/src/names.ts +19 -6
- package/src/notices.ts +483 -41
- package/src/open-body.ts +242 -40
- package/src/open-effect.ts +81 -8
- package/src/open.ts +90 -6
- package/src/parts.ts +213 -30
- package/src/party.ts +101 -2
- package/src/pick.ts +497 -11
- package/src/pool.ts +282 -50
- package/src/registration.ts +133 -12
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +22 -3
- package/src/run-host.ts +665 -45
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +457 -71
- package/src/sandbox-kube.ts +66 -17
- package/src/secret-placement.ts +59 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +325 -62
- 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/tokens.ts +107 -32
- package/src/vocabulary.ts +88 -14
- package/src/wire.ts +100 -26
- package/src/workspace.ts +771 -238
- package/src/worktree.ts +249 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# A release is a pushed tag, and the publish guard is the credential
|
|
2
|
+
|
|
3
|
+
[ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md) made the accidental-publish guard
|
|
4
|
+
a manifest line — `publishConfig.registry: http://localhost:4873` — and named the edit of that line the "we are ready"
|
|
5
|
+
commit. Preparing the first real publish (2026-09-20) found the guard has no exit: `publishConfig` outranks `--registry`
|
|
6
|
+
by design, so the edit that lets the kit reach npmjs is the same edit that breaks every reader of the guard at once —
|
|
7
|
+
`packages/cli/test/publish.test.ts` asserts a localhost registry, `scripts/dist-publish.sh` refuses any other, and the
|
|
8
|
+
`@dist` fixture publishes to whatever the manifest names. After the flip the local loop cannot publish at all, and
|
|
9
|
+
flipping it back per release keeps the unit gate red for the length of the release. The property that made the guard
|
|
10
|
+
strong is the property that makes it unreleasable. This ADR amends ADR-0043's guard and settles the rest of the release:
|
|
11
|
+
version, images, gate, trigger.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
- **The guard is the credential, not the manifest.** `publishConfig.registry` leaves every manifest. The only holder of
|
|
16
|
+
an npmjs credential is the release job, and it holds no token: it publishes with npm trusted publishing (OIDC from
|
|
17
|
+
GitHub Actions), so nothing long-lived exists to leak. A `pnpm -r publish` on a dev box reaches npmjs and fails
|
|
18
|
+
`ENEEDAUTH`, which is the guard — an absence that cannot be edited past, where the manifest line was a presence that
|
|
19
|
+
had to be. `publishConfig.access: public` stays on all four, because a scoped package's first publish is 402 without
|
|
20
|
+
it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that **no** public
|
|
21
|
+
manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a release to
|
|
22
|
+
localhost.
|
|
23
|
+
- **Lockstep, and `main` carries the next version.** One number across `packages/*` and `templates/default`, because
|
|
24
|
+
[ADR-0019](0019-one-converging-command-against-the-current-context.md)'s one release train already reads a single
|
|
25
|
+
`KIT_VERSION`, ADR-0043's scaffold pins two packages at one exact number, and `workspace:*` packs to an exact
|
|
26
|
+
inter-dependency — independent versions would need ranges, and 0.x ranges break. `0.0.0` stops being special: it was
|
|
27
|
+
the ephemeral registry's value only because nothing was released, and a fresh verdaccio publishes any version cleanly.
|
|
28
|
+
`just release patch|minor|major` (or an explicit version) computes the number the way `npm version` does, writes it to
|
|
29
|
+
every manifest and the template, runs the unit gate (`init.test.ts` is the tripwire), commits, and tags `v<ver>`.
|
|
30
|
+
Nothing rewrites a version at publish time: a loop that edits manifests tests edited manifests (ADR-0043), and a
|
|
31
|
+
checkout `KIT_VERSION` must read the number the checkout will ship.
|
|
32
|
+
- **The tag push is the release, and the train runs in order.** `release.yml` runs on `v*`. Its first job refuses if the
|
|
33
|
+
tag disagrees with any manifest, and then asks every other refusal of the publish
|
|
34
|
+
(`scripts/publish.sh --stage --check`: the tree, the ref, the strict docs build, no output block not yet run). These
|
|
35
|
+
take seconds, so a tag that cannot publish starts no tier. Then the full gate, as parallel jobs that each need that
|
|
36
|
+
first one — unit, harness conformance, the default e2e profile, the docs site build and the Custodian suite in one
|
|
37
|
+
job; `@kind` as four shards, each on a kind cluster its own runner creates (the operator's own e2e job already works
|
|
38
|
+
this way); and `@dist` on one more — so the installed path is proven on the tree that publishes, not on a maintainer's
|
|
39
|
+
box the day before. The tiers are jobs and not steps of one job because one runner ran them in series: v0.5.0's job
|
|
40
|
+
spent 37 minutes in `@kind` alone, two workers being the most a 4-vCPU runner holds, and then failed at a refusal of
|
|
41
|
+
the publish that takes a second. Only when every tier job passes does the one job that holds the rights run:
|
|
42
|
+
`scripts/kit-push.sh ghcr.io/snapwich` (multi-arch, `GITHUB_TOKEN` with `packages: write`), and **only then** npm:
|
|
43
|
+
each public package is packed with pnpm, which rewrites `workspace:*` to the exact version, and **staged** with
|
|
44
|
+
`npm stage publish`, the client that speaks trusted publishing — in pnpm's topological order, dependencies first,
|
|
45
|
+
skipping a version npm already holds live so a re-run converges. The order is the contract: a package whose images are
|
|
46
|
+
not at the home fails at pull on the user's first `jr2 up`
|
|
47
|
+
([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)); images with no package are inert,
|
|
48
|
+
and stay so while the packages sit staged. A second job, `pages`, follows the release job and deploys the docs site of
|
|
49
|
+
the same tree to GitHub Pages
|
|
50
|
+
([ADR-0072](0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md)). It does not wait for the
|
|
51
|
+
npm approval, so the site can be ahead of the live packages until the maintainer approves them.
|
|
52
|
+
- **The job stages; the maintainer approves.** Each trusted publisher is configured for `npm stage publish` only, so the
|
|
53
|
+
tag produces four staged tarballs and one 2FA approval per package (`npm stage approve`, dependencies first) makes
|
|
54
|
+
them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is bound to the
|
|
55
|
+
repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise publish under
|
|
56
|
+
the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be remembered in
|
|
57
|
+
order, the staged tarballs wait.
|
|
58
|
+
- **Two executors of one train.** `scripts/publish.sh` is the irreversible half — images, then packages, dependencies
|
|
59
|
+
first, live versions skipped — and the job runs it with `--stage`. A maintainer runs it as `just publish` from the
|
|
60
|
+
pushed tag when a 0.x release cannot wait the job's hour: `npm publish` with a 2FA prompt per package in place of an
|
|
61
|
+
approval, and the job converges behind it. The checks are the same in both (lockstep, clean tree, HEAD is the pushed
|
|
62
|
+
tag), and so is the guard: a login alone publishes nothing under write-2FA, so a dev box holds no credential that
|
|
63
|
+
publishes by itself.
|
|
64
|
+
- **`ci.yml` on push and PR is the light gate**: frozen install, typecheck, format check, `pnpm -r test`, the default
|
|
65
|
+
Cucumber profile — minutes, no docker. The heavy tiers and the Custodian suite run on the tag and by hand
|
|
66
|
+
(`just e2e-kind`, `just e2e-dist`, `just custodian-test`) when a change touches build, deploy, or dist code.
|
|
67
|
+
- **`@jr2/machines` peers on `@jr2/orchestrator` exactly.** `workspace:*` stays; a Machine is written against one
|
|
68
|
+
orchestrator, and a mismatched pair fails at install with a peer error instead of at run.
|
|
69
|
+
- **`engines.node: ">=24"` on every public package and the root.** Every runtime the kit runs in is Node 24 already
|
|
70
|
+
(both Kit Dockerfiles, the instance Dockerfile, the `registerHooks` bin); the root's `>=22` was the straggler.
|
|
71
|
+
|
|
72
|
+
## Considered options
|
|
73
|
+
|
|
74
|
+
- **Keep the localhost line; the release job rewrites it before publish.** Rejected by ADR-0043's own rule — the
|
|
75
|
+
published manifest would be one no tier tested.
|
|
76
|
+
- **Point the manifests at npmjs; the dist loop rewrites them to localhost.** The same objection inverted: the tier that
|
|
77
|
+
proves the tarballs would test a manifest the release never ships.
|
|
78
|
+
- **A `prepublishOnly` guard** (refuse unless localhost or `JR2_RELEASE=1`). In-tree, but it rests on pnpm exposing the
|
|
79
|
+
effective registry to lifecycle scripts, and a one-variable bypass is a guard shaped like a suggestion.
|
|
80
|
+
- **CI stamps the version from the tag; `main` stays `0.0.0`.** Rejected twice: edited manifests, and a checkout's
|
|
81
|
+
`KIT_VERSION` would read `0.0.0` forever.
|
|
82
|
+
- **Changesets in fixed mode.** The right tool once there are contributors and changelogs; today it is a version PR in
|
|
83
|
+
front of a five-file edit. The contract it would implement — tag equals manifests, lockstep — is the one chosen here,
|
|
84
|
+
so adopting it later changes no ADR.
|
|
85
|
+
- **`workflow_dispatch` with a version input.** CI would make the bump commit, so the gate runs on a tree the maintainer
|
|
86
|
+
never saw at that version.
|
|
87
|
+
- **Manual `just kit-push` before tagging.** A runbook step in front of a release (ADR-0019 calls that an interface
|
|
88
|
+
failure), and nothing checks the images exist before npm publish.
|
|
89
|
+
|
|
90
|
+
## Consequences
|
|
91
|
+
|
|
92
|
+
- ADR-0043's "What publishes, and the guard" is amended: the guard is credential absence; the "we are ready" commit is
|
|
93
|
+
no longer an edit, it is the first pushed tag. Its consequence "localhost `publishConfig` … is the real shipping
|
|
94
|
+
configuration" no longer holds; the rest of that list does.
|
|
95
|
+
- **Bootstrap, once:** create the `jr2` org on npmjs (a scope is a user or an org), publish the first version by hand
|
|
96
|
+
with a token — npm attaches a trusted publisher only to a package that already exists — then configure the trusted
|
|
97
|
+
publisher on all four packages and revoke the token. After that no token that publishes on its own exists anywhere:
|
|
98
|
+
the job's identity stages, a maintainer's login needs its second factor. Likewise once: make the three GHCR packages
|
|
99
|
+
public after the first push, because GHCR creates them private.
|
|
100
|
+
- `publish.test.ts` asserts no registry, `access: public`, and `engines`; `dist-publish.sh` drops its manifest read-back
|
|
101
|
+
and passes `--registry`; the `@dist` fixture takes its registry from `JR2_DIST_PORT`'s default rather than the
|
|
102
|
+
manifest; `kit-push.sh`'s cli-vs-orchestrator cross-check widens to every manifest; `just release` is new;
|
|
103
|
+
`.github/workflows/` gains `ci.yml` and `release.yml`.
|
|
104
|
+
- A release costs a runner kind cluster and roughly ten minutes of docker. Accepted: it is the only time the installed
|
|
105
|
+
path must be proven on the exact tree that ships.
|
|
106
|
+
- The four tarballs gain the npm-page fields (`description`, `repository` with `directory`, `homepage`, `bugs`,
|
|
107
|
+
`keywords`) and a README each; the root README's "only scaffolding exists" line goes.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# A global `jr2` hands off to the Instance's own, and an Instance has one Kit version
|
|
2
|
+
|
|
3
|
+
[ADR-0043](0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md) made `jr2 init` pin `@jr2/cli` as
|
|
4
|
+
an exact devDependency beside `@jr2/orchestrator`, and said instances upgrade "by editing two dep lines". That pin only
|
|
5
|
+
means something if the Instance's copy of the CLI is the one that runs — and nothing made it so. `bin/jr2.js` ran
|
|
6
|
+
itself, whatever cwd it was run from, and `KIT_VERSION` was read from the `@jr2/orchestrator` the **running CLI**
|
|
7
|
+
resolved, never the one the Instance resolved. The first bump after 0.1.0 (2026-09-20) showed the hole: a global
|
|
8
|
+
`jr2@0.1.1` in an Instance pinning `@jr2/orchestrator@0.1.2` and `@jr2/cli@0.1.0` converged 0.1.1 Kit images around a
|
|
9
|
+
0.1.2 bundle, and nothing said a word. Published `@jr2/cli@X` depends on `@jr2/orchestrator@X` exact, so a mismatched
|
|
10
|
+
pin quietly nests a **second** orchestrator under the CLI — the class-identity failure commit 2685d9b had already
|
|
11
|
+
patched one symptom of. This ADR settles which binary runs, and what "the Instance's version" is.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
- **A global `jr2` hands off (the gulp/grunt model).** Before any verb, the binary walks up from cwd to `jr2.config.ts`
|
|
16
|
+
(ADR-0009's root marker). If that folder resolves `@jr2/cli` to a real path other than its own, it runs that package's
|
|
17
|
+
`bin.jr2` with the same arguments and stdio, and exits with its code. Nothing else — no flag parsing, no verb
|
|
18
|
+
exceptions (`--url` inside an Instance still hands off; the rule stays one sentence). _Amended 2026-09-20:_ plus one
|
|
19
|
+
env var, `JR2_HANDOFF_FROM=<version> <realpath>`, naming the copy that handed off. `jr2 version` prints it as the
|
|
20
|
+
`global` line, so "is my global out of date" is answered without leaving the Instance; nothing else reads it. It is
|
|
21
|
+
the only fact the local copy cannot learn on its own (`npm ls -g` is slow and per-manager). A global that predates the
|
|
22
|
+
variable hands off without it, and the report then shows no `global` line at all — indistinguishable from `npx jr2`,
|
|
23
|
+
and there is no third source to ask.
|
|
24
|
+
- **Spawn, not import.** gulp loads the local gulp in-process because local gulp is a _library_ its CLI drives; here the
|
|
25
|
+
local is a _binary_. Spawning couples the global to one contract — the local package's `bin` field, npm's own, stable
|
|
26
|
+
across versions — and the local's preamble runs: its pinned `ts-blank-space`, its hooks, whatever a future bin adds.
|
|
27
|
+
Importing `src/cli.ts` would make the local's internal layout and `main` signature a cross-version API the global must
|
|
28
|
+
honor forever, and the global is the copy that cannot be updated once it ships. One extra Node start per invocation is
|
|
29
|
+
the price. Ctrl-C is unchanged: both share the process group.
|
|
30
|
+
- **An Instance has one Kit version: the `@jr2/orchestrator` it resolves**, because that is the Kit image its
|
|
31
|
+
Orchestrator runs and the version its Assembly is written against
|
|
32
|
+
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)). Wherever the CLI loads
|
|
33
|
+
an Instance (the root walk every Instance verb goes through; `--url` verbs skip the walk per ADR-0009, `jr2 init` has
|
|
34
|
+
no Instance yet), it resolves `@jr2/orchestrator/package.json` from the Instance root and from itself and requires the
|
|
35
|
+
two **real paths** to be equal, and that copy's version to be the CLI's own. Not equal — or absent — is a refusal that
|
|
36
|
+
names both versions and the two lines to edit, then says reinstall. Real-path identity first: same file means same
|
|
37
|
+
version by construction under npm hoisting, pnpm dedup, and checkout symlinks alike, and it is exactly the property
|
|
38
|
+
the class-identity bug needs. But one copy is not yet one version: when the Instance edits only the
|
|
39
|
+
`@jr2/orchestrator` line, the root's own pin wins over the CLI's peer, the one copy is hoisted, and the old CLI
|
|
40
|
+
resolves it at the same real path. So the CLI's own number must equal it too, which one release train makes true for a
|
|
41
|
+
correct pin ([ADR-0055](0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md)).
|
|
42
|
+
- **`@jr2/orchestrator` is a peer of `@jr2/cli`, not a dependency.** `peerDependencies: workspace:*` (pnpm rewrites it
|
|
43
|
+
exact at publish) plus the same under `devDependencies` for the checkout's own tests. A mismatched pin is then a peer
|
|
44
|
+
conflict at install. npm does not always fail it: the root's own `@jr2/orchestrator` line wins over the peer of a
|
|
45
|
+
devDependency with `npm warn ERESOLVE overriding peer dependency`, and pnpm and bun warn too, so the check above is
|
|
46
|
+
what refuses. The CLI stops carrying an orchestrator of its own, so a second copy can only come from the Instance's
|
|
47
|
+
resolution — the copy we want. The CLI stays a **devDependency** of every Instance: the bundle installs
|
|
48
|
+
`--omit=dev`/`--prod`, and the image has no use for a CLI or its erasure hook; peers of a devDependency still
|
|
49
|
+
auto-install and still `ERESOLVE`.
|
|
50
|
+
- **`jr2 init` reads the CLI's own version.** A global with no Instance around it must not need its peer resolved, so
|
|
51
|
+
the scaffold's exact pin comes from `@jr2/cli`'s `package.json`. The numbers are equal by construction: one release
|
|
52
|
+
train, lockstep ([ADR-0055](0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md)).
|
|
53
|
+
|
|
54
|
+
## Consequences
|
|
55
|
+
|
|
56
|
+
- ADR-0043's "edit two dep lines" is amended: an Instance is upgraded by editing two lines **to the same number**, and
|
|
57
|
+
editing one is now a refusal (or an install error), never a silent skew.
|
|
58
|
+
- A global installed before this ADR does not hand off; the user updates it once. After that the global's version stops
|
|
59
|
+
mattering inside an Instance, which is the point.
|
|
60
|
+
- The `@dist` tier exercises the handoff for free: its installed global drives an Instance whose own `@jr2/cli` came
|
|
61
|
+
from the same verdaccio at a different real path. The mode-line assertion it already makes now asserts the local
|
|
62
|
+
copy's line.
|
package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# A Turn is its Frame, its Dials, and whether it continues
|
|
2
|
+
|
|
3
|
+
A live `task` run (2026-09-20, `fa45e88f`, seven tool calls, eight minutes, zero bytes of the target read) exposed the
|
|
4
|
+
seam: the prompt said `Work in /work/target/summarize` while every Working tool resolved relative paths at `/work`,
|
|
5
|
+
because `cwd` lived on the Agent definition and a definition is written before any run — before any worktree — exists.
|
|
6
|
+
The package compensated with prose, and a weak model ignored prose: it wrote `SUMMARY.md` outside the worktree, called
|
|
7
|
+
`finish`, and the run parked at `review` claiming done. The audit that followed found the invoke input carrying four
|
|
8
|
+
ways to name one conversation (`session: "continue"`, `scope`, `conversation`, and an undeclared `instanceId`), one of
|
|
9
|
+
them (`scope`) with two unrelated author intents, none with a stated precedence, and every `@kind` fixture bypassing all
|
|
10
|
+
of them. ADR-0028 already said the reviewing state hands a per-round worktree path as the reviewer's "cwd and prompt";
|
|
11
|
+
ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thing had no field to land in.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
- **A Turn's input is its Frame, its Dials, and whether it continues — nothing else.**
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
{ prompt, cwd?, continue?, model?, thinkingLevel? }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Identity — `instructions`, `workspace` — stays on the definition, and the definition is exactly identity plus one
|
|
22
|
+
default: `agent({ model, instructions, workspace?, thinkingLevel?, description? })`.
|
|
23
|
+
|
|
24
|
+
- **The Frame is `prompt` and `cwd`: what this Turn is about and where it works.** Neither identity nor a Dial. A
|
|
25
|
+
worktree path is per run by nature — `/work/<slot>/<name>` exists only once a run cuts it — so no definition can name
|
|
26
|
+
it, and a directory is not a property of a persona any more than a prompt is. `cwd` leaves `AgentDefinition`. The
|
|
27
|
+
Harness roots the Working tools at the Frame's `cwd`, which rides the admit body beside the prompt and the Dials.
|
|
28
|
+
|
|
29
|
+
- **The Briefing is not the Frame.** jr2's Turn part
|
|
30
|
+
([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)) follows the prompt — the resolved working
|
|
31
|
+
directory, the Allowed picks and any notices — in a block of its own. The prompt stays the Machine state's words
|
|
32
|
+
alone; jr2 never writes into it.
|
|
33
|
+
|
|
34
|
+
- **Under Repo Slots, `cwd` is required.** A Workspace that only clones has no Worktree for a default to name
|
|
35
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), and `/work` is the wrong default: an
|
|
36
|
+
Agent's search hits `default/`, and a relative write lands outside each Worktree, which is the incident above. So the
|
|
37
|
+
state says where — `cwd: "/work/target/fix"` — and the actor refuses the Turn before admission, naming the slots and
|
|
38
|
+
the line to add; `jr2 up` cannot see it because the invoke's input is a function. A Turn that wants `/work` states it.
|
|
39
|
+
A Workspace with no Repo Slot needs no `cwd`: its Turns work in `/work` (ADR-0051). A `workspace: "none"` Agent has no
|
|
40
|
+
Working tools and no `cwd` (ADR-0028); a stub-Harness run with no Workspace keeps `/work`. `task` passes its `cwd` as
|
|
41
|
+
any Machine does: "the first slot is the one the coder edits" is its convention, and a convention is stated, not
|
|
42
|
+
defaulted.
|
|
43
|
+
|
|
44
|
+
- **`continue: true` is the whole continuation surface.** Absent, every invocation is a fresh conversation — jr's lossy
|
|
45
|
+
handoff, ADR-0016's default, unchanged. Present, the Turn lands on **this Agent's one conversation in this Machine
|
|
46
|
+
instance**, whichever state of the Machine invokes it. The Instance ID is structural —
|
|
47
|
+
`<runId>/<machine actor path>/<agent>` — so the author writes a boolean, not a key: in a state machine the identity is
|
|
48
|
+
already on the page (the run, the Machine instance, the Agent slot), and a caller-chosen string would only restate it,
|
|
49
|
+
or contradict it. Every surveyed orchestration framework makes the caller hold a key (LangGraph `thread_id`, Temporal
|
|
50
|
+
Workflow Id, Mastra `threadId`) because nothing else can; here something else can.
|
|
51
|
+
|
|
52
|
+
- **Fan-out is safe by construction.** A Pool spawns each worker with the item id as its actor id (pool.ts), so three
|
|
53
|
+
`task` children each mint their own `…/backlog.worker.<item>/coder`. The packaged Machine never learns it is under a
|
|
54
|
+
Pool.
|
|
55
|
+
- **States of one Machine share by saying `continue`.** States differ by Menu — a coder in `implement` offers
|
|
56
|
+
`finish`, the same coder in `fix` offers `finish` and `dispute` — and the same conversation is wanted across them. A
|
|
57
|
+
state that wants a clean context leaves `continue` off. A Turn joins only a conversation that an earlier Turn
|
|
58
|
+
started with `continue: true`, so a retry loop says `continue` on each Turn, the first too: a first attempt without
|
|
59
|
+
it is not in the conversation that attempt 2 continues. Thus the docs show `continue: true` on each Turn of a retry
|
|
60
|
+
loop, and a conditional spread (`...(clean ? {} : { continue: true as const })`) only for a state that must start a
|
|
61
|
+
clean conversation on some Turns only (a state that always starts clean leaves `continue` off). A clean Turn does
|
|
62
|
+
not start the continued conversation again: the next Turn with `continue: true` joins the earlier one, so attempt 4
|
|
63
|
+
after a clean attempt 3 continues attempts 1 and 2. The alternative, a spread on `attempts > 0`, keeps attempt 1 out
|
|
64
|
+
of the conversation, so attempt 2 does not see it. The cost of `continue: true` on attempt 1 is the Runaway reroll
|
|
65
|
+
(ADR-0035): a continued Turn gets none, so a Runaway on attempt 1 sends `agent.fault` at once, and burns the
|
|
66
|
+
conversation. The docs accept this cost, so that attempt 2 sees attempt 1.
|
|
67
|
+
- **Two Agents never share a conversation.** The agent name is in the id, and it always was; a second persona under
|
|
68
|
+
one transcript would swap the system prompt mid-conversation. The memory-Agent shape ("read the coder's
|
|
69
|
+
conversation, store what matters") is served by handing the transcript as prompt _data_ to an Agent with its own
|
|
70
|
+
conversation — Letta's sleep-time agents — not by joining; how a transcript reaches a Machine as data is a follow-on
|
|
71
|
+
decision.
|
|
72
|
+
- **A faulted conversation is never continued.** No fault leaves a conversation worth continuing: a runaway either had
|
|
73
|
+
its reroll or has a poisoned context; infra and no-signal mean the Harness that held it is unreachable (ADR-0035).
|
|
74
|
+
jr2 keeps an **epoch** per continued conversation in the ledger beside the snapshot; the terminal `agent.fault`
|
|
75
|
+
bumps it, and the next `continue` on that Agent mints `<id>/<epoch>` — a virgin conversation. The author deletes
|
|
76
|
+
their generation counter. What stays with the author is re-briefing: the fault event is the signal that the next
|
|
77
|
+
prompt must carry the whole task again, and jr2 cannot write that prompt. The one fault that is not the
|
|
78
|
+
conversation's is a Harness restart that cut the Turn (`Turn lost`, or `memory limit` behind it): the rebuilt
|
|
79
|
+
Harness holds the conversation ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)), so the epoch
|
|
80
|
+
stays and the next `continue` lands on it; a rebuild that could not read it answers that Turn's `holds` check, which
|
|
81
|
+
gives a `conversation-new` notice (ADR-0062).
|
|
82
|
+
- **The reroll stays closed to continued conversations** (ADR-0035): the one recovery jr2 knows is the identical
|
|
83
|
+
prompt on a fresh conversation, and a continued Turn's prompt ("Continue.") is meaningless fresh. Fault route plus
|
|
84
|
+
re-brief is the recovery.
|
|
85
|
+
- **Two live invocations of one continued id — parallel states — are refused at the registration table**, one live
|
|
86
|
+
surface per address; a Submission that arrives behind a live one on the _Harness_ queues (ADR-0027). Sequential
|
|
87
|
+
states never race, because ADR-0024 ends the previous Turn before the next state invokes.
|
|
88
|
+
|
|
89
|
+
- **`session`, `scope`, `conversation`, and `instanceId` retire.** `session: "continue"` was already this boolean under
|
|
90
|
+
a longer name — its id was the invoking Machine's actor path plus `scope` — so what retires there is the name and the
|
|
91
|
+
suffix; `conversation` was the cross-Machine pin (a name _replacing_ the path, so two Machine instances — two Pool
|
|
92
|
+
children — collided by construction, the hazard ADR-0049 recorded and could not refuse); `scope` suffixed all of them
|
|
93
|
+
and carried two intents (a subject under `continue`, a generation counter in `task`); `instanceId` bypassed minting
|
|
94
|
+
for tests. One boolean replaces all four because the id is deterministic when continued, so nothing needs a handle to
|
|
95
|
+
address it.
|
|
96
|
+
|
|
97
|
+
- **The author type carries no mechanism.** `endpoint` and `sandbox` — set only by stub-tier fixtures that have no
|
|
98
|
+
`workspace()` to resolve from — move to a mechanism type the tests import. `tools`, the Menu override, retires: a
|
|
99
|
+
hand-written Menu can only agree with the invoking state's transitions or lie about them, and the lie is a pick that
|
|
100
|
+
moves nothing (ADR-0029). The one shape it hid — picks written on the CHILD STATES of the invoking state — is derived:
|
|
101
|
+
those child states are the state's modes (ADR-0015). A state that allows no picks stays legal, because a state a Gate
|
|
102
|
+
or a timer moves asks its Agent for text and nothing else. A real run never sets any of them: a Sandbox Agent resolves
|
|
103
|
+
ambiently (ADR-0016), a `workspace: "none"` Agent lands on the Instance Harness (ADR-0031).
|
|
104
|
+
|
|
105
|
+
## Considered options
|
|
106
|
+
|
|
107
|
+
- **Prompt-only** — tell the model that relative paths resolve at `/work` and demand absolute ones. Rejected by the
|
|
108
|
+
incident: ADR-0024 already established that the same prompt produces compliance on one run and not the next, and every
|
|
109
|
+
other Machine would repeat the sentence.
|
|
110
|
+
- **`cwd` stays on the definition, overridable per Turn.** Rejected: a definition-level `cwd` can only ever say `/work`,
|
|
111
|
+
the parent of every checkout, where grep and glob hit the pristine `default/` clone beside the worktree. A default
|
|
112
|
+
that is wrong under every `workspace()` is not a default.
|
|
113
|
+
- **`cwd` as a third Dial.** Rejected on the word: Dials say how hard to run. A directory says where, which is the same
|
|
114
|
+
kind of fact as the prompt — hence Frame.
|
|
115
|
+
- **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`").
|
|
116
|
+
- **Default `cwd` to the only Repo Slot's Worktree.** Rejected: a Workspace that only clones has no such Worktree. And a
|
|
117
|
+
refusal under every Workspace with Repo Slots has no composition trap: with a default, a composer who adds a second
|
|
118
|
+
slot on the `customize` line makes a Machine that ran yesterday refuse today, in a file the Machine's author never
|
|
119
|
+
sees. The cost is one line per invoke.
|
|
120
|
+
- **A caller-chosen conversation key (`conversation: "coder"`).** Rejected: it either restates the structure or
|
|
121
|
+
contradicts it. Path-scoping the key was proposed and made the key redundant — once the id is
|
|
122
|
+
`<run>/<machine path>/<agent>`, the author has nothing left to name.
|
|
123
|
+
- **Continuation scoped to the invoking state.** Rejected: splits one conversation across the states that differ only by
|
|
124
|
+
Menu, which is the case continuation exists for.
|
|
125
|
+
- **The cross-Machine pin kept as an opt-in.** Rejected: zero users since `examples/coding` went (ADR-0054), never sound
|
|
126
|
+
— the two states it joined sat on different Harnesses, and a conversation lives on one server (ADR-0031) — and an
|
|
127
|
+
unused opt-in is the fourth mechanism back under a new name. If a sound case appears it is a new decision.
|
|
128
|
+
- **Continue a faulted conversation, author's choice.** Rejected: there is no case. Every fault class leaves either a
|
|
129
|
+
poisoned context or no server — except a Harness restart, which cuts the Turn and keeps the conversation (above).
|
|
130
|
+
|
|
131
|
+
## Consequences
|
|
132
|
+
|
|
133
|
+
- `AgentTurnInput` is `{ prompt, cwd?, continue?, model?, thinkingLevel? }`; `AgentDefinition` loses `cwd`. The Harness
|
|
134
|
+
admit body gains the Frame's `cwd`; `ResolvedDefinition.cwd` resolves from it, not the definition; the Harness stops
|
|
135
|
+
validating `description`, which the Orchestrator strips before admission (it is Console material).
|
|
136
|
+
- **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case, it
|
|
137
|
+
reaches a Harness only where no Repo Slot is, and it says `/work` — a stated default. A relative path has no such
|
|
138
|
+
answer: it would resolve against whatever directory the Harness process sits in, which is the silent wrong directory
|
|
139
|
+
this ADR was written for, one level further from the author. The refusal is a 400 at admission, before a pod is spent.
|
|
140
|
+
- `task` passes the path of the Worktree it cuts as `cwd`, and `continue: true`, and drops `generation` (and its
|
|
141
|
+
`scope`). It keeps `turns`, reset on `agent.fault`, because re-briefing is prompt policy.
|
|
142
|
+
- The ledger record for a continued conversation carries its epoch; the input mapper reads it at mint time, so the
|
|
143
|
+
minted id persists with the input and restore re-attaches the same conversation (ADR-0016's mapper rule holds — the
|
|
144
|
+
fresh suffix is still the random component).
|
|
145
|
+
- The `@kind` fixtures drop `tools:` and `instanceId:`; the steps compute the continued id from the actor path, as they
|
|
146
|
+
already computed today's pin.
|
|
147
|
+
- A regression scenario for the silent success: a `task` whose model uses relative paths must produce its file inside
|
|
148
|
+
the worktree, and a Machine under Repo Slots with no `cwd` must be refused with the slots named.
|
|
149
|
+
- GLOSSARY.md: **Frame** enters; **Dials** loses `cwd`; **Instance ID** states the `continue` scope and that a faulted
|
|
150
|
+
conversation is not continued. ADR-0016 (continuation), ADR-0018 (identity line), ADR-0031 (placement scenario),
|
|
151
|
+
ADR-0035 (reroll gate wording), ADR-0049 (persona hazard), and ADR-0054 (`task`'s continuation) are edited to state
|
|
152
|
+
this truth.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# A Harness answers the Orchestrator alone
|
|
2
|
+
|
|
3
|
+
A red-team review (2026-09-22) found that the Harness wire had no authentication. `POST /agents/:name/:id`, the stream
|
|
4
|
+
and history views, and `.../abort` checked no bearer, and the server bound `0.0.0.0:8080`. Iids are derivable
|
|
5
|
+
(`<runId>/<machine>/<agent>` for a continued conversation,
|
|
6
|
+
[ADR-0057](0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md)), so any pod that could reach a Harness could
|
|
7
|
+
admit a Turn into another conversation, read its history, or abort it. An Agent with bash in one Sandbox could do this
|
|
8
|
+
to another Sandbox, or to the Instance Harness, which holds every Menu-only conversation in the Instance
|
|
9
|
+
([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)). No NetworkPolicy existed to limit the reach. The
|
|
10
|
+
review also found that the echo ([ADR-0023](0023-the-harness-prints-the-conversation.md)) sent the raw Instance token to
|
|
11
|
+
every Sandbox Harness on each push. The env held only its digest, but the token itself passed through the process that
|
|
12
|
+
the Agent executes code beside. So we authenticate the wire with a bearer that each Harness can verify and cannot mint,
|
|
13
|
+
and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harness pod.
|
|
14
|
+
|
|
15
|
+
## Decision
|
|
16
|
+
|
|
17
|
+
- **One bearer per placement.** The Orchestrator presents
|
|
18
|
+
`harnessToken(key, placement) = HMAC(signing key, "harness:" + placement)` on every Harness wire call: admit, each
|
|
19
|
+
long-poll, abort, and echo. `placement` is the name of the pod that hosts the Turn: the Sandbox's name, or
|
|
20
|
+
`jr2-instance-harness`. This is the same name the registration records as its delivery scope
|
|
21
|
+
([ADR-0013](0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md)), so one name scopes both
|
|
22
|
+
directions. The Custodian there may speak for the Turn's picks, and only this bearer may drive the conversation there.
|
|
23
|
+
- **A key per Effector placement, derived the same way**
|
|
24
|
+
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)).
|
|
25
|
+
`effectorKey(key, placement) = HMAC(signing key, "effector:" + placement)` signs every request the Orchestrator sends
|
|
26
|
+
an Effector and every answer it takes back. `placement` is `instance` for the Instance Effector and the Sandbox's name
|
|
27
|
+
for a Sandbox Effector, so each Sandbox pod has its own key and a request signed for one Sandbox is refused by every
|
|
28
|
+
other; no Sandbox name is `instance`, since each one starts `ws-`. The `effector:` prefix keeps it apart from every
|
|
29
|
+
Harness bearer and Sandbox token. Unlike a Harness, an Effector holds the key itself — it must sign its answers — in a
|
|
30
|
+
Secret only its own container mounts. `jr2 up` writes the Instance Effector's (`jr2-instance-effector`) from the kept
|
|
31
|
+
signing key. The Orchestrator writes a Sandbox Effector's at provision, as it writes the Sandbox token: into
|
|
32
|
+
`<sandbox>-effector`, a Secret the Sandbox CR owns, so it goes with the pod. Either way the Orchestrator stores
|
|
33
|
+
nothing and derives the key again after a restart, and a re-provision applies the same value.
|
|
34
|
+
- **The Harness holds only the digest.** Its env carries `JR2_HARNESS_TOKEN_SHA256 = sha256(bearer)`. The Agent can read
|
|
35
|
+
that env, and a digest inverts to nothing. So a Harness can verify its own bearer, and it can mint none, for itself or
|
|
36
|
+
for another pod. The HMAC key never leaves the Orchestrator's Secret.
|
|
37
|
+
- **The bearer is derived, not stored.** This is the reason the Sandbox token is a signed name (`tokens.ts`). The
|
|
38
|
+
Orchestrator holds no per-pod state. A restarted Orchestrator derives the same bearer that a live Harness already
|
|
39
|
+
checks, so re-attach ([ADR-0012](0012-workspace-wrapper-machine.md)) survives with no change. The `harness:` prefix
|
|
40
|
+
separates it from the Sandbox token: no DNS-label name produces it.
|
|
41
|
+
- **Every route is gated, and the gate runs first.** Admit, stream, history, abort and echo answer 401 without the
|
|
42
|
+
bearer. A 401 comes before a 404, so a refused caller does not learn which conversations exist. A refused admission
|
|
43
|
+
creates no conversation. The Harness requires `JR2_HARNESS_TOKEN_SHA256` at boot and fails loudly without it.
|
|
44
|
+
- **The echo uses the same bearer.** `JR2_ECHO_TOKEN_SHA256` is removed. The Instance token no longer enters any pod,
|
|
45
|
+
which is what [ADR-0032](0032-the-console-unlocks-with-the-instance-token.md) and
|
|
46
|
+
[Secrets and trust](../../concepts/secrets-and-trust.md) say.
|
|
47
|
+
- **A refused stream is a fault, not a reconnect.** The bearer is derived, so a Harness that refuses it once refuses it
|
|
48
|
+
on every retry. `wait` settles a 401 or 403 as a `SettlementFault` and does not park the Turn forever.
|
|
49
|
+
- **Ingress to every Harness pod comes from the Orchestrator alone.** `jr2 up` converges two NetworkPolicies in the
|
|
50
|
+
instance namespace. `jr2-sandbox-ingress` selects every pod that has the operator's `sandbox.jr2.dev/name` label.
|
|
51
|
+
`jr2-instance-harness-ingress` selects `app: jr2-instance-harness`. Both admit ingress only from this instance's
|
|
52
|
+
Orchestrator pods (`app: jr2-orchestrator`, `jr2.dev/instance: <name>`). They are converged unconditionally, so a
|
|
53
|
+
Harness pod is never created before its policy exists. The Instance Effector gets a third of the same form,
|
|
54
|
+
`jr2-instance-effector`, applied with it in one List (ADR-0068).
|
|
55
|
+
- **Starting a run needs the Instance token.** `POST /workflows/:name/runs` moves from `authenticated` to
|
|
56
|
+
`instanceOnly`. The scope of a Sandbox token is its own agent surface. The Custodian's route allowlist does not
|
|
57
|
+
forward this route, but that is not a control.
|
|
58
|
+
|
|
59
|
+
## Considered options
|
|
60
|
+
|
|
61
|
+
- **Ed25519, one short-lived token per admission, scoped to one iid.** This was the first proposal. Rejected. `wait`
|
|
62
|
+
reconnects indefinitely by design, and re-attach after a restart follows a ledgered admission of any age, so a short
|
|
63
|
+
expiry breaks both unless each poll gets a new token. That adds a public key in every pod and a dependency on clock
|
|
64
|
+
sync. What it buys over a per-placement bearer is scoping inside one pod. But the Agent already has code execution in
|
|
65
|
+
that pod's Harness container, so that scope protects nothing.
|
|
66
|
+
- **The Instance token's digest, as the echo used.** Rejected. It is one secret for every pod, and it sends the Instance
|
|
67
|
+
token, the full-trust credential, into each Harness process.
|
|
68
|
+
- **One Instance-wide Harness token.** Rejected. A digest does not let a Harness mint, but the bearer arrives in every
|
|
69
|
+
Harness process on every call. A per-placement bearer that leaks from one pod opens only that pod, which the Agent
|
|
70
|
+
there already controls.
|
|
71
|
+
- **NetworkPolicy alone.** Rejected, for the reason ADR-0013 gives: a policy bounds where a pod may talk, and only a
|
|
72
|
+
token bounds what it may do. The policy cannot separate the Harness from the Custodian in one pod, and a cluster whose
|
|
73
|
+
CNI does not enforce policy gets nothing from it.
|
|
74
|
+
|
|
75
|
+
## Consequences
|
|
76
|
+
|
|
77
|
+
- **The stub Harness checks nothing.** It is a host-side test fixture that is reached by an explicit `endpoint`, and a
|
|
78
|
+
run on the stub path names no placement, so it sends no bearer. This is the second documented divergence from the
|
|
79
|
+
normative model ([ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)). Conformance tests
|
|
80
|
+
inject the check (`checkBearer`), and `auth.test.ts` in `@jr2/harness` pins the routing of the gate.
|
|
81
|
+
- **kindnet enforces NetworkPolicy.** On the kind version this repo pins, a deny-ingress policy blocks pod-to-pod
|
|
82
|
+
traffic, so the `@kind` tier proves both controls. A Sandbox's own Harness refuses an unauthenticated admission over
|
|
83
|
+
loopback, where no policy applies. A Sandbox cannot connect to the Instance Harness at all. The second test asserts a
|
|
84
|
+
transport failure, not a status, because a 401 would mean the bearer stopped it and the policy did not.
|
|
85
|
+
- **Port-forward and probes are not affected.** `kubectl port-forward` enters the pod's network namespace through the
|
|
86
|
+
kubelet, and the kubelet's probes come from the node. On kind, neither is filtered by an ingress policy. So the CLI,
|
|
87
|
+
the Console, and a human's shell into the User Container ([ADR-0005](0005-sandbox-pod-composition.md)) keep working.
|
|
88
|
+
If routable access to the User Container is added later (ADR-0005 leaves it open), it must add its own ingress rule.
|
|
89
|
+
- **The `@kind` history reads present the bearer.** The steps derive it from the instance Secret's signing key, as the
|
|
90
|
+
CLI reads the Instance token: over the kube API, with RBAC as the gate. A human reading a conversation does the same.
|
|
91
|
+
- **Model keys are not in the Harness container.** `JR2_PROVIDER_API_KEY` and every other held secret live in the
|
|
92
|
+
Custodian, and the Harness container's env holds their Stand-ins (ADR-0059). `jr2 up` refuses a known model key in
|
|
93
|
+
`harness.env` or in an `envFrom` Secret.
|
|
94
|
+
- **Open gaps, recorded here and not fixed:**
|
|
95
|
+
- **Sandbox egress.** An Agent with bash can reach the internet and send out anything it can read — which no longer
|
|
96
|
+
includes a held key: a client that goes around the Custodian carries only Stand-ins. One exception: no Harness pod
|
|
97
|
+
reaches a cloud metadata address
|
|
98
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). A default-deny egress
|
|
99
|
+
policy needs an allowlist: DNS, the Orchestrator, the model provider, and whatever the work needs (package
|
|
100
|
+
registries, for example). That needs a config surface and cluster CIDRs, so it is a separate decision.
|
|
101
|
+
- **A held key can be spent.** The Agent can use a key through the Custodian while its pod lives, and a SigV4 or ADC
|
|
102
|
+
credential cannot be held at all ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
|
|
103
|
+
- **The Orchestrator's Secret writes are namespace-wide.** Its Role may create and patch every Secret, and may not get
|
|
104
|
+
or list one ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)): it applies its own
|
|
105
|
+
per-Sandbox Secrets and holds no git credential and no Effect secret. RBAC cannot pin `create` to a name, and
|
|
106
|
+
`resourceNames` has no prefix form. Two residuals stay, named in ADR-0068: a PATCH answers with the object, so a
|
|
107
|
+
caller that knows a Secret's name can still read it; and a Role that creates pods (through the Sandbox CR) can mount
|
|
108
|
+
a Secret into one.
|