@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
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: WorktreePath
3
+ description: A Worktree of the Repo Slots `S`.
4
+ ---
5
+
6
+ # `WorktreePath`
7
+
8
+ Type, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
9
+
10
+ ```ts
11
+ type WorktreePath<S extends string> = `/work/${S}/${NameStart}${string}`;
12
+ ```
13
+
14
+ A Worktree of the Repo Slots `S`. One segment below the slot, that starts with a letter, a
15
+ digit or `_`: `${context.workspace.repos.app}/run`. The compiler sees the start of the name; a
16
+ `/` further on is the Effect's own refusal.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: WorktreeReady
3
+ description: Type, an export of @jr2/orchestrator.
4
+ ---
5
+
6
+ # `WorktreeReady`
7
+
8
+ Type, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
9
+
10
+ ```ts
11
+ type WorktreeReady<S extends string = string> = {
12
+ outcome: "ready";
13
+ slot: S;
14
+ path: WorktreePath<S>;
15
+ branch: string | null;
16
+ head: string;
17
+ found: WorktreeFound;
18
+ stale?: string;
19
+ };
20
+ ```
21
+
22
+ **Fields**
23
+
24
+ - `outcome`: `"ready"`
25
+ - `slot`: `S`
26
+ - `path`: `WorktreePath<S>`
27
+ - `branch`: `string | null`
28
+
29
+ The branch of the cut, from git and the cut's record, never from the folder or HEAD. `null`
30
+ when the Worktree is detached.
31
+ - `head`: `string`
32
+
33
+ The tip of `branch` (of the commit, when detached). Never HEAD.
34
+ - `found`: [`WorktreeFound`](type-worktreefound.md)
35
+ - `stale` (optional): `string`
36
+
37
+ [ADR-0053](../decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md)'s words when the remote did not answer. The cut used what the clone holds.
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: WorktreeRefused
3
+ description: "No Worktree, and nothing changed: a bad path, a name git or the push refuses, a re-cut that does not agree with the first, a branch that another Worktree holds, an `expect` that does not hold, or a name that the remote cannot confirm."
4
+ ---
5
+
6
+ # `WorktreeRefused`
7
+
8
+ Type, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
9
+
10
+ ```ts
11
+ type WorktreeRefused = { outcome: "refused"; reason: string };
12
+ ```
13
+
14
+ No Worktree, and nothing changed: a bad path, a name git or the push refuses, a re-cut that does
15
+ not agree with the first, a branch that another Worktree holds, an `expect` that does not hold, or a
16
+ name that the remote cannot confirm.
17
+
18
+ **Fields**
19
+
20
+ - `outcome`: `"refused"`
21
+ - `reason`: `string`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: workspace
3
- description: "Run a body Machine in a Sandbox: a pod of its own, with a Worktree of each Repo Slot on one branch."
3
+ description: "Run a body Machine in a Sandbox: a pod of its own, with a clone of each Repo Slot."
4
4
  ---
5
5
 
6
6
  # `workspace`
@@ -20,30 +20,33 @@ function workspace<
20
20
  ): WorkspaceMachine<WithoutEmptyIndex<z.infer<TSchema>>, OutputFrom<TBody>, TBody, TSlots>
