@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/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# A Repo is a slot on the `workspace()` and a cache on the node
|
|
2
2
|
|
|
3
3
|
ADR-0049 moved Agents and the Sandbox Image into the Machine and left one dependency outside it: the Repo, catalogued in
|
|
4
|
-
`jr2.config.ts` and named by string from a `workspace()
|
|
4
|
+
`jr2.config.ts` and named by string from a `workspace()`, with the name typed back through a `Register` interface
|
|
5
5
|
(ADR-0050). The grill of 2026-09-13 found two faults. A packaged Machine still could not say which repository it works
|
|
6
6
|
on, nor could a consumer bind one, except by the consumer's config holding the right NAME and the door threading it —
|
|
7
7
|
the same encapsulation gap ADR-0049 closed for Agents. And the catalog's shape was dictated by storage: one read-only
|
|
@@ -20,28 +20,32 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
20
20
|
target: open, // the consumer's — must be bound
|
|
21
21
|
docs: { url: "https://github.com/acme/handbook.git", ref: "v3" }, // the package's own
|
|
22
22
|
},
|
|
23
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
24
23
|
});
|
|
25
24
|
```
|
|
26
25
|
|
|
27
26
|
A slot is in one of three states, and the walk tells them apart without evaluating anything: **bound** (a url, or
|
|
28
27
|
`{ url, ref? }`), **open** (the `open` sentinel — someone downstream binds it), or **per-run** (a mapper over the
|
|
29
28
|
wrapper's door: `feature: ({ input }) => ({ url: input.feature.repo, ref: input.feature.baseRef })`). `ref` is the
|
|
30
|
-
base
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
Binding's base: `default/` stays on it, and a new branch starts from it. A branch is attached, and a tag or a commit
|
|
30
|
+
is detached. Absent, the base is the repository's own default branch. The body's handles are keyed by slot
|
|
31
|
+
(`workspace.repos.target` is `/work/target`), and so are the in-pod paths (`/work/<slot>/<name>`): a slot key is
|
|
32
|
+
collision-free by construction and the same in every Instance that consumes the package, so a prompt can name a path
|
|
33
|
+
and be right everywhere.
|
|
35
34
|
|
|
36
|
-
- **The handles are the slot map
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
- **The handles are the slot map — no slot is the `workdir`.** `workspace.repos` keeps the slots in declaration order,
|
|
36
|
+
and jr2 reads nothing into that order. A handle is the slot's folder and not a Worktree: the Workspace only clones,
|
|
37
|
+
and a Machine cuts each Worktree at `/work/<slot>/<name>`
|
|
38
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). Which checkout an Agent works in is a
|
|
39
|
+
fact about that Agent's Turn, not about the Workspace: a body with a `backend` and a `frontend` slot frames one Agent
|
|
40
|
+
with a Worktree of the first and another with a Worktree of the second, and either may read the other's tree. An
|
|
41
|
+
Effect names its Worktree as a Turn does, in its input: a `command` and an `http` take no `slot`, and a `push` keeps
|
|
42
|
+
its static `slot`, which says the Repo Slot whose credential it spends. A slot that is only read needs no Worktree: an
|
|
43
|
+
Agent reads `/work/<slot>/default`. A body that names no slot may give the order a meaning of its own and document it
|
|
44
|
+
(`task`: the first slot is the one the coder edits). The order is the kit's promise — a slot key starts with a letter,
|
|
45
|
+
because JS reads an integer-like key first wherever it was written — and the meaning is the Machine's. Rejected: a
|
|
46
|
+
`workdir` handle, the first declared slot's worktree — it made a singular "primary repo" a property of every
|
|
47
|
+
Workspace, which is wrong for a body whose Agents work in different slots or in more than one, and it hid a rule in
|
|
48
|
+
key order that the body never chose.
|
|
45
49
|
|
|
46
50
|
- **A Machine whose body names no slot declares the whole map Open: `repos: open`.** A body that enumerates whatever is
|
|
47
51
|
attached — `@jr2/machines`'s `task` — knows nothing a slot name could say, and a `target: open` on it would claim "one
|
|
@@ -61,21 +65,18 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
61
65
|
have a second Agent edit it, upload it. Its steps share `/work`, as the steps of one GitHub Actions job share that
|
|
62
66
|
job's folder; two Workspaces are two jobs and share nothing. So `workspace(body, { input })` with no `repos` is a
|
|
63
67
|
Workspace with no Repo Slot: the attach is skipped (there is nothing to clone, and the pod's `fsGroup` already makes
|
|
64
|
-
`/work` the work group's), the body's handles are `{ repos: {} }
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Lost Sandbox takes everything with it — with a repository, pushed commits outlive the pod; here only what an Effect
|
|
74
|
-
sent out does, and the body hears `workspace.lost` as any body does. Storage that outlives the pod, the job's
|
|
68
|
+
`/work` the work group's), the body's handles are `{ repos: {} }`, typed `Workspaced<…, never>`, and a Turn that
|
|
69
|
+
frames no `cwd` and a `command` both work in `/work`. `repos: {}` is refused with the pointer to leaving it out, so
|
|
70
|
+
"none" has one spelling and an empty map never reads as one somebody forgot to fill. An Open map still asks
|
|
71
|
+
`customize` for at least one slot: Open says the composer names the slots, and a body that enumerates its checkouts
|
|
72
|
+
(`task`) has nothing to do with none. Three Effects need a slot and are refused there, by `jr2 up` and at invoke, for
|
|
73
|
+
one reason: a `worktree()` cuts in a slot's clone, a `push` publishes a slot's branch, and a `command` with an Effect
|
|
74
|
+
secret runs against one commit of it (ADR-0067). A file-body `http` reads `/work` and stays. `/work` is an `emptyDir`,
|
|
75
|
+
so a Lost Sandbox takes everything with it — with a repository, pushed commits outlive the pod; here only what an
|
|
76
|
+
Effect sent out does, and the body hears `workspace.lost` as any body does. Storage that outlives the pod, the job's
|
|
75
77
|
"artifacts", is a decision of its own. Rejected: a placeholder repository bound to satisfy the rule — a clone nobody
|
|
76
|
-
uses, a
|
|
77
|
-
|
|
78
|
-
needed.
|
|
78
|
+
uses, a Worktree that means nothing, a `cwd` that each Turn must then state, and a cache entry to warm. Rejected: a
|
|
79
|
+
new term for the repository-less Sandbox — the Glossary's Workspace never named a repository, so none was needed.
|
|
79
80
|
|
|
80
81
|
- **The consumer binds with `customize`**, in the same call as Agents and the image, nested through `actors` like every
|
|
81
82
|
other part: `customize(codeReview, { repos: { target: "git@github.com:ourorg/app.git" } })`. Any of the three forms is
|
|
@@ -89,6 +90,14 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
89
90
|
is the thing, not a reference into someone else's file. Two spellings of one repository (`https://`, `git@…:`,
|
|
90
91
|
`ssh://`, with or without `.git`) resolve to one identity — host plus path, scheme and user dropped — and therefore
|
|
91
92
|
one cache. A door that offers a menu of repositories is the Instance's own `z.enum` of urls.
|
|
93
|
+
- **The cache key names one identity.** The key is the Repo CR's name, the hostPath folder, the volume name `repo-<key>`
|
|
94
|
+
and the mount `/repos/<key>`: a slug of the identity's last segment, at most 16 characters, a dash, and the first 32
|
|
95
|
+
hex digits (128 bits) of the identity's sha256, at most 49 characters in all. No one can find two identities with one
|
|
96
|
+
key, and a per-run url is the Agent's text, so a shorter hash is a way into another Repo's cache: with 32 bits, a
|
|
97
|
+
search found a url of an attacker's repository with the key of a private one in seconds. Each Repo CR states its
|
|
98
|
+
identity (`jr2.dev/identity`). The Orchestrator refuses to write a CR whose stated identity is not the one that it
|
|
99
|
+
attaches or binds, and names both urls: no url, no `secretRef` and no clock of one repository is written on a CR of
|
|
100
|
+
another.
|
|
92
101
|
- **The cluster keeps a bare cache per node, not a volume.** Each repository is a `Repo` custom resource the operator
|
|
93
102
|
reconciles (spec: url, refresh interval; status: per node — synced, generation, last error). A cache agent runs as a
|
|
94
103
|
DaemonSet per Instance over its Sandbox nodes (ADR-0052) with a hostPath directory: it clones a repository onto its
|
|
@@ -100,9 +109,8 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
100
109
|
on-demand fetch; every cache speaks git protocol v2, so a fetch advertises only the refs it asks for; and the refresh
|
|
101
110
|
interval carries ±20% jitter, like the Lease, so R Repos on N nodes do not hit a remote on one beat (scaling review
|
|
102
111
|
R13, 2026-09-30). The agent also consolidates a cache's packs without deleting an object (ADR-0004). The
|
|
103
|
-
Orchestrator's attach then runs unchanged: `git clone --shared` off the mount
|
|
104
|
-
|
|
105
|
-
run on any node.
|
|
112
|
+
Orchestrator's attach then runs unchanged: `git clone --shared` off the mount. A Sandbox that lands on a cold node
|
|
113
|
+
pays one clone there, once — the same economics as an image pull, and the reason a Sandbox can run on any node.
|
|
106
114
|
- **The Orchestrator creates `Repo` CRs; it does not sync them.** At boot it walks its registered Machines and creates
|
|
107
115
|
one CR per bound identity, so a statically known repository is KNOWN before a run can ask: the cache agent on every
|
|
108
116
|
node probes it (`git ls-remote`) as soon as the CR exists, so a wrong url or a credential that does not reach shows in
|
|
@@ -123,10 +131,13 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
123
131
|
whose clone Secret `git-clone.json` does not list gets none, and the clone is anonymous; the cache agent reads only
|
|
124
132
|
that Secret, and the operator matches nothing. The Orchestrator writes and reads no git credential (ADR-0068). The
|
|
125
133
|
Secret uses Flux's key names (`username`/`password`; `identity`, `identity.pub`, `known_hosts`), so a Flux or Argo
|
|
126
|
-
user reuses the Secret they have.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
user reuses the Secret they have. With a token, the cache agent's helper answers for the Repo's own origin alone
|
|
135
|
+
(scheme, host and port), and its clone, probe and fetch follow no redirect, so the token never reaches an origin
|
|
136
|
+
outside the entry. A repository that moved, and that its old url redirects to, is cloned by its new url. **The list is
|
|
137
|
+
also the fence.** A per-run url — a run input, a ticket field — is otherwise a way to spend the cluster's credential
|
|
138
|
+
against any host: one that matches no entry is refused at attach, naming the list. A static binding is code the
|
|
139
|
+
Instance typechecked and deployed, so it is admitted without a match and cloned anonymously; a private one then fails
|
|
140
|
+
at the cache with git's error. `jr2 init` scaffolds one wildcard entry —
|
|
130
141
|
`{ match: "*", token: "JR2_GIT_TOKEN", sshKey: "jr2-git-ssh" }`, today's two implicit defaults made visible, with a
|
|
131
142
|
comment saying to narrow it before anything untrusted can start a run, and that it also pushes: a `push()` whose Repo
|
|
132
143
|
Slot url it matches, a per-run url included, spends the same token or key — so the open fence is in the user's file
|
|
@@ -198,15 +209,15 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
198
209
|
the kit's first cluster requirement a storage class most small clusters lack.
|
|
199
210
|
- **A Flux-style `GitRepository` serving an artifact over HTTP.** Rejected: an artifact download is a clone per Sandbox
|
|
200
211
|
— the cost the cache exists to avoid.
|
|
201
|
-
- **Urls
|
|
202
|
-
walk cannot see inside
|
|
203
|
-
would have nothing to bind.
|
|
212
|
+
- **Urls from a per-run function of the `workspace()`, not the slot** (the shape before this decision, with a url in
|
|
213
|
+
place of a name). Rejected: the walk cannot see inside a function, so a bound repository would be invisible to
|
|
214
|
+
`jr2 up` and to prewarm, and `customize` would have nothing to bind.
|
|
204
215
|
|
|
205
216
|
## Consequences
|
|
206
217
|
|
|
207
218
|
- The operator becomes git-aware, which ADR-0001 kept it from being: the `Repo` CRD and the cache agent are its. The
|
|
208
|
-
Sandbox CRD stays free of git semantics beyond a list of identities; clone
|
|
209
|
-
|
|
219
|
+
Sandbox CRD stays free of git semantics beyond a list of identities; the clone remains the Orchestrator's post-`Ready`
|
|
220
|
+
step, and a Worktree is a Machine's cut (ADR-0004).
|
|
210
221
|
- `workspace()`'s `repos` names at least one slot, or is `open` whole — in which case the `customize` that names the map
|
|
211
222
|
is held to the same rule — or is left out, for a Workspace with no Repo Slot; a Machine that composes no Sandbox has
|
|
212
223
|
no repos to customize, and the data-plane switch ("does this Instance need Sandboxes") is "a registered Machine
|
|
@@ -214,12 +225,12 @@ decision: **what a Machine says about a repository, and what the cluster does wi
|
|
|
214
225
|
Sandboxes, the cache agent and the Sandbox Images; a deployed Orchestrator wires its kube client whatever its current
|
|
215
226
|
Assembly composes, because a publish can bring the first `workspace()` without a roll
|
|
216
227
|
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)).
|
|
217
|
-
-
|
|
218
|
-
|
|
228
|
+
- A slot has no read-only mode, and needs none: a slot in which a Machine cuts nothing holds only `default/`, on the
|
|
229
|
+
Binding's base.
|
|
219
230
|
- The Console's start form loses the catalog dropdown: a per-run repository is a url field unless the Machine's door
|
|
220
231
|
enumerates its own.
|
|
221
|
-
- The volume paths change: the in-pod source mount is `/repos/<key>` where `<key>` is
|
|
222
|
-
pod-local layout is `/work/<slot>/{default,<
|
|
232
|
+
- The volume paths change: the in-pod source mount is `/repos/<key>` where `<key>` is the identity's cache key, and the
|
|
233
|
+
pod-local layout is `/work/<slot>/{default,<name>}`. Existing checkouts on a pre-0051 PVC are not migrated.
|
|
223
234
|
- ADR-0047's deploy-key prompt reads ssh urls off the walk. A per-run ssh url first seen at attach was never prompted
|
|
224
235
|
for; its first attach fails with the key hint, as any unregistered key does.
|
|
225
236
|
- A `customize` of a per-run slot with a static url is legal and makes it bound; a Machine that wants a slot to stay the
|
|
@@ -27,6 +27,11 @@ use. **Which nodes are an Instance's Sandbox nodes, and who follows that set** i
|
|
|
27
27
|
Sandbox node, so a cache is only ever present where a Sandbox can land, and the affinity it feeds only ever names a
|
|
28
28
|
usable node. The build set (ADR-0045) is the union of the nodes an ordinary pod lands on — the Orchestrator
|
|
29
29
|
Deployment's own placement — and the Sandbox nodes, intersected with the supported set as before.
|
|
30
|
+
- **The cache agent also reports Landlock for its node.** It probes Landlock when it starts, and on a node without it
|
|
31
|
+
the agent is NotReady, with the reason. `jr2 up` reads the DaemonSet's pods and refuses to converge while a Sandbox
|
|
32
|
+
node lacks Landlock, naming each one ([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)). This is the
|
|
33
|
+
one refusal on the node set: Landlock is a fact that each Sandbox node must have, not a set that can be empty. The
|
|
34
|
+
admin keeps a bad node out with `sandbox.nodeSelector` or a taint.
|
|
30
35
|
- **The Sandbox CR carries the placement.** `Sandbox.spec` gains `nodeSelector` and `tolerations` beside `resources`,
|
|
31
36
|
`initContainers`, and `volumes`; the Orchestrator writes them from its baked config at provision and the operator
|
|
32
37
|
copies them onto the pod, merging nothing. The operator stays config-blind (ADR-0001), and every Instance's placement
|
|
@@ -29,8 +29,8 @@ a fetch inside a pod reaches the remote, and who may ask** is one decision.
|
|
|
29
29
|
Binding's own spelling, the caller's own credential, never the Agent's (ADR-0005).
|
|
30
30
|
|
|
31
31
|
- **The ask rides the Sandbox CR and reaches the agent through the pod.** The program asks the Custodian on `localhost`
|
|
32
|
-
(`POST /fetch`, with the
|
|
33
|
-
puts the token
|
|
32
|
+
(`POST /fetch`, with the fetch token's Stand-in — a constant, because the User Container gets no env); the Custodian
|
|
33
|
+
puts the fetch token in place of the Stand-in, on this route alone and asks the Orchestrator for THIS pod's Sandbox
|
|
34
34
|
(`POST /sandboxes/<name>/fetch` — the program cannot name another,
|
|
35
35
|
[ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)); the Orchestrator patches one
|
|
36
36
|
annotation on the Sandbox CR, per Repo key, timestamp value — the Lease's shape, an annotation the Orchestrator
|
|
@@ -45,16 +45,28 @@ a fetch inside a pod reaches the remote, and who may ask** is one decision.
|
|
|
45
45
|
sticky, once per pod life. The Orchestrator's route waits on that entry — a Sandbox watch event, never a poll
|
|
46
46
|
([ADR-0063](0063-the-orchestrator-watches-the-cluster.md)) — then answers the Custodian, which answers the program.
|
|
47
47
|
One wait, and it is the wait an attach already makes.
|
|
48
|
-
- **The scope is the
|
|
49
|
-
and
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
the
|
|
48
|
+
- **The scope is the fetch token's scope, and no seat gains a credential.** The fetch token is valid on the fetch route
|
|
49
|
+
alone, and the control token is not valid there
|
|
50
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). A Sandbox may ask for the
|
|
51
|
+
caches it mounts and nothing else; the remote is reached by the cache agent with the Repo CR's `secretRef`, the
|
|
52
|
+
credential that cloned the cache. The pod holds none before and none after. The Menu is untouched — a fetch is not
|
|
53
|
+
something the Agent _says_, it is something it _does_ — and no Machine declares anything: the ability is ambient in
|
|
54
|
+
every Workspace pod, the way Working tools are, and absent on the Instance Harness, which mounts no Repo.
|
|
53
55
|
- **Freshness degrades, absence does not, and the caller is told.** A remote fetch that fails or outruns the on-demand
|
|
54
56
|
budget lets the program fall through to the objects the cache holds — ADR-0051's stance for attach — and writes one
|
|
55
57
|
line to stderr, which git passes through verbatim:
|
|
56
58
|
`warning: jr2: remote fetch failed (<git's error>); serving the cache as of <lastFetched>`. The budget is the cache
|
|
57
|
-
agent's one on-demand number; the program's wait is that plus watch slack, and nothing else owns a timeout.
|
|
59
|
+
agent's one on-demand number; the program's wait is that plus watch slack, and nothing else owns a timeout. A cut
|
|
60
|
+
reads that line too: its output carries the line in `stale`, and the cut uses what the clone holds. A branch name that
|
|
61
|
+
the clone does not hold is then `refused`, because the remote cannot confirm that it is new
|
|
62
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
|
|
63
|
+
- **One fetch at a time in a clone.** All Worktrees of a clone share `refs/remotes/origin/*`, and git updates a ref only
|
|
64
|
+
if it still holds the value that the fetch read. So two fetches that overlap on a moved remote can fail with
|
|
65
|
+
`cannot lock ref`. The failure does no harm, but it costs a Turn. So the program takes a per-clone lock after the ask,
|
|
66
|
+
so a dead remote does not make coders wait in line. `git upload-pack` can end before the fetch writes its refs: that
|
|
67
|
+
happens when a pack moves. In a shared clone, git often keeps the connection open to the end. The lock must last to
|
|
68
|
+
the end of the fetch in both cases, so a small child process that the program leaves watches the fetch and holds the
|
|
69
|
+
lock. ADR-0075 holds the measurements.
|
|
58
70
|
- **Every seat that holds the checkouts holds the program.** The fetch url lives in the shared `default/.git/config`, so
|
|
59
71
|
a seat without the program has checkouts whose `git fetch` dies. That is ADR-0005's own argument for `/repos` — half
|
|
60
72
|
of one exception, not a second — applied once more: the pod's `/opt/jr2` volume is mounted read-only into the User
|
package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
CHANGED
|
@@ -22,24 +22,24 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
|
|
|
22
22
|
default: `agent({ model, instructions, workspace?, thinkingLevel?, description? })`.
|
|
23
23
|
|
|
24
24
|
- **The Frame is `prompt` and `cwd`: what this Turn is about and where it works.** Neither identity nor a Dial. A
|
|
25
|
-
worktree path is per run by nature — `/work/<slot>/<
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
worktree path is per run by nature — `/work/<slot>/<name>` exists only once a run cuts it — so no definition can name
|
|
26
|
+
it, and a directory is not a property of a persona any more than a prompt is. `cwd` leaves `AgentDefinition`. The
|
|
27
|
+
Harness roots the Working tools at the Frame's `cwd`, which rides the admit body beside the prompt and the Dials.
|
|
28
28
|
|
|
29
29
|
- **The Briefing is not the Frame.** jr2's Turn part
|
|
30
30
|
([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)) follows the prompt — the resolved working
|
|
31
31
|
directory, the Allowed picks and any notices — in a block of its own. The prompt stays the Machine state's words
|
|
32
32
|
alone; jr2 never writes into it.
|
|
33
33
|
|
|
34
|
-
- **Under
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
- **Under Repo Slots, `cwd` is required.** A Workspace that only clones has no Worktree for a default to name
|
|
35
|
+
([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), and `/work` is the wrong default: an
|
|
36
|
+
Agent's search hits `default/`, and a relative write lands outside each Worktree, which is the incident above. So the
|
|
37
|
+
state says where — `cwd: "/work/target/fix"` — and the actor refuses the Turn before admission, naming the slots and
|
|
38
|
+
the line to add; `jr2 up` cannot see it because the invoke's input is a function. A Turn that wants `/work` states it.
|
|
39
|
+
A Workspace with no Repo Slot needs no `cwd`: its Turns work in `/work` (ADR-0051). A `workspace: "none"` Agent has no
|
|
40
|
+
Working tools and no `cwd` (ADR-0028); a stub-Harness run with no Workspace keeps `/work`. `task` passes its `cwd` as
|
|
41
|
+
any Machine does: "the first slot is the one the coder edits" is its convention, and a convention is stated, not
|
|
42
|
+
defaulted.
|
|
43
43
|
|
|
44
44
|
- **`continue: true` is the whole continuation surface.** Absent, every invocation is a fresh conversation — jr's lossy
|
|
45
45
|
handoff, ADR-0016's default, unchanged. Present, the Turn lands on **this Agent's one conversation in this Machine
|
|
@@ -112,11 +112,11 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
|
|
|
112
112
|
that is wrong under every `workspace()` is not a default.
|
|
113
113
|
- **`cwd` as a third Dial.** Rejected on the word: Dials say how hard to run. A directory says where, which is the same
|
|
114
114
|
kind of fact as the prompt — hence Frame.
|
|
115
|
-
- **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`").
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
- **Default `cwd` to the first Repo Slot.** Rejected: reverses ADR-0051 ("there is no `workdir`").
|
|
116
|
+
- **Default `cwd` to the only Repo Slot's Worktree.** Rejected: a Workspace that only clones has no such Worktree. And a
|
|
117
|
+
refusal under every Workspace with Repo Slots has no composition trap: with a default, a composer who adds a second
|
|
118
|
+
slot on the `customize` line makes a Machine that ran yesterday refuse today, in a file the Machine's author never
|
|
119
|
+
sees. The cost is one line per invoke.
|
|
120
120
|
- **A caller-chosen conversation key (`conversation: "coder"`).** Rejected: it either restates the structure or
|
|
121
121
|
contradicts it. Path-scoping the key was proposed and made the key redundant — once the id is
|
|
122
122
|
`<run>/<machine path>/<agent>`, the author has nothing left to name.
|
|
@@ -133,19 +133,19 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
|
|
|
133
133
|
- `AgentTurnInput` is `{ prompt, cwd?, continue?, model?, thinkingLevel? }`; `AgentDefinition` loses `cwd`. The Harness
|
|
134
134
|
admit body gains the Frame's `cwd`; `ResolvedDefinition.cwd` resolves from it, not the definition; the Harness stops
|
|
135
135
|
validating `description`, which the Orchestrator strips before admission (it is Console material).
|
|
136
|
-
- **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case,
|
|
137
|
-
says `/work` — a stated default. A relative path has no such
|
|
138
|
-
Harness process sits in, which is the silent wrong directory
|
|
139
|
-
author. The refusal is a 400 at admission, before a pod is spent.
|
|
140
|
-
- `task` passes
|
|
141
|
-
`turns`, reset on `agent.fault`, because re-briefing is prompt policy.
|
|
136
|
+
- **A Frame's `cwd` is absolute or the admission is refused**, naming the Frame. Absent is the one silent case, it
|
|
137
|
+
reaches a Harness only where no Repo Slot is, and it says `/work` — a stated default. A relative path has no such
|
|
138
|
+
answer: it would resolve against whatever directory the Harness process sits in, which is the silent wrong directory
|
|
139
|
+
this ADR was written for, one level further from the author. The refusal is a 400 at admission, before a pod is spent.
|
|
140
|
+
- `task` passes the path of the Worktree it cuts as `cwd`, and `continue: true`, and drops `generation` (and its
|
|
141
|
+
`scope`). It keeps `turns`, reset on `agent.fault`, because re-briefing is prompt policy.
|
|
142
142
|
- The ledger record for a continued conversation carries its epoch; the input mapper reads it at mint time, so the
|
|
143
143
|
minted id persists with the input and restore re-attaches the same conversation (ADR-0016's mapper rule holds — the
|
|
144
144
|
fresh suffix is still the random component).
|
|
145
145
|
- The `@kind` fixtures drop `tools:` and `instanceId:`; the steps compute the continued id from the actor path, as they
|
|
146
146
|
already computed today's pin.
|
|
147
147
|
- A regression scenario for the silent success: a `task` whose model uses relative paths must produce its file inside
|
|
148
|
-
the worktree, and a
|
|
148
|
+
the worktree, and a Machine under Repo Slots with no `cwd` must be refused with the slots named.
|
|
149
149
|
- GLOSSARY.md: **Frame** enters; **Dials** loses `cwd`; **Instance ID** states the `continue` scope and that a faulted
|
|
150
150
|
conversation is not continued. ADR-0016 (continuation), ADR-0018 (identity line), ADR-0031 (placement scenario),
|
|
151
151
|
ADR-0035 (reroll gate wording), ADR-0049 (persona hazard), and ADR-0054 (`task`'s continuation) are edited to state
|
|
@@ -93,9 +93,11 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
|
|
|
93
93
|
`harness.env` or in an `envFrom` Secret.
|
|
94
94
|
- **Open gaps, recorded here and not fixed:**
|
|
95
95
|
- **Sandbox egress.** An Agent with bash can reach the internet and send out anything it can read — which no longer
|
|
96
|
-
includes a held key: a client that goes around the Custodian carries only Stand-ins.
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
includes a held key: a client that goes around the Custodian carries only Stand-ins. One exception: no Harness pod
|
|
97
|
+
reaches a cloud metadata address
|
|
98
|
+
([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). A default-deny egress
|
|
99
|
+
policy needs an allowlist: DNS, the Orchestrator, the model provider, and whatever the work needs (package
|
|
100
|
+
registries, for example). That needs a config surface and cluster CIDRs, so it is a separate decision.
|
|
99
101
|
- **A held key can be spent.** The Agent can use a key through the Custodian while its pod lives, and a SigV4 or ADC
|
|
100
102
|
credential cannot be held at all ([ADR-0059](0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md)).
|
|
101
103
|
- **The Orchestrator's Secret writes are namespace-wide.** Its Role may create and patch every Secret, and may not get
|