@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
|
@@ -111,8 +111,14 @@ can differ when your Assistant used other names. Other Workflows of the Instance
|
|
|
111
111
|
…
|
|
112
112
|
typecheck: tsc --noEmit (the instance's own compiler)
|
|
113
113
|
effects: workflow "fix" — what it runs outside itself (ADR-0067)
|
|
114
|
+
cut: worktree — 600s, in the Harness
|
|
114
115
|
tests: command ["node","--test","docs/site/tutorial/greet/test/*.test.js"] — 300s, in the Harness
|
|
115
116
|
publish: push — 300s, in the Sandbox Effector
|
|
117
|
+
…
|
|
118
|
+
failures: workflow "fix" — the states that fail the run (ADR-0073)
|
|
119
|
+
fix.notCut: invokes fail
|
|
120
|
+
fix.lost: invokes fail
|
|
121
|
+
…
|
|
116
122
|
seats: workflow "fix" — who may send what, per state (ADR-0015)
|
|
117
123
|
fix.working agent:coder: [finish]
|
|
118
124
|
fix.review gate: [approve, request_changes]
|
|
@@ -126,8 +132,9 @@ git.credentials "*": push Secret jr2-git-push-684888c0 — pushes over https
|
|
|
126
132
|
|
|
127
133
|
Read these lines as a first check:
|
|
128
134
|
|
|
129
|
-
- `effects` shows
|
|
135
|
+
- `effects` shows three Effects: the cut, a command and a push. A Workflow that pushes with no
|
|
130
136
|
[Gate](../reference/glossary.md#gate) before the push shows the same lines. The graph shows where the push is.
|
|
137
|
+
- `failures` shows the two states that end the run as `failed`.
|
|
131
138
|
- `seats` shows that the coder can send only `finish`. Only the Gate in `review` can send `approve` and
|
|
132
139
|
`request_changes`.
|
|
133
140
|
- The `warning` line is about git over ssh. The `jr2-git-ssh` Secret is for an ssh key, and it does not exist. Your fork
|
|
@@ -199,7 +206,7 @@ jr2 run fix --input '{"prompt":"In docs/site/tutorial/greet, one test fails. Fix
|
|
|
199
206
|
```text
|
|
200
207
|
…
|
|
201
208
|
→ context kind-jr2 / namespace my-instance
|
|
202
|
-
run
|
|
209
|
+
run 790ad78a-db4e-4b09-a248-ab62da088cde (fix) — attached
|
|
203
210
|
→ active "placing"
|
|
204
211
|
→ active "provisioning"
|
|
205
212
|
→ active "attaching"
|
|
@@ -221,12 +228,14 @@ jr2 status <run id>
|
|
|
221
228
|
|
|
222
229
|
[`jr2 status`](../reference/cli.md#status) prints the status of the run as one line of JSON. Its `gates` list holds the
|
|
223
230
|
Gate of `review`. Its `gate` is `body.review`. Give that value to `--gate`. The `meta` of the Gate gives the coder's
|
|
224
|
-
`summary`, the `branch` and the `worktree
|
|
231
|
+
`summary`, the `branch` and the `worktree`. After the JSON, `jr2 status` prints one line for each Worktree that the run
|
|
232
|
+
cut:
|
|
225
233
|
|
|
226
234
|
```text
|
|
227
235
|
…
|
|
228
236
|
→ context kind-jr2 / namespace my-instance
|
|
229
|
-
{…,"gates":[{"gate":"body.review",…,"meta":{"summary":"Fixed the \"trims the name\" test failure in docs/site/tutorial/greet
|
|
237
|
+
{…,"gates":[{"gate":"body.review",…,"meta":{"summary":"Fixed the \"trims the name\" test failure in docs/site/tutorial/greet. The bug was that the `greet` function didn't trim whitespace from the name argument. Added `.trim()` to the name extraction so that `greet([\" ada \"])` correctly returns `\"hello, ada\"` instead of `\"hello, ada \"`.","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395","worktree":"/work/target/fix"}}]}
|
|
238
|
+
worktree: /work/target/fix (target), branch jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395
|
|
230
239
|
```
|
|
231
240
|
|
|
232
241
|
The `summary` is in the words of the model, so your summary is different.
|
|
@@ -242,7 +251,7 @@ jr2 send <run id> --gate body.review --event approve
|
|
|
242
251
|
```text
|
|
243
252
|
…
|
|
244
253
|
→ context kind-jr2 / namespace my-instance
|
|
245
|
-
delivered approve to gate "body.review" on
|
|
254
|
+
delivered approve to gate "body.review" on 790ad78a-db4e-4b09-a248-ab62da088cde
|
|
246
255
|
```
|
|
247
256
|
|
|
248
257
|
The run goes to `pushing`, and the push Effect pushes the branch. Then the run ends, and jr2 removes the
|
|
@@ -253,7 +262,7 @@ status as one line of JSON:
|
|
|
253
262
|
…
|
|
254
263
|
→ active "teardown"
|
|
255
264
|
→ done "done"
|
|
256
|
-
{"runId":"
|
|
265
|
+
{"runId":"790ad78a-db4e-4b09-a248-ab62da088cde","workflow":"fix",…,"output":{"outcome":"pushed","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395","commit":"ee7f5d8084db32c3610efb5c9b889a710ef30f70"},"worktrees":[{"slot":"target","path":"/work/target/fix","branch":"jr2/fix-a78c793e-607a-4b33-9784-ccf2cfb6f395"}]}
|
|
257
266
|
```
|
|
258
267
|
|
|
259
268
|
In that JSON, `context.output` and `output` are the output of `fix`. In this result, it is
|
|
@@ -289,9 +298,11 @@ import {
|
|
|
289
298
|
command,
|
|
290
299
|
defineEvent,
|
|
291
300
|
fail,
|
|
301
|
+
isReady,
|
|
292
302
|
jr2Setup,
|
|
293
303
|
push,
|
|
294
304
|
workspace,
|
|
305
|
+
worktree,
|
|
295
306
|
type CommandOutput,
|
|
296
307
|
type HostInjectedInput,
|
|
297
308
|
type PushOutput,
|
|
@@ -339,6 +350,15 @@ type Context = BodyInput & {
|
|
|
339
350
|
// The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
|
|
340
351
|
type Output = { outcome: "pushed"; branch: string; commit: string };
|
|
341
352
|
|
|
353
|
+
// The Worktree of the coder. The Machine names the folder, and each Effect that works there names it.
|
|
354
|
+
function fixTree(context: BodyInput) {
|
|
355
|
+
return `${context.workspace.repos.target}/fix` as const;
|
|
356
|
+
}
|
|
357
|
+
// One branch for each run, from its seed Instance ID.
|
|
358
|
+
function branchOf(context: BodyInput): string {
|
|
359
|
+
return `jr2/fix-${context.instanceId}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
342
362
|
// Two guards. A guard with a name shows that name on its arrow in the Console's graph.
|
|
343
363
|
function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
|
|
344
364
|
return event.output.outcome === "exited" && event.output.code === 0;
|
|
@@ -362,7 +382,9 @@ const body = jr2Setup({
|
|
|
362
382
|
- Make the smallest change that fixes the bug.
|
|
363
383
|
- End your turn with exactly one call to \`finish\`.`,
|
|
364
384
|
}),
|
|
365
|
-
//
|
|
385
|
+
// Cut a Worktree of `target`: the Workspace only clones.
|
|
386
|
+
cut: worktree(),
|
|
387
|
+
// A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
|
|
366
388
|
tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
|
|
367
389
|
// Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
|
|
368
390
|
publish: push(),
|
|
@@ -372,16 +394,24 @@ const body = jr2Setup({
|
|
|
372
394
|
}).createMachine({
|
|
373
395
|
id: "fix",
|
|
374
396
|
context: ({ input }) => ({ ...input, fresh: true }),
|
|
375
|
-
initial: "
|
|
397
|
+
initial: "cutting",
|
|
376
398
|
// The Sandbox is gone, and every commit that was not pushed went with it.
|
|
377
399
|
on: { "workspace.lost": { target: ".lost" } },
|
|
378
400
|
states: {
|
|
401
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
402
|
+
cutting: {
|
|
403
|
+
invoke: {
|
|
404
|
+
src: "cut",
|
|
405
|
+
input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
|
|
406
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
379
409
|
working: {
|
|
380
410
|
invoke: {
|
|
381
411
|
src: "coder",
|
|
382
412
|
input: ({ context }) => ({
|
|
383
413
|
prompt: promptOf(context),
|
|
384
|
-
cwd: context
|
|
414
|
+
cwd: fixTree(context),
|
|
385
415
|
// Each Turn continues the coder's one conversation, so the coder remembers its work.
|
|
386
416
|
continue: true,
|
|
387
417
|
}),
|
|
@@ -406,6 +436,7 @@ const body = jr2Setup({
|
|
|
406
436
|
testing: {
|
|
407
437
|
invoke: {
|
|
408
438
|
src: "tests",
|
|
439
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
409
440
|
onDone: [
|
|
410
441
|
{ guard: testsPassed, target: "review" },
|
|
411
442
|
{
|
|
@@ -423,8 +454,8 @@ const body = jr2Setup({
|
|
|
423
454
|
meta: {
|
|
424
455
|
...(context.summary !== undefined ? { summary: context.summary } : {}),
|
|
425
456
|
...(context.reason !== undefined ? { reason: context.reason } : {}),
|
|
426
|
-
branch: context
|
|
427
|
-
worktree: context
|
|
457
|
+
branch: branchOf(context),
|
|
458
|
+
worktree: fixTree(context),
|
|
428
459
|
},
|
|
429
460
|
}),
|
|
430
461
|
},
|
|
@@ -442,6 +473,7 @@ const body = jr2Setup({
|
|
|
442
473
|
pushing: {
|
|
443
474
|
invoke: {
|
|
444
475
|
src: "publish",
|
|
476
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
445
477
|
onDone: [
|
|
446
478
|
{
|
|
447
479
|
guard: pushed,
|
|
@@ -457,11 +489,12 @@ const body = jr2Setup({
|
|
|
457
489
|
type: "final",
|
|
458
490
|
output: ({ context }): Output => ({
|
|
459
491
|
outcome: "pushed",
|
|
460
|
-
branch: context
|
|
492
|
+
branch: branchOf(context),
|
|
461
493
|
commit: context.commit ?? "",
|
|
462
494
|
}),
|
|
463
495
|
},
|
|
464
|
-
// Not
|
|
496
|
+
// Not final states: the run ends `failed`, and jr2 deletes the Sandbox.
|
|
497
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
465
498
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
|
|
466
499
|
},
|
|
467
500
|
// The output of the run. xstate reads only this root `output` as the output of the Machine. It
|
|
@@ -476,14 +509,14 @@ function promptOf(context: Context): string {
|
|
|
476
509
|
const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
|
|
477
510
|
return (
|
|
478
511
|
`${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
|
|
479
|
-
`Work in ${context
|
|
512
|
+
`Work in ${fixTree(context)}, on the branch ${branchOf(context)}. ` +
|
|
480
513
|
"Commit your fix there, then call finish."
|
|
481
514
|
);
|
|
482
515
|
}
|
|
483
516
|
|
|
484
517
|
// What the coder reads when the tests fail: the end of the output of `node --test`.
|
|
485
518
|
function testFeedback(output: CommandOutput): string {
|
|
486
|
-
const text =
|
|
519
|
+
const text = "tail" in output ? output.tail : output.reason;
|
|
487
520
|
return (
|
|
488
521
|
`The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
|
|
489
522
|
"Fix the code, commit, then call finish."
|
|
@@ -503,8 +536,6 @@ function pushFailure(output: PushOutput): string {
|
|
|
503
536
|
export const machine = workspace(body, {
|
|
504
537
|
input: door,
|
|
505
538
|
repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
|
|
506
|
-
// One branch for each run, from its seed Instance ID: `input.instanceId`.
|
|
507
|
-
spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
|
|
508
539
|
});
|
|
509
540
|
```
|
|
510
541
|
|
|
@@ -518,9 +549,11 @@ import {
|
|
|
518
549
|
command,
|
|
519
550
|
defineEvent,
|
|
520
551
|
fail,
|
|
552
|
+
isReady,
|
|
521
553
|
jr2Setup,
|
|
522
554
|
push,
|
|
523
555
|
workspace,
|
|
556
|
+
worktree,
|
|
524
557
|
type CommandOutput,
|
|
525
558
|
type HostInjectedInput,
|
|
526
559
|
type PushOutput,
|
|
@@ -568,6 +601,15 @@ type Context = BodyInput & {
|
|
|
568
601
|
// The output of a run that ends `done`. A lost Sandbox fails the run, with no output of this type.
|
|
569
602
|
type Output = { outcome: "pushed"; branch: string; commit: string };
|
|
570
603
|
|
|
604
|
+
// The Worktree of the coder. The Machine names the folder, and each Effect that works there names it.
|
|
605
|
+
function fixTree(context: BodyInput) {
|
|
606
|
+
return `${context.workspace.repos.target}/fix` as const;
|
|
607
|
+
}
|
|
608
|
+
// One branch for each run, from its seed Instance ID.
|
|
609
|
+
function branchOf(context: BodyInput): string {
|
|
610
|
+
return `jr2/fix-${context.instanceId}`;
|
|
611
|
+
}
|
|
612
|
+
|
|
571
613
|
// Two guards. A guard with a name shows that name on its arrow in the Console's graph.
|
|
572
614
|
function testsPassed({ event }: { event: { output: CommandOutput } }): boolean {
|
|
573
615
|
return event.output.outcome === "exited" && event.output.code === 0;
|
|
@@ -591,7 +633,9 @@ const body = jr2Setup({
|
|
|
591
633
|
- Make the smallest change that fixes the bug.
|
|
592
634
|
- End your turn with exactly one call to \`finish\`.`,
|
|
593
635
|
}),
|
|
594
|
-
//
|
|
636
|
+
// Cut a Worktree of `target`: the Workspace only clones.
|
|
637
|
+
cut: worktree(),
|
|
638
|
+
// A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
|
|
595
639
|
tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
|
|
596
640
|
// Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
|
|
597
641
|
publish: push(),
|
|
@@ -601,16 +645,24 @@ const body = jr2Setup({
|
|
|
601
645
|
}).createMachine({
|
|
602
646
|
id: "fix",
|
|
603
647
|
context: ({ input }) => ({ ...input, fresh: true }),
|
|
604
|
-
initial: "
|
|
648
|
+
initial: "cutting",
|
|
605
649
|
// The Sandbox is gone, and every commit that was not pushed went with it.
|
|
606
650
|
on: { "workspace.lost": { target: ".lost" } },
|
|
607
651
|
states: {
|
|
652
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
653
|
+
cutting: {
|
|
654
|
+
invoke: {
|
|
655
|
+
src: "cut",
|
|
656
|
+
input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
|
|
657
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
|
|
658
|
+
},
|
|
659
|
+
},
|
|
608
660
|
working: {
|
|
609
661
|
invoke: {
|
|
610
662
|
src: "coder",
|
|
611
663
|
input: ({ context }) => ({
|
|
612
664
|
prompt: promptOf(context),
|
|
613
|
-
cwd: context
|
|
665
|
+
cwd: fixTree(context),
|
|
614
666
|
// Each Turn continues the coder's one conversation, so the coder remembers its work.
|
|
615
667
|
continue: true,
|
|
616
668
|
}),
|
|
@@ -635,6 +687,7 @@ const body = jr2Setup({
|
|
|
635
687
|
testing: {
|
|
636
688
|
invoke: {
|
|
637
689
|
src: "tests",
|
|
690
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
638
691
|
onDone: [
|
|
639
692
|
{ guard: testsPassed, target: "review" },
|
|
640
693
|
{
|
|
@@ -652,8 +705,8 @@ const body = jr2Setup({
|
|
|
652
705
|
meta: {
|
|
653
706
|
...(context.summary !== undefined ? { summary: context.summary } : {}),
|
|
654
707
|
...(context.reason !== undefined ? { reason: context.reason } : {}),
|
|
655
|
-
branch: context
|
|
656
|
-
worktree: context
|
|
708
|
+
branch: branchOf(context),
|
|
709
|
+
worktree: fixTree(context),
|
|
657
710
|
},
|
|
658
711
|
}),
|
|
659
712
|
},
|
|
@@ -671,6 +724,7 @@ const body = jr2Setup({
|
|
|
671
724
|
pushing: {
|
|
672
725
|
invoke: {
|
|
673
726
|
src: "publish",
|
|
727
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
674
728
|
onDone: [
|
|
675
729
|
{
|
|
676
730
|
guard: pushed,
|
|
@@ -686,11 +740,12 @@ const body = jr2Setup({
|
|
|
686
740
|
type: "final",
|
|
687
741
|
output: ({ context }): Output => ({
|
|
688
742
|
outcome: "pushed",
|
|
689
|
-
branch: context
|
|
743
|
+
branch: branchOf(context),
|
|
690
744
|
commit: context.commit ?? "",
|
|
691
745
|
}),
|
|
692
746
|
},
|
|
693
|
-
// Not
|
|
747
|
+
// Not final states: the run ends `failed`, and jr2 deletes the Sandbox.
|
|
748
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
694
749
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost, with every commit that was not pushed" } } },
|
|
695
750
|
},
|
|
696
751
|
// The output of the run. xstate reads only this root `output` as the output of the Machine. It
|
|
@@ -705,14 +760,14 @@ function promptOf(context: Context): string {
|
|
|
705
760
|
const reason = context.reason ? `\n\nThe last attempt ended: ${context.reason}` : "";
|
|
706
761
|
return (
|
|
707
762
|
`${context.prompt}${reason}${feedback ? `\n\n${feedback}` : ""}\n\n` +
|
|
708
|
-
`Work in ${context
|
|
763
|
+
`Work in ${fixTree(context)}, on the branch ${branchOf(context)}. ` +
|
|
709
764
|
"Commit your fix there, then call finish."
|
|
710
765
|
);
|
|
711
766
|
}
|
|
712
767
|
|
|
713
768
|
// What the coder reads when the tests fail: the end of the output of `node --test`.
|
|
714
769
|
function testFeedback(output: CommandOutput): string {
|
|
715
|
-
const text =
|
|
770
|
+
const text = "tail" in output ? output.tail : output.reason;
|
|
716
771
|
return (
|
|
717
772
|
`The tests do not pass (${output.outcome}). The end of their output:\n${text}\n\n` +
|
|
718
773
|
"Fix the code, commit, then call finish."
|
|
@@ -732,8 +787,6 @@ function pushFailure(output: PushOutput): string {
|
|
|
732
787
|
export const machine = workspace(body, {
|
|
733
788
|
input: door,
|
|
734
789
|
repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
|
|
735
|
-
// One branch for each run, from its seed Instance ID: `input.instanceId`.
|
|
736
|
-
spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
|
|
737
790
|
});
|
|
738
791
|
```
|
|
739
792
|
|
|
@@ -765,16 +818,19 @@ const requestChanges = defineEvent({
|
|
|
765
818
|
});
|
|
766
819
|
```
|
|
767
820
|
|
|
768
|
-
**The Effects.** `tests` and `publish` are actor slots, as the coder is. A state runs one when it invokes it.
|
|
769
|
-
[`
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
[`push()`](../reference/authoring-surface/push.md) pushes the
|
|
821
|
+
**The Effects.** `cut`, `tests` and `publish` are actor slots, as the coder is. A state runs one when it invokes it.
|
|
822
|
+
[`worktree()`](../reference/authoring-surface/worktree.md) cuts a [Worktree](../reference/glossary.md#worktree) of the
|
|
823
|
+
[Repo Slot](../reference/glossary.md#repo-slot). [`command()`](../reference/authoring-surface/command.md) takes a list
|
|
824
|
+
of arguments, not a line of shell text. Its input names the Worktree. A command runs in the root of the Worktree, so
|
|
825
|
+
`tests` names the test files by their path from that root. [`push()`](../reference/authoring-surface/push.md) pushes the
|
|
826
|
+
tip of the coder's branch. Its input names the Worktree too, and jr2 reads the branch from the cut.
|
|
773
827
|
[`fail()`](../reference/authoring-surface/fail.md) is a Part, not an Effect. The `lost` state invokes it to end the run
|
|
774
828
|
`failed`, because a final state means `done`.
|
|
775
829
|
|
|
776
830
|
```ts
|
|
777
|
-
//
|
|
831
|
+
// Cut a Worktree of `target`: the Workspace only clones.
|
|
832
|
+
cut: worktree(),
|
|
833
|
+
// A program in the Harness container of the Sandbox, in the Worktree that its input names. Node expands the glob itself.
|
|
778
834
|
tests: command(["node", "--test", "docs/site/tutorial/greet/test/*.test.js"], { timeout: "5m" }),
|
|
779
835
|
// Push the tip of the coder's branch to the Repo of `target`, with the token of `git.credentials`.
|
|
780
836
|
publish: push(),
|
|
@@ -782,6 +838,21 @@ publish: push(),
|
|
|
782
838
|
fail: fail(),
|
|
783
839
|
```
|
|
784
840
|
|
|
841
|
+
**The cut.** The Workspace only clones. `cutting` is the first state, and it makes the coder's Worktree. The path is
|
|
842
|
+
`/work/target/fix`, and the branch has the Instance ID of the run in it. See
|
|
843
|
+
[Cut a Worktree](../guides/cut-a-worktree.md).
|
|
844
|
+
|
|
845
|
+
```ts
|
|
846
|
+
// The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
|
|
847
|
+
cutting: {
|
|
848
|
+
invoke: {
|
|
849
|
+
src: "cut",
|
|
850
|
+
input: ({ context }) => ({ path: fixTree(context), branch: branchOf(context) }),
|
|
851
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "working" }, { target: "notCut" }],
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
```
|
|
855
|
+
|
|
785
856
|
**The coder's state.** `working` invokes the coder. Each [Turn](../reference/glossary.md#turn) gets a prompt and the
|
|
786
857
|
folder to work in. `continue: true` keeps one conversation, so the coder remembers its last Turn. `finish` is the
|
|
787
858
|
coder's pick: the run goes to `testing`. [`agent.fault`](../reference/mechanism-events.md#agent-fault) is an event from
|
|
@@ -793,7 +864,7 @@ working: {
|
|
|
793
864
|
src: "coder",
|
|
794
865
|
input: ({ context }) => ({
|
|
795
866
|
prompt: promptOf(context),
|
|
796
|
-
cwd: context
|
|
867
|
+
cwd: fixTree(context),
|
|
797
868
|
// Each Turn continues the coder's one conversation, so the coder remembers its work.
|
|
798
869
|
continue: true,
|
|
799
870
|
}),
|
|
@@ -825,6 +896,7 @@ catches every other outcome. See [`CommandOutput`](../reference/authoring-surfac
|
|
|
825
896
|
testing: {
|
|
826
897
|
invoke: {
|
|
827
898
|
src: "tests",
|
|
899
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
828
900
|
onDone: [
|
|
829
901
|
{ guard: testsPassed, target: "review" },
|
|
830
902
|
{
|
|
@@ -849,8 +921,8 @@ review: {
|
|
|
849
921
|
meta: {
|
|
850
922
|
...(context.summary !== undefined ? { summary: context.summary } : {}),
|
|
851
923
|
...(context.reason !== undefined ? { reason: context.reason } : {}),
|
|
852
|
-
branch: context
|
|
853
|
-
worktree: context
|
|
924
|
+
branch: branchOf(context),
|
|
925
|
+
worktree: fixTree(context),
|
|
854
926
|
},
|
|
855
927
|
}),
|
|
856
928
|
},
|
|
@@ -875,6 +947,7 @@ commits in it. See [`PushOutput`](../reference/authoring-surface/type-pushoutput
|
|
|
875
947
|
pushing: {
|
|
876
948
|
invoke: {
|
|
877
949
|
src: "publish",
|
|
950
|
+
input: ({ context }) => ({ worktree: fixTree(context) }),
|
|
878
951
|
onDone: [
|
|
879
952
|
{
|
|
880
953
|
guard: pushed,
|
|
@@ -890,15 +963,13 @@ pushing: {
|
|
|
890
963
|
|
|
891
964
|
**The Workspace.** [`workspace()`](../reference/authoring-surface/workspace.md) runs the body in a Sandbox. `input` is
|
|
892
965
|
the schema of the run input that `jr2 run --input` sends. `repos` binds the Repo Slot `target` to your fork, at the
|
|
893
|
-
release tag.
|
|
966
|
+
release tag. A new branch starts from that tag. The body also gets `instanceId`, an id that jr2 makes for each run, so
|
|
894
967
|
each run gets a branch of its own.
|
|
895
968
|
|
|
896
969
|
```ts
|
|
897
970
|
export const machine = workspace(body, {
|
|
898
971
|
input: door,
|
|
899
972
|
repos: { target: { url: "https://github.com/<you>/jr2.git", ref: "v<version>" } },
|
|
900
|
-
// One branch for each run, from its seed Instance ID: `input.instanceId`.
|
|
901
|
-
spec: ({ input }) => ({ branch: `jr2/fix-${input.instanceId}` }),
|
|
902
973
|
});
|
|
903
974
|
```
|
|
904
975
|
|
|
@@ -20,6 +20,10 @@ This is the first page of the tutorial. You need a computer with these tools:
|
|
|
20
20
|
kind makes the cluster from Docker containers. `jr2 up` builds an image with Docker and loads it into the kind cluster.
|
|
21
21
|
kubectl is the tool that `jr2 up`, `jr2 run` and the other commands use to speak to the cluster.
|
|
22
22
|
|
|
23
|
+
The kind cluster uses the Linux kernel of Docker: the kernel of your computer, or on macOS and Windows, the kernel of
|
|
24
|
+
the virtual machine of Docker. jr2 needs Landlock in that kernel: Linux 6.2 or later, with `landlock` in its list of
|
|
25
|
+
security modules. `jr2 up` refuses to converge without Landlock.
|
|
26
|
+
|
|
23
27
|
## Check Node.js
|
|
24
28
|
|
|
25
29
|
Run:
|
|
@@ -137,26 +137,39 @@ RUN pip install --no-cache-dir pytest ruff
|
|
|
137
137
|
|
|
138
138
|
```ts
|
|
139
139
|
import { z } from "zod";
|
|
140
|
-
import { command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
140
|
+
import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
|
|
141
141
|
|
|
142
142
|
const runInput = z.object({ branch: z.string() });
|
|
143
143
|
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
|
|
144
144
|
|
|
145
|
+
// The Worktree that the tests run in.
|
|
146
|
+
const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
|
|
147
|
+
|
|
145
148
|
const body = jr2Setup({
|
|
146
|
-
types: {} as { input: BodyInput },
|
|
149
|
+
types: {} as { input: BodyInput; context: BodyInput },
|
|
147
150
|
actors: {
|
|
151
|
+
cut: worktree(),
|
|
148
152
|
// python and pytest come from the Sandbox Image in ./python.
|
|
149
153
|
tests: command(["python", "-m", "pytest"], { timeout: "15m" }),
|
|
150
154
|
fail: fail(),
|
|
151
155
|
},
|
|
152
156
|
}).createMachine({
|
|
153
157
|
id: "pytest",
|
|
158
|
+
context: ({ input }) => input,
|
|
154
159
|
on: { "workspace.lost": ".lost" },
|
|
155
|
-
initial: "
|
|
160
|
+
initial: "cutting",
|
|
156
161
|
states: {
|
|
162
|
+
cutting: {
|
|
163
|
+
invoke: {
|
|
164
|
+
src: "cut",
|
|
165
|
+
input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
|
|
166
|
+
onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
157
169
|
testing: {
|
|
158
170
|
invoke: {
|
|
159
171
|
src: "tests",
|
|
172
|
+
input: ({ context }) => ({ worktree: testTree(context) }),
|
|
160
173
|
onDone: [
|
|
161
174
|
{
|
|
162
175
|
guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
|
|
@@ -169,6 +182,7 @@ const body = jr2Setup({
|
|
|
169
182
|
passed: { type: "final" },
|
|
170
183
|
// Not final states: each one fails the run, and the run ends `failed`.
|
|
171
184
|
failed: { invoke: { src: "fail", input: { reason: "pytest did not pass" } } },
|
|
185
|
+
notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
172
186
|
lost: { invoke: { src: "fail", input: { reason: "the Sandbox was lost" } } },
|
|
173
187
|
},
|
|
174
188
|
});
|
|
@@ -178,7 +192,6 @@ export const machine = workspace(body, {
|
|
|
178
192
|
// The docker context in the folder beside this file. jr2 up builds it.
|
|
179
193
|
image: import.meta.resolve("./python"),
|
|
180
194
|
repos: { app: "https://github.com/you/app.git" },
|
|
181
|
-
spec: ({ input }) => ({ branch: input.branch }),
|
|
182
195
|
});
|
|
183
196
|
```
|
|
184
197
|
|
|
@@ -121,9 +121,10 @@ jr2 run fix-app --input '{"prompt":"Make the failing test in test/greet.test.ts
|
|
|
121
121
|
The key of each Repo Slot is the name of the slot, and the directory `/work/<slot>` in the
|
|
122
122
|
[Sandbox](../reference/glossary.md#sandbox). The value is one of these:
|
|
123
123
|
|
|
124
|
-
- a url: `"https://github.com/<you>/handbook.git"`. The
|
|
125
|
-
|
|
126
|
-
- a [Binding](../reference/glossary.md#binding): `{ url, ref }`. The
|
|
124
|
+
- a url: `"https://github.com/<you>/handbook.git"`. The clone stays on the default branch of the Repo, and a new
|
|
125
|
+
[Worktree](../reference/glossary.md#worktree) branch starts from it.
|
|
126
|
+
- a [Binding](../reference/glossary.md#binding): `{ url, ref }`. The clone stays on `ref`, a branch, a tag or a commit,
|
|
127
|
+
and a new branch starts from it.
|
|
127
128
|
- a function of the run input: `({ input }) => url`, or `({ input }) => ({ url, ref })`. Each run chooses its Repo. The
|
|
128
129
|
url must agree with an entry of [`git.credentials`](../reference/config.md#git-credentials) in `jr2.config.ts`.
|
|
129
130
|
- `open`, to leave the slot Open for the Instance that registers your Machine.
|
|
@@ -191,7 +192,7 @@ gives the values, and [Run a command or call a URL with an Effect](run-a-command
|
|
|
191
192
|
- `customize()` refuses an Open map that you bind with no slot:
|
|
192
193
|
``customize(): machine "<id>" declares its Repo Slots open as a map — name at least one: `repos: { app: "https://…" }` (ADR-0051).``
|
|
193
194
|
- `customize()` refuses a Repo Slot key that is not a directory name. The key becomes `/work/<slot>`:
|
|
194
|
-
``customize(): Repo Slot key "<slot>" is not a directory name — a slot becomes `/work/<slot
|
|
195
|
+
``customize(): Repo Slot key "<slot>" is not a directory name — a slot becomes `/work/<slot>`, `/work/.jr2/` is reserved for jr2, and its order matters, so it starts with a letter and matches /^[A-Za-z][A-Za-z0-9._-]*$/ (ADR-0051, ADR-0075).``
|
|
195
196
|
- `customize()` refuses an empty url:
|
|
196
197
|
``customize(): Repo Slot "<slot>" is bound to an empty url (got <value>) — a slot is `open`, a url, `{ url, ref? }`, or a mapper `({ input }) => url | { url, ref? }` over the door (ADR-0051).``
|
|
197
198
|
For an empty `ref`, the text says `bound with an empty ref`.
|