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