@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
@@ -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 Workspace, and sign its commits with a key that the Agent never reads.
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 [Workspace](../reference/glossary.md#workspace) with a `push`
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. With
14
- `sign: true`, it pushes signed copies of the Agent's commits. In this [Workflow](../reference/glossary.md#workflow), a
15
- coder fixes the tests and commits. A person reads the branch at the [Gate](../reference/glossary.md#gate) of the state
16
- `review`, then sends `approve`. Then the [Machine](../reference/glossary.md#machine) pushes the branch, signed. Save the
17
- file as `workflows/publish.ts` in your [Instance](../reference/glossary.md#instance):
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: "coding",
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.workspace.repos.app }),
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.workspace.branch } }) },
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: "coding",
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.workspace.repos.app }),
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.workspace.branch } }) },
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 commit
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, `{ message: "<text>" }`.
400
- Without `sign`, the [`committer`](../reference/config.md#git-credentials-committer) of the entry commits it.
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), a coder fixes the tests. It picks `run_test` to run one test file
14
- with a [`command`](../reference/authoring-surface/command.md) Effect. When it picks `finished`, an
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: "coding",
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.workspace.repos.app }),
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.workspace.branch })),
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: "coding",
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.workspace.repos.app }),
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.workspace.branch })),
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 program starts in the root of the Worktree. In a Workspace with no Repo Slot, it starts in `/work`, the folder that
308
- the steps of the Workspace share (see [Workspaces](../concepts/workspaces.md#a-workspace-with-no-repo)). To run it in a
309
- subfolder, give the path as an argument, such as `["node", "--test", "<dir>/test/*.test.js"]`, or write
310
- `["sh", "-c", "cd <dir> && <program>"]`.
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), in the Worktree, beside the
314
- [Working tools](../reference/glossary.md#working-tools) of the Agent. It uses the tools of the
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. It runs against
336
- one commit of the branch, not against the live Worktree:
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 for a
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 the Worktree as the body, use [`file(path)`](../reference/authoring-surface/file.md) as `body`. In a
366
- Workspace with no Repo Slot, the path is relative to `/work`. jr2 sends a file body only with `PUT`, `POST` or `PATCH`.
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 `slot` under a Workspace with more than one Repo Slot:
450
- `command "<slot>" runs under a Workspace carrying more than one Repo Slot (<slots>) and names none — say which Worktree it runs in: command(argv, { slot: "<slot>" }) (ADR-0051)`.
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, { slot?, segments?, contentType? }) — json and text come from `request` ``
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 a state outside a
493
- `workspace()`:
494
- `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)`
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),