@jr2/orchestrator 0.6.0 → 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 +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- 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 +24 -8
- 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/fix-a-failed-converge.md +18 -6
- 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 +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- 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 +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- 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 +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- 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 +56 -1
- 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 +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- 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-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- 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-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- 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 +1 -1
- 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-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-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 +16 -7
- 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 +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- 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 +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- 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 +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- 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 +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- 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 +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- 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 +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- 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 -18
- 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 +4 -3
- 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 +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- 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 +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- 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 +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- 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 +17 -13
- 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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- 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 +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -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 +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- 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/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md
CHANGED
|
@@ -50,7 +50,7 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
50
50
|
each Sandbox, whatever the count of its Agents.
|
|
51
51
|
- The **fetch Stand-in** is public, `jr2-held-JR2_SANDBOX_TOKEN`. `jr2-upload-pack` holds it as a constant. The
|
|
52
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.**
|
|
53
|
+
- **The Harness reads its Menu itself.** No MCP carries the Menu. For each Submission, `menu.ts` reads
|
|
54
54
|
`GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian's control
|
|
55
55
|
socket, `/run/jr2/custodian/control.sock`, with the control Stand-in as the bearer and the slot it states for the
|
|
56
56
|
conversation (ADR-0076). `menu-tools.ts` gives pi each accepted event as a tool. The tool names stay
|
|
@@ -64,7 +64,7 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
64
64
|
([ADR-0042](0042-ready-is-not-routable-an-admission-retries-its-connection.md)), and a `poll` event refuses the turn
|
|
65
65
|
(`deferred` is retired, ADR-0006). The event's own JSON Schema is the tool's parameters, so the model sees the
|
|
66
66
|
contract the Orchestrator validates against.
|
|
67
|
-
- **The control listener forwards
|
|
67
|
+
- **The control listener forwards six routes and no other.** Plain HTTP, to the Orchestrator's Service
|
|
68
68
|
(`jr2-orchestrator.<ns>.svc:4000`, written into the pod's bootstrap by whoever makes the pod — see "Objects, per
|
|
69
69
|
pod"):
|
|
70
70
|
|
|
@@ -73,21 +73,23 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
73
73
|
POST /agents/<iid>/events → the same path a pick
|
|
74
74
|
GET /agents/<iid>/deliveries/<id> → the same path an outstanding pick's answer (ADR-0024)
|
|
75
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)
|
|
76
77
|
POST /fetch → /sandboxes/<this pod's Sandbox>/fetch the ask (ADR-0053)
|
|
77
78
|
anything else → 404, from the Custodian
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
The
|
|
81
|
+
The five agent routes are on a Unix socket, `/run/jr2/custodian/control.sock`, in a folder that the Harness and the
|
|
81
82
|
Custodian share and that no confined program can write (ADR-0076). The fetch is on `127.0.0.1:8081`, because
|
|
82
83
|
`jr2-upload-pack` runs in the domain, and the fetch Stand-in is public. Each listener answers 404 to the other's
|
|
83
84
|
routes. Landlock does not limit a connect to a named socket, so a confined program can connect to the control socket.
|
|
84
85
|
It holds no control Stand-in, so the Custodian refuses each agent route that it asks for there, with the fetch
|
|
85
86
|
Stand-in or with none, and the control socket answers 404 to the fetch route.
|
|
86
87
|
|
|
87
|
-
A read — the surface, a delivery's poll —
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
outstanding pick never rides
|
|
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).
|
|
91
93
|
|
|
92
94
|
On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token of that route — the
|
|
93
95
|
control token on an agent route, the fetch token on the fetch route — and removes every other credential header. A
|
|
@@ -124,7 +126,10 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
124
126
|
`paths` gives 403. It is checked after Envoy removes dot segments and reads `\` as `/` and `..;` as `..`, and the
|
|
125
127
|
upstream gets that path. So IIS and Tomcat cannot read a different path. A credential header sent twice gives 400.
|
|
126
128
|
- The swap: in each header the secret names, the Stand-in alone, or one auth scheme and the Stand-in
|
|
127
|
-
(`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)).
|
|
128
133
|
- Every credential header that is not the product of a swap is removed. A request with no swap gets 403 and never
|
|
129
134
|
leaves the pod: `jr2 custodian: <target> needs the stand-in of <NAME> in <headers>`.
|
|
130
135
|
- Envoy opens its own TLS to the target, verifies the chain against `upstream.crt` and the name against the target's
|
|
@@ -203,23 +208,25 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
203
208
|
and `harness.catalog` (a catalog provider id → `{ baseUrl }`). The list serves both sides
|
|
204
209
|
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). `harness.provider.apiKey` is an entry
|
|
205
210
|
too, implicitly: `JR2_PROVIDER_API_KEY`, bound to its `baseUrl`'s host. `loadConfig` checks the shape in both worlds.
|
|
206
|
-
- **Naming is placement: a pod gets the held secrets something in it names
|
|
207
|
-
(`secretPlacementsOf`), and an entry's `hosts`, `headers` and `paths` are
|
|
208
|
-
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:
|
|
209
214
|
|
|
210
|
-
| Pod | The held secrets its Custodian holds
|
|
211
|
-
| --------------------- |
|
|
212
|
-
| A Workspace's Sandbox | the names its `workspace()` lists under `secrets: [...]`,
|
|
213
|
-
| 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 |
|
|
214
219
|
|
|
215
220
|
A model key is claimed by the PROVIDER: an Agent names its model, and jr2 knows which keys that provider reads
|
|
216
221
|
(`MODEL_KEYS`; the custom provider's id for `JR2_PROVIDER_API_KEY`). An Agent's Turns run in the Workspace below which
|
|
217
222
|
it is declared, down to the next `workspace()`, unless it is `"none"`, whose Turns run at the Instance Harness
|
|
218
|
-
([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)).
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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:
|
|
223
230
|
`Workspace of workflow "coding": Custodian holds ANTHROPIC_API_KEY (provider), JIRA_TOKEN`.
|
|
224
231
|
|
|
225
232
|
`jr2 up` alone checks the claimed entries (`heldSecretsOf`), host-side, because `.env` values are absent in-cluster.
|
|
@@ -231,14 +238,14 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
231
238
|
| R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
|
|
232
239
|
| R3 | One name held twice, or held and set by `harness.env`. |
|
|
233
240
|
| R4 | Not exactly one of `value`/`valueFrom`; an empty value; a byte outside `0x20`–`0x7E`; leading or trailing space. |
|
|
234
|
-
| 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
|
|
235
|
-
| R6 | A `headers` entry that is not a lowercase token, or that frames or routes the request; a duplicate.
|
|
236
|
-
| R7 | `provider.apiKey` with a non-`https` `baseUrl`; a non-`https` catalog `baseUrl`.
|
|
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`. |
|
|
237
244
|
| R8 | A catalog key that is not a known provider, or that is the custom provider's id. |
|
|
238
|
-
| R9 | Explicit `hosts` on a known model key that leave out where a carried Agent's provider sends its calls.
|
|
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. |
|
|
239
246
|
| R10 | A `secretKeyRef` whose Secret or key does not exist. |
|
|
240
247
|
| R11 | While a secret is held: `harness.env` sets a proxy or trust variable. |
|
|
241
|
-
| R12 | A `paths` entry that does not start with `/`, or holds `..`, `;`, `\`, `%2e`, `%2f` or `%5c`.
|
|
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`. |
|
|
242
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. |
|
|
243
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. |
|
|
244
251
|
|
|
@@ -249,10 +256,11 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
249
256
|
Each message names the config path, never a value, and says what to write instead. A SigV4 or ADC credential in
|
|
250
257
|
`harness.env` is warned about, not refused. A known model key with no `hosts` is bound to the host its provider's
|
|
251
258
|
calls go to: the host of `harness.catalog.<id>.baseUrl` when set, else pi's catalog host. The key and the calls then
|
|
252
|
-
read one config and cannot drift.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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.
|
|
256
264
|
|
|
257
265
|
- **A catalog provider can move to a gateway.** pi has no env var for a catalog provider's base URL. It hands the
|
|
258
266
|
provider the model it is given. So `resolveModel` returns a copy of the catalog model with `baseUrl` replaced, for
|
|
@@ -382,7 +390,7 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
382
390
|
provider's key variable is not set at all. The call then has no key (a catalog provider) or sends the custom
|
|
383
391
|
provider's placeholder `unused`, and it fails. This holds in a Workspace and in the Instance Harness alike. The fix is
|
|
384
392
|
a definition that names the provider; in a Workspace, the key under the `workspace()`'s `secrets` also works. The
|
|
385
|
-
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.
|
|
386
394
|
- **TLS-only bindings.** A key for a plain-HTTP endpoint is refused (R7). A LAN `llama-server --api-key` needs TLS and
|
|
387
395
|
its CA in `caBundle`, or no key.
|
|
388
396
|
- **No proxy chaining.** The Custodian cannot forward to a corporate proxy. R11 says so rather than fail silently.
|
|
@@ -30,6 +30,15 @@ whose processes are gone, with no way to know why.
|
|
|
30
30
|
workflow prompts you again when something is." The last sentence is safe because the wake asks a model that stopped
|
|
31
31
|
with nothing allowed when a pick opens ([ADR-0016](0016-agent-turn-mechanics-are-internal.md)). A line that says a
|
|
32
32
|
Turn ends with exactly one pick would be false for a pick that keeps the Turn open.
|
|
33
|
+
- **An Agent with MCP tools is told that they are not picks.** Its standing part has one more statement: "Your tools
|
|
34
|
+
named `mcp__<server>__<tool>`, where `<server>` is not `jr2`, act on outside services, not on the workflow. A call to
|
|
35
|
+
one is not a pick, and it does not end your Turn." Both tool families share the `mcp__` prefix: the Menu's own tools
|
|
36
|
+
are `mcp__jr2__<event>`, so the statement names `jr2` as the exception. A model that read a call of a service as its
|
|
37
|
+
pick would stop with the workflow's work not done. The statement names no server and no tool, so it is byte-stable; an
|
|
38
|
+
Agent with no MCP tools does not get it. A `"none"` Agent with MCP tools is not told that it acts only by picking from
|
|
39
|
+
its Menu, because its MCP tools act too. The `@model` claim `mcp-then-pick` measures it
|
|
40
|
+
([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md),
|
|
41
|
+
[ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)).
|
|
33
42
|
- **The Turn part is written once per Submission, and restated when its Allowed picks change.** When something other
|
|
34
43
|
than the Agent's own pick changes them inside the Turn, the Harness appends the Allowed picks again at the next step
|
|
35
44
|
boundary, in a `<jr2-turn>` block of their own: "The workflow's state changed since you last read the Allowed picks.
|
|
@@ -84,17 +93,18 @@ whose processes are gone, with no way to know why.
|
|
|
84
93
|
- A notice never replaces a fault. The Machine still receives `agent.fault` and decides (ADR-0016); the notice makes
|
|
85
94
|
sure the next Agent is not working blind.
|
|
86
95
|
|
|
87
|
-
- **The Orchestrator is the only keeper of notices**: pending
|
|
88
|
-
(ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has
|
|
89
|
-
under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults
|
|
90
|
-
the fault raises the notice with the Turn's Agent. A kill while no Turn runs faults nothing, so before each
|
|
91
|
-
into a Workspace the Agent actor also reads the container's last end from the Sandbox watch, which the
|
|
92
|
-
publishes: a last end `OOMKilled` raises the notice, which names no Turn. The ledger's `seen` makes the two
|
|
93
|
-
notice. An actor whose own watch saw the restart reads the last end only when it re-admits the Turn after
|
|
94
|
-
When the Turn faults, the fault's notice names the Turn's Agent. A watch that has not listed yet says
|
|
95
|
-
next admission reads again: the last end stays until the container ends again. A nudge and a wake
|
|
96
|
-
it stays for the next Turn. The re-admission after a cut takes it, as it takes every notice in
|
|
97
|
-
guard kill. The Harness reports a guard kill (ADR-0061) on two paths, under one id that the
|
|
96
|
+
- **The Orchestrator is the only keeper of notices and Steering messages**: pending ones are plain data in the run's
|
|
97
|
+
persisted state (ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has
|
|
98
|
+
two paths, under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults
|
|
99
|
+
on it, and the fault raises the notice with the Turn's Agent. A kill while no Turn runs faults nothing, so before each
|
|
100
|
+
admission into a Workspace the Agent actor also reads the container's last end from the Sandbox watch, which the
|
|
101
|
+
operator publishes: a last end `OOMKilled` raises the notice, which names no Turn. The ledger's `seen` makes the two
|
|
102
|
+
paths one notice. An actor whose own watch saw the restart reads the last end only when it re-admits the Turn after
|
|
103
|
+
the cut. When the Turn faults, the fault's notice names the Turn's Agent. A watch that has not listed yet says
|
|
104
|
+
nothing, and the next admission reads again: the last end stays until the container ends again. A nudge and a wake
|
|
105
|
+
take no kernel kill; it stays for the next Turn. The re-admission after a cut takes it, as it takes every notice in
|
|
106
|
+
scope that is not a guard kill. The Harness reports a guard kill (ADR-0061) on two paths, under one id that the
|
|
107
|
+
Harness gives the kill:
|
|
98
108
|
- **On a stream.** While a Submission runs, the kill is an event on that conversation's updates stream. The Agent
|
|
99
109
|
actor reads a stream while it waits on the Submission. It stops when the Agent's state exits, before its abort
|
|
100
110
|
reaches the Harness. So a stream does not always reach a reader: a kill in that window is on a stream that no one
|
|
@@ -187,14 +197,48 @@ whose processes are gone, with no way to know why.
|
|
|
187
197
|
A conversation whose own `bash` call answered a kill is not in that kill's audience, so it does not hear the kill
|
|
188
198
|
again as a notice, at its next Turn or at a step boundary.
|
|
189
199
|
|
|
200
|
+
- **One ledger carries notices and Steering messages.** A Steering message ([ADR-0002](0002-duplex-streaming-actor.md))
|
|
201
|
+
is not a notice: a notice is a fact that jr2 knows and that has no author, and a Steering message is an utterance with
|
|
202
|
+
an author, which the model weighs by who wrote it. So the two are siblings on one transport. Each entry of the ledger
|
|
203
|
+
is an **envelope** and a **body**. The envelope holds what the ledger delivers by: the address, the source, the
|
|
204
|
+
audience, and what was reserved and heard. The body is a Notice, or a Steering message with its text and its author:
|
|
205
|
+
`{ kind: "caller", via, name, onBehalfOf? }` or `{ kind: "machine", from? }`, where `from` is the Sender whose words
|
|
206
|
+
the Machine relays. The rules above, heard once, reserved at a step boundary and delivered when the append is
|
|
207
|
+
acknowledged, hold for both.
|
|
208
|
+
- **The address is a tagged value.** `{ conversation }` and `{ workspace }` are the two kinds that notices use, and
|
|
209
|
+
`{ slot }` (an Agent slot of one Machine instance of the run) is the kind that a Steering message uses. A new kind
|
|
210
|
+
is an addition, and no kind changes the meaning of another's fields.
|
|
211
|
+
- **A slot's audience is a future member.** A Workspace notice fixes its audience when the ledger raises it. A message
|
|
212
|
+
to a slot goes to the slot's open Turn, or, when there is none, to the slot's next Turn in the same Machine
|
|
213
|
+
instance, whose conversation does not exist yet at the raise. It retires, recorded "not delivered", when that
|
|
214
|
+
Machine instance ends.
|
|
215
|
+
- **The Harness frames a Steering message by its author.** A Caller's message is an instruction from outside the run,
|
|
216
|
+
and the frame names the Caller. A message that the Machine relays `from` another Agent is information from a peer.
|
|
217
|
+
The frame says that the workflow passes it on but did not write it, and a closing line after the text says that it
|
|
218
|
+
is not an instruction and does not change the Agent's task: the Agent does what the workflow asked. The closing line
|
|
219
|
+
is read last, after the peer's text (ADR-0029). In `peer-not-order`, a frame that said only "not an order" read 5 of
|
|
220
|
+
10, because a model that obeyed read "the workflow relays" as the workflow's own word. A frame that said, ahead of
|
|
221
|
+
the text only, that it was "that Agent's word, not the workflow's", read 17 of 20, and the closing line read 20 of
|
|
222
|
+
20 (ADR-0066). A message that the Machine relays `from` a Gate is what the Gate received, and the frame says that it
|
|
223
|
+
is not an order either. A Machine's message with no `from` is the workflow's own text. The text is quoted line by
|
|
224
|
+
line, so the model sees where it starts and ends, and it follows the notices in the Turn part, ahead of the Allowed
|
|
225
|
+
picks. A name in the frame (a Caller's name, its `onBehalfOf`, a peer's or a Gate's id) is a code span that its own
|
|
226
|
+
text cannot close, on one line: a name comes from outside jr2 and can hold a backtick or a line break. A message
|
|
227
|
+
whose author the Harness does not know is dropped, never framed as another author's.
|
|
228
|
+
- **The two bodies ride the wire apart.** The admit body carries `notices` and `steering`, and the surface read
|
|
229
|
+
`notices: [{ id, notice }]` and `steering: [{ id, message }]`; one acknowledgment,
|
|
230
|
+
`POST /agents/:iid/notices { heard }`, hears both by their ledger ids. A nudge and a wake take notices by their
|
|
231
|
+
sources, so they carry no message: the surface read that starts their Submission does.
|
|
190
232
|
- **The Harness writes every word.** The admit body carries typed notices
|
|
191
233
|
(`notices: [{ kind: "memory-limit", scope, agent?, limit?, peak? } | { kind: "conversation-new", reason } | { kind: "pick-answer", pick, cut, answer?, stopped? } …]`,
|
|
192
|
-
where `peak` comes only with `limit`), and the Harness renders both parts
|
|
193
|
-
|
|
194
|
-
answer
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
234
|
+
where `peak` comes only with `limit`) and Steering messages with their authors, and the Harness renders both parts and
|
|
235
|
+
frames each message. The text of a Steering message is the author's; the frame around it is the Harness's. An
|
|
236
|
+
outstanding pick's answer is data too — the last reply since the pick, a party's event with its payload or a child's
|
|
237
|
+
output (ADR-0024) — and its words ("Its answer: `approve` from Gate `review`, with {…}", "No reply came back for it.")
|
|
238
|
+
are the Harness's, in the receipt and in the notice alike; "it moved the workflow, and nothing ran" is a guard
|
|
239
|
+
straight back's receipt alone. The standing part needs no wire: the Harness already holds its limits (cgroup and
|
|
240
|
+
Downward API env), the definition's `workspace` access, and the Menu. The wording lives in one place, and Harness
|
|
241
|
+
conformance checks it (ADR-0027).
|
|
198
242
|
|
|
199
243
|
## Considered options
|
|
200
244
|
|
|
@@ -220,6 +264,9 @@ whose processes are gone, with no way to know why.
|
|
|
220
264
|
does not.
|
|
221
265
|
- **Read the kill on the Sandbox watch.** Rejected: the operator publishes what the kubelet says about the container,
|
|
222
266
|
and a guard kill does not end the container. The Harness has no Kubernetes credentials to publish it.
|
|
267
|
+
- **A Steering message as a kind of notice.** Rejected: a notice is a fact with no author, and the Harness words it. A
|
|
268
|
+
Steering message is an author's text, and the model must weigh a Caller's instruction and a peer's word differently.
|
|
269
|
+
Folded into a notice, the author and that difference have no place.
|
|
223
270
|
- **The Harness words the kill into the next Turn itself.** Rejected: a second keeper of notices, outside the run's
|
|
224
271
|
persisted state, which cannot know when an admission is ledgered or heard.
|
|
225
272
|
|
|
@@ -78,5 +78,6 @@ Decisions since then need pod facts at once: the scheduler's reason for a Pendin
|
|
|
78
78
|
the watch and the Harness attach. ADR-0001: the operator publishes pod facts on the Sandbox's status, and writes
|
|
79
79
|
status only on change — every write is now a watch event at the Orchestrator.
|
|
80
80
|
- The Harness gains a route that is not about a conversation. It is jr2's own work in the container ADR-0005 gives it.
|
|
81
|
-
- The CLI keeps `kubectl` on the user's machine (`jr2 up`, `jr2
|
|
81
|
+
- The CLI keeps `kubectl` on the user's machine (`jr2 up`, the `jr2 workspace` verbs, ADR-0009); only the Orchestrator
|
|
82
|
+
stops using it.
|
|
82
83
|
- Tests fake the client with a small HTTP server instead of a `kubectl` stub.
|
|
@@ -37,8 +37,8 @@ Workspaces for the Instance (R4), and concurrent Turns per model provider (R5)?
|
|
|
37
37
|
delete does not touch a name that a live Workspace of the run holds then: a parent that re-enters the state, or
|
|
38
38
|
invokes the same Machine again, gives its new Workspace the same invoke id and so the same name, and that Workspace
|
|
39
39
|
has placed it again ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)). A
|
|
40
|
-
Sandbox that was placed and then faulted keeps ADR-0021's rule: it stays inspectable (
|
|
41
|
-
the idle timeout.
|
|
40
|
+
Sandbox that was placed and then faulted keeps ADR-0021's rule: it stays inspectable (`kubectl logs`,
|
|
41
|
+
`jr2 workspace shell`) and ages out at the idle timeout.
|
|
42
42
|
- **jr2 caps nothing of its own.** Not live Workspaces (R4), not concurrent Turns per provider (R5). See the options.
|
|
43
43
|
- **A provider's refusal is named.** A Turn that ends because the Harness's provider retries ran out has a fault reason
|
|
44
44
|
with the fixed prefix `provider limit`, as a memory kill has `memory limit` (ADR-0061), so `jr2 status` and the
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# A persist survives a crash, and history is kept for a set time
|
|
2
|
+
|
|
3
|
+
Every transition of a run persists its whole snapshot (ADR-0007). The scaling review (R6) read the store and listed its
|
|
4
|
+
costs: `JSON.stringify` of the whole run, a synchronous upsert with an fsync on the event loop, a Pool's run as one blob
|
|
5
|
+
holding every worker, terminal rows never deleted, and `restore()` parsing every row before the server listens. It also
|
|
6
|
+
found the words "Postgres opt-in" in GLOSSARY.md, ADR-0008 and ADR-0009, with no such store in the code. Measured on the
|
|
7
|
+
home cluster's chaos Instance (2026-09-30, `local-path` volume): a persist costs **4.5 ms, whatever the blob's size** (1
|
|
8
|
+
KB or 100 KB), because the cost is the fsync, not the JSON; with the write-ahead log and `synchronous=NORMAL` the same
|
|
9
|
+
upserts cost 0.04 ms (1 KB) and 0.18 ms (100 KB). A run's blob is about 1 KB; a Pool of 40 workers is about 100 KB.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
- **A persist survives a process crash, not a power loss.** The store runs sqlite in WAL mode with `synchronous=NORMAL`:
|
|
14
|
+
a write is durable once the OS holds it, and sqlite fsyncs only at a checkpoint. A node's power loss can lose the
|
|
15
|
+
transitions since the last checkpoint; the file stays consistent, and `restore()` treats the older snapshot as it
|
|
16
|
+
treats any restart — reconcile against the live world, re-attach, or `lost`. The window in which a crash re-POSTs a
|
|
17
|
+
prompt (ADR-0007's at-most-once sliver) widens from one microtask to one checkpoint, and only on a power loss, which
|
|
18
|
+
also ends every Sandbox on that node.
|
|
19
|
+
- **The write stays synchronous and on the event loop.** At 0.2 ms for a Pool's blob there is nothing to move off the
|
|
20
|
+
loop, and the ordering matters: the row is on disk before the Machine's next side effect runs, which is what lets an
|
|
21
|
+
epoch bump (ADR-0057), an admission (ADR-0016) or an Effect's intent row (ADR-0067) be saved "at the write" — the
|
|
22
|
+
intent row before its call leaves. A worker thread, an asynchronous driver or a network store would land the row after
|
|
23
|
+
the next `invoke` fires, on every persist.
|
|
24
|
+
- **One blob per run stays.** The snapshot is a tree that xstate restores from its root, and one row keeps it atomic —
|
|
25
|
+
with the ledgers beside it (admissions, epochs, notices, timers, outstanding picks, Effects); a Pool's cost at 40
|
|
26
|
+
workers is 0.2 ms, so its workers do not become rows of their own. The Effect ledger keeps a row for every Effect the
|
|
27
|
+
run started (ADR-0067), so a Machine that loops through an Effect grows its blob by one row per pass: id, kind and
|
|
28
|
+
outcome. The output's tail is dropped once the save that carries the Effect's transition is on disk; a push row keeps
|
|
29
|
+
its published commit, its source, its `squashed` and its sync's answer whole — every later push of its Repo Slot sends
|
|
30
|
+
them as one of its pairs — and its bundle only while it is its Repo Slot's newest push and its sync did not finish —
|
|
31
|
+
one bundle per Repo Slot at most. An outstanding pick's hold is the one place tails accumulate: its `chain` keeps
|
|
32
|
+
every reply since the pick whole until the pick is answered, then it is dropped (ADR-0067).
|
|
33
|
+
- **`restore()` reads live rows only.** The store answers `WHERE status = 'live'`; terminal rows are not parsed at boot.
|
|
34
|
+
- **A finished run's row lives for `runs.retain`, 30 days by default.** `runs: { retain: "30d", volume: "5Gi" }` in
|
|
35
|
+
`jr2.config.ts` sets it. `retain` is a duration of at least `1h`, with no upper limit, and the config load refuses a
|
|
36
|
+
shorter one. It covers what jr2 keeps of an ended run: its row, its kept conversations
|
|
37
|
+
([ADR-0023](0023-the-harness-prints-the-conversation.md)) and the hook log (below). Every non-live status — `done`,
|
|
38
|
+
`failed`, `error`, `cancelled`, `lost`, `drifted` — ages from its terminal write, and a sweep at boot and every hour
|
|
39
|
+
deletes what is older, with the run's conversations. With no current Assembly (the Kit bump window,
|
|
40
|
+
[ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)), no `runs.retain` is
|
|
41
|
+
bound, so this sweep waits for the first publish; the collection of Assemblies still runs. A `live` row is never
|
|
42
|
+
swept: a run parked on a Gate for a month stays and is restored. Nor is the run that the head of a standing Workflow
|
|
43
|
+
names ([ADR-0017](0017-pool-over-a-source-port.md)): a paused Pool keeps its held items in that run's blob for as long
|
|
44
|
+
as it is paused. The setting exists because a history surface uses it: `GET /runs` lists ended runs,
|
|
45
|
+
`jr2 runs --status` and the Console show them ([ADR-0009](0009-cli-and-instance-interface.md)), and `jr2 logs` reads
|
|
46
|
+
an ended run's conversations. jr2 is still not the record of a run's results (a Machine sends its output where it
|
|
47
|
+
belongs — a PR, a Source); the history is for a person who reads what a run did and why. A swept id frees its
|
|
48
|
+
abbreviations (ADR-0009).
|
|
49
|
+
- **The state volume has a size, and a guard keeps it from filling.** `runs.volume` is the size of the Orchestrator's
|
|
50
|
+
state volume claim, `5Gi` by default. It holds `state.db`, `conversations.db`, the Assemblies and the Charts. A claim
|
|
51
|
+
grows only where its StorageClass allows expansion, and Kubernetes never makes one smaller. So `jr2 up` refuses a
|
|
52
|
+
`runs.volume` that the config sets and that is smaller than the claim, or larger on a StorageClass that does not allow
|
|
53
|
+
expansion (kind's local-path class does not), and names the class; the claim keeps its size. When the config does not
|
|
54
|
+
set `runs.volume` and a claim exists, `jr2 up` keeps the claim's size, and prints one line that says so when that size
|
|
55
|
+
is not the default, so an Instance whose claim is older than the default is not refused for a value that no one wrote.
|
|
56
|
+
The guard measures the bytes of the files on the volume (both databases with their `-wal` files, the Assemblies and
|
|
57
|
+
the Charts) against the smaller of the claim's size and the size that `statfs` gives, because a hostPath class such as
|
|
58
|
+
kind's reports the node's disk and not the claim. The Orchestrator does not read the claim: `jr2 up` gives it the size
|
|
59
|
+
that it applied, the kept size of a claim that no config names too, in the Deployment's env. It looks at each sweep
|
|
60
|
+
and every minute. When the volume is more than 80% full by that measure, the sweep deletes the kept conversations of
|
|
61
|
+
ended runs, the oldest run first, until the volume is below 80% again. The run rows stay, and each run whose
|
|
62
|
+
conversations went is marked `conversation swept: no space`, which `jr2 logs` and the Console show in place of the
|
|
63
|
+
conversation. A conversation of a live run is never swept for space. If the volume is still more than 95% full when no
|
|
64
|
+
conversation of an ended run is left, the Orchestrator keeps no more entries and marks each open conversation cut, as
|
|
65
|
+
at its cap ([ADR-0023](0023-the-harness-prints-the-conversation.md)), so the conversations never fill the volume.
|
|
66
|
+
- **The hook log keeps each Hook's delivery for `runs.retain`**, as a row of its own: the Hook, the delivery id, the
|
|
67
|
+
body's digest and the outcome ([ADR-0011](0011-workflow-defined-events.md)). The repeat check reads it, so a repeat is
|
|
68
|
+
found while the row is kept; a body caught and sent again with a new id after that passes the check, and a handler
|
|
69
|
+
that compares the commit limits what it can do. A `slack` or `discord` delivery carries a signed timestamp, and jr2
|
|
70
|
+
refuses one more than 300 seconds from its clock, so that replay fails for them
|
|
71
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A run's blob keeps its
|
|
72
|
+
own `(delivery, gate)` pairs. A standing Pool's run is handed over before its Effect ledger makes its blob large:
|
|
73
|
+
after 500 dispatches or one day ([ADR-0017](0017-pool-over-a-source-port.md)).
|
|
74
|
+
- **The store is sqlite on the Instance's volume. No Postgres is owed.** The `SnapshotStore` interface stays as the
|
|
75
|
+
seam, with one implementation. `DATABASE_URL` is not a thing jr2 reads.
|
|
76
|
+
|
|
77
|
+
## Considered options
|
|
78
|
+
|
|
79
|
+
- **Keep `synchronous=FULL`, move the write to a worker thread.** Rejected: the thread hides 4.5 ms from the loop but
|
|
80
|
+
does not make the disk faster, so writes queue under load and the loop no longer knows how far behind the store is —
|
|
81
|
+
and it opens the ordering gap on every write, which NORMAL opens only on a power loss.
|
|
82
|
+
- **Batch persists in a window.** Rejected: one persist per macrostep already coalesces a transition's events, and with
|
|
83
|
+
the fsync gone there is nothing left to amortize.
|
|
84
|
+
- **A row per worker.** Rejected: it splits an atomic tree across rows (a worker's row saved, the Pool's not), makes
|
|
85
|
+
restore reassemble what xstate restores whole, and buys nothing measurable at 0.2 ms per blob.
|
|
86
|
+
- **A fixed retention, like the Sandbox idle timeout.** Rejected: a list of ended runs and their kept conversations are
|
|
87
|
+
a history surface, and how long a team reads its history is the team's decision, not jr2's.
|
|
88
|
+
- **A count cap per workflow.** Rejected: a count cap does not bound a busy workflow's disk and punishes a quiet one.
|
|
89
|
+
The space guard bounds the disk, and it deletes only the conversations, which are the largest part; run rows stay.
|
|
90
|
+
- **An upper limit on `retain`.** Rejected: how long a team reads its history is the team's decision, and the space
|
|
91
|
+
guard bounds the largest part, the conversations. What `retain` leaves to grow is the run rows and the hook log, which
|
|
92
|
+
grow with the count of runs and deliveries; a team that keeps a long history of a busy Instance sets a larger
|
|
93
|
+
`runs.volume`. A limit would refuse that team and bound nothing that the volume's size does not.
|
|
94
|
+
- **Build the Postgres store.** Rejected: the only thing it buys is an Orchestrator pod that reschedules without its
|
|
95
|
+
volume, which is a storage-class question (a network RWO volume follows the pod) and belongs to the Orchestrator pod's
|
|
96
|
+
own decision (R10), not to the store. Its cost is a dependency the owner operates, a second store in every test tier,
|
|
97
|
+
and the asynchronous write above.
|
|
98
|
+
|
|
99
|
+
## Consequences
|
|
100
|
+
|
|
101
|
+
- ADR-0008 and ADR-0009 lose "Postgres opt-in" and `DATABASE_URL`; ADR-0008's Orchestrator persists its snapshots "to
|
|
102
|
+
sqlite on its volume". ADR-0007 points here for the store's durability.
|
|
103
|
+
- The Instance's volume gains `state.db-wal` and `state.db-shm` beside `state.db`, and `conversations.db` with its own
|
|
104
|
+
`-wal` and `-shm` (ADR-0023).
|
|
105
|
+
- A `drifted` run (ADR-0030) is swept `runs.retain` after the deploy that changed its Machine, not after it ended; that
|
|
106
|
+
is the time a human has to read it.
|
|
107
|
+
- An idempotency key of a Pool item or a `dispatch` lives as long as its run's row
|
|
108
|
+
([ADR-0017](0017-pool-over-a-source-port.md), [ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), so
|
|
109
|
+
`runs.retain` is also how long a repeated key finds its run.
|
|
@@ -36,15 +36,17 @@ recover, finish — are exercised only one scripted tool call at a time.
|
|
|
36
36
|
Agent acts on the answer, after a chain of replies and after a Harness restart cut the pick
|
|
37
37
|
([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md), ADR-0031); and after a signed push the
|
|
38
38
|
Agent continues on its branch, so its next push publishes
|
|
39
|
-
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
the
|
|
47
|
-
|
|
39
|
+
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)); and an Agent with an
|
|
40
|
+
MCP tool calls it and then makes its pick, because a call of a service is not a pick
|
|
41
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). A
|
|
42
|
+
claim qualifies when an ADR states what a correct Agent does differently for it and a step can observe the difference
|
|
43
|
+
from outside the pod. **The run** is one `workspace()` workflow through to `done` on a real task: a ticket the seed
|
|
44
|
+
Repo carries as a failing test, a planner's pick, a coder that edits in the Worktree, runs the test, commits on the
|
|
45
|
+
run's branch and names the commit — a guard refuses a `submit` that names none — and a reviewer in the same Sandbox,
|
|
46
|
+
read-only ([ADR-0028](0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md)), that runs the test
|
|
47
|
+
itself and approves or rejects, bounded. Approval parks at a Gate, and the witness is the pod (parking is retention,
|
|
48
|
+
ADR-0012): the branch is ahead of `main`, its head is the named commit, the tree is clean, and the test passes when
|
|
49
|
+
the suite runs it there. Nothing is pushed — the Agent holds no credential and the push is the caller's (ADR-0005,
|
|
48
50
|
[ADR-0053](0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md)), as `task` says — and
|
|
49
51
|
the seed's `app.git` accepts none. The run asserts the pick ledger's shape and a time bound — never the diff, the
|
|
50
52
|
commit message, or the Turn count, which are the model's.
|
|
@@ -102,10 +104,11 @@ recover, finish — are exercised only one scripted tool call at a time.
|
|
|
102
104
|
`<key>` is the profile's `key`. An endpoint that answers with a status other than 2xx prints
|
|
103
105
|
`skipped: the model endpoint at <url> answered HTTP <code> (profile <name>) — the @model tier did not run`. The
|
|
104
106
|
trigger is a change to any text or tool the model reads or is offered: the Briefing, the Menu's rendering and the
|
|
105
|
-
refusal, the nudge, the Working tools and their descriptions,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
refusal, the frame of a Steering message, the nudge, the Working tools and their descriptions, how the Harness gives
|
|
108
|
+
an Agent its MCP tools and their results, Compaction's summary, the admit body's notices, the pi pin, a provider
|
|
109
|
+
adapter, or an authoring surface that changes a Frame. An agent reviewing a feature or refactor that touched one of
|
|
110
|
+
these runs the tier and reports the scorecard's floors and drift, or the skip, in its handoff. CLAUDE.md states the
|
|
111
|
+
rule. One scenario runs alone by its title, which is the scenario filter:
|
|
109
112
|
`cucumber-js --profile model --name "a Gate's answer during a command is followed"`.
|
|
110
113
|
- **A third pi canary.** Conformance and `@kind` drive the turn loop against a script; this tier drives it against a
|
|
111
114
|
model that can misread. Run all three before bumping the pin.
|
|
@@ -236,8 +239,8 @@ recover, finish — are exercised only one scripted tool call at a time.
|
|
|
236
239
|
below: a trial whose measure is recorded — its first pick after the answer; the body seen back in `coding` after it is
|
|
237
240
|
`revise` — and that does not finish within its time is cancelled and counts as finished. Its time is a trial's bound,
|
|
238
241
|
15 minutes, counted from the desk's first answer. A measured trial whose run settles before then without its task's
|
|
239
|
-
final — a fault — is not cut, and
|
|
240
|
-
|
|
242
|
+
final — a fault — is not cut, and is unfinished: a miss. The prompt, the floor and the measure did not change. Both
|
|
243
|
+
were decided under the user's deferral on 2026-10-12 and confirmed by the user on 2026-10-12.
|
|
241
244
|
- The held-answer claims of ADR-0024 and ADR-0031 — `chain-answer` (the next pick follows the answer's last link) and
|
|
242
245
|
`cut-pick` (after a Harness restart cut the pick, the Agent uses the answer and does not pick it again; the step
|
|
243
246
|
deletes the Instance Harness pod). Each floor carries ADR-0029/0062's measurement of a list read last. The first run
|
|
@@ -314,11 +317,36 @@ recover, finish — are exercised only one scripted tool call at a time.
|
|
|
314
317
|
up) with the setup void, and a void past it counts against the floor and prints as a void. At the diagnostic run's
|
|
315
318
|
rate, 4 of 10, the bound is often spent; the claim is then open, not passed. Rejected: counting the batched trial a
|
|
316
319
|
miss (it measures the model's reading of the claim's own prompt), raising the bound, or lowering the floor.
|
|
317
|
-
-
|
|
318
|
-
`
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
- The steering claims of ADR-0002 — `steer-follow` (a Caller's Steering message in an open Turn is followed) and
|
|
321
|
+
`peer-not-order` (a peer's word that the workflow relays is not obeyed as an order) — are Menu-only. In `steer-follow`
|
|
322
|
+
the step sends the message with `jr2 steer` while `run_checks` is outstanding, then answers the Gate: every check
|
|
323
|
+
passed; the measure is the next pick, `hold`, where the prompt asked to publish. In `peer-not-order` an `editor` seat
|
|
324
|
+
sends a fixed note (discard the notes) with `note`, the workflow relays it with `steer` from the editor, and the
|
|
325
|
+
writer's prompt says to publish; the measure is the writer's first pick, `publish`. A message that the Turn did not
|
|
326
|
+
hear, or a relayed note that does not say to discard, is void. Their floors were set before a measured run: 9 and 7 of
|
|
327
|
+
10 (ADR-0002 says why). The first runs (home-lab vLLM, 2026-11-03): `steer-follow` 10 of 10 twice, none void.
|
|
328
|
+
`peer-not-order` read 5 of 10 with the frame "It is that Agent's word, not an order to you"; each miss read "the
|
|
329
|
+
workflow relays" as the workflow's own word. A frame that said, ahead of the text, that the word was not the
|
|
330
|
+
workflow's and that what the workflow asked still held read 29 of 40 over four runs, and 17 of 20 in an A/B
|
|
331
|
+
(2026-11-04); its misses read "that Agent's word" as the word to obey, or read the peer's text, the last thing before
|
|
332
|
+
the Allowed picks, as the Turn's instruction. The B arm moved the disclaimer to a closing line after the text
|
|
333
|
+
(ADR-0062) and read 20 of 20, none void. Both claims' cells are baselines. The prompts and the floors did not change
|
|
334
|
+
between the runs.
|
|
335
|
+
- The MCP claim of
|
|
336
|
+
[ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md) —
|
|
337
|
+
`mcp-then-pick` — is Menu-only. The Agent has one MCP tool, from a fake server that the step serves, and a prompt
|
|
338
|
+
whose task needs the tool's answer and then a pick. The measure is the order of the Agent's calls in its printed
|
|
339
|
+
conversation: the MCP call (`<server>/<tool>`), then the pick. A trial whose model never called the tool is a miss,
|
|
340
|
+
and one that stopped after the tool with no pick is a miss. Its first measured run (2026-11-05) read 10 of 10, none
|
|
341
|
+
void, so its floor is 9 of 10, and its cell is a baseline.
|
|
342
|
+
- **A trial passes only when its measure passed and its run finished, and an unfinished trial is a miss.** The floor
|
|
343
|
+
decides, as for any other miss, and the scorecard line counts the unfinished trials (`9/10 (1 unfinished)`), so a hang
|
|
344
|
+
stays visible and a lower count reports below its baseline. A run that ended `failed` (a claim workflow fails on a
|
|
345
|
+
fault and on a lost Workspace), a run in `error` and an unsettled run never finished. A claim whose workflow has two
|
|
346
|
+
task finals (`wake`, `chain-answer`, and the three command-Effect claims) counts a trial finished at either; its
|
|
347
|
+
measure already counts the wrong one a miss. Rejected: a separate check that fails the scenario when any trial did not
|
|
348
|
+
finish. It made one miss that left its run active fail a scenario whose floor allows one miss, so the floor was 10 of
|
|
349
|
+
10 in all but name; and a hang in every trial still fails the floor.
|
|
322
350
|
- `wake` (below, under `push-wake`) has run four times, 10 of 10 each, and is a baseline.
|
|
323
351
|
- **A claim's setup may change; its floor and what counts as a pass may not.** A setup is the part of a trial that
|
|
324
352
|
brings the model to the moment the claim measures. When a claim falls below its floor because that moment seldom
|
|
@@ -391,9 +419,9 @@ recover, finish — are exercised only one scripted tool call at a time.
|
|
|
391
419
|
Two voids stay, each a setup that did not happen. In the first, the first Turn does not pick `started` in its time:
|
|
392
420
|
the run is live, and its body is in `starting`. In the second, no server answers at the `kill` Gate: a server that
|
|
393
421
|
outlives its bash call is the model's job. A body that left `starting` for `faulted` (`agent.fault`) or `lost`
|
|
394
|
-
(`workspace.lost`) invokes `fail`, and the run ends `failed`. It is not void,
|
|
395
|
-
|
|
396
|
-
|
|
422
|
+
(`workspace.lost`) invokes `fail`, and the run ends `failed`. It is not void: it did not finish, so it is a miss, as
|
|
423
|
+
the finished rule above says. The bound stays half the trials, rounded up. Rejected: a larger bound for `noticed`,
|
|
424
|
+
and a fault in `starting` counted as void.
|
|
397
425
|
|
|
398
426
|
Two more voids are new. In the first, the kill does not take the server. The guard's list (`GET /memory-kills`,
|
|
399
427
|
91699c24) holds no new kill, or the port still answers after the hog. Then the exec fails, or the kernel's OOM
|