@jr2/orchestrator 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +56 -20
- package/docs/concepts/architecture.md +30 -17
- package/docs/concepts/charts.md +6 -3
- package/docs/concepts/converge.md +3 -0
- package/docs/concepts/effects.md +86 -31
- package/docs/concepts/machines-and-workflows.md +9 -0
- package/docs/concepts/pools-and-sources.md +6 -6
- package/docs/concepts/secrets-and-trust.md +126 -42
- package/docs/concepts/the-menu.md +3 -0
- package/docs/concepts/workspaces.md +129 -51
- package/docs/get-started/an-agent-in-a-sandbox.md +21 -19
- 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 +17 -4
- package/docs/guides/bind-open-parts.md +5 -4
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +1 -1
- package/docs/guides/let-an-agent-write-a-chart.md +5 -1
- package/docs/guides/look-inside-a-sandbox.md +18 -15
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +19 -18
- package/docs/guides/set-an-agents-access-and-dials.md +67 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/use-a-hosted-provider.md +6 -0
- package/docs/guides/watch-a-run.md +47 -24
- package/docs/guides/write-a-workflow.md +1 -1
- package/docs/llms.txt +33 -12
- package/docs/reference/authoring-surface/agent.md +34 -11
- package/docs/reference/authoring-surface/answer.md +19 -3
- package/docs/reference/authoring-surface/arg.md +27 -5
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/command.md +36 -21
- package/docs/reference/authoring-surface/fail.md +17 -4
- package/docs/reference/authoring-surface/file.md +36 -16
- package/docs/reference/authoring-surface/http.md +108 -8
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +1 -1
- package/docs/reference/authoring-surface/open.md +36 -5
- package/docs/reference/authoring-surface/paramsof.md +22 -4
- package/docs/reference/authoring-surface/pool.md +4 -4
- package/docs/reference/authoring-surface/push.md +30 -12
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +25 -4
- package/docs/reference/authoring-surface/source.md +3 -3
- package/docs/reference/authoring-surface/type-agentdefinition.md +2 -2
- 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 +5 -6
- 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 +22 -4
- package/docs/reference/authoring-surface/type-customize.md +4 -2
- package/docs/reference/authoring-surface/type-dispatchoutput.md +8 -1
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- 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-hostinjectedinput.md +2 -4
- 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 +7 -1
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- 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-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 +2 -2
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +32 -7
- package/docs/reference/authoring-surface/type-reposlot.md +5 -5
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcespec.md +10 -10
- package/docs/reference/authoring-surface/type-workspaced.md +29 -8
- 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 +65 -37
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +30 -10
- package/docs/reference/cli.md +1 -1
- package/docs/reference/config.md +13 -4
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +17 -10
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +29 -19
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +30 -30
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +35 -17
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +8 -2
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +6 -4
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +21 -9
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +2 -1
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +8 -9
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +62 -65
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +9 -1
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +13 -12
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +4 -1
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +18 -19
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +8 -8
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +4 -4
- 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/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +24 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +5 -3
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +110 -67
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +101 -74
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +5 -6
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +2 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +181 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +74 -63
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +31 -20
- 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 +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +3 -0
- package/docs/reference/glossary.md +51 -12
- package/docs/reference/http-api.md +45 -7
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +5 -4
- package/package.json +3 -3
- package/src/actor.ts +161 -35
- package/src/agent.ts +6 -6
- package/src/ambient.ts +3 -5
- package/src/chart.ts +186 -19
- package/src/charts.ts +9 -2
- package/src/config.ts +16 -6
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +251 -77
- package/src/customize.ts +11 -3
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +343 -127
- package/src/effector-wire.ts +6 -3
- package/src/fail.ts +17 -4
- package/src/harness-client.ts +74 -31
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +88 -32
- package/src/http.ts +56 -17
- package/src/index.ts +15 -1
- package/src/internal.ts +1 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +3 -4
- package/src/model-keys.ts +24 -0
- package/src/names.ts +19 -6
- package/src/notices.ts +443 -54
- package/src/open-body.ts +85 -19
- package/src/open.ts +36 -5
- package/src/parts.ts +40 -28
- package/src/party.ts +33 -0
- package/src/pick.ts +41 -7
- package/src/pool.ts +44 -23
- package/src/registration.ts +53 -11
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +106 -20
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +63 -16
- package/src/secret-placement.ts +9 -2
- package/src/setup.ts +86 -6
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +10 -10
- package/src/workspace.ts +264 -253
- 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/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
|
-
|
|
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.
|
|
32
53
|
- **The Harness reads its Menu itself.** There is no MCP. For each Submission, `menu.ts` reads
|
|
33
|
-
`GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`
|
|
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,42 @@ 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 five 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)
|
|
53
76
|
POST /fetch → /sandboxes/<this pod's Sandbox>/fetch the ask (ADR-0053)
|
|
54
77
|
anything else → 404, from the Custodian
|
|
55
78
|
```
|
|
56
79
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
The four agent routes are on a Unix socket, `/run/jr2/custodian/control.sock`, in a folder that the Harness and the
|
|
81
|
+
Custodian share and that no confined program can write (ADR-0076). The fetch is on `127.0.0.1:8081`, because
|
|
82
|
+
`jr2-upload-pack` runs in the domain, and the fetch Stand-in is public. Each listener answers 404 to the other's
|
|
83
|
+
routes. Landlock does not limit a connect to a named socket, so a confined program can connect to the control socket.
|
|
84
|
+
It holds no control Stand-in, so the Custodian refuses each agent route that it asks for there, with the fetch
|
|
85
|
+
Stand-in or with none, and the control socket answers 404 to the fetch route.
|
|
60
86
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
87
|
+
A read — the surface, a delivery's poll — and an acknowledgment of notices, which marks them heard however often it
|
|
88
|
+
lands, are retried on any reset; a pick and the ask only on a reset before they were sent, so one the Orchestrator may
|
|
89
|
+
have read is not sent twice. A poll holds up to about 25 s, far below the listener's 3600 s idle limit, so an
|
|
90
|
+
outstanding pick never rides one long request (ADR-0002).
|
|
91
|
+
|
|
92
|
+
On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token of that route — the
|
|
93
|
+
control token on an agent route, the fetch token on the fetch route — and removes every other credential header. A
|
|
94
|
+
request without the Stand-in of its route gets 403. The ask's path comes from the Custodian (`JR2_SANDBOX` in its env,
|
|
95
|
+
set per pod); on the Instance Harness there is none, and the ask gets 404. The route allowlist is the second layer.
|
|
96
|
+
The control stays in the Orchestrator: the token's scope, the live registration, and the event's name and payload are
|
|
97
|
+
judged there, as before (ADR-0013). The Orchestrator also accepts each token on its own routes only, and checks the
|
|
98
|
+
slot that the Harness states against the registration's (ADR-0076).
|
|
66
99
|
|
|
67
100
|
- **Model keys go the same way, over HTTPS.** When its pod holds a held secret, the Harness container also gets:
|
|
68
101
|
|
|
69
102
|
```sh
|
|
70
|
-
<NAME>=jr2-held-<NAME> one per held secret of the pod
|
|
71
103
|
HTTPS_PROXY / https_proxy=http://127.0.0.1:15001
|
|
72
104
|
NO_PROXY / no_proxy=localhost,127.0.0.1,::1
|
|
73
105
|
NODE_USE_ENV_PROXY=1
|
|
@@ -75,11 +107,11 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
75
107
|
SSL_CERT_FILE / REQUESTS_CA_BUNDLE / GIT_SSL_CAINFO=/etc/jr2/ca/bundle.crt
|
|
76
108
|
```
|
|
77
109
|
|
|
78
|
-
These ride `env`, after `harness.env` and before the wire's digest.
|
|
79
|
-
Stand-in
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Custodian.
|
|
110
|
+
These ride `env`, after `harness.env` and before the wire's digest. This is the Harness container's env, and it holds
|
|
111
|
+
no Stand-in: the Stand-ins are files (above). A process that the Harness confines gets an explicit env instead, which
|
|
112
|
+
holds only the Stand-ins that its `workspace()` grants (ADR-0076). There is no `HTTP_PROXY`: the Custodian carries TLS
|
|
113
|
+
only, and plain HTTP (the Custodian's control listener, an in-cluster seed repository) goes direct. `NO_PROXY` is
|
|
114
|
+
loopback only, so an in-cluster gateway that a secret is bound to still goes through the Custodian.
|
|
83
115
|
|
|
84
116
|
- **The egress listener: CONNECT only.** `127.0.0.1:15001`. The CONNECT target decides everything (lowercased, without a
|
|
85
117
|
trailing dot) — never the SNI, never the `Host` header. A target that a held secret is bound to goes to an internal
|
|
@@ -143,10 +175,11 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
143
175
|
(`program.lua`); each pod's own `custodian.lua` is a generated `HELD` table — names, headers, paths and targets, never
|
|
144
176
|
a value — that ends with `dofile` of the program, so the program runs after the table it reads. At start the program
|
|
145
177
|
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
|
-
|
|
178
|
+
start-up with a line that names the secret when a value is missing, empty, or has a byte outside `0x20`–`0x7E`. Each
|
|
179
|
+
Stand-in but the fetch Stand-in is a value too: the Custodian reads it from its values, never from the table. Envoy's
|
|
180
|
+
`credential_injector` is not used: it is alpha, it breaks on a secret file that ends in a newline (1.39.1), and it
|
|
181
|
+
replaces a whole header, where the swap must see the Stand-in. Values never enter a ConfigMap. Envoy runs one worker,
|
|
182
|
+
with hot restart off, and flushes its access log twice a second.
|
|
150
183
|
- **The log is a contract.** One line per decision on stdout, with the prefix `jr2.custodian` and `key=value` pairs:
|
|
151
184
|
`ready secrets=… hosts=N` (at the first probe), `action=intercept target=… secret=… method=… status=…`,
|
|
152
185
|
`action=tunnel target=…`, `action=refuse target=… reason=…`. Never a header value, a body, a path, a query, a value or
|
|
@@ -192,21 +225,26 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
192
225
|
`jr2 up` alone checks the claimed entries (`heldSecretsOf`), host-side, because `.env` values are absent in-cluster.
|
|
193
226
|
An entry no pod claims is not checked as a held secret:
|
|
194
227
|
|
|
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; two secrets on one host in one header.
|
|
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
|
|
228
|
+
| # | Refused |
|
|
229
|
+
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
230
|
+
| R1 | A known model key (`MODEL_KEYS`) in `harness.env`, or as a key of a Secret or ConfigMap `harness.envFrom` names. |
|
|
231
|
+
| R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
|
|
232
|
+
| R3 | One name held twice, or held and set by `harness.env`. |
|
|
233
|
+
| 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; two secrets on one host in one header. |
|
|
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`. |
|
|
237
|
+
| 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. |
|
|
239
|
+
| R10 | A `secretKeyRef` whose Secret or key does not exist. |
|
|
240
|
+
| 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`. |
|
|
242
|
+
| 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
|
+
| 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
|
+
|
|
245
|
+
R14 exists because the Stand-ins are files, not env: no Stand-in hides such a key, so its real value would reach the
|
|
246
|
+
Harness's env, and the Harness uses a held secret but never holds it. `up` already reads each `envFrom` Secret to
|
|
247
|
+
check that it exists (ADR-0019).
|
|
210
248
|
|
|
211
249
|
Each message names the config path, never a value, and says what to write instead. A SigV4 or ADC credential in
|
|
212
250
|
`harness.env` is warned about, not refused. A known model key with no `hosts` is bound to the host its provider's
|
|
@@ -309,16 +347,18 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
309
347
|
|
|
310
348
|
## Consequences
|
|
311
349
|
|
|
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.
|
|
350
|
+
- **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
|
|
351
|
+
Harness spends the model keys for the Turns. Workspace code can still **spend** a key through the Custodian while its
|
|
352
|
+
pod lives, on any path `paths` allows, when the `workspace()` grants that key. A key with admin rights can mint a new
|
|
353
|
+
key, and a minted key is one the Agent can take away: use a key with no admin rights, and narrow `paths`. The User
|
|
354
|
+
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
355
|
- **Response echo.** Responses pass unchanged. An upstream that echoes the full credential in a response hands it to the
|
|
318
356
|
Agent. Providers mask keys in errors; jr2 does not scrub responses.
|
|
319
357
|
- **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
|
-
|
|
358
|
+
blocked: egress control stays the cluster's decision (ADR-0058's open egress gap), except for the cloud metadata
|
|
359
|
+
addresses, which no Harness pod reaches
|
|
360
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The same holds for the
|
|
361
|
+
control plane: a caller that skips the Custodian and sends the Stand-in to the Orchestrator gets 401 on every route
|
|
322
362
|
(`auth.test.ts`).
|
|
323
363
|
- **Clients that do not trust the jr2 CA** (Java, a pinned client) fail TLS to a bound host. They fail closed and send
|
|
324
364
|
nothing. Unbound hosts still work for them.
|
|
@@ -338,7 +378,7 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
338
378
|
- **Rotation of a value** reaches a live Sandbox only when its pod is replaced.
|
|
339
379
|
- **The walk reads definitions, not Turns.** A pod's model keys come from the models its Agents' definitions name. A
|
|
340
380
|
Turn whose invocation overrides the model with another provider's (`AgentTurnInput.model`) finds no key for that
|
|
341
|
-
provider in its pod: the Harness
|
|
381
|
+
provider in its pod: the Harness process holds a Stand-in only for the names in its own pod's manifest, so that
|
|
342
382
|
provider's key variable is not set at all. The call then has no key (a catalog provider) or sends the custom
|
|
343
383
|
provider's placeholder `unused`, and it fails. This holds in a Workspace and in the Instance Harness alike. The fix is
|
|
344
384
|
a definition that names the provider; in a Workspace, the key under the `workspace()`'s `secrets` also works. The
|
|
@@ -346,16 +386,19 @@ decision generalizes that container. It holds every credential the Harness in it
|
|
|
346
386
|
- **TLS-only bindings.** A key for a plain-HTTP endpoint is refused (R7). A LAN `llama-server --api-key` needs TLS and
|
|
347
387
|
its CA in `caBundle`, or no key.
|
|
348
388
|
- **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
|
-
|
|
389
|
+
- **Two Harness env shapes.** With no held secret in it, a pod gets the Custodian's address and no proxy, and its HTTPS
|
|
390
|
+
goes direct. The Harness process holds the control Stand-in in both shapes. The Custodian runs in both shapes, because
|
|
391
|
+
it carries the Menu.
|
|
392
|
+
- **The dial guard adds no confinement while egress is open.** The Agent can dial those addresses itself, except the
|
|
393
|
+
cloud metadata addresses, which jr2's egress policy closes on a CNI that enforces it (ADR-0076). The guard matters
|
|
394
|
+
when a cluster closes egress to everything but the Custodian: then the Custodian is not a way around it.
|
|
353
395
|
- **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")`.
|
|
396
|
+
that holds both the Sandbox's tokens and the model keys. The branch with a separate Adapter keeps them apart.
|
|
397
|
+
- **The Harness process speaks the Orchestrator's agent routes itself.** Nothing is judged in the pod. No process that
|
|
398
|
+
the Harness confines holds the control Stand-in, so none can make those calls (ADR-0076).
|
|
399
|
+
- **`jr2-upload-pack` carries the fetch Stand-in as a constant.** The User Container gets no env (ADR-0005), and asks
|
|
400
|
+
like any other seat. A test pins the Go literal to `standIn("JR2_SANDBOX_TOKEN")`. The Custodian swaps it on the fetch
|
|
401
|
+
route alone, so the constant reaches no agent route.
|
|
359
402
|
- **The Custodian's image is a Pinned image.** It is deployed and never built, which
|
|
360
403
|
[ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md) now states. `kitRegistry` re-homes it and `jr2 kit push`
|
|
361
404
|
mirrors it ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)). The image sweep never
|
|
@@ -34,16 +34,22 @@ whose processes are gone, with no way to know why.
|
|
|
34
34
|
than the Agent's own pick changes them inside the Turn, the Harness appends the Allowed picks again at the next step
|
|
35
35
|
boundary, in a `<jr2-turn>` block of their own: "The workflow's state changed since you last read the Allowed picks.
|
|
36
36
|
Allowed now: …" ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). It is
|
|
37
|
-
appended, never a rewrite of the first, so the cache argument above holds for it too. It carries
|
|
38
|
-
|
|
37
|
+
appended, never a rewrite of the first, so the cache argument above holds for it too. It carries a pick's briefing
|
|
38
|
+
when the restatement opens that pick. The same read at a step boundary returns the conversation's pending notices, and
|
|
39
|
+
the Harness appends them in the same way (below).
|
|
39
40
|
- **The Turn part comes last, because a model obeys what it read last.** Measured on the home-lab vLLM model: with a
|
|
40
41
|
prompt that argued for a pick the Turn did not allow, the Allowed picks placed ahead of the prompt were obeyed 0/10
|
|
41
42
|
(thinking off) and 5/10 (thinking on); placed after it, 10/10 both. Wording made no difference. The author's task
|
|
42
43
|
still reads first, as it would with no jr2.
|
|
43
44
|
- **Always on.** No author can turn it off: every fact in it passes the rule above, so removing one can only make the
|
|
44
45
|
Agent act worse. An `instructions` that repeats a fact is harmless.
|
|
45
|
-
- **A notice is
|
|
46
|
-
|
|
46
|
+
- **A notice is heard once by each member of its audience.** A member takes it at its next admission, or at a step
|
|
47
|
+
boundary of an open Turn: the surface read of the open Turn returns the conversation's pending notices, and the
|
|
48
|
+
Harness appends them, as it appends a changed Allowed-picks list
|
|
49
|
+
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md), ADR-0002). For a guard
|
|
50
|
+
kill that the Agent of an open Turn was not told, a member also takes it at that Turn's nudge or wake (below). A
|
|
51
|
+
conversation is an Instance ID ([ADR-0057](0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md)): a fresh
|
|
52
|
+
Turn, a runaway reroll and a burned conversation's successor each start a new one.
|
|
47
53
|
- **Conversation scope** ("this conversation is new; earlier context is gone") goes to that Agent's next Turn. So does
|
|
48
54
|
a pick's answer that never reached its call
|
|
49
55
|
([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)): the route left the Agent's state, and
|
|
@@ -53,18 +59,31 @@ whose processes are gone, with no way to know why.
|
|
|
53
59
|
and that stopped it" (`stopped`), with an earlier link's reply when one came. No pick-answer notice says the work
|
|
54
60
|
ran to its end. A fresh picking Turn has no such conversation, and its answer is no notice.
|
|
55
61
|
- **Workspace scope** ("the Sandbox's processes were killed at its memory limit during `coder`'s Turn; `/dev/shm` was
|
|
56
|
-
cleared. Anything that was running in the Sandbox at that time is gone.") goes to
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
cleared. Anything that was running in the Sandbox at that time is gone.") goes to an **audience** that the ledger
|
|
63
|
+
fixes when it raises the notice. The rule is: an Agent hears a kill if it was there for it.
|
|
64
|
+
1. Each conversation admitted in that Workspace before the kill that can still take a Turn: the conversation of an
|
|
65
|
+
open Turn, which hears it at its next step boundary, or a continued conversation, which hears it at its next
|
|
66
|
+
Turn. A fresh Turn that already ended is not in the audience.
|
|
67
|
+
2. When the kill cut a specific Turn — a kill read on that Turn's stream names it — that Agent's next Turn, fresh or
|
|
68
|
+
continued: the retry of the broken work.
|
|
69
|
+
3. Minus each conversation whose own `bash` answer returned the kill's verdict (`told`, below). That answer told it
|
|
70
|
+
already.
|
|
71
|
+
|
|
72
|
+
Each member hears the notice once. The notice retires when every member has heard it. A conversation that starts
|
|
73
|
+
after the kill does not hear it, except the retry of (2). So a Pool of coders in one Workspace
|
|
74
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)) all hear a kill that cut their work.
|
|
75
|
+
Only a kill read on a stream while its wait is live names the Turn whose Submission ran. Both forms end with the
|
|
76
|
+
whole Sandbox, because every process in it died, a shared Workspace's too, and in the past tense, because the Agent
|
|
77
|
+
may hear it after processes started again. A kill read from the pod's list names no Turn and says nothing of when or
|
|
78
|
+
whose: "the Sandbox's processes were killed at its memory limit (peak X of Y); /dev/shm was cleared. Anything that
|
|
79
|
+
was running in the Sandbox at that time is gone." The list cannot say whose Turn a kill ended: the kill may land
|
|
80
|
+
after a state exits, or end an attach, a sync or a command. A kernel kill read from the Sandbox's last end (below)
|
|
81
|
+
says the same, with the limit and no peak: "the Sandbox's processes were killed at its memory limit of Y; …". A
|
|
82
|
+
Menu-only Agent outside a Workspace receives conversation notices only.
|
|
83
|
+
|
|
66
84
|
- A notice never replaces a fault. The Machine still receives `agent.fault` and decides (ADR-0016); the notice makes
|
|
67
85
|
sure the next Agent is not working blind.
|
|
86
|
+
|
|
68
87
|
- **The Orchestrator is the only keeper of notices**: pending notices are plain data in the run's persisted state
|
|
69
88
|
(ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has two paths,
|
|
70
89
|
under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults on it, and
|
|
@@ -89,13 +108,15 @@ whose processes are gone, with no way to know why.
|
|
|
89
108
|
the only limit. A read that never left the Orchestrator, or a draining Harness's 503, is sent again in that time. A
|
|
90
109
|
read that fails or times out is none, and the next admission reads again: the list keeps the kill. The Orchestrator
|
|
91
110
|
logs one line when the reads from a Harness start to fail. It logs no line when a read works again, and it logs the
|
|
92
|
-
failure again only when a read fails after a read that worked. The
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
failure again only when a read fails after a read that worked. The file is beside the Harness's conversations, which
|
|
112
|
+
no confined child can reach
|
|
113
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The bounds stay as a
|
|
114
|
+
second layer: a `peak` or a `limit` that is not a size in the guard's form, or is long, is left out and logged, on
|
|
115
|
+
the Harness's read of the file and on the Orchestrator's read of the list and of a stream. The kill is kept, so a
|
|
116
|
+
real kill is never lost, and its notice says no sizes: "the Sandbox's processes were killed at its memory limit;
|
|
117
|
+
/dev/shm was cleared. …". Only a kill with no id or no time, or one outside the bounds below, is dropped. A stream
|
|
118
|
+
event from an older Harness has no id; its source is then its place on the stream, exactly as the Orchestrator
|
|
119
|
+
raised it before.
|
|
99
120
|
|
|
100
121
|
Both paths raise the notice with the same source, the Workspace and the kill's id. The ledger keeps every source it
|
|
101
122
|
raised (`seen`) in the run's persisted state. So a kill read on both paths, or on two streams, is one notice. Each
|
|
@@ -107,62 +128,64 @@ whose processes are gone, with no way to know why.
|
|
|
107
128
|
restart of the Orchestrator raises no kill twice. A restart of the Harness container keeps the file, which is on the
|
|
108
129
|
pod's emptyDir with its conversations (ADR-0031). A lost pod takes the file with it, but then the Workspace is lost
|
|
109
130
|
(ADR-0021). The list keeps the newest 16 kills, each with at most 32 conversations in `told`, and the Orchestrator
|
|
110
|
-
reads at most 256 KiB of it. The
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
reads at most 256 KiB of it. The two sides hold the same bounds: an `id` and an `at` of at most 64, an `agent` and an
|
|
132
|
+
`instanceId` of at most 128, and an `at` that parses as a time. A kill outside them is dropped, and a `told` entry
|
|
133
|
+
outside them is left out. The Harness counts the bytes of the JSON text, and the Orchestrator counts characters, which
|
|
134
|
+
are never more, so the Harness keeps no kill that the Orchestrator drops. The Harness reads at most the last 256 KiB
|
|
135
|
+
of the file. With these bounds the largest list is 149,259 bytes. When more come between two admissions, the oldest is
|
|
136
|
+
lost, and a newer kill tells the same thing. When more than 16 come in one Turn, the Turn's mark leaves the list too,
|
|
137
|
+
and every kill in the list then counts as after the mark; a nudge or a wake of that Turn may then carry a kill from
|
|
138
|
+
before it. The Instance Harness keeps no kill, because it holds no Workspace.
|
|
118
139
|
|
|
119
140
|
A notice counts as delivered when its admission is ledgered, so a restart before that point delivers it again and a
|
|
120
|
-
restart after it does not.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
so
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
the
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
alone
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
restart after it does not. A notice at a step boundary has no admission. The surface read of the open Turn reserves it
|
|
142
|
+
for that member, and it counts as delivered, and heard, when the Harness acknowledges the append. When the Turn ends
|
|
143
|
+
with no acknowledgment, the reservation is released, and the notice is pending again for that member. A reservation
|
|
144
|
+
lives in the Orchestrator's memory only, so a restart forgets it: the notice is pending again, and the next read or
|
|
145
|
+
admission carries it. So a conversation can hear a notice twice after a crash, as for an admission. Delivered is not
|
|
146
|
+
yet heard: if the Orchestrator ends the Turn before the Harness reads its surface, the Harness never prompts it
|
|
147
|
+
(ADR-0026), so the Orchestrator raises that admission's notices again. This holds for a nudge and a wake too: each one
|
|
148
|
+
must have its surface read again before its notices count as heard. An admission that fails leaves the earlier
|
|
149
|
+
admission's notices heard. A nudge and a wake re-prompt the Turn within itself. Of the notices, they take only the
|
|
150
|
+
guard kills of the list that come after the newest kill whose `told` names their conversation. That `bash` answer told
|
|
151
|
+
the Agent that all its processes died, so it covers every kill up to it. They also take no kill from before the Turn
|
|
152
|
+
started. Such a kill would tell the Agent that processes it started in this Turn died. The Turn's first read of the
|
|
153
|
+
list sets its mark: the id of the newest kill it saw, or "none". The mark is ledgered with every admission of the
|
|
154
|
+
Turn, and a restore reads it back, so a nudge or a wake takes only kills after it. Only when the first read failed is
|
|
155
|
+
the Turn's start the mark, ledgered the same way, with one second allowed for a pod clock that runs behind. A read
|
|
156
|
+
that failed is "could not tell", never an empty list, so it sets no mark. The kills they do not take stay pending for
|
|
157
|
+
the next Turn. The re-admission after a Harness restart cut the Turn continues the same Turn, so it takes every notice
|
|
158
|
+
in scope except the guard kills that this rule leaves out. When its read could not tell, it takes no guard kill at
|
|
159
|
+
all, as a nudge then takes none: it cannot know which kills its own `bash` reported. The notices that the Turn's
|
|
160
|
+
admissions carried since the Harness last read the Turn's surface are pending again first, each with the source it was
|
|
161
|
+
raised with, and this re-admission carries them whatever else its scope leaves out. A notice with no source merges
|
|
162
|
+
only into a pending one with no source, so an equal notice of another kill never takes its place. When such an unread
|
|
163
|
+
admission carried the Turn's task, the task follows the re-admit text. When the rebuilt Harness lost the conversation,
|
|
164
|
+
nothing of the Turn is left: every notice that the Turn's admissions carried is pending again whatever the surface
|
|
165
|
+
reads said, the re-admission takes every notice in scope, as a Turn's admission does, and the Turn's task follows the
|
|
166
|
+
re-admit text. It says once that the conversation is new. A restore drops the prompt from a Turn's input, so the task
|
|
167
|
+
rides the ledgered admission too; a lost conversation with no task to send faults the Turn instead. Whether the
|
|
168
|
+
Harness still holds the conversation is asked again for up to 30 seconds while the new container does not listen yet
|
|
169
|
+
or drains, and only then is the answer taken as "held". The continue path asks the same way, so a continued Turn after
|
|
170
|
+
a restart can wait up to 30 seconds before its admission. A Harness in CrashLoopBackOff can take longer than that: the
|
|
171
|
+
answer is then "held", and it can be wrong. So every admission to a conversation that the Harness was taken to keep
|
|
172
|
+
says so with `kept: true`: the re-admission after a cut, a continued Turn, and a nudge or a wake. A Harness that keeps
|
|
173
|
+
no such conversation refuses it with 404 and `kept: false` in the body, and seats none. The Orchestrator takes only
|
|
174
|
+
that 404 as the refusal; another 404 is a failed admission. Without the refusal the Harness would seat a new
|
|
175
|
+
conversation, and a model on an empty context would read "continue your task" with no task. On the refusal the Turn
|
|
176
|
+
takes the path of a lost conversation. The re-admission is admitted as a Turn, with the task after the re-admit text
|
|
177
|
+
and the notice that the conversation is new, or faults when it has no task to send. A continued Turn is told that its
|
|
178
|
+
conversation is new, and is admitted again. A nudge or a wake is admitted again as a Turn: its prompt is the task
|
|
179
|
+
alone, because nothing was cut, with the notice that the conversation is new and every notice in scope. Every notice
|
|
180
|
+
that the Turn's admissions carried is pending again first, as for a lost conversation after a cut. With no task to
|
|
181
|
+
send, the Turn faults. The Instance Harness's restart is on no watch, so this refusal is the only way a nudge or a
|
|
182
|
+
wake learns of it. What the Turn's admissions carried, and whether the Harness read their surface, is kept in the
|
|
183
|
+
Orchestrator's memory alone: after a restore of the Orchestrator before a surface read, nothing is raised again. A
|
|
184
|
+
fresh Turn's "this conversation is new", which rides an admission alone, is carried again by the next re-admission
|
|
185
|
+
when the Harness has not read the one before.
|
|
161
186
|
|
|
162
|
-
A
|
|
163
|
-
|
|
164
|
-
admission of a continued Turn too; it does not now, because a fresh Turn's conversation holds no earlier answer, and
|
|
165
|
-
the rule would then need the conversation in the ledger's scope.
|
|
187
|
+
A conversation whose own `bash` call answered a kill is not in that kill's audience, so it does not hear the kill
|
|
188
|
+
again as a notice, at its next Turn or at a step boundary.
|
|
166
189
|
|
|
167
190
|
- **The Harness writes every word.** The admit body carries typed notices
|
|
168
191
|
(`notices: [{ kind: "memory-limit", scope, agent?, limit?, peak? } | { kind: "conversation-new", reason } | { kind: "pick-answer", pick, cut, answer?, stopped? } …]`,
|
|
@@ -209,4 +232,8 @@ whose processes are gone, with no way to know why.
|
|
|
209
232
|
- The Briefing is how a Turn learns its Allowed picks: the tools block is the whole Menu, fixed for a conversation so it
|
|
210
233
|
does not break the cache
|
|
211
234
|
([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)).
|
|
235
|
+
- A Workspace notice reaches each conversation that was there for the kill, and the retry of the Turn that it cut; an
|
|
236
|
+
open Turn hears it at its next step boundary, not only at its next admission (ADR-0002,
|
|
237
|
+
[ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). So a memory kill reaches
|
|
238
|
+
every coder of a Pool that shares one Workspace (ADR-0075).
|
|
212
239
|
- GLOSSARY.md gains **Briefing**.
|