21
21
  ```
22
22
 
23
- Run a body Machine in a Sandbox: a pod of its own, with a Worktree of each Repo Slot on one
24
- branch. The result is a Machine. Register it as a Workflow, or invoke it from another Machine as
23
+ Run a body Machine in a Sandbox: a pod of its own, with a clone of each Repo Slot. The result is
24
+ a Machine. Register it as a Workflow, or invoke it from another Machine as
25
25
  an actor slot.
26
26
 
27
27
  When a run enters it, the Workspace makes the Sandbox and waits for room on a node, with no
28
- deadline. It clones each Repo Slot and makes its Worktree on the `branch` that `spec` gives. Then
29
- it runs the body with the run input plus `workspace`, the handles (see [`Workspaced`](type-workspaced.md)). The
30
- Agents with Working tools and the `command` Effects in the body run in that Sandbox. An Agent
28
+ deadline. It clones each Repo Slot to `/work/<slot>/default`, on the Binding's base, and makes no
29
+ Worktree: the body cuts each Worktree that it needs with `worktree()`. A slot that is only read
30
+ needs no cut. No work happens in `default/`. Then it runs the body with the run input plus
31
+ `workspace`, the handles (see [`Workspaced`](type-workspaced.md)). The Agents with Working tools and the `command` Effects in the body run in that Sandbox. An Agent
31
32
  with `workspace: "none"` runs on the Instance Harness. When the body reaches a final state, the
32
- Workspace deletes the Sandbox, and its output is the body's output. Push the branch before
33
+ Workspace deletes the Sandbox, and its output is the body's output. Push each branch before
33
34
  then: jr2 deletes the Sandbox's commits with it.
34
35
 
36
+ The author chooses how many Agents work in a Workspace. Agents in one Worktree share git, as
37
+ people in one checkout do. Each coder that needs its own branch gets its own Worktree of the one
38
+ clone.
39
+
35
40
  Leave out `repos` for a Workspace with no Repo Slot: its Agents and `command` Effects share
36
- `/work`, as the steps of one job share a folder, and `spec` is optional and gives only
37
- `workGroup`. The body's input is then `Workspaced<RunInput, never>`. An `http` Effect sends a
38
- file of `/work` with `file(path)`. A `push`, and a `command` with an Effect secret in its `env`,
39
- need a Repo Slot, so jr2 refuses them there. jr2 deletes `/work` with the Sandbox: send what
41
+ `/work`, as the steps of one job share a folder. The body's input is then `Workspaced<RunInput, never>`. An `http` Effect sends a
42
+ file of `/work` with `file(path)`. A `worktree()`, a `push`, and a `command` with an Effect secret in its
43
+ `env`, need a Repo Slot, so jr2 refuses them there. jr2 deletes `/work` with the Sandbox: send what
40
44
  must stay before the body ends.
41
45
 
42
46
  Give each run a different branch, so that two runs do not push to one branch. Make it from a
43
47
  field of the run input, or from `instanceId` when the run input has no field that is different
44
- for each run: ``spec: ({ input }) => ({ branch: `fix/${input.instanceId}` })``. The input of
45
- `spec` holds `instanceId` with no cast, also for the door `z.object({})`. Only a root
46
- `workspace()` gets `instanceId` from jr2. A `workspace()` that a different Machine invokes gets
48
+ for each run: ``branch: `fix/${context.instanceId}` `` in the input of the cut. Add
49
+ `HostInjectedInput` to the body's input type to read it. Only a root `workspace()` gets `instanceId` from jr2. A `workspace()` that a different Machine invokes gets
47
50
  it only when its parent gives it.
48
51
 
49
52
  The Sandbox is Lost when its pod is evicted or its node goes. Then the body gets the event
@@ -67,7 +70,7 @@ It refuses, when you call it:
67
70
  - a body with its own `input`: ``workspace(): the body "<id>" declares its own run input, which
68
71
  nothing will ever serve or enforce — the wrapper feeds the body the run input PLUS the injected
69
72
  `workspace` handles, so the body's schema is not the door. Move it to the wrapper:
70
- workspace(body, { input, spec }) (ADR-0033).``
73
+ workspace(body, { input }) (ADR-0033).``
71
74
  - an empty `image`: ``workspace(): `image` must be a non-empty string (got <value>) — either a
72
75
  `file:` URL to a docker context this module ships (`import.meta.resolve("./image")`) or a
73
76
  registry ref (ADR-0037).``
@@ -77,9 +80,7 @@ It refuses, when you call it:
77
80
  `repos: { app: "https://…" }` — or `open` whole, for a map the consumer names; leave it out
78
81
  for a Workspace with no Repo Slot (ADR-0051).``
79
82
  - a bad Repo Slot name or value (see [`RepoSlot`](type-reposlot.md))
80
- - a `spec` that is not a function, or no `spec` on a Workspace with Repo Slots: ``workspace():
81
- `spec` must be a function from the run input to the spec — spec: ({ input }) => ({ branch }) —
82
- and only a Workspace with no Repo Slot may leave it out (ADR-0051).``
83
+ - a `workGroup` that is not a gid: ``workspace(): `workGroup` must be a gid (got <value>)``
83
84
  - a bad `secrets` list: ``workspace(): `secrets` must be a list of distinct names from
84
85
  jr2.config.ts's `secrets` list (got <value>) — secrets: ["JIRA_TOKEN"] (ADR-0059).``
85
86
  - a bad `effector`: ``workspace(): `effector` is { image } — a `file:` URL to a docker context
@@ -95,19 +96,16 @@ It refuses, when you call it:
95
96
  (ADR-0060)``, or the same text for `resources.limits.<key>`. The other texts are in
96
97
  [`Size`](type-size.md).
97
98
 
98
- And when a run enters it, a spec without a `branch` (or with `branch: "default"`), or with a bad
99
- `reviewSha` or `workGroup`: ``workspace spec invalid: <fields> — the spec derives from run input;
100
- does `jr2 run --input` carry every field this workflow's workspace() mapping reads?``. With no
101
- Repo Slot, a spec with a `branch` or a `reviewSha`: ``workspace spec invalid: <field> (the
102
- Workspace has no Repo Slot, so it has no branch and no review Worktree; its spec gives only
103
- workGroup) …``
99
+ And when a run enters it, a per-run Repo Slot's mapper that gives no `url`, or a bad `ref`:
100
+ ``workspace invalid: repos.<slot> mapper returned <fields> — the mapper derives from run
101
+ input; does `jr2 run --input` carry every field this workflow's workspace() slot reads?``
104
102
 
105
103
  **Parameters**
106
104
 
107
105
  - `body`: The Machine to run in the Sandbox. Its input is `Workspaced<RunInput, Slots>`.
108
106
  - `options` ([`WorkspaceOptions`](type-workspaceoptions.md) | [`NoSlotWorkspaceOptions`](type-noslotworkspaceoptions.md)): What the Sandbox is made of (`image`, `user`, `resources`, `effector`), the
109
- Repo Slots it clones (`repos`), the Held secrets it can use (`secrets`), the run input
110
- (`input`), and the function from the run input to the branch (`spec`).
107
+ Repo Slots it clones (`repos`), the Held secrets it can use (`secrets`), the pod's `fsGroup`
108
+ (`workGroup`), and the run input (`input`).
111
109
 
112
110
  **Returns**: A Machine whose input is the run input and whose output is the body's output.
113
111
 
@@ -118,8 +116,10 @@ import { z } from "zod";
118
116
  import {
119
117
  command,
120
118
  fail,
119
+ isReady,
121
120
  jr2Setup,
122
121
  workspace,
122
+ worktree,
123
123
  type CommandOutput,
124
124
  type Workspaced,
125
125
  } from "@jr2/orchestrator";
@@ -127,23 +127,36 @@ import {
127
127
  const runInput = z.object({ ticket: z.string(), repo: z.string() });
128
128
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app" | "docs">;
129
129
 
130
+ // The Worktree of this run. An Agent or a command that only reads `docs` uses `/work/docs/default`.
131
+ const buildTree = (context: BodyInput) => `${context.workspace.repos.app}/build` as const;
132
+
130
133
  const passed = (output: CommandOutput): boolean =>
131
134
  output.outcome === "exited" && output.code === 0;
132
135
 
133
136
  const body = jr2Setup({
134
- types: {} as { input: BodyInput },
137
+ types: {} as { input: BodyInput; context: BodyInput },
135
138
  actors: {
136
- build: command(["npm", "run", "build"], { slot: "app" }),
139
+ cut: worktree(),
140
+ build: command(["npm", "run", "build"]),
137
141
  fail: fail(),
138
142
  },
139
143
  }).createMachine({
140
144
  id: "build",
145
+ context: ({ input }) => input,
141
146
  on: { "workspace.lost": ".lost" },
142
- initial: "building",
147
+ initial: "cutting",
143
148
  states: {
149
+ cutting: {
150
+ invoke: {
151
+ src: "cut",
152
+ input: ({ context }) => ({ path: buildTree(context), branch: `fix/${context.ticket}` }),
153
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "building" }, { target: "notCut" }],
154
+ },
155
+ },
144
156
  building: {
145
157
  invoke: {
146
158
  src: "build",
159
+ input: ({ context }) => ({ worktree: buildTree(context) }),
147
160
  onDone: [
148
161
  { guard: ({ event }) => passed(event.output), target: "built" },
149
162
  { target: "broken" },
@@ -152,6 +165,7 @@ const body = jr2Setup({
152
165
  },
153
166
  built: { type: "final" },
154
167
  broken: { invoke: { src: "fail", input: { reason: "npm run build did not pass" } } },
168
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
155
169
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
156
170
  },
157
171
  });
@@ -164,7 +178,6 @@ export const machine = workspace(body, {
164
178
  app: ({ input }) => input.repo,
165
179
  docs: { url: "https://github.com/you/docs.git", ref: "main" },
166
180
  },
167
- spec: ({ input }) => ({ branch: `fix/${input.ticket}` }),
168
181
  });
169
182
  ```
