@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
|
@@ -45,11 +45,13 @@ loudly narrated, safe to re-run:
|
|
|
45
45
|
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). Referenced-but-unmanaged Secrets
|
|
46
46
|
(`envFrom` refs, git credentials) are **preflighted**: `up` fails naming the missing Secret with the exact creation
|
|
47
47
|
hint — converting the `CreateContainerConfigError` hang into an immediate error. Sealed Secrets/External Secrets ride
|
|
48
|
-
this seam untouched: jr2's contract is "a Secret named X exists", however it got there.
|
|
49
|
-
is a held secret (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
this seam untouched: jr2's contract is "a Secret named X exists", however it got there. `up` also refuses a held
|
|
49
|
+
secret's name that is a key of an `envFrom` Secret, and names the Secret, the key and the held secret (ADR-0059, R14).
|
|
50
|
+
A credential the Harness uses is a held secret
|
|
51
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)): `up` writes a literal value into
|
|
52
|
+
`jr2-held-secrets-<pod>` for each pod that names it, which only that pod's Custodian mounts, and checks that a
|
|
53
|
+
referenced Secret and key exist. Like the signing key, the held-secret CA (`jr2-held-ca`) is kept across converges —
|
|
54
|
+
minted on first need, never rotated by `up` — because a live pod trusts it.
|
|
53
55
|
- **Repos**: `up`'s walk of the registered Machines collects every bound Repo Slot (ADR-0049, ADR-0051) — to refuse an
|
|
54
56
|
open slot nobody bound, and to see which urls need a key. The Orchestrator creates one `Repo` resource per url at
|
|
55
57
|
boot, and the operator's cache agent clones each onto the nodes that need it (ADR-0004). There is no `repos` config
|
|
@@ -59,6 +61,11 @@ loudly narrated, safe to re-run:
|
|
|
59
61
|
in-cluster deploy keypair (the recommended default, public key printed to register), a local key, or one pasted on
|
|
60
62
|
stdin ([ADR-0047](0047-the-git-ssh-key-source-is-the-users-choice.md)) — declining bails. A personal key never enters
|
|
61
63
|
a cluster silently: only by that explicit, warned choice.
|
|
64
|
+
- **Landlock**: `up` refuses to converge while a Sandbox node lacks Landlock. It reads the pods of the Repo cache
|
|
65
|
+
agent's DaemonSet, which probe Landlock on each Sandbox node, within the rollout wait it already makes for the cache.
|
|
66
|
+
It is an ordinary converge failure: it names each node whose agent reports none, the probe's reason, and the fix (keep
|
|
67
|
+
the node out with `sandbox.nodeSelector` or a taint, ADR-0052, or enable Landlock); a run after the fix converges
|
|
68
|
+
([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)).
|
|
62
69
|
- **Provider preflight**: `up` checks each provider that a carried model names. It checks from inside the cluster, with
|
|
63
70
|
one trivial tool-call completion. An endpoint that the pods cannot reach, a wrong or missing key, or a vLLM without
|
|
64
71
|
`--enable-auto-tool-choice` then fails at converge time, not as `agent.fault` in a run. Reachability is the user's
|
|
@@ -75,14 +82,17 @@ loudly narrated, safe to re-run:
|
|
|
75
82
|
the config, each model's result says why, in words.
|
|
76
83
|
- **The check pod.** It is a Harness pod with no Harness in it. It has a Custodian and the stock Harness image's
|
|
77
84
|
`provider-preflight.ts`. The function that composes the Custodian of every Harness pod composes this Custodian too.
|
|
78
|
-
Its token is a value that the Orchestrator never signed.
|
|
79
|
-
|
|
85
|
+
Its token is a value that the Orchestrator never signed. Its Stand-ins are secret and its own: `jr2 up` mints them
|
|
86
|
+
with the signing key, as it does for the Instance Harness
|
|
87
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The check pod runs no Workspace
|
|
88
|
+
code, so it may hold them in env. `provider-preflight.ts` gets the env of the Harness container, with the Stand-ins
|
|
89
|
+
in it: the harness config, `harness.env`, `harness.envFrom`, the Stand-ins, the proxy and the trust. pi resolves
|
|
80
90
|
each model and uses its api, as in a Turn. The `harness.catalog` gateway applies. The key never enters the check
|
|
81
91
|
container, its argv or its log.
|
|
82
92
|
- **Its own objects.** The check pod reads no object of the Instance. `up` writes copies from the values on the host:
|
|
83
93
|
- a ConfigMap with the Custodian's files, the program and the harness config;
|
|
84
94
|
- a ConfigMap with the trust bundles;
|
|
85
|
-
- a Secret with the token and the literal values of the held secrets;
|
|
95
|
+
- a Secret with the token, the check pod's Stand-ins and the literal values of the held secrets;
|
|
86
96
|
- a Secret with the leaves;
|
|
87
97
|
- a Secret with the literal `harness.env` values.
|
|
88
98
|
|
|
@@ -194,7 +204,9 @@ loudly narrated, safe to re-run:
|
|
|
194
204
|
- **A limit.** The check pod has labels of its own. It does not get the labels of a Harness pod: the Instance Harness
|
|
195
205
|
Service selects its pods by label, and would send Turns to the check pod. So a NetworkPolicy that selects Harness
|
|
196
206
|
pods by label does not select the check pod. When such a policy closes the egress of the Harness pods, the check can
|
|
197
|
-
pass while a Turn fails.
|
|
207
|
+
pass while a Turn fails. jr2's own egress policy, which closes the cloud metadata addresses
|
|
208
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)), gives the check pod one
|
|
209
|
+
of its own, so it does not have this limit.
|
|
198
210
|
|
|
199
211
|
**`jr2 down`** removes the instance from the cluster. It asks for confirmation unless `--yes` is given (for CI). `--all`
|
|
200
212
|
takes the operator too.
|
|
@@ -31,7 +31,8 @@ end. And a restarted Harness answered a `continue` with no memory of the convers
|
|
|
31
31
|
Sandbox goes to the terminal phase **`Lost`**, with the pod's own reason and message (for a deleted pod, the last it
|
|
32
32
|
saw). It never returns to `Pending` and is never given a second pod: `work` is an `emptyDir`, so a new pod could
|
|
33
33
|
continue nothing, and it would hold a whole Size for nobody while hiding the loss. The CR stays, so `jr2 status` can
|
|
34
|
-
say why, until teardown or the idle GC.
|
|
34
|
+
say why, until teardown or the idle GC. A Sandbox delete waits for its pod, because a retry can take the name of a pod
|
|
35
|
+
that is still going away.
|
|
35
36
|
- **The operator judges identity, because it knows which pod it created.** It still publishes `status.podUID`; the
|
|
36
37
|
Orchestrator reads `Lost`, not a UID comparison.
|
|
37
38
|
- **The watch answers; the lease asserts.** The Orchestrator watches its Sandboxes
|
|
@@ -122,10 +122,10 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
122
122
|
|
|
123
123
|
- **Per-`agentRun` containers, or a pod per Agent.** Rejected on lifetime before merit: the container list is fixed when
|
|
124
124
|
the CR is created, before the body has run, while `agentRun` is a per-turn invocation. Pre-declaring per _Agent_ is
|
|
125
|
-
possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
(ADR-0004
|
|
125
|
+
possible but pays N resident copies of a Harness image that deliberately carries the agent toolchain (ADR-0005). It
|
|
126
|
+
buys no isolation — containers in a pod share the network namespace, so it does not even fix the port contention that
|
|
127
|
+
motivates it (ADR-0005:31) — and it does not remove the git contention, because the containers share one `/work` and
|
|
128
|
+
one clone (ADR-0004). What it actually buys is `kubectl logs -c <agent>`: a container, to avoid a prefix.
|
|
129
129
|
- **A trace from the Custodian.** Tempting, and wrong on the facts. The Custodian carries the Menu's reads and the
|
|
130
130
|
Agent's picks — a handful of deliveries per turn — and the model calls, and it logs names only, never a body
|
|
131
131
|
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). The Agent's working tools execute in
|
|
@@ -159,11 +159,10 @@ replayable from any offset. The gap was a read decision, not a missing capabilit
|
|
|
159
159
|
what it happened to read; what jr2 told it includes an Effect's output, so a `command` Effect's output and a Held
|
|
160
160
|
value echoed into it reach the log as they reach the model, and so does an Effect secret in a form an Effector's scrub
|
|
161
161
|
does not catch (a transformed secret, a partial echo; ADR-0068). Fine on kind; a real decision on a shared cluster.
|
|
162
|
-
- **
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
sees every Agent in a Workspace, and the place to refuse a second concurrent admission.
|
|
162
|
+
- **The conversations of concurrent Agents interleave in one log.** The author chooses how many Agents work in a
|
|
163
|
+
Workspace ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), so two conversations can
|
|
164
|
+
print at one time on one container's stdout. The prefix keeps them apart: every line carries its label and its iid, so
|
|
165
|
+
a reader keeps one conversation's lines by its iid.
|
|
167
166
|
- **Truncation is load-bearing, not cosmetic.** Kubelet rotates container logs at 10Mi by default and drops the
|
|
168
167
|
remainder. jr's filter capped `Bash` at 100 chars and unknown tools at 150 and reduced `Write`/`Edit` to a path; that
|
|
169
168
|
was readability, and it is now also what keeps a long turn inside the window.
|
package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md
CHANGED
|
@@ -34,8 +34,7 @@ state. A prompt fix cannot be relied on and cannot even be shown to have worked.
|
|
|
34
34
|
Workspace; the Machine's states are sequential, but the Agents are not. Throughout `reviewing`, the orphaned coder was
|
|
35
35
|
still live in the worktree the reviewer was reviewing — and still live at `humanReview`, the park that deliberately
|
|
36
36
|
retains the Sandbox so a human can exec in and inspect the diff. In the runs we watched, the orphan only read. Nothing
|
|
37
|
-
prevented it writing. An un-ended turn is
|
|
38
|
-
single-threaded.
|
|
37
|
+
prevented it writing. An un-ended turn is a concurrent writer in a Workspace that the Machine did not put there.
|
|
39
38
|
|
|
40
39
|
## Decision
|
|
41
40
|
|
|
@@ -190,69 +189,67 @@ single-threaded.
|
|
|
190
189
|
is local to its Machine: two Machines of one tree, and the workers of a pool, which run one body, may each have a
|
|
191
190
|
child of one id, and one never reads or changes the other's entry. A child started on an event that names no Agent
|
|
192
191
|
anywhere else is no Agent's. The other Agent's pick keeps its default answer, and when the Agent the event names has
|
|
193
|
-
no pick being answered, it sets nothing.
|
|
194
|
-
invoke as the pick that sent the event or started the
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
enqueued
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
state
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
stopped
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
the pick with `turnComplete`. There is no default time bound on a held Turn; the Console shows each outstanding pick
|
|
255
|
-
"held since".
|
|
192
|
+
no pick being answered, it sets nothing. One Agent slot can be invoked by two states that are not active together (two
|
|
193
|
+
states one after the other). Then it shapes the pick at the same invoke as the pick that sent the event or started the
|
|
194
|
+
child, and nothing otherwise: the done event of a child that the earlier state's pick started, after that pick's Turn
|
|
195
|
+
ended (its child runs on), shapes no pick, and the later Turn's pick keeps its default answer. When the event names no
|
|
196
|
+
Agent (a timer, a Gate's answer, an outside send, the done event of a child no Agent's pick started), it shapes the
|
|
197
|
+
pick in its own send; outside one, it shapes the one such pick outstanding only while one Agent's state is active.
|
|
198
|
+
Outside a pick's own send, while two Agents' states are active, it sets nothing, whatever is outstanding: the event
|
|
199
|
+
cannot say whose pick it is. On an entry or an exit below no Agent's state it sets nothing, whatever is outstanding,
|
|
200
|
+
and the load walk warns, naming the Agent states it could be moved under: an entry or exit there is no transition an
|
|
201
|
+
Agent's event takes. One action object written in two places runs from each by that place's rule alone, never by their
|
|
202
|
+
sum: written below two Agents' states, from A's state it shapes only A's pick and from B's only B's; written below A's
|
|
203
|
+
state and on a transition below none, from A's state it shapes only A's pick and from the transition the pick of the
|
|
204
|
+
Agent the event names; written on an entry or exit below no Agent's state as well, it sets nothing as that entry or
|
|
205
|
+
exit. An `answer` that an `enqueueActions` enqueues, at any depth, runs from the place the enqueuing action is
|
|
206
|
+
written, by that place's rule: enqueued on an entry or exit below no Agent's state it sets nothing, and enqueued below
|
|
207
|
+
an Agent's state it shapes only that Agent's pick. The load walk cannot see inside an `enqueueActions`, so it does not
|
|
208
|
+
warn on one enqueued on an entry or exit below no Agent's state. An `answer` that runs while no pick of that Agent is
|
|
209
|
+
being answered (a state's first entry, a later Turn's re-entry, another Agent's region of a parallel state) sets
|
|
210
|
+
nothing. A reply comes from a child invoked on an active state below the Agent's state, or from a party whose Gate
|
|
211
|
+
invoke id or Agent slot is invoked there (its sender, ADR-0015). The events the Machine sent itself (a `raise`, a
|
|
212
|
+
`sendTo(self)`, a timer) are no reply, and neither is an event from outside that set (a Gate in a sibling region, a
|
|
213
|
+
root Gate, an outside send), nor a child's error: every outcome of work is its output, and an error is a jr2 or config
|
|
214
|
+
defect, so by default its message never reaches the model. The operator reads it: while a pick is outstanding, an
|
|
215
|
+
error from a child below the Agent's state goes on the run's feed (`child-error`: the Agent, its pick, the child and
|
|
216
|
+
the message; Instance-token band, as `fault` is), and `jr2 run` and `jr2 logs` print it. An author who wants the Agent
|
|
217
|
+
told passes it explicitly: `answer(...)` on the `onError` route (ADR-0067), or an `onError` route to a state whose
|
|
218
|
+
prompt carries it. With none, the answer carries no reply, and its words say only that ("No reply came back for it"):
|
|
219
|
+
never that nothing ran, because a timer or an outside event may have stopped work that did run. The pick's `POST`
|
|
220
|
+
answers at once with `outstanding` and a delivery id, and the Harness long-polls the answer inside the one tool call
|
|
221
|
+
(ADR-0002, ADR-0013). A child that settles inside the pick's own send — a decision Machine that is final as it starts
|
|
222
|
+
— replies before the `POST` answers: when that reply leaves the workflow at Rest, or out of the Agent's state, the
|
|
223
|
+
`POST` itself is the answer, with `answered` and nothing to poll. So is a guard straight back: a pick that enters a
|
|
224
|
+
state and leaves it again inside its own send (an `always` back), with no reply, leaves the state value as it was, and
|
|
225
|
+
is answered with `straightBack` — the one answer that says "it moved the workflow, and nothing ran". An outstanding
|
|
226
|
+
pick's answer never carries it. A pick that changes the state value moved to a mode, and gets the plain receipt with
|
|
227
|
+
its Allowed picks; a pick with no target went nowhere, and gets the same, as does a pick whose target is the state it
|
|
228
|
+
was taken in (it enters nothing it leaves). Every receipt that leaves the Turn open (the plain receipt, a guard
|
|
229
|
+
straight back, and an answered pick back at Rest) ends its state part with the same words, "Your turn is not over.":
|
|
230
|
+
the plain receipt once said "the workflow is still in the state that asked for this turn, so it is not over yet", and
|
|
231
|
+
real Haiku 5.5 runs stopped after it, because "it" can be the workflow or the Turn. The refusals and the notices of a
|
|
232
|
+
cut or unsent pick keep their own words. A route that leaves the Agent's state ends the Turn like any other: the poll
|
|
233
|
+
reads `turnComplete`, the next state's prompt is the author's, and the answer is a `pick-answer` notice to the Agent
|
|
234
|
+
that picked: the next Turn of the conversation the picking Turn continued (`continue: true`, ADR-0062). A FRESH
|
|
235
|
+
picking Turn's conversation is never heard again, and a later continued Turn of the same slot never made the pick, so
|
|
236
|
+
its answer goes nowhere: the ledger keeps no notice for it. The route of a reply includes the events the Machine sends
|
|
237
|
+
itself AT ONCE while it runs (a `raise`, or a `sendTo(self)` with no delay, in `onDone` or in the next state's entry):
|
|
238
|
+
a reply whose route leaves the Agent's state that way is the answer, never stopped. Something other than the route can
|
|
239
|
+
leave the Agent's state while the pick's work still runs below it — a timer the Machine set (a Turn timeout `after`, a
|
|
240
|
+
named delay, a delayed `raise` or `sendTo(self)`), an event from outside the states below, a Gate's event, the Agent's
|
|
241
|
+
own end. The exit stops that work (an invoke is stopped when its state exits), and the answer says so (`stopped`): the
|
|
242
|
+
poll reads "The workflow left the state that asked for this turn while it ran your pick, and that stopped the pick's
|
|
243
|
+
work before it finished: your turn is over.", and the next continued Turn's notice reads "Your pick `X` did not
|
|
244
|
+
finish: the workflow left the state that asked for your last turn while it ran, and that stopped it." Both keep the
|
|
245
|
+
last reply an earlier link gave, if any, and state it: an earlier link's reply is never dropped because a later link
|
|
246
|
+
was stopped. The run host tells a fired timer from an event sent at once by the scheduler's own table: it watches what
|
|
247
|
+
the actor system arms, cancels and fires (`timers.ts`, the same xstate internals as the timer table, ADR-0007). Back
|
|
248
|
+
at Rest INSIDE the Agent's state — a per-step timeout `after` on the child state that exits the work — the answer is
|
|
249
|
+
the last reply since the pick, with the Allowed picks; with no reply since the pick, the answer carries none. A
|
|
250
|
+
restored hold is the Turn of the first registration of its address in the restored tree: a restore that ends that Turn
|
|
251
|
+
and starts another at the same address before anything settles answers the pick with `turnComplete`. There is no
|
|
252
|
+
default time bound on a held Turn; the Console shows each outstanding pick "held since".
|
|
256
253
|
- **The one exception is the Orchestrator ending it for its own reasons**, which is ADR-0007's requirement and exactly
|
|
257
254
|
one place: `RunHost.stop()`. Process shutdown never stops actors at all (`instance.ts` calls `host.close()`, which
|
|
258
255
|
ends observation feeds and nothing else), and restore is a fresh process. So "the host did this" is a flag it sets,
|
package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md
CHANGED
|
@@ -106,6 +106,13 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
|
|
|
106
106
|
no adapter: a test rig can pass another (`TurnDeps.engineAdapter`). So a second engine changes `engine-choice.ts`, not
|
|
107
107
|
the Turn. The Working tools, the printer, the transcript and Compaction do not change.
|
|
108
108
|
|
|
109
|
+
- **The Working tools run as confined children beside the engine.** The engine runs in the Harness process, which runs
|
|
110
|
+
jr2's code only and holds the control credential
|
|
111
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). Each process that a
|
|
112
|
+
Working tool starts — each `bash` call and what it runs, and the search tools' `rg` or `grep` — runs confined by the
|
|
113
|
+
Effector's Landlock confiner ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), with an
|
|
114
|
+
explicit env. The file tools (read, write, edit) run in the Harness process, and resolve each path beneath `/work`,
|
|
115
|
+
with no escape by `..` or by a symbolic link. A second engine keeps this rule: it is the Harness's, not pi's.
|
|
109
116
|
- **Assembly is runtime construction, not codegen.** `main.ts` constructs the server and listens; each admission carries
|
|
110
117
|
its Agent's definition (ADR-0049), validated loudly and re-read per Submission. `boot.mjs`, the generated shims, the
|
|
111
118
|
boot-time `flue build`, and the readiness lag it caused are deleted, and so — later — was the `JR2_AGENTS_JSON` roster
|
|
@@ -219,7 +226,8 @@ does not exist for a caller that owns tool assembly. jr2 owns the rest: the engi
|
|
|
219
226
|
- **Kept verbatim**: the wire, the stub as the mechanics fixture, `AgentRunPort` and the admission ledger, ADR-0024's
|
|
220
227
|
rule, receipt, and ordering, and the container split — restated in jr2 terms: the pod's credentials live in a separate
|
|
221
228
|
container (the Custodian, ADR-0059) _because working tools execute in the Harness container_, so the
|
|
222
|
-
credential-isolation premise survives while `local()` leaves the vocabulary.
|
|
229
|
+
credential-isolation premise survives while `local()` leaves the vocabulary. The one credential that the Harness
|
|
230
|
+
process holds, the control Stand-in, is out of reach of the Working tools' children, which run confined (ADR-0076).
|
|
223
231
|
- **The `flue-contract` tier dissolves into the conformance suite.** Its rig — scripted OpenAI-compatible provider, the
|
|
224
232
|
Menu over a real socket to a killable fake Orchestrator, real server — moves into `packages/harness/test/` and runs in
|
|
225
233
|
the default `test` gate: the opt-in-ness existed only because the tier owned a foreign pin and build. Its claims
|
|
@@ -13,8 +13,8 @@ instructions that say "Do not modify the code yourself".
|
|
|
13
13
|
[ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md) already established what that means: the same
|
|
14
14
|
prompt, model, and state produce compliance on one run and not the next, "which is what rules out fixing this with
|
|
15
15
|
words." The prose ban is a persona, not a boundary. ADR-0024 also named the stakes — an Agent with write access is a
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
writer that the Machine did not plan for — and [ADR-0012](0012-workspace-wrapper-machine.md) puts coder and reviewer in
|
|
17
|
+
one Workspace on purpose.
|
|
18
18
|
|
|
19
19
|
Under the retired flue runtime this had no seat — its tool list was take-it-or-replace-it, and the only recorded out was
|
|
20
20
|
ejecting to a foreign harness project. [ADR-0027](0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md)
|
|
@@ -41,19 +41,20 @@ definition can state.
|
|
|
41
41
|
tools (the tool set that the Harness gives the loop for each Submission). `bash` stays — the reviewer's own
|
|
42
42
|
instructions require running the tests — and the ADR says plainly what that means: a shell can write, so the tool
|
|
43
43
|
layer states intent and stops the honest path. It is known leaky, and it is not the guarantee.
|
|
44
|
-
- **The worktree layer contains the blast radius: a reviewer works in a detached review
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
— it is containment, the property ADR-0024 bought for
|
|
44
|
+
- **The worktree layer contains the blast radius: a reviewer works in a detached review Worktree.** The Machine cuts it
|
|
45
|
+
with `worktree()`, `{ path, detach: sha }`, at the sha under review, with a detached HEAD, sibling to the coder's
|
|
46
|
+
Worktree per the Project layout ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). A write
|
|
47
|
+
there cannot move the branch or dirty the coder's Worktree; a rogue commit lands on a detached HEAD and goes when the
|
|
48
|
+
next cut forces the folder. This is not a permission boundary — it is containment, the property ADR-0024 bought for
|
|
49
|
+
turns, bought for the filesystem.
|
|
49
50
|
- **Two layers, one structure: the tool list is the hint, the worktree is the guarantee** — the same shape as ADR-0024's
|
|
50
51
|
receipt (hint) and abort (guarantee). Neither layer alone: tools without the worktree leak through bash; the worktree
|
|
51
52
|
without tools invites the model to fight its checkout instead of declining politely.
|
|
52
|
-
- **
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
- **A state before the reviewing state cuts the review Worktree on each round.** The sha under review moves between
|
|
54
|
+
rounds, so each round cuts the one folder again at the new sha. The cut forces the folder to the commit and cleans it
|
|
55
|
+
with `-ffd`. Ignored files stay, because the reviewer runs the tests. The folder's path frames the reviewer's turn —
|
|
56
|
+
cwd and prompt — instead of the coder's Worktree. What is decided here is the seat: worktree mechanics stay in
|
|
57
|
+
`worktree()` and the Machine's states, never in Agent instructions.
|
|
57
58
|
|
|
58
59
|
## Considered options
|
|
59
60
|
|
|
@@ -42,7 +42,10 @@ built.
|
|
|
42
42
|
carries the Instance Harness's model calls the same way. It holds only the model keys of the `"none"` Agents it hosts
|
|
43
43
|
— the keys their providers read — and none of the held secrets a `workspace()` lists: a `"none"` Agent cannot execute
|
|
44
44
|
code, so it had no way to use the rest. Its held objects are its own (`held-instance.json`,
|
|
45
|
-
`jr2-held-secrets-instance`).
|
|
45
|
+
`jr2-held-secrets-instance`). Its held secrets get secret Stand-ins, as in every Harness pod: `jr2 up` mints them with
|
|
46
|
+
the signing key when it writes that pod's Secrets, as it does for the Instance Effector's key
|
|
47
|
+
([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)). It starts no confined child, so this
|
|
48
|
+
is for consistency, not a boundary. Defense-in-depth is the bonus, not the reason.
|
|
46
49
|
- **The Instance Effector is the second Instance-level pod, beside it**
|
|
47
50
|
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)), on the same convention: `jr2 up`
|
|
48
51
|
converges it when a registered Machine places an Effect there — an `http` Effect — and deletes it otherwise. It runs
|
|
@@ -24,9 +24,9 @@ machine is the one piece of its vocabulary that pattern missed.
|
|
|
24
24
|
Thus a `workspace()` or `pool()` wrapper with its own door passes. A `.provide()` clone keeps the config of its
|
|
25
25
|
original, so the walk gets the same result for the clone. A Machine with no `input`, or with an `input` that is not
|
|
26
26
|
zod, can use `setup()`. The `ping` Workflow that `jr2 init` writes uses it. The walk checks the registered roots only,
|
|
27
|
-
because
|
|
28
|
-
when you call it. It reads the config and the attached door, so it also refuses a body that
|
|
29
|
-
and rejected:
|
|
27
|
+
because a run starts at a root, and a door under a parent is parsed at invoke (see below). `workspace()` refuses a
|
|
28
|
+
body with a zod `input` when you call it. It reads the config and the attached door, so it also refuses a body that
|
|
29
|
+
`setup()` made. Considered and rejected:
|
|
30
30
|
- Refuse all `setup()` Machines. This adds work to the first Workflow and gives nothing.
|
|
31
31
|
- Refuse at boot. Then an Assembly that is already published stops the Orchestrator. The walk refuses only the next
|
|
32
32
|
publish.
|
|
@@ -34,7 +34,7 @@ machine is the one piece of its vocabulary that pattern missed.
|
|
|
34
34
|
two ways to declare one door. Also, the zod schema stays in `machine.config`, and a schema must not go into the
|
|
35
35
|
fingerprint and serialization paths that read the config. And that Machine does not get the Vocabulary checks of
|
|
36
36
|
`jr2Setup`.
|
|
37
|
-
- **A wrapper declares its own door; it never borrows its child's.** `workspace(body, { input
|
|
37
|
+
- **A wrapper declares its own door; it never borrows its child's.** `workspace(body, { input })` and
|
|
38
38
|
`pool(worker, { input, … })` each take an `input` of their own, and neither propagates one upward. The reason is the
|
|
39
39
|
same in both, at different strengths: a wrapper does not feed its child what a caller sent. `pool` is the sharp case —
|
|
40
40
|
a worker is fed per-item, never the run body, so propagating its schema would demand fields the pool never passes and
|
|
@@ -55,10 +55,10 @@ machine is the one piece of its vocabulary that pattern missed.
|
|
|
55
55
|
it was rejected because the author then chooses between two names for one statement, and finds the second only after
|
|
56
56
|
the opaque error. A body that _still_ declares a schema is refused by `workspace()` with a message naming the fix:
|
|
57
57
|
nothing would ever serve or validate it, and a contract nobody enforces is worse than none.
|
|
58
|
-
- **The door types the mapping, and constrains the body.**
|
|
59
|
-
schema (`z.infer`), so a workflow states its shape once and the workspace mapping is checked against it — the
|
|
60
|
-
this ADR exists to close, closed in the one place that used to re-declare the shape by hand. The same schema
|
|
61
|
-
the _body_, in **one direction only**: the body may not demand more than the wrapper will hand it, which is
|
|
58
|
+
- **The door types the mapping, and constrains the body.** A per-run Repo Slot's mapper reads its `input` from the
|
|
59
|
+
declared schema (`z.infer`), so a workflow states its shape once and the workspace mapping is checked against it — the
|
|
60
|
+
drift this ADR exists to close, closed in the one place that used to re-declare the shape by hand. The same schema
|
|
61
|
+
checks the _body_, in **one direction only**: the body may not demand more than the wrapper will hand it, which is
|
|
62
62
|
`Workspaced<door, slots>`; demanding _less_ is safe, because it is fed a superset. So a body reading a field the door
|
|
63
63
|
never carries is a build error at the `workspace()` call instead of an `undefined` mid-run. The guard counts the raw
|
|
64
64
|
door as provided beside `Workspaced<door, slots>`: for the door `z.object({})` the two differ, and a body that writes
|
|
@@ -75,22 +75,21 @@ machine is the one piece of its vocabulary that pattern missed.
|
|
|
75
75
|
a parent invoking a nested `workspace()` has its input mapper checked against that wrapper's door, and gets the body's
|
|
76
76
|
output typed back. With no schema declared the door is permissive, so there is nothing to infer and the mapper's
|
|
77
77
|
argument is `unknown` — an honest "jr2 does not know", not `any`; a wrapper fed by something other than a caller (a
|
|
78
|
-
pool worker) states what it is fed by annotating the parameter, as `PoolSpec.cap`/`itemInput` do.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
83
|
-
For the door `z.object({})` that input is `{}`, the door without its `Record<string, never>`, so a parent can give
|
|
84
|
-
`instanceId` to a nested `spec` that reads it; with the raw `z.infer` it could not, and the nested Workspace then
|
|
85
|
-
pushed each run to `fix/undefined`. `z.infer<…> & Partial<HostInjectedInput>` was the alternative; it was rejected
|
|
86
|
-
because the index signature makes `instanceId` `never` there too. A per-run Repo Slot's mapper keeps the door alone,
|
|
87
|
-
because a url does not come from a run's id.
|
|
78
|
+
pool worker) states what it is fed by annotating the parameter, as `PoolSpec.cap`/`itemInput` do. The input of the
|
|
79
|
+
wrapper stays the door alone, so no parent must send `instanceId`, and a nested body that reads it needs a parent that
|
|
80
|
+
gives it. For the door `z.object({})` that input is `{}`, the door without its `Record<string, never>`, so a parent
|
|
81
|
+
can give `instanceId` to such a body; with the raw `z.infer` it could not. A per-run Repo Slot's mapper keeps the door
|
|
82
|
+
alone, because a url does not come from a run's id.
|
|
88
83
|
- **The schema is structure, so it is open.** Workflow-detail JSON (`GET /workflows/:name`, the address ADR-0032's
|
|
89
84
|
negotiation kept) serves it as JSON Schema — same band as the Machine doc, and what drives the Console's start form
|
|
90
85
|
before any token is entered. The _submit_ stays `authenticated`; schema open, trigger guarded.
|
|
91
86
|
- **A declared schema is enforced at the door.** `POST /workflows/:name/runs` validates the body and 400s naming the
|
|
92
87
|
accepted shape — parity with what Gates already do for deliveries. No schema declared → accept anything (today's
|
|
93
|
-
behavior), and the Console falls back from a generated form to a raw-JSON textarea.
|
|
88
|
+
behavior), and the Console falls back from a generated form to a raw-JSON textarea. A wrapper's door is parsed under a
|
|
89
|
+
parent too, so a parent that passes less than a `workspace()` or a `pool()` requires fails at invoke, before any pod.
|
|
90
|
+
That is a defect of the parent's Machine, so it goes to the parent's `onError`, and a Pool's `itemInput` is held to
|
|
91
|
+
the door of a `workspace()` worker in the same way. A plain child Machine's door is a type: no wrapper reads it at run
|
|
92
|
+
time.
|
|
94
93
|
- **The door takes an idempotency key, out of band.** A start may carry one in a header, never in the body (a body field
|
|
95
94
|
would be one every door had to reserve); a repeat with the same key answers with the run the first one made. A
|
|
96
95
|
`dispatch` Effect sends its ledger row's id ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), so a
|
|
@@ -101,14 +101,14 @@ every build) and promotes incidental image properties into contract items.
|
|
|
101
101
|
its first provision, in the `preflight` init container's log, with an error that names the fix (because "node did not
|
|
102
102
|
execute" is not actionable) — never as a mid-turn tool failure, and never as a converge refusal for a contract the
|
|
103
103
|
image was not under.
|
|
104
|
-
- **`workspace()` names the image as a STATIC OPTION, never
|
|
105
|
-
wrapper's business in the same way its
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
- **`workspace()` names the image as a STATIC OPTION, never from run input** — what the Sandbox is _made of_ is the
|
|
105
|
+
wrapper's business in the same way its clones are, and it is a fact about the MACHINE, not about a run: `jr2 up` finds
|
|
106
|
+
it by walking the registered Machines ([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), and no
|
|
107
|
+
walk can evaluate a function of run input. The User Container rides the same rule and the same resolution as one more
|
|
108
|
+
option beside `image` (`user?: string`, [ADR-0005](0005-sandbox-pod-composition.md)). Neither is ever persisted: the
|
|
109
|
+
option rides the Machine (a parts map keyed on `machine.config`, as the vocabulary is) and the provisioning state
|
|
110
|
+
re-reads it at invoke time, so a restore provisions what the Machine carries now — and a resolved content-addressed
|
|
111
|
+
tag, which would outlive the image it names, never leaves the port's side of the seam at all.
|
|
112
112
|
- **A Menu-only Agent never gets a Sandbox Image.** `workspace: "none"` withholds the entire Working toolset (ADR-0028),
|
|
113
113
|
so there is no tooling to carry; the Instance Harness (ADR-0031) runs the stock image permanently. The feature has
|
|
114
114
|
exactly one seat, and it is the Workspace.
|
|
@@ -31,11 +31,11 @@ because they are one problem: **what a Machine depends on that is not inside it.
|
|
|
31
31
|
names of the secrets it names — or the `open` sentinel where a package left the url, the argv or a secret's name for
|
|
32
32
|
its composer ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). `jr2 up` reads them
|
|
33
33
|
without running anything, and a secret named there is what places it.
|
|
34
|
-
- **The Sandbox Image is a `workspace()` option**, static: `workspace(body, { input, image, repos,
|
|
35
|
-
is ADR-0051's, and follows the same rule). `image` is a string in one of two shapes — a `file:` URL
|
|
34
|
+
- **The Sandbox Image is a `workspace()` option**, static: `workspace(body, { input, image, repos, workGroup })`
|
|
35
|
+
(`repos` is ADR-0051's, and follows the same rule). `image` is a string in one of two shapes — a `file:` URL
|
|
36
36
|
(`import.meta.resolve("./image")`, a docker context the module ships) or a registry ref — or absent, which keeps
|
|
37
|
-
ADR-0037's fallback. It
|
|
38
|
-
|
|
37
|
+
ADR-0037's fallback. It is static because `jr2 up` must find it with no run. It is never persisted: the provisioning
|
|
38
|
+
state re-reads it off the Machine on restore.
|
|
39
39
|
- **The definition rides the Turn.** The admission body carries the slot's definition; the Harness runs what it was
|
|
40
40
|
handed and re-reads it per Submission as before. The Instance roster (`agents/`, then `config.agents`), its ConfigMap,
|
|
41
41
|
and `JR2_AGENTS_JSON` retire — a flat roster cannot hold two `researcher`s, and a Machine edit already needs `jr2 up`
|