@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.
Files changed (185) hide show
  1. package/docs/agents-block.md +2 -2
  2. package/docs/concepts/agents-and-turns.md +56 -20
  3. package/docs/concepts/architecture.md +30 -17
  4. package/docs/concepts/charts.md +6 -3
  5. package/docs/concepts/converge.md +3 -0
  6. package/docs/concepts/effects.md +86 -31
  7. package/docs/concepts/machines-and-workflows.md +9 -0
  8. package/docs/concepts/pools-and-sources.md +6 -6
  9. package/docs/concepts/secrets-and-trust.md +126 -42
  10. package/docs/concepts/the-menu.md +3 -0
  11. package/docs/concepts/workspaces.md +129 -51
  12. package/docs/get-started/an-agent-in-a-sandbox.md +21 -19
  13. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  14. package/docs/get-started/install.md +4 -0
  15. package/docs/guides/add-tools-to-the-sandbox.md +17 -4
  16. package/docs/guides/bind-open-parts.md +5 -4
  17. package/docs/guides/cut-a-worktree.md +560 -0
  18. package/docs/guides/fix-a-failed-converge.md +1 -1
  19. package/docs/guides/let-an-agent-write-a-chart.md +5 -1
  20. package/docs/guides/look-inside-a-sandbox.md +18 -15
  21. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  22. package/docs/guides/push-and-sign-a-branch.md +67 -21
  23. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  24. package/docs/guides/run-one-worker-per-item.md +19 -18
  25. package/docs/guides/set-an-agents-access-and-dials.md +67 -22
  26. package/docs/guides/size-a-workspace.md +19 -6
  27. package/docs/guides/use-a-hosted-provider.md +6 -0
  28. package/docs/guides/watch-a-run.md +47 -24
  29. package/docs/guides/write-a-workflow.md +1 -1
  30. package/docs/llms.txt +33 -12
  31. package/docs/reference/authoring-surface/agent.md +34 -11
  32. package/docs/reference/authoring-surface/answer.md +19 -3
  33. package/docs/reference/authoring-surface/arg.md +27 -5
  34. package/docs/reference/authoring-surface/branchname.md +15 -0
  35. package/docs/reference/authoring-surface/command.md +36 -21
  36. package/docs/reference/authoring-surface/fail.md +17 -4
  37. package/docs/reference/authoring-surface/file.md +36 -16
  38. package/docs/reference/authoring-surface/http.md +108 -8
  39. package/docs/reference/authoring-surface/isready.md +14 -0
  40. package/docs/reference/authoring-surface/jr2setup.md +1 -1
  41. package/docs/reference/authoring-surface/open.md +36 -5
  42. package/docs/reference/authoring-surface/paramsof.md +22 -4
  43. package/docs/reference/authoring-surface/pool.md +4 -4
  44. package/docs/reference/authoring-surface/push.md +30 -12
  45. package/docs/reference/authoring-surface/readyof.md +21 -0
  46. package/docs/reference/authoring-surface/secret.md +25 -4
  47. package/docs/reference/authoring-surface/source.md +3 -3
  48. package/docs/reference/authoring-surface/type-agentdefinition.md +2 -2
  49. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  50. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  51. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  52. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  53. package/docs/reference/authoring-surface/type-binding.md +5 -6
  54. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  55. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  56. package/docs/reference/authoring-surface/type-commandoutput.md +22 -4
  57. package/docs/reference/authoring-surface/type-customize.md +4 -2
  58. package/docs/reference/authoring-surface/type-dispatchoutput.md +8 -1
  59. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  60. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  61. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  62. package/docs/reference/authoring-surface/type-hostinjectedinput.md +2 -4
  63. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  64. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  65. package/docs/reference/authoring-surface/type-httpoutput.md +7 -1
  66. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  67. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  68. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  69. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  70. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  71. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  72. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  73. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  74. package/docs/reference/authoring-surface/type-poolspec.md +2 -2
  75. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  76. package/docs/reference/authoring-surface/type-pushoutput.md +32 -7
  77. package/docs/reference/authoring-surface/type-reposlot.md +5 -5
  78. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  79. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  80. package/docs/reference/authoring-surface/type-sourcespec.md +10 -10
  81. package/docs/reference/authoring-surface/type-workspaced.md +29 -8
  82. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  83. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  84. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  85. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  86. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  87. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  88. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  89. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  90. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  91. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  92. package/docs/reference/authoring-surface/workspace.md +65 -37
  93. package/docs/reference/authoring-surface/worktree.md +71 -0
  94. package/docs/reference/authoring-surface.md +30 -10
  95. package/docs/reference/cli.md +1 -1
  96. package/docs/reference/config.md +13 -4
  97. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  98. package/docs/reference/decisions/0002-duplex-streaming-actor.md +17 -10
  99. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  100. package/docs/reference/decisions/0005-sandbox-pod-composition.md +29 -19
  101. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  102. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +30 -30
  103. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +35 -17
  104. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +8 -2
  105. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  106. package/docs/reference/decisions/0017-pool-over-a-source-port.md +6 -4
  107. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +21 -9
  108. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +2 -1
  109. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +8 -9
  110. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +62 -65
  111. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +9 -1
  112. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +13 -12
  113. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +4 -1
  114. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +18 -19
  115. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +8 -8
  116. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +4 -4
  117. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  118. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  119. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  120. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +24 -24
  121. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +5 -3
  122. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +110 -67
  123. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +101 -74
  124. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +5 -6
  125. 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
  126. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +181 -153
  127. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +74 -63
  128. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +31 -20
  129. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  130. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  131. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  132. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  133. package/docs/reference/decisions.md +3 -0
  134. package/docs/reference/glossary.md +51 -12
  135. package/docs/reference/http-api.md +45 -7
  136. package/docs/reference/machines.md +0 -1
  137. package/docs/why.md +5 -4
  138. package/package.json +3 -3
  139. package/src/actor.ts +161 -35
  140. package/src/agent.ts +6 -6
  141. package/src/ambient.ts +3 -5
  142. package/src/chart.ts +186 -19
  143. package/src/charts.ts +9 -2
  144. package/src/config.ts +16 -6
  145. package/src/custodian.lua +17 -5
  146. package/src/custodian.ts +251 -77
  147. package/src/customize.ts +11 -3
  148. package/src/effect-checks.ts +50 -3
  149. package/src/effect.ts +343 -127
  150. package/src/effector-wire.ts +6 -3
  151. package/src/fail.ts +17 -4
  152. package/src/harness-client.ts +74 -31
  153. package/src/harness-env.ts +33 -0
  154. package/src/held-secrets.ts +125 -16
  155. package/src/holds.ts +3 -0
  156. package/src/http-effect.ts +88 -32
  157. package/src/http.ts +56 -17
  158. package/src/index.ts +15 -1
  159. package/src/internal.ts +1 -0
  160. package/src/kube-client.ts +6 -3
  161. package/src/machine-doc.ts +3 -4
  162. package/src/model-keys.ts +24 -0
  163. package/src/names.ts +19 -6
  164. package/src/notices.ts +443 -54
  165. package/src/open-body.ts +85 -19
  166. package/src/open.ts +36 -5
  167. package/src/parts.ts +40 -28
  168. package/src/party.ts +33 -0
  169. package/src/pick.ts +41 -7
  170. package/src/pool.ts +44 -23
  171. package/src/registration.ts +53 -11
  172. package/src/repo-identity.ts +7 -3
  173. package/src/repos.ts +20 -2
  174. package/src/run-host.ts +106 -20
  175. package/src/sandbox-effect.ts +217 -66
  176. package/src/sandbox-kube.ts +63 -16
  177. package/src/secret-placement.ts +9 -2
  178. package/src/setup.ts +86 -6
  179. package/src/tokens.ts +107 -32
  180. package/src/vocabulary.ts +23 -4
  181. package/src/wire.ts +10 -10
  182. package/src/workspace.ts +264 -253
  183. package/src/worktree.ts +249 -0
  184. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  185. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
