@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
|
@@ -54,6 +54,22 @@ below explains one field. Set only the fields that your cluster needs.
|
|
|
54
54
|
API server and in the kubelets.
|
|
55
55
|
- A container runtime that mounts image volumes, such as containerd 2.1 or later, or CRI-O 1.31 or later. `jr2 up` does
|
|
56
56
|
not check the runtime. With an older runtime, the first Sandbox fails, and its pod events give the cause.
|
|
57
|
+
- Landlock ABI 3 or later in the kernel of each [Sandbox node](../reference/glossary.md#sandbox-node): Linux 6.2 or
|
|
58
|
+
later, with `landlock` in its list of security modules. A distribution kernel can have it at an older version: the
|
|
59
|
+
check, not the version, decides. The Harness confines each program of an Agent with it. The Repo cache agent on each
|
|
60
|
+
Sandbox node checks it, and `jr2 up` refuses to converge while a Sandbox node has no Landlock. It names each such
|
|
61
|
+
node. To keep a node out, use `sandbox.nodeSelector` or a taint (see
|
|
62
|
+
[Choose the Sandbox nodes](#choose-the-sandbox-nodes)). A node that joins later and has no Landlock gets no working
|
|
63
|
+
Sandbox: its Harness does not start.
|
|
64
|
+
- A CNI that enforces NetworkPolicy, and no node credential that a pod can get. `jr2 up` applies a NetworkPolicy that
|
|
65
|
+
stops each Harness pod at the cloud metadata addresses: `169.254.169.254`, `fd00:ec2::254`, `169.254.170.2`,
|
|
66
|
+
`169.254.170.23` and `100.100.100.200`. There, the metadata service of a node can give the node's cloud credential to
|
|
67
|
+
a pod. `jr2 up` does not check these. Do these checks:
|
|
68
|
+
- Find the metadata address of your cloud provider. If it is not in the list, block it with a NetworkPolicy of your
|
|
69
|
+
own.
|
|
70
|
+
- Make sure that the CNI enforces NetworkPolicy. Without it, the policy stops nothing.
|
|
71
|
+
- Give pods no node credential. On GKE, use Workload Identity. On AWS, use IMDSv2 with a hop limit of 1. Then a pod
|
|
72
|
+
gets no node credential from the metadata service, also where the policy does not apply.
|
|
57
73
|
- Your kubectl user can apply objects outside the namespace of the Instance. `jr2 up` applies the operator in the
|
|
58
74
|
`jr2-system` namespace, with its CustomResourceDefinitions, ClusterRoles and ClusterRoleBindings. It also applies two
|
|
59
75
|
PriorityClasses, unless you name your own.
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Push and sign a branch
|
|
3
|
-
description: Push the branch of a
|
|
3
|
+
description: Push the branch of a Worktree, and sign its commits with a key that the Agent never reads.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Push and sign a branch
|
|
7
7
|
|
|
8
|
-
Push the branch of a [
|
|
8
|
+
Push the branch of a [Worktree](../reference/glossary.md#worktree) with a `push`
|
|
9
9
|
[Effect](../reference/glossary.md#effect), signed with a key that the [Agent](../reference/glossary.md#agent) never
|
|
10
10
|
reads.
|
|
11
11
|
|
|
12
12
|
[`push(options)`](../reference/authoring-surface/push.md) pushes the branch to the [Repo](../reference/glossary.md#repo)
|
|
13
|
-
that the [Binding](../reference/glossary.md#binding) of its [Repo Slot](../reference/glossary.md#repo-slot) names.
|
|
14
|
-
`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
that the [Binding](../reference/glossary.md#binding) of its [Repo Slot](../reference/glossary.md#repo-slot) names. Its
|
|
14
|
+
input names the Worktree, `{ worktree }`, and has no branch. jr2 reads the branch from the cut of that path, so an Agent
|
|
15
|
+
that moves `HEAD` does not change what jr2 pushes. With `sign: true`, it pushes signed copies of the Agent's commits. In
|
|
16
|
+
this [Workflow](../reference/glossary.md#workflow), a coder fixes the tests and commits. A person reads the branch at
|
|
17
|
+
the [Gate](../reference/glossary.md#gate) of the state `review`, then sends `approve`. Then the
|
|
18
|
+
[Machine](../reference/glossary.md#machine) pushes the branch, signed. Save the file as `workflows/publish.ts` in your
|
|
19
|
+
[Instance](../reference/glossary.md#instance):
|
|
18
20
|
|
|
19
21
|
**Anthropic**
|
|
20
22
|
|
|
@@ -25,9 +27,11 @@ import {
|
|
|
25
27
|
agent,
|
|
26
28
|
defineEvent,
|
|
27
29
|
fail,
|
|
30
|
+
isReady,
|
|
28
31
|
jr2Setup,
|
|
29
32
|
push,
|
|
30
33
|
workspace,
|
|
34
|
+
worktree,
|
|
31
35
|
type PushOutput,
|
|
32
36
|
type Workspaced,
|
|
33
37
|
} from "@jr2/orchestrator";
|
|
@@ -45,6 +49,9 @@ const runInput = z.object({ prompt: z.string(), branch: z.string() });
|
|
|
45
49
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
46
50
|
type Context = BodyInput & { pushed?: PushOutput };
|
|
47
51
|
|
|
52
|
+
// The Worktree of the coder. The push names it, and jr2 reads its branch from the cut.
|
|
53
|
+
const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
|
|
54
|
+
|
|
48
55
|
const body = jr2Setup({
|
|
49
56
|
types: {} as { input: BodyInput; context: Context },
|
|
50
57
|
events: [finished, approve, stop],
|
|
@@ -53,6 +60,7 @@ const body = jr2Setup({
|
|
|
53
60
|
model: "anthropic/claude-haiku-5-5",
|
|
54
61
|
instructions: "You fix failing tests. You change no test. You commit the fix.",
|
|
55
62
|
}),
|
|
63
|
+
cut: worktree(),
|
|
56
64
|
// Push signed copies of the Agent's commits, with the key of the git.credentials entry.
|
|
57
65
|
publish: push({ sign: true }),
|
|
58
66
|
fail: fail(),
|
|
@@ -61,23 +69,32 @@ const body = jr2Setup({
|
|
|
61
69
|
id: "publish",
|
|
62
70
|
context: ({ input }) => input,
|
|
63
71
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
64
|
-
initial: "
|
|
72
|
+
initial: "cutting",
|
|
65
73
|
states: {
|
|
74
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
75
|
+
cutting: {
|
|
76
|
+
invoke: {
|
|
77
|
+
src: "cut",
|
|
78
|
+
input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
|
|
79
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
66
82
|
coding: {
|
|
67
83
|
invoke: {
|
|
68
84
|
src: "coder",
|
|
69
|
-
input: ({ context }) => ({ prompt: context.prompt, cwd: context
|
|
85
|
+
input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
|
|
70
86
|
},
|
|
71
87
|
on: { finished: "review" },
|
|
72
88
|
},
|
|
73
89
|
// A person reads the branch, then answers.
|
|
74
90
|
review: {
|
|
75
|
-
invoke: { src: "gate", input: ({ context }) => ({ meta: { branch: context.
|
|
91
|
+
invoke: { src: "gate", input: ({ context }) => ({ meta: { branch: context.branch } }) },
|
|
76
92
|
on: { approve: "publishing", stop: "done" },
|
|
77
93
|
},
|
|
78
94
|
publishing: {
|
|
79
95
|
invoke: {
|
|
80
96
|
src: "publish",
|
|
97
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
81
98
|
onDone: [
|
|
82
99
|
{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
83
100
|
// rejected, diverged or unknown: the run waits, so that the Sandbox and its commits stay.
|
|
@@ -90,6 +107,7 @@ const body = jr2Setup({
|
|
|
90
107
|
on: { approve: "publishing", stop: "done" },
|
|
91
108
|
},
|
|
92
109
|
done: { type: "final" },
|
|
110
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
93
111
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
94
112
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
95
113
|
},
|
|
@@ -98,7 +116,6 @@ const body = jr2Setup({
|
|
|
98
116
|
export const machine = workspace(body, {
|
|
99
117
|
input: runInput,
|
|
100
118
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
101
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
102
119
|
});
|
|
103
120
|
```
|
|
104
121
|
|
|
@@ -111,9 +128,11 @@ import {
|
|
|
111
128
|
agent,
|
|
112
129
|
defineEvent,
|
|
113
130
|
fail,
|
|
131
|
+
isReady,
|
|
114
132
|
jr2Setup,
|
|
115
133
|
push,
|
|
116
134
|
workspace,
|
|
135
|
+
worktree,
|
|
117
136
|
type PushOutput,
|
|
118
137
|
type Workspaced,
|
|
119
138
|
} from "@jr2/orchestrator";
|
|
@@ -131,6 +150,9 @@ const runInput = z.object({ prompt: z.string(), branch: z.string() });
|
|
|
131
150
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
132
151
|
type Context = BodyInput & { pushed?: PushOutput };
|
|
133
152
|
|
|
153
|
+
// The Worktree of the coder. The push names it, and jr2 reads its branch from the cut.
|
|
154
|
+
const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
|
|
155
|
+
|
|
134
156
|
const body = jr2Setup({
|
|
135
157
|
types: {} as { input: BodyInput; context: Context },
|
|
136
158
|
events: [finished, approve, stop],
|
|
@@ -139,6 +161,7 @@ const body = jr2Setup({
|
|
|
139
161
|
model: "local/qwen3.6-35b-a3b",
|
|
140
162
|
instructions: "You fix failing tests. You change no test. You commit the fix.",
|
|
141
163
|
}),
|
|
164
|
+
cut: worktree(),
|
|
142
165
|
// Push signed copies of the Agent's commits, with the key of the git.credentials entry.
|
|
143
166
|
publish: push({ sign: true }),
|
|
144
167
|
fail: fail(),
|
|
@@ -147,23 +170,32 @@ const body = jr2Setup({
|
|
|
147
170
|
id: "publish",
|
|
148
171
|
context: ({ input }) => input,
|
|
149
172
|
on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
|
|
150
|
-
initial: "
|
|
173
|
+
initial: "cutting",
|
|
151
174
|
states: {
|
|
175
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
176
|
+
cutting: {
|
|
177
|
+
invoke: {
|
|
178
|
+
src: "cut",
|
|
179
|
+
input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
|
|
180
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
152
183
|
coding: {
|
|
153
184
|
invoke: {
|
|
154
185
|
src: "coder",
|
|
155
|
-
input: ({ context }) => ({ prompt: context.prompt, cwd: context
|
|
186
|
+
input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
|
|
156
187
|
},
|
|
157
188
|
on: { finished: "review" },
|
|
158
189
|
},
|
|
159
190
|
// A person reads the branch, then answers.
|
|
160
191
|
review: {
|
|
161
|
-
invoke: { src: "gate", input: ({ context }) => ({ meta: { branch: context.
|
|
192
|
+
invoke: { src: "gate", input: ({ context }) => ({ meta: { branch: context.branch } }) },
|
|
162
193
|
on: { approve: "publishing", stop: "done" },
|
|
163
194
|
},
|
|
164
195
|
publishing: {
|
|
165
196
|
invoke: {
|
|
166
197
|
src: "publish",
|
|
198
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
167
199
|
onDone: [
|
|
168
200
|
{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" },
|
|
169
201
|
// rejected, diverged or unknown: the run waits, so that the Sandbox and its commits stay.
|
|
@@ -176,6 +208,7 @@ const body = jr2Setup({
|
|
|
176
208
|
on: { approve: "publishing", stop: "done" },
|
|
177
209
|
},
|
|
178
210
|
done: { type: "final" },
|
|
211
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
179
212
|
failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
|
|
180
213
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
181
214
|
},
|
|
@@ -184,7 +217,6 @@ const body = jr2Setup({
|
|
|
184
217
|
export const machine = workspace(body, {
|
|
185
218
|
input: runInput,
|
|
186
219
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
187
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
188
220
|
});
|
|
189
221
|
```
|
|
190
222
|
|
|
@@ -313,6 +345,7 @@ jr2 up
|
|
|
313
345
|
```text
|
|
314
346
|
…
|
|
315
347
|
effects: workflow "publish" — what it runs outside itself (ADR-0067)
|
|
348
|
+
cut: worktree — 600s, in the Harness
|
|
316
349
|
publish: push (sign) — 300s, in the Sandbox Effector
|
|
317
350
|
…
|
|
318
351
|
Workspace of workflow "publish": Sandbox Effector (image the Sandbox Image) holds no Effect secret; pushes Repo Slot "app" with git.credentials "github.com/<you>/"
|
|
@@ -372,7 +405,8 @@ Each outcome is output for `onDone`. Route on `outcome`
|
|
|
372
405
|
([`PushOutput`](../reference/authoring-surface/type-pushoutput.md)):
|
|
373
406
|
|
|
374
407
|
- `pushed`: the remote has the commit. `published` is the commit on the remote, and `source` is the Agent's commit.
|
|
375
|
-
- `rejected`: git, the remote or the Sandbox Effector refused the push. `stage` tells where, and `reason` tells why.
|
|
408
|
+
- `rejected`: git, the remote or the Sandbox Effector refused the push. `stage` tells where, and `reason` tells why. A
|
|
409
|
+
`worktree` that jr2 cannot use is `rejected` at the `stage` `start`, and jr2 pushes nothing. See [Rules](#rules).
|
|
376
410
|
- `diverged`: in a signed push, the commit on the remote is not in the history of the Agent's branch, and jr2 cannot
|
|
377
411
|
pair it with an Agent commit. jr2 pushed nothing. An unsigned push to a remote branch that moved is `rejected`.
|
|
378
412
|
- `unknown`: jr2 cannot tell what the push did.
|
|
@@ -394,12 +428,14 @@ jr2 send <run id> --gate body.notPushed --event approve
|
|
|
394
428
|
|
|
395
429
|
## Other options
|
|
396
430
|
|
|
397
|
-
- `pin: true`: push the commit that the input of the Effect names, `{ sha: "<full commit id>" }`. Use it for a
|
|
398
|
-
that a Gate approved. Without `pin`, jr2 pushes the tip of the branch when the Effect starts.
|
|
399
|
-
- `squash: true`: push one commit that holds all of the changes. The input gives its message,
|
|
400
|
-
Without `sign`, the [`committer`](../reference/config.md#git-credentials-committer)
|
|
431
|
+
- `pin: true`: push the commit that the input of the Effect names, `{ worktree, sha: "<full commit id>" }`. Use it for a
|
|
432
|
+
commit that a Gate approved. Without `pin`, jr2 pushes the tip of the branch when the Effect starts.
|
|
433
|
+
- `squash: true`: push one commit that holds all of the changes. The input gives its message,
|
|
434
|
+
`{ worktree, message: "<text>" }`. Without `sign`, the [`committer`](../reference/config.md#git-credentials-committer)
|
|
435
|
+
of the entry commits it.
|
|
401
436
|
- `force: true`: replace what the remote branch holds.
|
|
402
|
-
- `slot`: the Repo Slot to push. Write it only when the Workspace has more than one Repo Slot.
|
|
437
|
+
- `slot`: the Repo Slot to push. Write it only when the Workspace has more than one Repo Slot. It is static, because
|
|
438
|
+
`jr2 up` places the credential for each slot.
|
|
403
439
|
- `timeout`: how long jr2 waits for the push. The default is `"5m"`. After it, the outcome is `unknown`.
|
|
404
440
|
|
|
405
441
|
## Rules
|
|
@@ -461,6 +497,15 @@ When a `push` starts, it refuses:
|
|
|
461
497
|
`push({ pin: true }): its input's sha is not a full commit id (got <sha>)`
|
|
462
498
|
- with `squash: true`, an input with no message: `push({ squash: true }): its input carries no message`
|
|
463
499
|
|
|
500
|
+
A push settles `rejected`, with `stage` `start`, for each of these. Its `reason` ends `; nothing was pushed`:
|
|
501
|
+
|
|
502
|
+
- a `worktree` that is not a Worktree path, or a slot that the Workspace does not have:
|
|
503
|
+
`"<path>" is not a Worktree path: /work/<slot>/<name>, with one segment in <name>`, or
|
|
504
|
+
`"<path>" names Repo Slot "<slot>", which its Workspace does not carry (<slots>)`
|
|
505
|
+
- a `worktree` that no cut made in this Sandbox: `no cut made <path> in this Sandbox: cut it with worktree()`
|
|
506
|
+
- a Worktree of a different Repo Slot: `<path> is a Worktree of Repo Slot "<slot>", not of "<slot>"`
|
|
507
|
+
- a detached Worktree: `<path> is detached: a push publishes the branch of a Worktree, and it holds none`
|
|
508
|
+
|
|
464
509
|
`push()` refuses a timeout that is not a positive duration:
|
|
465
510
|
`timeout "<timeout>" is not a positive duration (like "30s", "5m")`
|
|
466
511
|
|
|
@@ -474,6 +519,7 @@ the push Secret of the entry, `jr2-git-push-<hash>`, as `jr2 up` shows it. It is
|
|
|
474
519
|
- [Secrets and trust](../concepts/secrets-and-trust.md): who holds the push credential and the signing key.
|
|
475
520
|
- [Give the cluster git credentials](give-the-cluster-git-credentials.md): the entries of `git.credentials`.
|
|
476
521
|
- [Add tools to the Sandbox](add-tools-to-the-sandbox.md): the Sandbox Image.
|
|
522
|
+
- [Cut a Worktree](cut-a-worktree.md): the Worktree that a `push` publishes.
|
|
477
523
|
- Reference: [`push`](../reference/authoring-surface/push.md),
|
|
478
524
|
[`PushOptions`](../reference/authoring-surface/type-pushoptions.md),
|
|
479
525
|
[`PushOutput`](../reference/authoring-surface/type-pushoutput.md) and
|
|
@@ -10,8 +10,9 @@ Run a program in the [Workspace](../reference/glossary.md#workspace), or call a
|
|
|
10
10
|
|
|
11
11
|
Put the Effect under `actors` in [`jr2Setup`](../reference/authoring-surface/jr2setup.md), beside the
|
|
12
12
|
[Agents](../reference/glossary.md#agent). Then invoke it by its key from a state, and route on its output in `onDone`.
|
|
13
|
-
In this [Workflow](../reference/glossary.md#workflow),
|
|
14
|
-
|
|
13
|
+
In this [Workflow](../reference/glossary.md#workflow), the Machine cuts a [Worktree](../reference/glossary.md#worktree),
|
|
14
|
+
and a coder fixes the tests there. It picks `run_test` to run one test file with a
|
|
15
|
+
[`command`](../reference/authoring-surface/command.md) Effect. When it picks `finished`, an
|
|
15
16
|
[`http`](../reference/authoring-surface/http.md) Effect posts its summary to a URL. Save the file as
|
|
16
17
|
`workflows/fix-and-report.ts` in your [Instance](../reference/glossary.md#instance):
|
|
17
18
|
|
|
@@ -29,9 +30,11 @@ import {
|
|
|
29
30
|
fail,
|
|
30
31
|
fromPick,
|
|
31
32
|
http,
|
|
33
|
+
isReady,
|
|
32
34
|
jr2Setup,
|
|
33
35
|
secret,
|
|
34
36
|
workspace,
|
|
37
|
+
worktree,
|
|
35
38
|
type CommandOutput,
|
|
36
39
|
type Workspaced,
|
|
37
40
|
} from "@jr2/orchestrator";
|
|
@@ -50,6 +53,9 @@ const finished = defineEvent({
|
|
|
50
53
|
const runInput = z.object({ prompt: z.string(), branch: z.string() });
|
|
51
54
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
52
55
|
|
|
56
|
+
// The Worktree that the coder and the command name.
|
|
57
|
+
const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
|
|
58
|
+
|
|
53
59
|
const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
|
|
54
60
|
|
|
55
61
|
const body = jr2Setup({
|
|
@@ -60,6 +66,8 @@ const body = jr2Setup({
|
|
|
60
66
|
model: "anthropic/claude-haiku-5-5",
|
|
61
67
|
instructions: "You fix failing tests. You change no test. You commit the fix.",
|
|
62
68
|
}),
|
|
69
|
+
// Cut the Worktree: an Effect too.
|
|
70
|
+
cut: worktree(),
|
|
63
71
|
// One program, with no shell. arg("file") is one argument, from the input of the Effect.
|
|
64
72
|
test: command(["node", "--test", arg("file")], { timeout: "5m" }),
|
|
65
73
|
// A call to a fixed URL. The Effector puts the value of the secret in the header.
|
|
@@ -77,12 +85,20 @@ const body = jr2Setup({
|
|
|
77
85
|
id: "fix-and-report",
|
|
78
86
|
context: ({ input }) => input,
|
|
79
87
|
on: { "workspace.lost": ".lost" },
|
|
80
|
-
initial: "
|
|
88
|
+
initial: "cutting",
|
|
81
89
|
states: {
|
|
90
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
91
|
+
cutting: {
|
|
92
|
+
invoke: {
|
|
93
|
+
src: "cut",
|
|
94
|
+
input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
|
|
95
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
82
98
|
coding: {
|
|
83
99
|
invoke: {
|
|
84
100
|
src: "coder",
|
|
85
|
-
input: ({ context }) => ({ prompt: context.prompt, cwd: context
|
|
101
|
+
input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
|
|
86
102
|
},
|
|
87
103
|
on: {
|
|
88
104
|
finished: "reporting",
|
|
@@ -95,8 +111,8 @@ const body = jr2Setup({
|
|
|
95
111
|
testing: {
|
|
96
112
|
invoke: {
|
|
97
113
|
src: "test",
|
|
98
|
-
// The `file` of the pick goes to the arg("file") of the command.
|
|
99
|
-
input: fromPick(runTest),
|
|
114
|
+
// The `file` of the pick goes to the arg("file") of the command. The Machine gives `worktree`.
|
|
115
|
+
input: fromPick(runTest, ({ context }) => ({ worktree: fixTree(context) })),
|
|
100
116
|
onDone: {
|
|
101
117
|
target: "ready",
|
|
102
118
|
// The Agent reads this text as the answer to its run_test pick.
|
|
@@ -114,7 +130,7 @@ const body = jr2Setup({
|
|
|
114
130
|
invoke: {
|
|
115
131
|
src: "report",
|
|
116
132
|
// The Agent gives `summary`. The Machine gives `branch`.
|
|
117
|
-
input: fromPick(finished, ({ context }) => ({ branch: context.
|
|
133
|
+
input: fromPick(finished, ({ context }) => ({ branch: context.branch })),
|
|
118
134
|
onDone: [
|
|
119
135
|
{ guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
|
|
120
136
|
{ target: "notReported" },
|
|
@@ -123,6 +139,7 @@ const body = jr2Setup({
|
|
|
123
139
|
},
|
|
124
140
|
done: { type: "final" },
|
|
125
141
|
// Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
|
|
142
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
126
143
|
notReported: { invoke: { src: "fail", input: { reason: "the report did not go out" } } },
|
|
127
144
|
failed: { invoke: { src: "fail", input: { reason: "the coder could not go on" } } },
|
|
128
145
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
|
|
@@ -132,7 +149,6 @@ const body = jr2Setup({
|
|
|
132
149
|
export const machine = workspace(body, {
|
|
133
150
|
input: runInput,
|
|
134
151
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
135
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
136
152
|
});
|
|
137
153
|
```
|
|
138
154
|
|
|
@@ -150,9 +166,11 @@ import {
|
|
|
150
166
|
fail,
|
|
151
167
|
fromPick,
|
|
152
168
|
http,
|
|
169
|
+
isReady,
|
|
153
170
|
jr2Setup,
|
|
154
171
|
secret,
|
|
155
172
|
workspace,
|
|
173
|
+
worktree,
|
|
156
174
|
type CommandOutput,
|
|
157
175
|
type Workspaced,
|
|
158
176
|
} from "@jr2/orchestrator";
|
|
@@ -171,6 +189,9 @@ const finished = defineEvent({
|
|
|
171
189
|
const runInput = z.object({ prompt: z.string(), branch: z.string() });
|
|
172
190
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
173
191
|
|
|
192
|
+
// The Worktree that the coder and the command name.
|
|
193
|
+
const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
|
|
194
|
+
|
|
174
195
|
const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
|
|
175
196
|
|
|
176
197
|
const body = jr2Setup({
|
|
@@ -181,6 +202,8 @@ const body = jr2Setup({
|
|
|
181
202
|
model: "local/qwen3.6-35b-a3b",
|
|
182
203
|
instructions: "You fix failing tests. You change no test. You commit the fix.",
|
|
183
204
|
}),
|
|
205
|
+
// Cut the Worktree: an Effect too.
|
|
206
|
+
cut: worktree(),
|
|
184
207
|
// One program, with no shell. arg("file") is one argument, from the input of the Effect.
|
|
185
208
|
test: command(["node", "--test", arg("file")], { timeout: "5m" }),
|
|
186
209
|
// A call to a fixed URL. The Effector puts the value of the secret in the header.
|
|
@@ -198,12 +221,20 @@ const body = jr2Setup({
|
|
|
198
221
|
id: "fix-and-report",
|
|
199
222
|
context: ({ input }) => input,
|
|
200
223
|
on: { "workspace.lost": ".lost" },
|
|
201
|
-
initial: "
|
|
224
|
+
initial: "cutting",
|
|
202
225
|
states: {
|
|
226
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
227
|
+
cutting: {
|
|
228
|
+
invoke: {
|
|
229
|
+
src: "cut",
|
|
230
|
+
input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
|
|
231
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
203
234
|
coding: {
|
|
204
235
|
invoke: {
|
|
205
236
|
src: "coder",
|
|
206
|
-
input: ({ context }) => ({ prompt: context.prompt, cwd: context
|
|
237
|
+
input: ({ context }) => ({ prompt: context.prompt, cwd: fixTree(context) }),
|
|
207
238
|
},
|
|
208
239
|
on: {
|
|
209
240
|
finished: "reporting",
|
|
@@ -216,8 +247,8 @@ const body = jr2Setup({
|
|
|
216
247
|
testing: {
|
|
217
248
|
invoke: {
|
|
218
249
|
src: "test",
|
|
219
|
-
// The `file` of the pick goes to the arg("file") of the command.
|
|
220
|
-
input: fromPick(runTest),
|
|
250
|
+
// The `file` of the pick goes to the arg("file") of the command. The Machine gives `worktree`.
|
|
251
|
+
input: fromPick(runTest, ({ context }) => ({ worktree: fixTree(context) })),
|
|
221
252
|
onDone: {
|
|
222
253
|
target: "ready",
|
|
223
254
|
// The Agent reads this text as the answer to its run_test pick.
|
|
@@ -235,7 +266,7 @@ const body = jr2Setup({
|
|
|
235
266
|
invoke: {
|
|
236
267
|
src: "report",
|
|
237
268
|
// The Agent gives `summary`. The Machine gives `branch`.
|
|
238
|
-
input: fromPick(finished, ({ context }) => ({ branch: context.
|
|
269
|
+
input: fromPick(finished, ({ context }) => ({ branch: context.branch })),
|
|
239
270
|
onDone: [
|
|
240
271
|
{ guard: ({ event }) => event.output.outcome === "response" && event.output.status < 300, target: "done" },
|
|
241
272
|
{ target: "notReported" },
|
|
@@ -244,6 +275,7 @@ const body = jr2Setup({
|
|
|
244
275
|
},
|
|
245
276
|
done: { type: "final" },
|
|
246
277
|
// Not final states: a final state means `done`. Each one fails the run, and the run ends `failed`.
|
|
278
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
247
279
|
notReported: { invoke: { src: "fail", input: { reason: "the report did not go out" } } },
|
|
248
280
|
failed: { invoke: { src: "fail", input: { reason: "the coder could not go on" } } },
|
|
249
281
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
|
|
@@ -253,7 +285,6 @@ const body = jr2Setup({
|
|
|
253
285
|
export const machine = workspace(body, {
|
|
254
286
|
input: runInput,
|
|
255
287
|
repos: { app: "https://github.com/<you>/app.git" },
|
|
256
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
257
288
|
});
|
|
258
289
|
```
|
|
259
290
|
|
|
@@ -261,7 +292,7 @@ The `coding` state routes [`agent.fault`](../reference/mechanism-events.md#agent
|
|
|
261
292
|
Turn of the coder fails with no pick. Each state that invokes an Agent has a route for it, on the state or on a state
|
|
262
293
|
above it: `jr2 check` refuses a Machine without one. When no transition takes the event, the run ends `error`.
|
|
263
294
|
|
|
264
|
-
`failed`, `notReported` and `lost` are not final states. Each one invokes
|
|
295
|
+
`notCut`, `failed`, `notReported` and `lost` are not final states. Each one invokes
|
|
265
296
|
[`fail`](../reference/authoring-surface/fail.md), and the run ends `failed` with its reason: a final state means `done`.
|
|
266
297
|
Do not throw from a promise to fail, because a throw is a defect, and the run ends `error`.
|
|
267
298
|
|
|
@@ -301,25 +332,24 @@ The options:
|
|
|
301
332
|
- `timeout`: how long the program can run, such as `"90s"`, `"10m"` or `"1h"`. The default is `"10m"`. After it, jr2
|
|
302
333
|
kills the process group, and the outcome is `timeout`.
|
|
303
334
|
- `tail`: how many characters of the end of the output the Machine gets. The default is 4096.
|
|
304
|
-
- `slot`: the [Repo Slot](../reference/glossary.md#repo-slot) whose [Worktree](../reference/glossary.md#worktree) the
|
|
305
|
-
program runs in. Write it only when the Workspace has more than one Repo Slot.
|
|
306
335
|
|
|
307
|
-
The
|
|
308
|
-
the
|
|
309
|
-
|
|
310
|
-
|
|
336
|
+
The input of the Effect says where the program runs. Under [Repo Slots](../reference/glossary.md#repo-slot), `worktree`
|
|
337
|
+
is required: the path of a Worktree that a cut made, or `/work/<slot>/default`. The program starts in the root of that
|
|
338
|
+
folder. In a Workspace with no Repo Slot, it starts in `/work`, the folder that the steps of the Workspace share (see
|
|
339
|
+
[Workspaces](../concepts/workspaces.md#a-workspace-with-no-repo)). To run it in a subfolder, give the path as an
|
|
340
|
+
argument, such as `["node", "--test", "<dir>/test/*.test.js"]`, or write `["sh", "-c", "cd <dir> && <program>"]`.
|
|
311
341
|
|
|
312
342
|
A `command` with no Effect secret runs in the [Harness](../reference/glossary.md#harness) container of the
|
|
313
|
-
[Sandbox](../reference/glossary.md#sandbox),
|
|
314
|
-
[
|
|
315
|
-
[Sandbox Image](../reference/glossary.md#sandbox-image). Thus invoke it in the body of a
|
|
343
|
+
[Sandbox](../reference/glossary.md#sandbox), beside the [Working tools](../reference/glossary.md#working-tools) of the
|
|
344
|
+
Agent. It uses the tools of the [Sandbox Image](../reference/glossary.md#sandbox-image). Thus invoke it in the body of a
|
|
316
345
|
[`workspace()`](../reference/authoring-surface/workspace.md).
|
|
317
346
|
|
|
318
347
|
Each outcome is output for `onDone`, also a failure. Route on `outcome`
|
|
319
348
|
([`CommandOutput`](../reference/authoring-surface/type-commandoutput.md)):
|
|
320
349
|
|
|
321
350
|
- `exited`, with `code` and `tail`. A test that fails is `exited` with a `code` that is not 0. A program that the
|
|
322
|
-
container does not have is `exited` with code 127.
|
|
351
|
+
container does not have is `exited` with code 127. A bad `worktree` is `exited` with code 127 too, and `tail` gives
|
|
352
|
+
the reason. A bad path does not add an outcome.
|
|
323
353
|
- `timeout`, with `tail`.
|
|
324
354
|
- `killed`, with `signal` and `tail`.
|
|
325
355
|
- `unknown`, with `reason`: jr2 cannot tell what the program did. jr2 does not run it again.
|
|
@@ -332,13 +362,14 @@ the value. `secret("<name>").path` gives the path of a file that holds the value
|
|
|
332
362
|
list of `jr2.config.ts`, as for `hook-token`.
|
|
333
363
|
|
|
334
364
|
A `command` with an Effect secret in `env` does not run beside the Agent. It runs in the
|
|
335
|
-
[Sandbox Effector](../reference/glossary.md#sandbox-effector), a container that the Agent cannot reach.
|
|
336
|
-
|
|
365
|
+
[Sandbox Effector](../reference/glossary.md#sandbox-effector), a container that the Agent cannot reach. Its `worktree`
|
|
366
|
+
must be a branch Worktree that a cut made in this Sandbox, because a path alone does not name the target of a
|
|
367
|
+
credential. It runs against one commit of that branch, not against the live Worktree:
|
|
337
368
|
|
|
338
369
|
- By default, the commit is the tip of the branch when the Effect starts. When the Worktree holds changes that the
|
|
339
370
|
program did not see, its output has `dirty: true`.
|
|
340
|
-
- With `pin: true`, the commit is the `sha` of the input of the Effect: `{ sha: "<full commit id>" }`. Use it
|
|
341
|
-
commit that a [Gate](../reference/glossary.md#gate) approved.
|
|
371
|
+
- With `pin: true`, the commit is the `sha` of the input of the Effect: `{ worktree, sha: "<full commit id>" }`. Use it
|
|
372
|
+
for a commit that a [Gate](../reference/glossary.md#gate) approved.
|
|
342
373
|
|
|
343
374
|
The Sandbox Effector runs in the Sandbox Image, or in the image of the `effector` option of `workspace()`. Thus that
|
|
344
375
|
image must hold the program.
|
|
@@ -362,8 +393,10 @@ image must hold the program.
|
|
|
362
393
|
- `maxBody`: how many bytes of a text body the output keeps. The default is 65536.
|
|
363
394
|
- `idempotent`: `true` when a second call is safe. The default is `false`. See [Effects](../concepts/effects.md).
|
|
364
395
|
|
|
365
|
-
To send a file of
|
|
366
|
-
|
|
396
|
+
To send a file of a Worktree as the body, use [`file(path)`](../reference/authoring-surface/file.md) as `body`. The
|
|
397
|
+
input of the Effect names the Worktree, `{ worktree }`, and a cut in this Sandbox must have made it. The path is
|
|
398
|
+
relative to that Worktree. In a Workspace with no Repo Slot, the input has no `worktree`, and the path is relative to
|
|
399
|
+
`/work`. jr2 sends a file body only with `PUT`, `POST` or `PATCH`.
|
|
367
400
|
|
|
368
401
|
An `http` Effect runs in the [Instance Effector](../reference/glossary.md#instance-effector). The
|
|
369
402
|
[Effector](../reference/glossary.md#effector) puts the value of each Effect secret into the call. The Agent never reads
|
|
@@ -414,9 +447,12 @@ jr2 up
|
|
|
414
447
|
```text
|
|
415
448
|
…
|
|
416
449
|
effects: workflow "fix-and-report" — what it runs outside itself (ADR-0067)
|
|
450
|
+
cut: worktree — 600s, in the Harness
|
|
417
451
|
test: command ["node","--test","<file>"] — 300s, in the Harness
|
|
418
452
|
report: http POST https://hooks.example.com — 30s, in the Instance Effector, secrets "hook-token"
|
|
453
|
+
…
|
|
419
454
|
failures: workflow "fix-and-report" — the states that fail the run (ADR-0073)
|
|
455
|
+
fix-and-report.notCut: invokes fail
|
|
420
456
|
fix-and-report.notReported: invokes fail
|
|
421
457
|
fix-and-report.failed: invokes fail
|
|
422
458
|
fix-and-report.lost: invokes fail
|
|
@@ -440,20 +476,19 @@ When you call `command()`, it refuses:
|
|
|
440
476
|
- `pin: true` with no Effect secret in `env`:
|
|
441
477
|
``command(argv, { pin: true }) names no secret in its env: `pin` chooses the commit a credentialed command runs against in the Sandbox Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``
|
|
442
478
|
|
|
479
|
+
`workspace()` refuses a body with an invoke that does not say where it works, when it can read the input:
|
|
480
|
+
``workspace(): state "<state>": "<slot>" names no `<key>`, and a Workspace with Repo Slots says where each Turn and Effect works: a Worktree, or `cwd: "/work"` for a Turn (ADR-0057)``
|
|
481
|
+
|
|
443
482
|
When a `command` starts, it refuses:
|
|
444
483
|
|
|
445
484
|
- a state outside a `workspace()`. With no Effect secret in `env`:
|
|
446
485
|
`command "<slot>" runs in the Harness container of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`.
|
|
447
486
|
With one:
|
|
448
487
|
`command "<slot>" runs in the Sandbox Effector of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
|
|
449
|
-
- no `
|
|
450
|
-
|
|
451
|
-
With an Effect secret in `env`, the text is
|
|
452
|
-
`command "<slot>" runs under a Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" } (ADR-0051)`
|
|
488
|
+
- no `worktree` under Repo Slots, with no Effect secret in `env`:
|
|
489
|
+
``command "<slot>" runs under a Workspace with Repo Slots (<slots>) and its input names no `worktree`: say which, a Worktree that a cut gave or `<slot>/default` ``
|
|
453
490
|
- with an Effect secret in `env`, a Workspace with no Repo Slot:
|
|
454
491
|
`command "<slot>" runs under a Workspace with no Repo Slot: a command with an Effect secret runs against one commit of a Repo Slot's branch, and there is none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent (ADR-0051)`
|
|
455
|
-
- a `slot` that the Workspace does not have:
|
|
456
|
-
`command "<slot>" names Repo Slot "<name>", which its Workspace does not carry (<slots>)`
|
|
457
492
|
- an `arg()` with no string in the input. The angle brackets around the name are in the text, and `<type>` is `nothing`
|
|
458
493
|
when the input has no such key:
|
|
459
494
|
`command "<slot>": argv slot <<name>> has no string value in its input (got <type>) — map it in the invoke's input`
|
|
@@ -477,7 +512,7 @@ When you call `http()`, it refuses:
|
|
|
477
512
|
- a file body with a method other than `PUT`, `POST` or `PATCH`:
|
|
478
513
|
`http({ method: "<method>", body: file(...) }): a file body is sent with PUT, POST, PATCH (ADR-0068)`
|
|
479
514
|
- a `body` that is not `file()`:
|
|
480
|
-
``http({ body }): a body is file(path, {
|
|
515
|
+
``http({ body }): a body is file(path, { segments?, contentType? }) — json and text come from `request` ``
|
|
481
516
|
- a `content-type` header beside the `contentType` of `file()`:
|
|
482
517
|
`http({ headers, body: file(path, { contentType }) }): "content-type" is set by file()'s contentType — write one of them (ADR-0067)`
|
|
483
518
|
- a `request` with a file body:
|
|
@@ -489,9 +524,16 @@ When an `http` Effect starts, it refuses an input that its `input` schema does n
|
|
|
489
524
|
`http "<slot>": its input does not match the definition's input schema — <path>: <message>`. With a file body, the text
|
|
490
525
|
ends after `input schema`.
|
|
491
526
|
|
|
492
|
-
An `http` Effect with a file body runs in the Sandbox Effector. When it starts, it refuses
|
|
493
|
-
|
|
494
|
-
|
|
527
|
+
An `http` Effect with a file body runs in the Sandbox Effector. When it starts, it refuses:
|
|
528
|
+
|
|
529
|
+
- a state outside a `workspace()`:
|
|
530
|
+
`http "<slot>" runs in the Sandbox Effector of a Workspace, and its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
|
|
531
|
+
- no `worktree` under Repo Slots:
|
|
532
|
+
``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no `worktree` ``
|
|
533
|
+
|
|
534
|
+
A bad `worktree` is output, because a word of a run can cause it. An `http` Effect with a file body settles `refused`. A
|
|
535
|
+
`command` settles `exited` with code 127, with the reason in `tail`. A credentialed `command` and a file body need a
|
|
536
|
+
cut: `no cut made <path> in this Sandbox: cut it with worktree()`
|
|
495
537
|
|
|
496
538
|
When you call `secret()`, `bearer()` or `text`, they refuse:
|
|
497
539
|
|
|
@@ -534,6 +576,7 @@ When you call `secret()`, `bearer()` or `text`, they refuse:
|
|
|
534
576
|
- [Secrets and trust](../concepts/secrets-and-trust.md): who holds an Effect secret.
|
|
535
577
|
- [The Menu](../concepts/the-menu.md): how a pick waits for its answer.
|
|
536
578
|
- [Push and sign a branch](push-and-sign-a-branch.md): the `push` Effect.
|
|
579
|
+
- [Cut a Worktree](cut-a-worktree.md): the Worktree that a `command` runs in, and the removal of a Worktree.
|
|
537
580
|
- Reference: [`command`](../reference/authoring-surface/command.md),
|
|
538
581
|
[`CommandOptions`](../reference/authoring-surface/type-commandoptions.md),
|
|
539
582
|
[`http`](../reference/authoring-surface/http.md), [`secret`](../reference/authoring-surface/secret.md),
|