170
183
 
@@ -175,9 +188,12 @@ import { z } from "zod";
175
188
  import {
176
189
  command,
177
190
  fail,
191
+ isReady,
178
192
  jr2Setup,
179
193
  workspace,
194
+ worktree,
180
195
  type CommandOutput,
196
+ type HostInjectedInput,
181
197
  type Workspaced,
182
198
  } from "@jr2/orchestrator";
183
199
 
@@ -187,18 +203,30 @@ const passed = (output: CommandOutput): boolean =>
187
203
  // The run input has no field that is different for each run, so the branch comes from
188
204
  // `instanceId`.
189
205
  const runInput = z.object({});
206
+ type BodyInput = Workspaced<z.infer<typeof runInput> & HostInjectedInput, "app">;
207
+
208
+ const nightlyTree = (context: BodyInput) => `${context.workspace.repos.app}/nightly` as const;
190
209
 
191
210
  const body = jr2Setup({
192
- types: {} as { input: Workspaced<z.infer<typeof runInput>, "app"> },
193
- actors: { test: command(["npm", "test"], { slot: "app" }), fail: fail() },
211
+ types: {} as { input: BodyInput; context: BodyInput },
212
+ actors: { cut: worktree(), test: command(["npm", "test"]), fail: fail() },
194
213
  }).createMachine({
195
214
  id: "nightly",
215
+ context: ({ input }) => input,
196
216
  on: { "workspace.lost": ".lost" },
197
- initial: "testing",
217
+ initial: "cutting",
198
218
  states: {
219
+ cutting: {
220
+ invoke: {
221
+ src: "cut",
222
+ input: ({ context }) => ({ path: nightlyTree(context), branch: `nightly/${context.instanceId}` }),
223
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
224
+ },
225
+ },
199
226
  testing: {
200
227
  invoke: {
201
228
  src: "test",
229
+ input: ({ context }) => ({ worktree: nightlyTree(context) }),
202
230
  onDone: [
203
231
  { guard: ({ event }) => passed(event.output), target: "tested" },
204
232
  { target: "red" },
@@ -207,6 +235,7 @@ const body = jr2Setup({
207
235
  },
208
236
  tested: { type: "final" },
209
237
  red: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
238
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
210
239
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
211
240
  },
212
241
  });
@@ -215,7 +244,6 @@ export const machine = workspace(body, {
215
244
  input: runInput,
216
245
  image: "docker.io/library/node:24",
217
246
  repos: { app: "https://github.com/you/app.git" },
218
- spec: ({ input }) => ({ branch: `fix/${input.instanceId}` }),
219
247
  });
220
248
  ```
221
249
 
@@ -234,10 +262,10 @@ export const machine = workspace(body, {
234
262
  function workspace<
235
263
  TBody extends AnyStateMachine,
236
264
  TSlots extends string = never,
237
- TInput = unknown,
265
+ TInput = DoorlessInput<TBody>,
238
266
  >(
239
267
  body: TBody & BodyAcceptsSlots<TBody, TSlots>,
240
- options: PermissiveWorkspaceOptions<TSlots, TInput> | PermissiveNoSlotWorkspaceOptions<TInput>,
268
+ options: PermissiveWorkspaceOptions<TSlots, Exclude<TInput, undefined>> | PermissiveNoSlotWorkspaceOptions,
241
269
  ): WorkspaceMachine<TInput, OutputFrom<TBody>, TBody, TSlots>
242
270
  ```
243
271
 
@@ -249,7 +277,7 @@ what it refuses.
249
277
 
250
278
  - `body`: The Machine to run in the Sandbox. Its input is the input of the Workspace plus
251
279
  `workspace`, the handles.
252
- - `options` ([`PermissiveWorkspaceOptions`](type-permissiveworkspaceoptions.md) | [`PermissiveNoSlotWorkspaceOptions`](type-permissivenoslotworkspaceoptions.md)): The options of the Sandbox, and `spec`. There is no `input`.
280
+ - `options` ([`PermissiveWorkspaceOptions`](type-permissiveworkspaceoptions.md) | [`PermissiveNoSlotWorkspaceOptions`](type-permissivenoslotworkspaceoptions.md)): The options of the Sandbox. There is no `input`.
253
281
 
254
282
  **Returns**: A Machine whose input is the run input and whose output is the body's output.
255
283
 
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: worktree
3
+ description: "Make a `worktree` Effect: it cuts a Worktree of a Repo Slot, or reads the one that is there."
4
+ ---
5
+
6
+ # `worktree`
7
+
8
+ Function, in `@jr2/orchestrator/src/worktree.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.
9
+
10
+ ```ts
11
+ function worktree(options: WorktreeOptions = {}): EffectLogic<WorktreeInput, WorktreeOutput>
12
+ ```
13
+
14
+ Make a `worktree` Effect: it cuts a Worktree of a Repo Slot, or reads the one that is there. Put it
15
+ in `actors`, and invoke it from a `workspace()` body.
16
+
17
+ With `{ path, branch, from? }`, the branch resolves in this order, after a fetch: a local branch;
18
+ `origin/<branch>`, tracked; a new branch from `from` or the Binding's base, with no upstream. With
19
+ `{ path, detach }`, the Worktree is forced to that commit and cleaned, each time. `path` is exactly
20
+ `/work/<slot>/<name>`. Each outcome is output: `ready`, `refused`, `unknown`.
21
+
22
+ `expect` says if the branch can exist: `worktree({ expect: "new" })` refuses a branch that exists,
23
+ and `worktree({ expect: "existing" })` refuses a branch that does not exist. A run cannot change it.
24
+
25
+ A cut needs a Repo Slot. Under a Workspace with no Repo Slot, `jr2 up` refuses it, and the invoke
26
+ fails with `worktree "<slot>" runs under a Workspace with no Repo Slot: a cut makes a Worktree of a
27
+ Repo Slot, and there is none — declare one: repos: { app: "<url>" } (ADR-0075)`.
28
+
29
+ **Parameters**
30
+
31
+ - `options` ([`WorktreeOptions`](type-worktreeoptions.md)): Optional. `expect`, and `timeout`, how long the fetch and the cut can take.
32
+ Default: `"10m"`.
33
+
34
+ **Returns**: The actor logic of the Effect's slot. Its output is a [`WorktreeOutput`](type-worktreeoutput.md).
35
+
36
+ **Example**
37
+
38
+ ```ts
39
+ import { fail, isReady, jr2Setup, worktree, type Workspaced } from "@jr2/orchestrator";
40
+
41
+ type BodyInput = Workspaced<{ branch: string }, "app">;
42
+ const tree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
43
+
44
+ export const body = jr2Setup({
45
+ types: {} as { input: BodyInput; context: BodyInput },
46
+ actors: { cut: worktree({ expect: "new" }), fail: fail() },
47
+ }).createMachine({
48
+ context: ({ input }) => input,
49
+ on: { "workspace.lost": ".notCut" },
50
+ initial: "cutting",
51
+ states: {
52
+ cutting: {
53
+ invoke: {
54
+ src: "cut",
55
+ input: ({ context }) => ({ path: tree(context), branch: context.branch }),
56
+ // The last arm has no guard: it takes `refused`, `unknown` and each later outcome.
57
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "done" }, { target: "notCut" }],
58
+ },
59
+ },
60
+ done: { type: "final" },
61
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
62
+ },
63
+ });
64
+ ```
65
+
66
+ **See also**
67
+
68
+ - [Cut a Worktree](../../guides/cut-a-worktree.md)
69
+ - [Workspaces](../../concepts/workspaces.md)
70
+ - [Effects](../../concepts/effects.md)
71
+ - [ADR-0075: A Machine cuts a Worktree, and the Workspace only clones](../decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)
@@ -35,8 +35,8 @@ An Agent slot and the definition it carries ([ADR-0028](decisions/0028-what-an-a
35
35
 
36
36
  A Sandbox and the Repo Slots it binds ([ADR-0012](decisions/0012-workspace-wrapper-machine.md), [ADR-0051](decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md)).
37
37
 
38
- - [`workspace`](authoring-surface/workspace.md) (function): Run a body Machine in a Sandbox: a pod of its own, with a Worktree of each Repo Slot on one branch.
39
- - [`Workspaced`](authoring-surface/type-workspaced.md) (type): The input of a `workspace()` body: the run input, plus `workspace`, the handles that the Workspace adds (`workspace.repos`, `workspace.branch`, `workspace.review`).
38
+ - [`workspace`](authoring-surface/workspace.md) (function): Run a body Machine in a Sandbox: a pod of its own, with a clone of each Repo Slot.
39
+ - [`Workspaced`](authoring-surface/type-workspaced.md) (type): The input of a `workspace()` body: the run input, plus `workspace`, the handles that the Workspace adds (`workspace.repos`, the folder of each Repo Slot).
40
40
  - [`Binding`](authoring-surface/type-binding.md) (type): The repository of a Repo Slot: `{ url, ref }`.
41
41
  - [`RepoSlot`](authoring-surface/type-reposlot.md) (type): The value of one Repo Slot in `workspace({ repos })`.
42
42
 
@@ -46,6 +46,7 @@ Work a Machine does outside itself, as an actor slot ([ADR-0067](decisions/0067-
46
46
 
47
47
  - [`arg`](authoring-surface/arg.md) (constant): Mark one argv entry of a `command` that the input of the Effect fills.
48
48
  - [`CommandOutput`](authoring-surface/type-commandoutput.md) (type): The output of a `command` Effect: what the `onDone` of its state gets as `event.output`.
49
+ - [`LaterOutcome`](authoring-surface/type-lateroutcome.md) (type): A value of `outcome` that this kit does not give.
49
50
  - [`command`](authoring-surface/command.md) (function): Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a state invokes it.
50
51
  - [`basic`](authoring-surface/basic.md) (function): Make the `auth` of an `http` Effect that sends `Authorization: Basic <value>`, where the value is the base64 of `user:password`.
51
52
  - [`bearer`](authoring-surface/bearer.md) (function): Make the `auth` of an `http` Effect that sends `Authorization: Bearer <value>`, with the value of an Effect secret.
@@ -54,8 +55,22 @@ Work a Machine does outside itself, as an actor slot ([ADR-0067](decisions/0067-
54
55
  - [`secret`](authoring-surface/secret.md) (function): Name an Effect secret: `secret("jira")`.
55
56
  - [`text`](authoring-surface/text.md) (function): Make a string from literal text and Effect secrets, as a tagged template: `` text`Token ${secret("x")}` ``.
56
57
  - [`HttpOutput`](authoring-surface/type-httpoutput.md) (type): The output of an `http` Effect: what the `onDone` of its state gets as `event.output`.
57
- - [`push`](authoring-surface/push.md) (function): Make a `push` Effect: it pushes the branch of the Workspace to the Binding of its Repo Slot.
58
+ - [`push`](authoring-surface/push.md) (function): Make a `push` Effect: it pushes the branch of a Worktree to the Binding of its Repo Slot.
58
59
  - [`PushOutput`](authoring-surface/type-pushoutput.md) (type): The output of a `push` Effect: what the `onDone` of its state gets as `event.output`.
60
+ - [`branchName`](authoring-surface/branchname.md) (constant): A kit schema for a pick param that names a branch.
61
+ - [`isReady`](authoring-surface/isready.md) (constant): Is it `ready`?
62
+ - [`readyOf`](authoring-surface/readyof.md) (function): The `ready` output of an `onDone` arm that `isReady` guards.
63
+ - [`worktree`](authoring-surface/worktree.md) (function): Make a `worktree` Effect: it cuts a Worktree of a Repo Slot, or reads the one that is there.
64
+ - [`AnyFramePath`](authoring-surface/type-anyframepath.md) (type): A Turn's Frame: a Worktree, or `/work`, stated.
65
+ - [`AnyWorktreePath`](authoring-surface/type-anyworktreepath.md) (type): A Worktree of any Repo Slot: what a kit factory states, before `jr2Setup` narrows it to the slots of the body.
66
+ - [`FramePath`](authoring-surface/type-framepath.md) (type): A Turn's Frame in a Workspace with the Repo Slots `S`.
67
+ - [`WorktreeFound`](authoring-surface/type-worktreefound.md) (type): What a cut found: `held`, a cut made this Worktree earlier, so what is in it is earlier work; `local`, a local branch; `remote`, `origin/<branch>`, now tracked; `none`, a new branch with no upstream; `commit`, a detached cut.
68
+ - [`WorktreeInput`](authoring-surface/type-worktreeinput.md) (type): The input of a `worktree()`: a branch Worktree, or a detached one.
69
+ - [`WorktreeOptions`](authoring-surface/type-worktreeoptions.md) (type): The options of `worktree()`.
70
+ - [`WorktreeOutput`](authoring-surface/type-worktreeoutput.md) (type): The output of a `worktree()`.
71
+ - [`WorktreePath`](authoring-surface/type-worktreepath.md) (type): A Worktree of the Repo Slots `S`.
72
+ - [`WorktreeReady`](authoring-surface/type-worktreeready.md) (type)
73
+ - [`WorktreeRefused`](authoring-surface/type-worktreerefused.md) (type): No Worktree, and nothing changed: a bad path, a name git or the push refuses, a re-cut that does not agree with the first, a branch that another Worktree holds, an `expect` that does not hold, or a name that the remote cannot confirm.
59
74
  - [`dispatch`](authoring-surface/dispatch.md) (function): Make a `dispatch` Effect: it starts a run of a Workflow.
60
75
  - [`DispatchOutput`](authoring-surface/type-dispatchoutput.md) (type): The output of a `dispatch` Effect.
61
76
  - [`answer`](authoring-surface/answer.md) (function): Set what the Agent reads when jr2 answers its pick.
@@ -74,6 +89,7 @@ A part left Open, a Machine's parts retuned, a Pool over a Source ([ADR-0017](de
74
89
  - [`UserContainer`](authoring-surface/type-usercontainer.md) (type): The User Container in its object form: its image, and its part of the Workspace's Size.
75
90
  - [`pool`](authoring-surface/pool.md) (function): Make a Pool: a Machine that runs one worker Machine for each item that a Source gives, with at most `cap` workers at once.
76
91
  - [`source`](authoring-surface/source.md) (function): Declare a Source: where a `pool()` gets its work items, one at a time.
92
+ - [`PoolNextInput`](authoring-surface/type-poolnextinput.md) (type): The input of a Source's `next`: the ids of the items that workers have now, the ids of the items that settled, and the Pool's input.
77
93
 
78
94
  ## Charts
79
95
 
@@ -109,7 +125,7 @@ The signature of an export, or a type here, names each of these types. They are
109
125
  - [`WithoutEmptyIndex`](authoring-surface/type-withoutemptyindex.md): The fields of the run input `T`.
110
126
  - [`PermissiveWorkspaceOptions`](authoring-surface/type-permissiveworkspaceoptions.md): The options of a `workspace()` with no `input`: it starts with any input.
111
127
  - [`PermissiveNoSlotWorkspaceOptions`](authoring-surface/type-permissivenoslotworkspaceoptions.md): The options of a `workspace()` with no Repo Slot and no `input`: it starts with any input.
112
- - [`WorkspaceHandles`](authoring-surface/type-workspacehandles.md): What the body of a `workspace()` gets as `input.workspace`: the path of each Worktree, and the branch.
128
+ - [`WorkspaceHandles`](authoring-surface/type-workspacehandles.md): What the body of a `workspace()` gets as `input.workspace`: the folder of each Repo Slot.
113
129
  - [`ExecOutcome`](authoring-surface/type-execoutcome.md): How an exec ended.
114
130
  - [`Unknown`](authoring-surface/type-unknown.md): The output `{ outcome: "unknown", reason }`: the Effect may have acted, and jr2 cannot tell.
115
131
  - [`Arg`](authoring-surface/type-arg.md): One entry of a command's argv that the input of the Effect fills, as `arg("name")` makes it.
@@ -119,11 +135,14 @@ The signature of an export, or a type here, names each of these types. They are
119
135
  - [`Secret`](authoring-surface/type-secret.md): What `secret("name")` gives: an Effect secret, by its name in the `secrets` list of `jr2.config.ts`.
120
136
  - [`Text`](authoring-surface/type-text.md): What the `text` template gives: a string of literal text and Effect secrets, in order.
121
137
  - [`FromInput`](authoring-surface/type-frominput.md): A value of a file body's part: as written, or from the Effect's input.
122
- - [`FileBody`](authoring-surface/type-filebody.md): `file(path, { slot?, segments?, contentType? })`: a Worktree file as an `http` body.
138
+ - [`FileBody`](authoring-surface/type-filebody.md): `file(path, { segments?, contentType? })`: a Worktree file as an `http` body.
123
139
  - [`HttpDefinition`](authoring-surface/type-httpdefinition.md): An `http` Effect's definition.
140
+ - [`HttpFileInput`](authoring-surface/type-httpfileinput.md): The input of an `http` Effect with a Worktree file as its body.
124
141
  - [`OpenSecret`](authoring-surface/type-opensecret.md): `secret(open)`: a secret a packaged Machine names in its auth, a header or a command's env, and leaves for its composer to name (ADR-0054).
125
142
  - [`PushOptions`](authoring-surface/type-pushoptions.md): The options of `push()`.
126
- - [`PushInput`](authoring-surface/type-pushinput.md): The input of a `push` Effect: `{ sha }` with `pin`, and `{ message }` with `squash`.
143
+ - [`PushInput`](authoring-surface/type-pushinput.md): The input of a `push` Effect: the Worktree that a cut made, `{ sha }` with `pin`, and `{ message }` with `squash`.
144
+ - [`Duration`](authoring-surface/type-duration.md): A length of time: a positive number and a unit, `s`, `m` or `h`.
145
+ - [`NameStart`](authoring-surface/type-namestart.md): What a Worktree's name may start with: neither `.` nor `-`, and not the end of the path.
127
146
  - [`DispatchDefinition`](authoring-surface/type-dispatchdefinition.md): The definition of a `dispatch` Effect (ADR-0071).
128
147
  - [`DispatchInput`](authoring-surface/type-dispatchinput.md): A dispatch's input: the run's body — the slot's schema's type, or any JSON object.
129
148
  - [`DispatchOpenParts`](authoring-surface/type-dispatchopenparts.md): The Open part a dispatch definition states: its target, when it is `open`.
@@ -138,10 +157,7 @@ The signature of an export, or a type here, names each of these types. They are
138
157
  - [`Sender`](authoring-surface/type-sender.md): Who delivered an event: an Agent by its slot, or a Gate by its invoke id (ADR-0015).
139
158
  - [`FailureInput`](authoring-surface/type-failureinput.md): What a Failure carries: `reason`, text for a person, and `detail`, optional JSON for a machine reader.
140
159
  - [`SandboxOptions`](authoring-surface/type-sandboxoptions.md): The options of a `workspace()` that say what its Sandbox is made of and which Repos it clones.
141
- - [`WorkspaceSpec`](authoring-surface/type-workspacespec.md): What the `spec` of a `workspace()` returns: the branch that the Workspace works on, and two optional fields.
142
- - [`NoSlotSpec`](authoring-surface/type-noslotspec.md): What the `spec` of a `workspace()` with no Repo Slot returns: only `workGroup`.
143
160
  - [`SlotHandles`](authoring-surface/type-slothandles.md): The handles of a Workspace that declares Repo Slots.
144
- - [`Duration`](authoring-surface/type-duration.md): A length of time: a positive number and a unit, `s`, `m` or `h`.
145
161
  - [`CommandEnv`](authoring-surface/type-commandenv.md): The `env` of a `command`: for each variable, `secret("name")` for the value of an Effect secret, or `secret("name").path` for the path of a file that holds it.
146
162
  - [`TextPart`](authoring-surface/type-textpart.md): One part of a `Text`: `{ text }`, literal text, or `{ secret }`, the name of an Effect secret.
147
163
  - [`HttpMethod`](authoring-surface/type-httpmethod.md): The method of an `http` Effect: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`.
@@ -165,18 +181,20 @@ The signature of an export, or a type here, names each of these types. They are
165
181
  - [`ToProvidedActor`](authoring-surface/type-toprovidedactor.md)
166
182
  - [`MergedActors`](authoring-surface/type-mergedactors.md)
167
183
  - [`ToParameterizedObject`](authoring-surface/type-toparameterizedobject.md)
184
+ - [`BindActors`](authoring-surface/type-bindactors.md)
168
185
  - [`SentByGuards`](authoring-surface/type-sentbyguards.md)
169
186
  - [`AgentLogic`](authoring-surface/type-agentlogic.md)
170
187
  - [`BodyAcceptsDoor`](authoring-surface/type-bodyacceptsdoor.md)
171
188
  - [`WorkspaceMachine`](authoring-surface/type-workspacemachine.md)
189
+ - [`DoorlessInput`](authoring-surface/type-doorlessinput.md)
172
190
  - [`BodyAcceptsSlots`](authoring-surface/type-bodyacceptsslots.md)
173
191
  - [`EffectLogic`](authoring-surface/type-effectlogic.md)
174
192
  - [`OpenOf`](authoring-surface/type-openof.md)
175
193
  - [`EnvOpenParts`](authoring-surface/type-envopenparts.md)
176
194
  - [`OpenEffect`](authoring-surface/type-openeffect.md)
177
195
  - [`MarkedIf`](authoring-surface/type-markedif.md)
178
- - [`HttpInput`](authoring-surface/type-httpinput.md)
179
196
  - [`HttpOpenParts`](authoring-surface/type-httpopenparts.md)
197
+ - [`HttpInput`](authoring-surface/type-httpinput.md)
180
198
  - [`PushWireOutput`](authoring-surface/type-pushwireoutput.md)
181
199
  - [`SyncState`](authoring-surface/type-syncstate.md)
182
200
  - [`AnswerAction`](authoring-surface/type-answeraction.md)
@@ -192,6 +210,8 @@ The signature of an export, or a type here, names each of these types. They are
192
210
  - [`PoolMachine`](authoring-surface/type-poolmachine.md)
193
211
  - [`EventFrom`](authoring-surface/type-eventfrom.md)
194
212
  - [`ArgNames`](authoring-surface/type-argnames.md)
213
+ - [`WorktreeKey`](authoring-surface/type-worktreekey.md)
214
+ - [`NotWorktreeKeys`](authoring-surface/type-notworktreekeys.md)
195
215
  - [`OpenSecretMark`](authoring-surface/type-opensecretmark.md)
196
216
 
197
217
  These names come from a dependency: from `zod`, `z.ZodObject`, `z.infer`, `z.ZodType`; from `xstate`, `MachineContext`, `UnknownActorLogic`, `ParameterizedObject`, `NonReducibleUnknown`, `EventObject`, `MetaObject`, `ActionFunction`, `GuardPredicate`, `DelayConfig`, `SetupReturn`, `AnyStateMachine`, `OutputFrom`, `ProvidedActor`, `AnyActorRef`, `InputFrom`, `PromiseActorLogic`.
@@ -196,7 +196,7 @@ jr2 kit push <registry>
196
196
 
197
197
  Mirror the published kit images into a registry.
198
198
 
199
- The v0.5.1 kit images (harness, operator, orchestrator) from their canonical home, and the Custodian's pinned Envoy from its own, for a cluster whose `kitRegistry` names the registry.
199
+ The v0.6.0 kit images (harness, operator, orchestrator) from their canonical home, and the Custodian's pinned Envoy from its own, for a cluster whose `kitRegistry` names the registry.
200
200
 
201
201
  **Arguments**
202
202
 
@@ -76,8 +76,9 @@ config loads: `<file> at secrets[<i>]<path>: <problem>`, followed by the shape o
76
76
  - Type: `string`
77
77
  - Required: yes
78
78
 
79
- As a Held secret, the name of the env var that the Harness container sees, which holds the
80
- Stand-in. As an Effect secret, the name that `secret("<name>")` names, and the name of the
79
+ As a Held secret, the name of its Stand-in: the file the Harness reads it from, the env var
80
+ that the engine reads a model key from, and the env var of the Agent's programs when the
81
+ `workspace()` lists it under `secrets`. As an Effect secret, the name that `secret("<name>")` names, and the name of the
81
82
  Effector's file. For a Held secret, it must match `[A-Z_][A-Z0-9_]*`:
82
83
  `secrets[<i>].name "<name>" is not an env var name ([A-Z_][A-Z0-9_]*)`. It must not start with
83
84
  `JR2_` and must not be a proxy or trust variable of the kit:
@@ -377,7 +378,7 @@ checks from inside the cluster that the endpoint answers.
377
378
 
378
379
  The API key, when the endpoint needs one. Read it from `process.env`. It is a Held secret,
379
380
  `JR2_PROVIDER_API_KEY`: `jr2 up` gives the value to the Custodian only, bound to the host of
380
- `baseUrl`, and the Harness container holds only its Stand-in. Thus `baseUrl` must be
381
+ `baseUrl`, and the Harness process holds only its secret Stand-in. Thus `baseUrl` must be
381
382
  `https://` when you set it:
382
383
  `harness.provider.apiKey is set, and harness.provider.baseUrl is not https://`.
383
384
 
@@ -467,6 +468,11 @@ deployments, so read it from `process.env` (`.env`).
467
468
 
468
469
  Env vars for the Harness container. The Agent can read every one of them, so do not put a
469
470
  credential here. A known model key is refused here. Put it in the top-level `secrets` list.
471
+ The Harness process gets each one, except a name that loads code from a path, such as
472
+ `NODE_OPTIONS` or `LD_PRELOAD`, and `HOME`, which is its own; the Agent's programs get each
473
+ one. A name that starts with `JR2_` is jr2's own, and is refused. A credential file that the
474
+ Harness process reads, such as `AWS_CONFIG_FILE`, is refused in a folder that the Agent's code
475
+ writes (`/work`, `/tmp`, `/home/jr2`), and gives a warning anywhere else.
470
476
  See `HarnessEnvVar`.
471
477
 
472
478
  <a id="harness-env-name"></a>
@@ -508,7 +514,10 @@ pod as it is. A reference to a Secret of the kit is refused:
508
514
  - Required: no
509
515
 
510
516
  Secrets and ConfigMaps that you manage, whose keys all become env vars of the Harness
511
- container. See `HarnessEnvFromSource`.
517
+ process and of the Agent's programs, as `env` does. In the container's spec each key has the
518
+ prefix `JR2_ENVFROM_`, so that the Harness can tell jr2's names from the image's; the Harness
519
+ removes it. A key that starts with `JR2_` is refused: `jr2 up` refuses it, and the Harness does
520
+ not start with one. See `HarnessEnvFromSource`.
512
521
 
513
522
  <a id="harness-envfrom-prefix"></a>
514
523
 
@@ -34,7 +34,7 @@ Sandbox will run must be in the spec at spin-up.
34
34
 
35
35
  One Harness **image** serves many **Agents** — the Harness resolves model, instructions, and its Menu at runtime without
36
36
  a rebuild, reading the definition each admission carries (ADR-0018, ADR-0049) and reading the Menu through the Custodian
37
- per turn (ADR-0013). So the Orchestrator injects a fixed Harness image plus **config** (env + the prepared worktree),
37
+ per turn (ADR-0013). So the Orchestrator injects a fixed Harness image plus **config** (env + the prepared clones),
38
38
  never a per-Agent image build. The HTTP prompt body itself carries only `{message, images}`, so anything persona-shaping
39
39
  is set at provision time, not per request.
40
40
 
@@ -91,14 +91,15 @@ one keeps the name and the endpoint and costs no node anything. Deferred until a
91
91
  These are accepted gaps in the current operator, recorded so they aren't silently forgotten. Each is a deliberate
92
92
  deferral.
93
93
 
94
- - **Egress is not restricted.** The pod is hardened at the host boundary — `automountServiceAccountToken: false`,
95
- pod/container `securityContext` (`runAsNonRoot`, `allowPrivilegeEscalation: false`, drop `ALL`, seccomp
96
- `RuntimeDefault`) — and its _ingress_ is the Orchestrator's alone: `jr2 up` converges a NetworkPolicy that admits no
97
- other pod to a Sandbox ([ADR-0058](0058-a-harness-answers-the-orchestrator-alone.md)), which kind's `kindnet`
98
- enforces. Nothing yet restricts _egress_: an untrusted Agent can still reach the cluster's API server IP (it holds no
99
- credential there) and the internet. A default-deny egress policy with an allowlist is the next layer, and ADR-0058
100
- records it open. The ADR-0013 boundary does not depend on either: only the token bounds what a pod may _do_;
101
- NetworkPolicy bounds where it may _talk_.
94
+ - **Egress is open.** The pod is hardened at the host boundary — `automountServiceAccountToken: false`, pod/container
95
+ `securityContext` (`runAsNonRoot`, `allowPrivilegeEscalation: false`, drop `ALL`, seccomp `RuntimeDefault`) — and its
96
+ _ingress_ is the Orchestrator's alone: `jr2 up` converges a NetworkPolicy that admits no other pod to a Sandbox
97
+ ([ADR-0058](0058-a-harness-answers-the-orchestrator-alone.md)), which kind's `kindnet` enforces. _Egress_ is open
98
+ except for the cloud metadata addresses, which no Harness pod reaches
99
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)): an untrusted Agent can
100
+ still reach the cluster's API server IP (it holds no credential there) and the internet. A default-deny egress policy
101
+ with an allowlist is the next layer, and ADR-0058 records it open. The ADR-0013 boundary does not depend on either:
102
+ only the token bounds what a pod may _do_; NetworkPolicy bounds where it may _talk_.
102
103
  - **Sidecars are plain containers, not native sidecars.** They are scheduled as ordinary `containers`, not Kubernetes
103
104
  ≥1.29 native sidecars (`initContainers` with `restartPolicy: Always`). There is no start-ordering or
104
105
  termination-ordering guarantee between the Harness and its sidecars — the Harness may begin serving before the