@@ -7,9 +7,10 @@ description: How a Machine runs a command, calls a URL, pushes a branch or start
7
7
 
8
8
  An [Effect](../reference/glossary.md#effect) is work that a [Machine](../reference/glossary.md#machine) does outside
9
9
  itself. jr2 has four kinds: `command` runs a program in the [Workspace](../reference/glossary.md#workspace), `http`
10
- calls a URL, `push` pushes the Workspace's branch, and `dispatch` starts a run of a
11
- [Workflow](../reference/glossary.md#workflow). You write an Effect as an actor slot, beside the
12
- [Agents](../reference/glossary.md#agent) of the Machine.
10
+ calls a URL, `push` pushes the branch of a [Worktree](../reference/glossary.md#worktree), and `dispatch` starts a run of
11
+ a [Workflow](../reference/glossary.md#workflow). The Kit also gives
12
+ [`worktree()`](../reference/authoring-surface/worktree.md), an Effect on the `command` machinery that cuts a Worktree.
13
+ You write an Effect as an actor slot, beside the [Agents](../reference/glossary.md#agent) of the Machine.
13
14
 
14
15
  ## Why Effects exist
15
16
 
@@ -26,8 +27,8 @@ two times. A plain [xstate](https://xstate.js.org) promise has no such record.
26
27
 
27
28
  Put an Effect under `actors` in [`jr2Setup`](../reference/authoring-surface/jr2setup.md). Invoke it by its key from a
28
29
  state. The state's `onDone` gets the output of the Effect. This example is a Workflow with a Workspace. An Agent fixes
29
- the code, and it can ask for a test run with the pick `run_tests`. When the Agent picks `finished`, the Machine pushes
30
- the branch:
30
+ the code in a Worktree that the Machine cuts first. It can ask for a test run with the pick `run_tests`. When the Agent
31
+ picks `finished`, the Machine pushes the branch:
31
32
 
32
33
  **Anthropic**
33
34
 
@@ -39,9 +40,11 @@ import {
39
40
  command,
40
41
  defineEvent,
41
42
  fail,
43
+ isReady,
42
44
  jr2Setup,
43
45
  push,
44
46
  workspace,
47
+ worktree,
45
48
  type CommandOutput,
46
49
  type Workspaced,
47
50
  } from "@jr2/orchestrator";
@@ -61,6 +64,9 @@ const finished = defineEvent({
61
64
  const runInput = z.object({ prompt: z.string(), branch: z.string() });
62
65
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
63
66
 
67
+ // The Worktree that the coder, the tests and the push all name.
68
+ const appTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
69
+
64
70
  const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
65
71
 
66
72
  const body = jr2Setup({
@@ -71,7 +77,8 @@ const body = jr2Setup({
71
77
  model: "anthropic/claude-haiku-5-5",
72
78
  instructions: "You are a careful engineer. You commit your work on the branch.",
73
79
  }),
74
- // Two Effects. Each is an actor slot, beside the Agent.
80
+ // Three Effects. Each is an actor slot, beside the Agent.
81
+ cut: worktree(),
75
82
  tests: command(["node", "--test"], { timeout: "5m" }),
76
83
  publish: push(),
77
84
  fail: fail(),
@@ -80,12 +87,20 @@ const body = jr2Setup({
80
87
  id: "test-and-push",
81
88
  context: ({ input }) => input,
82
89
  on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
83
- initial: "coding",
90
+ initial: "cutting",
84
91
  states: {
92
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
93
+ cutting: {
94
+ invoke: {
95
+ src: "cut",
96
+ input: ({ context }) => ({ path: appTree(context), branch: context.branch }),
97
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
98
+ },
99
+ },
85
100
  coding: {
86
101
  invoke: {
87
102
  src: "coder",
88
- input: ({ context }) => ({ prompt: context.prompt, cwd: context.workspace.repos.app }),
103
+ input: ({ context }) => ({ prompt: context.prompt, cwd: appTree(context) }),
89
104
  },
90
105
  on: { finished: "publishing" },
91
106
  initial: "ready",
@@ -94,6 +109,7 @@ const body = jr2Setup({
94
109
  testing: {
95
110
  invoke: {
96
111
  src: "tests",
112
+ input: ({ context }) => ({ worktree: appTree(context) }),
97
113
  onDone: {
98
114
  target: "ready",
99
115
  // The Agent reads this text as the answer to its run_tests pick.
@@ -110,10 +126,12 @@ const body = jr2Setup({
110
126
  publishing: {
111
127
  invoke: {
112
128
  src: "publish",
129
+ input: ({ context }) => ({ worktree: appTree(context) }),
113
130
  onDone: [{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" }, { target: "notPushed" }],
114
131
  },
115
132
  },
116
133
  done: { type: "final" },
134
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
117
135
  // A Failure ends the body, and jr2 deletes the Sandbox with its commits. To keep them, wait at
118
136
  // a Gate before this state.
119
137
  notPushed: { invoke: { src: "fail", input: { reason: "the push did not publish the fix" } } },
@@ -125,7 +143,6 @@ const body = jr2Setup({
125
143
  export const machine = workspace(body, {
126
144
  input: runInput,
127
145
  repos: { app: "https://github.com/you/app.git" },
128
- spec: ({ input }) => ({ branch: input.branch }),
129
146
  });
130
147
  ```
131
148
 
@@ -139,9 +156,11 @@ import {
139
156
  command,
140
157
  defineEvent,
141
158
  fail,
159
+ isReady,
142
160
  jr2Setup,
143
161
  push,
144
162
  workspace,
163
+ worktree,
145
164
  type CommandOutput,
146
165
  type Workspaced,
147
166
  } from "@jr2/orchestrator";
@@ -161,6 +180,9 @@ const finished = defineEvent({
161
180
  const runInput = z.object({ prompt: z.string(), branch: z.string() });
162
181
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
163
182
 
183
+ // The Worktree that the coder, the tests and the push all name.
184
+ const appTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
185
+
164
186
  const passed = (output: CommandOutput): boolean => output.outcome === "exited" && output.code === 0;
165
187
 
166
188
  const body = jr2Setup({
@@ -171,7 +193,8 @@ const body = jr2Setup({
171
193
  model: "local/qwen3.6-35b-a3b",
172
194
  instructions: "You are a careful engineer. You commit your work on the branch.",
173
195
  }),
174
- // Two Effects. Each is an actor slot, beside the Agent.
196
+ // Three Effects. Each is an actor slot, beside the Agent.
197
+ cut: worktree(),
175
198
  tests: command(["node", "--test"], { timeout: "5m" }),
176
199
  publish: push(),
177
200
  fail: fail(),
@@ -180,12 +203,20 @@ const body = jr2Setup({
180
203
  id: "test-and-push",
181
204
  context: ({ input }) => input,
182
205
  on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
183
- initial: "coding",
206
+ initial: "cutting",
184
207
  states: {
208
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
209
+ cutting: {
210
+ invoke: {
211
+ src: "cut",
212
+ input: ({ context }) => ({ path: appTree(context), branch: context.branch }),
213
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
214
+ },
215
+ },
185
216
  coding: {
186
217
  invoke: {
187
218
  src: "coder",
188
- input: ({ context }) => ({ prompt: context.prompt, cwd: context.workspace.repos.app }),
219
+ input: ({ context }) => ({ prompt: context.prompt, cwd: appTree(context) }),
189
220
  },
190
221
  on: { finished: "publishing" },
191
222
  initial: "ready",
@@ -194,6 +225,7 @@ const body = jr2Setup({
194
225
  testing: {
195
226
  invoke: {
196
227
  src: "tests",
228
+ input: ({ context }) => ({ worktree: appTree(context) }),
197
229
  onDone: {
198
230
  target: "ready",
199
231
  // The Agent reads this text as the answer to its run_tests pick.
@@ -210,10 +242,12 @@ const body = jr2Setup({
210
242
  publishing: {
211
243
  invoke: {
212
244
  src: "publish",
245
+ input: ({ context }) => ({ worktree: appTree(context) }),
213
246
  onDone: [{ guard: ({ event }) => event.output.outcome === "pushed", target: "done" }, { target: "notPushed" }],
214
247
  },
215
248
  },
216
249
  done: { type: "final" },
250
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
217
251
  // A Failure ends the body, and jr2 deletes the Sandbox with its commits. To keep them, wait at
218
252
  // a Gate before this state.
219
253
  notPushed: { invoke: { src: "fail", input: { reason: "the push did not publish the fix" } } },
@@ -225,7 +259,6 @@ const body = jr2Setup({
225
259
  export const machine = workspace(body, {
226
260
  input: runInput,
227
261
  repos: { app: "https://github.com/you/app.git" },
228
- spec: ({ input }) => ({ branch: input.branch }),
229
262
  });
230
263
  ```
231
264
 
@@ -242,6 +275,7 @@ example above, `jr2 up` prints these lines:
242
275
 
243
276
  ```text
244
277
  effects: workflow "test-and-push" — what it runs outside itself (ADR-0067)
278
+ cut: worktree — 600s, in the Harness
245
279
  tests: command ["node","--test"] — 300s, in the Harness
246
280
  publish: push — 300s, in the Sandbox Effector
247
281
  ```
@@ -253,6 +287,7 @@ run `failed`:
253
287
 
254
288
  ```text
255
289
  failures: workflow "test-and-push" — the states that fail the run (ADR-0073)
290
+ test-and-push.notCut: invokes fail
256
291
  test-and-push.notPushed: invokes fail
257
292
  test-and-push.failed: invokes fail
258
293
  test-and-push.lost: invokes fail
@@ -268,10 +303,9 @@ argument that the input of the Effect fills. Thus an Agent's text cannot become
268
303
  in the argv: `["sh", "-c", "…"]`. Then the result is the result of your script. The Agent can change the files that the
269
304
  script runs, so do not trust its result more than the Agent.
270
305
 
271
- A `command` uses the [Worktree](../reference/glossary.md#worktree) of one
272
- [Repo Slot](../reference/glossary.md#repo-slot): the only slot, or the slot that its `slot` option names. Its default
273
- timeout is 10 minutes. Its output keeps the end of what the program wrote: 4096 characters by default (the `tail`
274
- option). The full output goes to the run's feed.
306
+ Under [Repo Slots](../reference/glossary.md#repo-slot), the input of a `command` names its Worktree: `{ worktree }`, a
307
+ Worktree that a cut made, or `/work/<slot>/default`. Its default timeout is 10 minutes. Its output keeps the end of what
308
+ the program wrote: 4096 characters by default (the `tail` option). The full output goes to the run's feed.
275
309
 
276
310
  ### `http`
277
311
 
@@ -413,15 +447,18 @@ export const machine = jr2Setup({
413
447
  });
414
448
  ```
415
449
 
416
- The body can also be a file of the Worktree: `body: file(path)` (see [`file`](../reference/authoring-surface/file.md)).
417
- The default timeout of an `http` Effect is 30 seconds.
450
+ The body can also be a file of a Worktree: `body: file(path)` (see [`file`](../reference/authoring-surface/file.md)).
451
+ The input then names the Worktree, `{ worktree }`, and a cut must have made it. A path alone does not name the target of
452
+ a credential. The default timeout of an `http` Effect is 30 seconds.
418
453
 
419
454
  ### `push`
420
455
 
421
- [`push`](../reference/authoring-surface/push.md) pushes the Workspace's branch to the
422
- [Binding](../reference/glossary.md#binding) of its Repo Slot. It uses the push credential of the `git.credentials` entry
423
- that the URL matches (see [`git.credentials`](../reference/config.md#git-credentials)). The Agent never reads that
424
- credential. These options change what a push publishes:
456
+ [`push`](../reference/authoring-surface/push.md) pushes the branch of a Worktree to the
457
+ [Binding](../reference/glossary.md#binding) of its Repo Slot. Its input names the Worktree, `{ worktree }`, and has no
458
+ branch. jr2 reads the branch from the cut of that path, so an Agent that moves `HEAD` does not change what jr2 pushes.
459
+ It uses the push credential of the `git.credentials` entry that the URL matches (see
460
+ [`git.credentials`](../reference/config.md#git-credentials)). The Agent never reads that credential. These options
461
+ change what a push publishes:
425
462
 
426
463
  - `pin`: push the commit that the input names, such as a commit that a [Gate](../reference/glossary.md#gate) approved.
427
464
  Without `pin`, jr2 pushes the tip of the Agent's branch when the Effect starts.
@@ -429,11 +466,18 @@ credential. These options change what a push publishes:
429
466
  - `squash`: push one commit that holds all the changes, with the message that the input gives.
430
467
  - `force`: replace what the remote branch holds.
431
468
 
432
- With `pin`, the input gives `{ sha }`. With `squash`, it gives `{ message }`.
469
+ With `pin`, the input also gives `{ sha }`. With `squash`, it also gives `{ message }`.
433
470
 
434
471
  After a signed or a squashed push, the commits on the remote are not the Agent's own commits. Then jr2 moves the Agent's
435
472
  branch to the published commits. The Agent's files, index and stash do not change. The default timeout is 5 minutes.
436
473
 
474
+ ### `worktree`
475
+
476
+ [`worktree()`](../reference/authoring-surface/worktree.md) cuts a Worktree, or reads the one that a cut made at that
477
+ path. Its outcomes are `ready`, `refused` and `unknown`. It runs in the Harness container, and it is idempotent. Each
478
+ thing that an Agent's word can cause is `refused`, and nothing changes. [Workspaces](workspaces.md) tells what a
479
+ Worktree is, and [Cut a Worktree](../guides/cut-a-worktree.md) tells how to use it.
480
+
437
481
  ### `dispatch`
438
482
 
439
483
  [`dispatch`](../reference/authoring-surface/dispatch.md) starts a run of a Workflow. The new run is a run of its own: it
@@ -467,6 +511,7 @@ commands in it:
467
511
  | The Effect | Where it runs |
468
512
  | --------------------------------------- | ----------------------------------------------------------------------------------- |
469
513
  | A `command` that names no Effect secret | The [Harness](../reference/glossary.md#harness) container of the Sandbox |
514
+ | A `worktree()` | The Harness container of the Sandbox |
470
515
  | A `command` that names an Effect secret | The [Sandbox Effector](../reference/glossary.md#sandbox-effector), against a commit |
471
516
  | An `http` Effect | The [Instance Effector](../reference/glossary.md#instance-effector) |
472
517
  | An `http` Effect with a Worktree file | The Sandbox Effector |
@@ -475,17 +520,18 @@ commands in it:
475
520
  | A `dispatch` with a `target` | The Instance Effector |
476
521
 
477
522
  A `command` that names no Effect secret runs beside the Agent's [Working tools](../reference/glossary.md#working-tools).
478
- It runs as the Agent's user, with the tools of the [Sandbox Image](../reference/glossary.md#sandbox-image). It has the
479
- same [Stand-ins](../reference/glossary.md#stand-in) as the Agent's shell, and no credential of its own.
523
+ It runs as the Agent's user, with the tools of the [Sandbox Image](../reference/glossary.md#sandbox-image). The Harness
524
+ confines it with Landlock, as it confines the Agent's shell. It has the same
525
+ [Stand-ins](../reference/glossary.md#stand-in) as the Agent's shell, and no credential of its own.
480
526
 
481
527
  A `command` that names an Effect secret in its `env` runs in the Sandbox Effector, a container in which the Agent runs
482
528
  nothing. It does not run in the live Worktree. It runs against one commit: the tip of the branch when it starts, or with
483
529
  `pin`, the commit that its input names. When the Worktree held changes that the command did not see, its output has
484
530
  `dirty: true`.
485
531
 
486
- A `command`, a `push` and an `http` Effect with a file body need a Workspace. Invoke them from the body of a
487
- [`workspace()`](../reference/authoring-surface/workspace.md). [Architecture](architecture.md) tells what each Effector
488
- is, and [Secrets and trust](secrets-and-trust.md) tells how an Effector holds the Effect secrets.
532
+ A `command`, a `worktree()`, a `push` and an `http` Effect with a file body need a Workspace. Invoke them from the body
533
+ of a [`workspace()`](../reference/authoring-surface/workspace.md). [Architecture](architecture.md) tells what each
534
+ Effector is, and [Secrets and trust](secrets-and-trust.md) tells how an Effector holds the Effect secrets.
489
535
 
490
536
  ## Every outcome is output
491
537
 
@@ -493,6 +539,7 @@ An Effect gives each outcome to `onDone`, a failure too. Route on the `outcome`
493
539
 
494
540
  | Kind | The outcomes | The output type |
495
541
  | ---------- | --------------------------------------------------------- | ------------------------------------------------------------------------- |
542
+ | `worktree` | `ready`, `refused`, `unknown` | [`WorktreeOutput`](../reference/authoring-surface/type-worktreeoutput.md) |
496
543
  | `command` | `exited`, `timeout`, `killed`, `unknown` | [`CommandOutput`](../reference/authoring-surface/type-commandoutput.md) |
497
544
  | `http` | `response`, `unreachable`, `refused`, `unknown` | [`HttpOutput`](../reference/authoring-surface/type-httpoutput.md) |
498
545
  | `push` | `pushed`, `rejected`, `diverged`, `unknown` | [`PushOutput`](../reference/authoring-surface/type-pushoutput.md) |
@@ -502,6 +549,11 @@ A test that fails is `exited` with a code that is not 0. A program that the cont
502
549
  code 127. An HTTP 4xx or 5xx status is a `response`. A Workflow that refuses the run input of a `dispatch` is `refused`,
503
550
  with the status code and the report of the refusal. `unknown` means that jr2 cannot tell what the Effect did.
504
551
 
552
+ Each union is open: it holds a stand-in member, `LaterOutcome`, so a later release can add an outcome and your Machine
553
+ still works. Route on what you know, and give the rest a last arm with no guard. With no match, the run stays in that
554
+ state and shows no fault. So `createMachine`, `jr2 check`, `jr2 up` and the Chart door refuse an `onDone` with a guard
555
+ on its last arm. An invoke with no `onDone` is legal: the Effect settles, and the state stays.
556
+
505
557
  The `onError` route of an Effect is only for a defect in the Machine or in the config. For example, the Effect is not in
506
558
  a Workspace, or an `arg()` has no value, or an Open part has no binding. An outcome that means that the work did not
507
559
  succeed goes to a state that invokes [`fail`](../reference/authoring-surface/fail.md). Then the run ends `failed`, with
@@ -521,6 +573,7 @@ jr2 records its output. After an Orchestrator restart, jr2 reads the record:
521
573
  | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
522
574
  | A `command` that names no Effect secret | jr2 attaches to it again. When the Harness does not know it, the outcome is `unknown`. It never runs again. |
523
575
  | A `command` that names an Effect secret | jr2 follows it. When the Sandbox Effector lost it, jr2 runs it again against the same commit. With no commit recorded, the outcome is `unknown`. |
576
+ | A `worktree()` | jr2 attaches to it again. When the Harness does not know it, jr2 sends it again: a cut is idempotent. |
524
577
  | A `push` | jr2 follows it. When the Sandbox Effector lost it, jr2 pushes the same commit again, and no other commit. With no commit recorded, the outcome is `unknown`. |
525
578
  | An `http` Effect | jr2 follows it. When no Effector holds it, jr2 sends it again only if it is `idempotent`. Else the outcome is `unknown`. |
526
579
  | A `dispatch` | jr2 sends the start again, with the same key. The Orchestrator answers with the run that the key started, and it starts no second run. |
@@ -569,8 +622,10 @@ pick waits for its answer.
569
622
  ## Rules
570
623
 
571
624
  - An Effect is a key of `actors`. `createMachine` refuses an Effect that you write directly in an `invoke`.
572
- - A `command`, a `push` and an `http` Effect with a file body run only inside a `workspace()`.
573
- - Under a Workspace with more than one Repo Slot, a `command`, a `push` and a file body name their slot.
625
+ - The last arm of an Effect's `onDone` has no guard. `createMachine` refuses one that has:
626
+ `machine "<id>": state "<state>" (Effect "<slot>") has an onDone with no unguarded last arm — … (ADR-0075)`.
627
+ - A `command`, a `worktree()`, a `push` and an `http` Effect with a file body run only inside a `workspace()`.
628
+ - Under a Workspace with more than one Repo Slot, a `push` names its `slot`.
574
629
  - The host of an `http` URL is literal. An Effect secret never goes in the host, the query or the fragment of a URL.
575
630
  - Each Effect secret that an Effect names is in the `secrets` list of `jr2.config.ts`, with a value of 8 characters or
576
631
  more. `jr2 up` refuses a name that is not in the list, and a value that is shorter.
@@ -143,6 +143,13 @@ has a door of its own. It does not use the door of the Machine inside it, becaus
143
143
  item. Thus the wrapper declares the door, with its `input` option. `workspace()` refuses a body that has a zod `input`,
144
144
  also a body that `setup()` made.
145
145
 
146
+ A `workspace()` that a parent Machine invokes parses its door too, before it makes a pod. When the parent passes less
147
+ than the door requires, the invoke fails with `workspace input invalid, before any pod: <zod message>`, and the parent's
148
+ `onError` takes it. This is a defect of the parent, so no pod is spent on it. The body gets the parsed value, as it does
149
+ on a root run: defaults are applied and unknown keys are removed. A `pool()` with a door does the same, and its error
150
+ starts `pool input invalid, before any worker`. The door of a plain child Machine is a type, and nothing parses it at
151
+ run time.
152
+
146
153
  ## The Vocabulary
147
154
 
148
155
  The Vocabulary of a Machine is the list of the workflow events that it takes. Each event comes from
@@ -253,6 +260,8 @@ moves a Machine.
253
260
  - A payload schema is a flat `z.object`, and it has no `sender` field. jr2 writes `sender` on each event that a party
254
261
  sends.
255
262
  - When a Machine declares a door, the Orchestrator refuses a start whose input does not agree with it.
263
+ - A `workspace()` or a `pool()` under a parent parses its door before it makes a pod. An input that does not agree fails
264
+ the invoke.
256
265
  - The body of a `workspace()` declares no door. `workspace()` refuses a body that declares one.
257
266
  - The output of a run is the root `output` of its Machine. jr2 refuses an `output` on a final state directly below the
258
267
  root when the root has no `output`.
@@ -31,7 +31,7 @@ example runs with no model. A worker is usually a Machine with an [Agent](../ref
31
31
  ```ts
32
32
  import { assign, fromPromise } from "xstate";
33
33
  import { z } from "zod";
34
- import { defineEvent, jr2Setup, pool, source } from "@jr2/orchestrator";
34
+ import { defineEvent, jr2Setup, pool, source, type PoolNextInput } from "@jr2/orchestrator";
35
35
 
36
36
  type Ticket = { id: string; title: string; needs: string[] };
37
37
 
@@ -50,7 +50,7 @@ const claimed = new Set<string>();
50
50
 
51
51
  // The Source: give the next ready ticket, or say how many tickets are not ready.
52
52
  const tickets = source<Ticket>({
53
- next: fromPromise(async ({ input }: { input: { active: string[] } }) => {
53
+ next: fromPromise(async ({ input }: { input: PoolNextInput }) => {
54
54
  const done = (id: string) => claimed.has(id) && !input.active.includes(id);
55
55
  const waiting = backlog.filter((t) => !claimed.has(t.id));
56
56
  const ready = waiting.find((t) => t.needs.every(done));
@@ -180,8 +180,9 @@ A Pool is a Machine. It can be the root of a Workflow, or a different Machine ca
180
180
  [`source(spec)`](../reference/authoring-surface/source.md) declares where a Pool gets its items. A Source has three
181
181
  parts ([`SourceSpec`](../reference/authoring-surface/type-sourcespec.md)):
182
182
 
183
- - `next`: an [xstate](https://xstate.js.org) promise actor that claims the next ready item. Its input is `{ active }`,
184
- the ids of the items that workers have now.
183
+ - `next`: an [xstate](https://xstate.js.org) promise actor that claims the next ready item. Its input is
184
+ `{ active, settled, input }`: the ids of the items that workers have now, the ids of the finished and failed items,
185
+ and the input of the Pool. Thus a Source can fan out over a plan with no state of its own.
185
186
  - `wake`: an optional event, made with [`defineEvent`](../reference/authoring-surface/defineevent.md), that tells the
186
187
  Pool that new work is ready.
187
188
  - `pollEvery`: an optional time in milliseconds. While the Pool waits, it calls `next` again at this interval.
@@ -211,8 +212,7 @@ the issue tracker.
211
212
 
212
213
  - `itemId` gives each item an id of its own, and the same id each time. The Pool uses it as the id of the worker and as
213
214
  the key in `items`.
214
- - `next` must not give an item that a worker has now, or an item that a worker finished. The Pool gives `next` the
215
- active ids only. Your Source keeps the finished items.
215
+ - `next` must not give an item in `active` or in `settled`.
216
216
  - `cap` is a number, or a function of the run input. Without `cap`, one worker runs at a time.
217
217
  - `onDrained` takes only `"final"`: the run ends.
218
218
  - The Pool calls `next` again on `wake` or after `pollEvery` only while it waits: workers run and no item is ready.