@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# jr2 requires Landlock on every Sandbox node
|
|
2
|
+
|
|
3
|
+
Two boundaries of jr2 are Landlock domains. [ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)
|
|
4
|
+
confines the child of a credentialed `command` in the Effector, so that the Agent's code cannot read the Effector's key
|
|
5
|
+
or a secret that the command does not name.
|
|
6
|
+
[ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md) confines each process that the
|
|
7
|
+
Harness starts in `/work`, so that no Workspace code can read the control credential and act as an Agent. On a node with
|
|
8
|
+
no Landlock, neither boundary exists. A path that runs there with less confinement is a second jr2, with a weaker
|
|
9
|
+
boundary, to support and to test.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
- **Landlock is a requirement of jr2 on every Sandbox node.** It holds where Workspace code runs, which is also where
|
|
14
|
+
the check reaches. No component has an unconfined branch, anywhere.
|
|
15
|
+
- **The Repo cache agent detects it, on each Sandbox node.** The cache agent's DaemonSet has one pod on each Sandbox
|
|
16
|
+
node, by the same `nodeSelector` and `tolerations` as the Sandboxes
|
|
17
|
+
([ADR-0052](0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md)). The agent probes Landlock
|
|
18
|
+
on its node. On a node without it, the agent is NotReady, and its status gives the reason.
|
|
19
|
+
- **`jr2 up` refuses to converge while a Sandbox node lacks Landlock.** It reads the DaemonSet's pods (one list call),
|
|
20
|
+
and it names each Sandbox node whose cache agent reports no Landlock
|
|
21
|
+
([ADR-0019](0019-one-converging-command-against-the-current-context.md)). The admin keeps a bad node out of the set
|
|
22
|
+
with `sandbox.nodeSelector` or a taint, as ADR-0052 says. There is still no jr2 node label.
|
|
23
|
+
- **The refusal is an ordinary converge failure** (ADR-0019). It comes within the cache agent's rollout wait, which
|
|
24
|
+
`jr2 up` already makes. It names each Sandbox node without Landlock, the probe's reason, and the fix: keep the node
|
|
25
|
+
out with `sandbox.nodeSelector` or a taint (ADR-0052), or enable Landlock in its kernel. A run of `jr2 up` after the
|
|
26
|
+
fix converges.
|
|
27
|
+
- **Each confining component probes at its start, and refuses to serve without it.** These are the Harness of a Sandbox
|
|
28
|
+
and the Sandbox Effector. The probe is `landlock_create_ruleset` with `LANDLOCK_CREATE_RULESET_VERSION`. This is the
|
|
29
|
+
backstop for a node that joins after the converge.
|
|
30
|
+
- **No component keeps a path for a node that cannot confine a child.** ADR-0068's Effector refuses to start on such a
|
|
31
|
+
node, whatever its Workspace holds.
|
|
32
|
+
- **The minimum ABI is 3.** Under ABI 1, a confined process can never move or link a file into another folder, whatever
|
|
33
|
+
the rules allow, and package managers, git and `mv` do that. ABI 2 adds the `REFER` right, so the domain can do it
|
|
34
|
+
(ADR-0076). Under ABI 2, Landlock does not handle `truncate(2)`, so a confined process can truncate a file that the
|
|
35
|
+
rules let it do nothing else to: a conversation under `/run/jr2`, which the Harness rebuilds an Agent's record from.
|
|
36
|
+
ABI 3 adds the `TRUNCATE` right, and the confiner always handles it. The confiner uses the scopes of abstract Unix
|
|
37
|
+
sockets and of signals (ABI 6) when the kernel offers them. Each probe refuses a kernel below ABI 3, with the ABI that
|
|
38
|
+
it found.
|
|
39
|
+
- **The components that probe.** The cache agent probes as the detector, though it confines nothing. The Harness of a
|
|
40
|
+
Sandbox and the Sandbox Effector probe because they confine. The Instance Harness and the Instance Effector confine
|
|
41
|
+
nothing: a Menu-only Agent has no Working tools ([ADR-0031](0031-menu-only-agents-run-on-the-instance-harness.md)),
|
|
42
|
+
and the Instance Effector runs no `command`. They do not probe.
|
|
43
|
+
|
|
44
|
+
## Considered options
|
|
45
|
+
|
|
46
|
+
- **A minimum of ABI 1.** Rejected: under it no confined process can move a file into another folder, which stops normal
|
|
47
|
+
work.
|
|
48
|
+
- **A minimum of ABI 2.** Rejected: under it the domain can truncate a file under `/run/jr2`. One Agent's code can then
|
|
49
|
+
erase or roll back another Agent's conversation, and below ABI 6 it can end the Harness so that the Harness rebuilds
|
|
50
|
+
from the cut file. It admits Linux 5.19 to 6.1 (Debian 12's stock kernel among them), for a boundary with a hole.
|
|
51
|
+
- **Refuse only a Sandbox with more than one Agent on a node without Landlock.** Rejected: it keeps a less secure
|
|
52
|
+
branch, which jr2 must then support and test.
|
|
53
|
+
- **Run unconfined, with a warning.** Rejected: Workspace code can then read the control credential, and a warning does
|
|
54
|
+
not stop it.
|
|
55
|
+
- **A check pod on each node at each `jr2 up`.** Rejected: it starts one pod for each node on every converge, and it
|
|
56
|
+
misses a node that joins later.
|
|
57
|
+
- **One check pod, with the start-up probe as the backstop.** Rejected: a bad node shows only when a run lands there and
|
|
58
|
+
fails.
|
|
59
|
+
- **jr2 labels the nodes that have Landlock, and a Sandbox requires the label.** Rejected: it reverses ADR-0052, and it
|
|
60
|
+
needs a cluster-wide permission to patch Nodes.
|
|
61
|
+
|
|
62
|
+
## Consequences
|
|
63
|
+
|
|
64
|
+
- **The kernels that this excludes.** Landlock ABI 3 is in Linux 6.2 and later. A node with an older kernel cannot run
|
|
65
|
+
jr2: for example RHEL 8 (4.18), Debian 11 (5.10), Debian 12 on its stock kernel (6.1), Ubuntu 20.04 and 22.04 on their
|
|
66
|
+
first kernels (5.4, 5.15), and Amazon Linux 2 (4.14 or 5.10). A distribution kernel can carry a backport: the probe,
|
|
67
|
+
not the version, decides. A newer kernel must also enable Landlock: its list of security modules (`CONFIG_LSM`, or the
|
|
68
|
+
`lsm=` boot parameter) must name `landlock`. On a node, `/sys/kernel/security/lsm` lists the modules that run.
|
|
69
|
+
Measured from a hardened pod with no capabilities: kind on WSL2 (6.6) offers ABI 3; the home cluster's arm64 nodes
|
|
70
|
+
(6.8), ABI 4; spark (6.11, arm64), ABI 5.
|
|
71
|
+
- **kind on a host inherits the host's kernel.** A kind node is a container, so the kernel of the host decides. On macOS
|
|
72
|
+
or Windows, Docker runs the nodes in a virtual machine, and the kernel of that machine decides.
|
|
73
|
+
- **A mixed cluster.** `jr2 up` names each Sandbox node without Landlock and refuses until the admin keeps it out of the
|
|
74
|
+
set. A node that joins after the converge shows as a NotReady cache agent. A Sandbox placed on it fails at its start,
|
|
75
|
+
because its Harness refuses to serve.
|
|
76
|
+
- **The check runs when a Machine uses a Sandbox.** `jr2 up` converges the cache agent then
|
|
77
|
+
([ADR-0051](0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md)). With no Sandbox, no node needs
|
|
78
|
+
Landlock, and there is nothing to check.
|
|
79
|
+
- **The docs list the requirement** where they list what jr2 needs from a cluster and from a host.
|
|
@@ -81,3 +81,6 @@ Each architecture decision record (ADR) says what jr2 decided, why, and what it
|
|
|
81
81
|
| 0072 | [The docs ship inside the Kit, and an Instance's `AGENTS.md` indexes them](decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md) |
|
|
82
82
|
| 0073 | [A Machine fails a run with `fail()`, and an uncaught Failure ends it `failed`](decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md) |
|
|
83
83
|
| 0074 | [An unrouted mechanism event is refused at load and faults the run](decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md) |
|
|
84
|
+
| 0075 | [A Machine cuts a Worktree, and the Workspace only clones](decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md) |
|
|
85
|
+
| 0076 | [An Agent's control credential lives only where no Workspace code runs](decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md) |
|
|
86
|
+
| 0077 | [jr2 requires Landlock on every Sandbox node](decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md) |
|
|
@@ -213,7 +213,16 @@ _Avoid_: humanGate (humans are one caller among many), approval (one possible ev
|
|
|
213
213
|
**Sender**:
|
|
214
214
|
The party that delivered an event: an Agent or a Gate. Only the Orchestrator writes the Sender, on each event that a
|
|
215
215
|
party delivers, and the Machine's own events have none.
|
|
216
|
-
_Avoid_: origin,
|
|
216
|
+
_Avoid_: origin, Caller (the holder of the credential behind a Gate's answer)
|
|
217
|
+
|
|
218
|
+
<a id="caller"></a>
|
|
219
|
+
|
|
220
|
+
**Caller**:
|
|
221
|
+
The holder of a credential that acts on an Instance from outside its Machines, such as a person, a CI job or a bridge
|
|
222
|
+
to a chat service: it starts a run, answers a Gate or cancels a run. A Caller can act **on behalf of** another party,
|
|
223
|
+
but that is the Caller's claim, which jr2 does not check.
|
|
224
|
+
_Avoid_: principal (it brings policies and roles that an Instance does not have), user (a CI job is a Caller), actor
|
|
225
|
+
(xstate's word), Sender (a party inside the run)
|
|
217
226
|
|
|
218
227
|
<a id="emit"></a>
|
|
219
228
|
|
|
@@ -414,7 +423,8 @@ re-prompt of an open Turn)
|
|
|
414
423
|
|
|
415
424
|
**Working tools**:
|
|
416
425
|
The file and shell tools that the Harness runs in its own container: what an Agent may do, as its Menu is what it may
|
|
417
|
-
say.
|
|
426
|
+
say. Every program that they run, and their file I/O, is confined, so it cannot reach what the Harness itself holds. The Agent's access to
|
|
427
|
+
the Workspace decides which of them it gets, and a Menu-only Agent gets none.
|
|
418
428
|
_Avoid_: tools (unqualified), sandbox tools
|
|
419
429
|
|
|
420
430
|
### Sandboxes, Harnesses and secrets
|
|
@@ -463,17 +473,37 @@ firewall
|
|
|
463
473
|
|
|
464
474
|
**Held secret**:
|
|
465
475
|
A credential that the Harness uses through its pod's Custodian but never holds: a model API key above all, and the
|
|
466
|
-
Sandbox
|
|
467
|
-
it through the Custodian while the pod lives.
|
|
476
|
+
Sandbox tokens. The Harness holds only its Stand-in, so no Agent can take the secret out of the pod. Workspace code can
|
|
477
|
+
spend it through the Custodian, while the pod lives, only when its Workspace grants that secret.
|
|
468
478
|
_Avoid_: masked secret, injected secret, proxied key, secret (unqualified)
|
|
469
479
|
|
|
470
480
|
<a id="stand-in"></a>
|
|
471
481
|
|
|
472
482
|
**Stand-in**:
|
|
473
|
-
The
|
|
474
|
-
|
|
483
|
+
The value that the Harness holds in place of a Held secret. It means something only to the Custodian, and only toward a
|
|
484
|
+
host or a route that the secret is bound to; sent anywhere else, it arrives as it left. It is secret, and only the
|
|
485
|
+
Harness itself holds it: Workspace code gets a Stand-in only for a secret that its Workspace grants, and never the one
|
|
486
|
+
for the control token. The one public Stand-in is the fetch token's, which can only ask for a fetch.
|
|
475
487
|
_Avoid_: placeholder (an Open part's word), dummy key, fake key, token, mask
|
|
476
488
|
|
|
489
|
+
<a id="sandbox-token"></a>
|
|
490
|
+
|
|
491
|
+
**Sandbox token**:
|
|
492
|
+
A credential that jr2 makes for one Sandbox, which its Custodian alone holds. There are two. The **control token** lets
|
|
493
|
+
the Harness read each Agent's Menu and deliver its picks, for the Agent that the Harness names. The **fetch token** lets
|
|
494
|
+
a process in the pod ask for a fetch of a Repo that the Sandbox holds. Each one is good only for its own work and in its
|
|
495
|
+
own Sandbox, and neither one reaches a Gate.
|
|
496
|
+
_Avoid_: pod token, agent token, slot token, Instance token (the Caller's credential)
|
|
497
|
+
|
|
498
|
+
<a id="workspace-code"></a>
|
|
499
|
+
|
|
500
|
+
**Workspace code**:
|
|
501
|
+
Each process in a Sandbox whose program or input comes from the Workspace's shared files: what an Agent's Working tools
|
|
502
|
+
run, tests and builds, git and what a repository's own config makes it run, and a command that a Machine runs there. Any
|
|
503
|
+
Agent can run what another Agent wrote, so jr2 trusts none of it, and no credential that acts for an Agent is where
|
|
504
|
+
Workspace code runs.
|
|
505
|
+
_Avoid_: agent code (any Agent can run another's), user code, untrusted code (unqualified), tool process
|
|
506
|
+
|
|
477
507
|
<a id="check-pod"></a>
|
|
478
508
|
|
|
479
509
|
**Check pod**:
|
|
@@ -629,20 +659,29 @@ _Avoid_: role, alias, repo name, workdir, primary repo
|
|
|
629
659
|
<a id="binding"></a>
|
|
630
660
|
|
|
631
661
|
**Binding**:
|
|
632
|
-
What a Repo Slot resolves to: a Repo's url and an optional base
|
|
633
|
-
|
|
662
|
+
What a Repo Slot resolves to: a Repo's url and an optional base. The Sandbox's own clone stays on that base, and a new
|
|
663
|
+
branch starts from it. With no base, both use the Repo's own default branch.
|
|
634
664
|
_Avoid_: config, catalog entry
|
|
635
665
|
|
|
636
666
|
<a id="project-layout"></a>
|
|
637
667
|
|
|
638
668
|
**Project layout**:
|
|
639
669
|
The convention for the checkouts in a Sandbox: under each Repo Slot, the Sandbox's own clone of the Repo, with the
|
|
640
|
-
Worktrees beside it. The node's cache of the Repo is not part of the
|
|
670
|
+
Worktrees beside it. An Agent reads the clone and works in a Worktree. The node's cache of the Repo is not part of the
|
|
671
|
+
layout.
|
|
641
672
|
_Avoid_: directory structure, repo tree
|
|
642
673
|
|
|
643
674
|
<a id="worktree"></a>
|
|
644
675
|
|
|
645
676
|
**Worktree**:
|
|
646
|
-
A git worktree of a Repo Slot
|
|
647
|
-
|
|
648
|
-
|
|
677
|
+
A git worktree of a Repo Slot, in a folder that a Machine names, beside the Sandbox's own clone. It holds one branch, or
|
|
678
|
+
one commit with no branch. The Machine, not jr2, chooses how many Worktrees it cuts, their branches, and which Agents
|
|
679
|
+
work in them.
|
|
680
|
+
_Avoid_: checkout, task branch
|
|
681
|
+
|
|
682
|
+
<a id="cut"></a>
|
|
683
|
+
|
|
684
|
+
**Cut**:
|
|
685
|
+
An Effect's act that makes a Worktree, or reads the one that a cut already made at that path. A cut that cannot happen
|
|
686
|
+
changes nothing.
|
|
687
|
+
_Avoid_: attach (the Workspace's step, which only clones), provision
|
|
@@ -178,8 +178,8 @@ Any token jr2 minted: the Instance token, or a Sandbox token. A Sandbox token re
|
|
|
178
178
|
|
|
179
179
|
### `GET /agents/:instanceId/surface`
|
|
180
180
|
|
|
181
|
-
- Summary: An Agent's Menu
|
|
182
|
-
- Response: [`AgentSurfaceView`](#type-agentsurfaceview)
|
|
181
|
+
- Summary: An Agent's Menu, this Turn's Allowed picks and the conversation's pending notices. A control token reads only its own pod's, for the slot it states.
|
|
182
|
+
- Response: [`AgentSurfaceView`](#type-agentsurfaceview) & { notices: Array<{ id, notice }> }
|
|
183
183
|
- Stream: no
|
|
184
184
|
- Decisions: [ADR-0013](decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md), [ADR-0029](decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)
|
|
185
185
|
|
|
@@ -202,11 +202,21 @@ Any token jr2 minted: the Instance token, or a Sandbox token. A Sandbox token re
|
|
|
202
202
|
- Stream: no
|
|
203
203
|
- Decisions: [ADR-0013](decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md), [ADR-0024](decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)
|
|
204
204
|
|
|
205
|
+
<a id="post-agents-instanceid-notices"></a>
|
|
206
|
+
|
|
207
|
+
### `POST /agents/:instanceId/notices`
|
|
208
|
+
|
|
209
|
+
- Summary: Acknowledge the notices a step boundary appended to the Turn: they are heard in its conversation.
|
|
210
|
+
- Body: { heard: string[] }
|
|
211
|
+
- Response: { heard: number }
|
|
212
|
+
- Stream: no
|
|
213
|
+
- Decisions: [ADR-0062](decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md), [ADR-0076](decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)
|
|
214
|
+
|
|
205
215
|
<a id="post-sandboxes-name-fetch"></a>
|
|
206
216
|
|
|
207
217
|
### `POST /sandboxes/:name/fetch`
|
|
208
218
|
|
|
209
|
-
- Summary: Ask the node cache to fetch one Repo that this Sandbox mounts. A
|
|
219
|
+
- Summary: Ask the node cache to fetch one Repo that this Sandbox mounts. A fetch token asks only for its own.
|
|
210
220
|
- Body: { identity }
|
|
211
221
|
- Response: [`FetchAnswer`](#type-fetchanswer): { fetched } | { stale, asOf }
|
|
212
222
|
- Stream: no
|
|
@@ -498,6 +508,7 @@ type RunStatus = RunRecord & {
|
|
|
498
508
|
chartHashes?: string[];
|
|
499
509
|
output?: unknown;
|
|
500
510
|
dispatched?: Dispatched[];
|
|
511
|
+
worktrees?: RunWorktree[];
|
|
501
512
|
};
|
|
502
513
|
```
|
|
503
514
|
|
|
@@ -554,6 +565,12 @@ A run's current observable state. `fault` carries the error message when status
|
|
|
554
565
|
The runs this run's `dispatch` Effects started, oldest first ([ADR-0071](decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md)): the chain's other
|
|
555
566
|
direction, read off the caller's own ledger rows. A foreign dispatch's run is a run of the
|
|
556
567
|
Orchestrator its target names. Absent when it started none.
|
|
568
|
+
- `worktrees` (optional): `RunWorktree[]`
|
|
569
|
+
|
|
570
|
+
The Worktrees this run's cuts made, oldest first ([ADR-0075](decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), read off its ledger's settled
|
|
571
|
+
`ready` rows, so an Agent's `git switch` does not change them. A re-cut of one path and branch
|
|
572
|
+
is one entry. Behind the token, like `context`: a branch name is the workflow's data. Absent
|
|
573
|
+
when the run cut none.
|
|
557
574
|
|
|
558
575
|
<a id="type-repostatus"></a>
|
|
559
576
|
|
|
@@ -632,6 +649,7 @@ type AgentSurfaceView = {
|
|
|
632
649
|
instanceId: string;
|
|
633
650
|
runId: string;
|
|
634
651
|
sandbox?: string;
|
|
652
|
+
slot?: string;
|
|
635
653
|
menu: SurfaceEvent[];
|
|
636
654
|
allowed: string[];
|
|
637
655
|
};
|
|
@@ -655,6 +673,10 @@ sandbox), and serving it lets a caller fail loudly on a surface that is not its
|
|
|
655
673
|
- `instanceId`: `string`
|
|
656
674
|
- `runId`: `string`
|
|
657
675
|
- `sandbox` (optional): `string`
|
|
676
|
+
- `slot` (optional): `string`
|
|
677
|
+
|
|
678
|
+
The slot this surface belongs to ([ADR-0076](decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)): its Agent's actor path and agent name — the slot
|
|
679
|
+
the Harness must state to speak here. Absent on a registration no pod hosts.
|
|
658
680
|
- `menu`: `SurfaceEvent[]`
|
|
659
681
|
- `allowed`: `string[]`
|
|
660
682
|
|
|
@@ -951,6 +973,23 @@ ledger row was written.
|
|
|
951
973
|
- `run`: `string`
|
|
952
974
|
- `at`: `string`
|
|
953
975
|
|
|
976
|
+
<a id="type-runworktree"></a>
|
|
977
|
+
|
|
978
|
+
### `RunWorktree`
|
|
979
|
+
|
|
980
|
+
```ts
|
|
981
|
+
type RunWorktree = { slot: string; path: string; branch: string | null };
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
One Worktree a `worktree()` Effect of this run cut ([ADR-0075](decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)): its Repo Slot, its path, and the
|
|
985
|
+
branch of the cut. `branch` is `null` for a detached Worktree.
|
|
986
|
+
|
|
987
|
+
**Fields**
|
|
988
|
+
|
|
989
|
+
- `slot`: `string`
|
|
990
|
+
- `path`: `string`
|
|
991
|
+
- `branch`: `string | null`
|
|
992
|
+
|
|
954
993
|
<a id="type-reponodestate"></a>
|
|
955
994
|
|
|
956
995
|
### `RepoNodeState`
|
|
@@ -1229,7 +1268,7 @@ How the party that sends an event gets its answer.
|
|
|
1229
1268
|
|
|
1230
1269
|
```ts
|
|
1231
1270
|
type EffectDoc =
|
|
1232
|
-
| { kind: "command"; argv: string[]; timeoutMs: number;
|
|
1271
|
+
| { kind: "command"; argv: string[]; timeoutMs: number; secrets?: string[]; pin?: true }
|
|
1233
1272
|
| {
|
|
1234
1273
|
kind: "http";
|
|
1235
1274
|
method: string;
|
|
@@ -1244,16 +1283,15 @@ type EffectDoc =
|
|
|
1244
1283
|
```
|
|
1245
1284
|
|
|
1246
1285
|
An Effect slot's parts, as the doc carries them ([ADR-0067](decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)): a command's argv (an argv slot reads
|
|
1247
|
-
`<name>`)
|
|
1286
|
+
`<name>`) and its timeout. Its input names its Worktree, so the doc does not ([ADR-0075](decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)).
|
|
1248
1287
|
|
|
1249
1288
|
**Forms**: it is one of these objects.
|
|
1250
1289
|
|
|
1251
|
-
- Form 1: `kind`, `argv`, `timeoutMs`, `
|
|
1290
|
+
- Form 1: `kind`, `argv`, `timeoutMs`, `secrets`, `pin`
|
|
1252
1291
|
|
|
1253
1292
|
- `kind`: `"command"`
|
|
1254
1293
|
- `argv`: `string[]`
|
|
1255
1294
|
- `timeoutMs`: `number`
|
|
1256
|
-
- `slot` (optional): `string`
|
|
1257
1295
|
- `secrets` (optional): `string[]`
|
|
1258
1296
|
- `pin` (optional): `true`
|
|
1259
1297
|
- Form 2: `kind`, `method`, `target`, `secrets`, `timeoutMs`, `idempotent`, `file`
|
|
@@ -119,7 +119,6 @@ import { body, type TaskInput } from "@jr2/machines";
|
|
|
119
119
|
|
|
120
120
|
const fixed = workspace(body, {
|
|
121
121
|
repos: { app: { url: "https://github.com/you/app.git" } },
|
|
122
|
-
spec: ({ input }: { input: TaskInput }) => ({ branch: input.branch ?? "jr2/fix" }),
|
|
123
122
|
});
|
|
124
123
|
|
|
125
124
|
export const machine = customize(fixed, {
|
package/docs/why.md
CHANGED
|
@@ -115,8 +115,8 @@ It talks to the model, and it runs the tools of the Agent. The Agent can run eac
|
|
|
115
115
|
Agent that needs only its Menu has no Sandbox. Its Turns run on the
|
|
116
116
|
[Instance Harness](reference/glossary.md#instance-harness).
|
|
117
117
|
|
|
118
|
-
The Harness
|
|
119
|
-
one of the two containers beside
|
|
118
|
+
The Harness runs each command of an Agent confined with Landlock, so the command cannot read a credential, and it cannot
|
|
119
|
+
send a pick for a different Agent. jr2 keeps each credential in one of the two containers beside the Harness:
|
|
120
120
|
|
|
121
121
|
- The [Custodian](reference/glossary.md#custodian) holds each [Held secret](reference/glossary.md#held-secret): a key
|
|
122
122
|
that the Agents can use, but cannot read. The Harness holds only a [Stand-in](reference/glossary.md#stand-in) for it.
|
|
@@ -192,8 +192,9 @@ These steps follow one run of the example Machine in the diagram. You write a Ma
|
|
|
192
192
|
1. **You start a run.** You run `jr2 run feature`, with the ticket as the input of the run. The Machine starts in its
|
|
193
193
|
first state, `code`.
|
|
194
194
|
2. **The coder works in a Sandbox.** The state `code` is in a Workspace, so jr2 makes a Sandbox for it. The Sandbox
|
|
195
|
-
holds a [Worktree](reference/glossary.md#worktree)
|
|
196
|
-
runs the tests and commits. Then it picks `finish`, and the Machine moves to
|
|
195
|
+
holds a clone of your Repo, and the Machine cuts a [Worktree](reference/glossary.md#worktree) on the branch of the
|
|
196
|
+
run. The coder edits the files, runs the tests and commits. Then it picks `finish`, and the Machine moves to
|
|
197
|
+
`review`.
|
|
197
198
|
3. **The reviewer gets a Turn.** The state `review` gives the reviewer a prompt and its Menu: `approved` and `changes`.
|
|
198
199
|
The reviewer reads the change in the same Sandbox.
|
|
199
200
|
4. **The reviewer picks.** If it picks `approved`, the Machine moves to `approval`. If it picks `changes`, the Machine
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jr2/orchestrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The jr2 Orchestrator: xstate runtime, HTTP API and Console, and the kit pieces a Workflow imports.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"ts-blank-space": "^0.9.0",
|
|
43
43
|
"xstate": "^5.18.0",
|
|
44
44
|
"zod": "^4.4.3",
|
|
45
|
-
"@jr2/agent-protocol": "0.
|
|
45
|
+
"@jr2/agent-protocol": "0.6.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^26.0.1",
|
|
49
|
-
"@jr2/harness": "0.
|
|
49
|
+
"@jr2/harness": "0.6.0"
|
|
50
50
|
},
|
|
51
51
|
"files": [
|
|
52
52
|
"bin",
|