@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
@@ -1,5 +1,5 @@
1
- jr2 is newer than your training data. Before you write a Machine or change jr2.config.ts, read the bundled docs in node_modules/@jr2/orchestrator/docs/. They are the docs of this Instance's Kit version, 0.5.1. Start with guides/write-a-workflow.md and with reference/authoring-surface.md, the contents page of the Authoring surface. Read one page with each call of your file tool, because a file tool cuts off a long output. Before you give a change to the user, run `npx --no jr2 check` in the Instance folder. It does the typecheck and the load of each Workflow that `jr2 up` does first, with no cluster, and it gives the same refusals.
1
+ jr2 is newer than your training data. Before you write a Machine or change jr2.config.ts, read the bundled docs in node_modules/@jr2/orchestrator/docs/. They are the docs of this Instance's Kit version, 0.6.0. Start with guides/write-a-workflow.md and with reference/authoring-surface.md, the contents page of the Authoring surface. Read one page with each call of your file tool, because a file tool cuts off a long output. Before you give a change to the user, run `npx --no jr2 check` in the Instance folder. It does the typecheck and the load of each Workflow that `jr2 up` does first, with no cluster, and it gives the same refusals.
2
2
 
3
3
  A final state means that the work succeeded: the run ends `done`. When the work did not succeed, invoke `fail()` from a state (reference/authoring-surface/fail.md), and the run ends `failed`. Do not throw to fail: a throw is a defect, and the run ends `error`.
4
4
 
5
- [jr2 docs index]|root: ./node_modules/@jr2/orchestrator/docs|reference:{authoring-surface.md,machines.md,config.md,cli.md,http-api.md,mechanism-events.md,glossary.md,decisions.md}|reference/authoring-surface:{one file per export and type; reference/authoring-surface.md lists them}|guides:{iterate-on-a-machine-on-kind.md,write-a-workflow.md,bind-open-parts.md,set-an-agents-access-and-dials.md,run-a-command-or-call-a-url.md,push-and-sign-a-branch.md,start-a-run-from-a-run.md,add-tools-to-the-sandbox.md,size-a-workspace.md,run-one-worker-per-item.md,publish-a-machine-as-an-npm-package.md,let-an-agent-write-a-chart.md,run-a-chart-by-name.md,start-a-run.md,answer-a-gate.md,watch-a-run.md,look-inside-a-sandbox.md,cancel-a-run.md,open-the-console.md,use-a-local-model.md,use-a-hosted-provider.md,give-the-cluster-git-credentials.md,move-from-kind-to-a-shared-cluster.md,mirror-the-kit-images.md,upgrade-the-kit-version.md,clean-up.md,fix-a-failed-converge.md}|concepts:{machines-and-workflows.md,agents-and-turns.md,the-menu.md,gates.md,effects.md,workspaces.md,composition.md,charts.md,pools-and-sources.md,architecture.md,secrets-and-trust.md,converge.md,durable-runs.md}|.:{index.md,why.md}|get-started:{install.md,create-an-instance.md,connect-a-model.md,a-model-decides.md,an-agent-in-a-sandbox.md,have-your-assistant-write-a-machine.md}|reference/decisions:{one file per ADR; reference/decisions.md lists them}
5
+ [jr2 docs index]|root: ./node_modules/@jr2/orchestrator/docs|reference:{authoring-surface.md,machines.md,config.md,cli.md,http-api.md,mechanism-events.md,glossary.md,decisions.md}|reference/authoring-surface:{one file per export and type; reference/authoring-surface.md lists them}|guides:{iterate-on-a-machine-on-kind.md,write-a-workflow.md,bind-open-parts.md,set-an-agents-access-and-dials.md,cut-a-worktree.md,run-a-command-or-call-a-url.md,push-and-sign-a-branch.md,start-a-run-from-a-run.md,add-tools-to-the-sandbox.md,size-a-workspace.md,run-one-worker-per-item.md,publish-a-machine-as-an-npm-package.md,let-an-agent-write-a-chart.md,run-a-chart-by-name.md,start-a-run.md,answer-a-gate.md,watch-a-run.md,look-inside-a-sandbox.md,cancel-a-run.md,open-the-console.md,use-a-local-model.md,use-a-hosted-provider.md,give-the-cluster-git-credentials.md,move-from-kind-to-a-shared-cluster.md,mirror-the-kit-images.md,upgrade-the-kit-version.md,clean-up.md,fix-a-failed-converge.md}|concepts:{machines-and-workflows.md,agents-and-turns.md,the-menu.md,gates.md,effects.md,workspaces.md,composition.md,charts.md,pools-and-sources.md,architecture.md,secrets-and-trust.md,converge.md,durable-runs.md}|.:{index.md,why.md}|get-started:{install.md,create-an-instance.md,connect-a-model.md,a-model-decides.md,an-agent-in-a-sandbox.md,have-your-assistant-write-a-machine.md}|reference/decisions:{one file per ADR; reference/decisions.md lists them}
@@ -49,8 +49,10 @@ for one Turn, because then the name of the Agent would not tell what it is.
49
49
  ## The Working tools
50
50
 
51
51
  The [Working tools](../reference/glossary.md#working-tools) are the file and shell tools of an Agent: read, write, edit,
52
- bash, grep and glob. The [Harness](../reference/glossary.md#harness) runs them in its own container. The `workspace`
53
- field selects them:
52
+ bash, grep and glob. The [Harness](../reference/glossary.md#harness) runs them in its own container. Each program that
53
+ they start, and the file I/O of read, write and edit, runs confined with Landlock. It can do all that the user of the
54
+ container can do, but it cannot read what the Harness holds. [Secrets and trust](secrets-and-trust.md) tells why. The
55
+ `workspace` field selects the Working tools:
54
56
 
55
57
  - `"write"` gives all the Working tools.
56
58
  - `"read"` gives no write tool and no edit tool. The Agent keeps bash, so a command can still write a file. A review
@@ -69,9 +71,10 @@ The field also tells where the Turn runs. An Agent with `"write"` or `"read"` wo
69
71
  The `input` of the invoke is the Turn. It has five keys, and no other:
70
72
 
71
73
  - **The [Frame](../reference/glossary.md#frame): `prompt` and `cwd`.** The prompt tells what this Turn is about. `cwd`
72
- is the directory where the Working tools work. A definition cannot hold a `cwd`, because the Worktree of a run exists
73
- only when the run starts. Under a Workspace with one [Repo Slot](../reference/glossary.md#repo-slot), the default
74
- `cwd` is the Worktree of that slot.
74
+ is the directory where the Working tools work. A definition cannot hold a `cwd`, because a Worktree exists only after
75
+ a run cuts it. Under a Workspace with [Repo Slots](../reference/glossary.md#repo-slot), a Turn of an Agent with
76
+ Working tools states its `cwd`. A Turn that wants `/work` states it too. There is no default, because a search in
77
+ `/work` hits `default/`, and a relative write lands outside each Worktree.
75
78
  - **The [Dials](../reference/glossary.md#dials): `model` and `thinkingLevel`.** They tell how hard the Agent works on
76
79
  this Turn, and they replace the values of the definition for this Turn only. The Agent stays the same Agent.
77
80
  - **`continue`.** It tells if the Turn continues the conversation of the Agent. See
@@ -90,7 +93,7 @@ changes, the coder continues its conversation and thinks harder on that Turn:
90
93
  ```ts
91
94
  import { z } from "zod";
92
95
  import { assign } from "xstate";
93
- import { agent, defineEvent, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
96
+ import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
94
97
 
95
98
  const finish = defineEvent({
96
99
  name: "finish",
@@ -112,6 +115,9 @@ const runInput = z.object({ branch: z.string() });
112
115
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
113
116
  type Context = BodyInput & { summary: string; notes: string };
114
117
 
118
+ // The Worktree of each Turn of the coder.
119
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
120
+
115
121
  const body = jr2Setup({
116
122
  types: {} as { input: BodyInput; context: Context },
117
123
  events: [finish, approve, requestChanges],
@@ -128,21 +134,30 @@ const body = jr2Setup({
128
134
  workspace: "none",
129
135
  thinkingLevel: "low",
130
136
  }),
137
+ cut: worktree(),
131
138
  fail: fail(),
132
139
  },
133
140
  }).createMachine({
134
141
  id: "fix",
135
142
  context: ({ input }) => ({ ...input, summary: "", notes: "" }),
136
143
  on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
137
- initial: "coding",
144
+ initial: "cutting",
138
145
  states: {
146
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
147
+ cutting: {
148
+ invoke: {
149
+ src: "cut",
150
+ input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
151
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
152
+ },
153
+ },
139
154
  coding: {
140
155
  invoke: {
141
156
  src: "coder",
142
157
  // The Frame (prompt and cwd) and continue. This Turn starts the coder's conversation.
143
158
  input: ({ context }) => ({
144
159
  prompt: "Make `node --test` pass, then commit.",
145
- cwd: context.workspace.repos.app,
160
+ cwd: fixTree(context),
146
161
  continue: true,
147
162
  }),
148
163
  },
@@ -161,7 +176,7 @@ const body = jr2Setup({
161
176
  // The same conversation as in coding, with a Dial: the coder thinks harder on this Turn.
162
177
  input: ({ context }) => ({
163
178
  prompt: `The fix is not complete: ${context.notes}\nFix it, then commit.`,
164
- cwd: context.workspace.repos.app,
179
+ cwd: fixTree(context),
165
180
  continue: true,
166
181
  thinkingLevel: "high",
167
182
  }),
@@ -169,6 +184,7 @@ const body = jr2Setup({
169
184
  on: { finish: { target: "checking", actions: assign({ summary: ({ event }) => event.summary }) } },
170
185
  },
171
186
  done: { type: "final" },
187
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
172
188
  failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
173
189
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
174
190
  },
@@ -177,7 +193,6 @@ const body = jr2Setup({
177
193
  export const machine = workspace(body, {
178
194
  input: runInput,
179
195
  repos: { app: "https://github.com/you/app.git" },
180
- spec: ({ input }) => ({ branch: input.branch }),
181
196
  });
182
197
  ```
183
198
 
@@ -186,7 +201,7 @@ export const machine = workspace(body, {
186
201
  ```ts
187
202
  import { z } from "zod";
188
203
  import { assign } from "xstate";
189
- import { agent, defineEvent, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
204
+ import { agent, defineEvent, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
190
205
 
191
206
  const finish = defineEvent({
192
207
  name: "finish",
@@ -208,6 +223,9 @@ const runInput = z.object({ branch: z.string() });
208
223
  type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
209
224
  type Context = BodyInput & { summary: string; notes: string };
210
225
 
226
+ // The Worktree of each Turn of the coder.
227
+ const fixTree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
228
+
211
229
  const body = jr2Setup({
212
230
  types: {} as { input: BodyInput; context: Context },
213
231
  events: [finish, approve, requestChanges],
@@ -224,21 +242,30 @@ const body = jr2Setup({
224
242
  workspace: "none",
225
243
  thinkingLevel: "low",
226
244
  }),
245
+ cut: worktree(),
227
246
  fail: fail(),
228
247
  },
229
248
  }).createMachine({
230
249
  id: "fix",
231
250
  context: ({ input }) => ({ ...input, summary: "", notes: "" }),
232
251
  on: { "workspace.lost": ".lost", "agent.fault": ".failed" },
233
- initial: "coding",
252
+ initial: "cutting",
234
253
  states: {
254
+ // The Workspace only clones. The Machine cuts the Worktree that the Agent works in.
255
+ cutting: {
256
+ invoke: {
257
+ src: "cut",
258
+ input: ({ context }) => ({ path: fixTree(context), branch: context.branch }),
259
+ onDone: [{ guard: ({ event }) => isReady(event.output), target: "coding" }, { target: "notCut" }],
260
+ },
261
+ },
235
262
  coding: {
236
263
  invoke: {
237
264
  src: "coder",
238
265
  // The Frame (prompt and cwd) and continue. This Turn starts the coder's conversation.
239
266
  input: ({ context }) => ({
240
267
  prompt: "Make `node --test` pass, then commit.",
241
- cwd: context.workspace.repos.app,
268
+ cwd: fixTree(context),
242
269
  continue: true,
243
270
  }),
244
271
  },
@@ -257,7 +284,7 @@ const body = jr2Setup({
257
284
  // The same conversation as in coding, with a Dial: the coder thinks harder on this Turn.
258
285
  input: ({ context }) => ({
259
286
  prompt: `The fix is not complete: ${context.notes}\nFix it, then commit.`,
260
- cwd: context.workspace.repos.app,
287
+ cwd: fixTree(context),
261
288
  continue: true,
262
289
  thinkingLevel: "high",
263
290
  }),
@@ -265,6 +292,7 @@ const body = jr2Setup({
265
292
  on: { finish: { target: "checking", actions: assign({ summary: ({ event }) => event.summary }) } },
266
293
  },
267
294
  done: { type: "final" },
295
+ notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
268
296
  failed: { invoke: { src: "fail", input: { reason: "the Agent's Turn could not go on" } } },
269
297
  lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
270
298
  },
@@ -273,7 +301,6 @@ const body = jr2Setup({
273
301
  export const machine = workspace(body, {
274
302
  input: runInput,
275
303
  repos: { app: "https://github.com/you/app.git" },
276
- spec: ({ input }) => ({ branch: input.branch }),
277
304
  });
278
305
  ```
279
306
 
@@ -355,9 +382,18 @@ itself. It tells nothing about the task: the prompt tells the task. It has two p
355
382
  carries a [Chart](../reference/glossary.md#chart), the Turn part also tells how a Chart runs (see
356
383
  [Charts](charts.md)).
357
384
 
358
- A notice tells the next Turn what happened before it. For example: the conversation is new and the earlier context is
359
- gone, or the processes of the Sandbox stopped at its memory limit. jr2 gives each notice one time, to the next Turn that
360
- it is for.
385
+ A notice tells a Turn what happened before it. For example: the conversation is new and the earlier context is gone, or
386
+ the processes of the Sandbox stopped at its memory limit. A notice about a conversation goes to the next Turn of that
387
+ conversation. A notice about the Workspace goes to each conversation that was in the Workspace when the processes
388
+ stopped:
389
+
390
+ - A Turn that is open hears it before its next model request.
391
+ - A continued conversation hears it at its next Turn.
392
+ - The next Turn of the Agent whose Turn the stop cut hears it, also when that Turn is fresh.
393
+
394
+ A conversation hears each notice one time. A conversation whose own bash call already told it about the stop does not
395
+ hear the notice. A conversation that starts after the stop does not hear it, except the next Turn of the Agent whose
396
+ Turn the stop cut.
361
397
 
362
398
  You cannot turn the Briefing off. It contains only facts that make the Agent work better. An `instructions` that tells
363
399
  the same fact again does no harm.
@@ -409,8 +445,8 @@ the model in `models`. A model with no context window gets no Compaction.
409
445
  different key.
410
446
  - A Turn cannot set `instructions` or `workspace`.
411
447
  - `cwd` is an absolute path. The Harness refuses a Turn with a relative `cwd`.
412
- - Under a Workspace with more than one Repo Slot, each Turn of an Agent with Working tools sets `cwd`. jr2 refuses the
413
- Turn without it.
448
+ - Under a Workspace with Repo Slots, each Turn of an Agent with Working tools sets `cwd`. jr2 refuses the Turn without
449
+ it.
414
450
  - An Agent with `"write"` or `"read"` runs in the body of a `workspace()`. jr2 refuses its Turn outside one.
415
451
  - `jr2 up` sees only the models of the definitions. It cannot see a model that a Turn sets as a Dial. The Harness
416
452
  refuses a model that it cannot find when the Turn starts.
@@ -19,8 +19,8 @@ runs and what it does.
19
19
  ## Why the parts are separate
20
20
 
21
21
  An Agent that works on a Repo runs code. Its [Working tools](../reference/glossary.md#working-tools) give it a shell.
22
- With the shell, it can read each file and each variable in its container. It can also connect to each address that its
23
- pod can reach. Thus jr2 puts each thing that the Agent must not touch in a different place:
22
+ With the shell, it can run each program of its image on the files in `/work`. It can also connect to each address that
23
+ its pod can reach. Thus jr2 puts each thing that the Agent must not touch in a different place:
24
24
 
25
25
  - The control of a run is in the Orchestrator. An Agent cannot change its own Machine. It can only pick from its
26
26
  [Menu](../reference/glossary.md#menu), and the Orchestrator decides what each pick does.
@@ -28,6 +28,8 @@ pod can reach. Thus jr2 puts each thing that the Agent must not touch in a diffe
28
28
  processes. Agents in two Workspaces share no files and no ports. [Workspaces](workspaces.md) tells more.
29
29
  - The credentials are in containers in which the Agent executes nothing: the Custodian, the Effector and the Repo cache.
30
30
  [Secrets and trust](secrets-and-trust.md) tells how.
31
+ - The Harness holds one secret value, with which it sends the picks. Each program that it starts runs confined with
32
+ Landlock, so the code of an Agent cannot read that value.
31
33
 
32
34
  ## What runs where
33
35
 
@@ -66,7 +68,7 @@ The [Instance Harness](../reference/glossary.md#instance-harness), the
66
68
  sections below tell when.
67
69
 
68
70
  A Sandbox pod can also have a [User Container](../reference/glossary.md#user-container), when its `workspace()` names
69
- one. It runs its own image, and it mounts the [Worktree](../reference/glossary.md#worktree) read-write.
71
+ one. It runs its own image, and it mounts `/work`, with each [Worktree](../reference/glossary.md#worktree), read-write.
70
72
  [Workspaces](workspaces.md) tells more.
71
73
 
72
74
  ## On your computer
@@ -115,14 +117,15 @@ To isolate two sets of Workflows, use two Instances. Each Instance has its own n
115
117
 
116
118
  The Harness is jr2's own server for the Turns of Agents. It speaks to the model provider. It gives the model the Agent's
117
119
  Menu and its Working tools. It executes each Working tool that the model calls, and sends each pick through the
118
- Custodian. It holds no Agents of its own. Each Turn brings the Agent's definition with it.
119
- [Agents and Turns](agents-and-turns.md) tells what happens in a Turn.
120
+ Custodian. Each program that a Working tool starts runs confined with Landlock. The Harness holds no Agents of its own.
121
+ Each Turn brings the Agent's definition with it. [Agents and Turns](agents-and-turns.md) tells what happens in a Turn.
120
122
 
121
123
  The Harness has two placements:
122
124
 
123
125
  - **In each Sandbox.** The Harness is the primary container of the Sandbox pod, named `harness`. It runs in the
124
126
  [Sandbox Image](../reference/glossary.md#sandbox-image), and jr2 mounts the Harness into the pod at `/opt/jr2`. Thus
125
- the Working tools of the Agent reach the tools of that image. An Agent that works on a Repo has its Turns here.
127
+ the Working tools of the Agent reach the tools of that image. Each Agent that works on a Repo has its Turns here: one
128
+ Harness serves all the Agents of the Sandbox.
126
129
  - **As the Instance Harness.** The Instance Harness is one StatefulSet for each Instance, `jr2-instance-harness`. Each
127
130
  Turn of a Menu-only Agent (`workspace: "none"`) runs here. Its pod has a Harness and a Custodian, and no Worktree. It
128
131
  keeps each conversation on its volume while a run can continue it. `jr2 up` converges it when a Machine of the
@@ -130,16 +133,20 @@ The Harness has two placements:
130
133
 
131
134
  A Harness answers only the Orchestrator. The Orchestrator sends a bearer token on each call, and the Harness accepts no
132
135
  call without it, except its health check. `jr2 up` also converges NetworkPolicies that let only the Orchestrator's pods
133
- connect to a Harness pod.
136
+ connect to a Harness pod, and that stop each Harness pod at the cloud metadata addresses
137
+ ([Secrets and trust](secrets-and-trust.md#what-a-held-secret-does-not-stop)).
134
138
 
135
139
  ## The Custodian
136
140
 
137
141
  Each Harness pod has a Custodian: each Sandbox, and the Instance Harness. The Custodian is a container beside the
138
142
  Harness. The Agent executes no code in it. It does two things:
139
143
 
140
- - It carries four kinds of call from the pod to the Orchestrator: the Menu reads, the picks, the polls for the answer to
141
- a pick, and the fetch asks. It sends each one with the pod's Sandbox token. These four routes are all that it
142
- forwards. It refuses all other routes.
144
+ - It carries five kinds of call from the pod to the Orchestrator: the Menu reads, the picks, the polls for the answer to
145
+ a pick, the reports that a step boundary gave its notices to the model, and the fetch asks. It sends the first four
146
+ with the control token of the Sandbox, and the fetch asks with its fetch token. These five routes are all that it
147
+ forwards. It refuses all other routes. The Harness sends the first four on a Unix socket in `/run/jr2`. A confined
148
+ program cannot make a socket there, and the Custodian refuses a request on it that does not have the control Stand-in.
149
+ The fetch asks go to port `8081` on loopback.
143
150
  - It holds the [Held secrets](../reference/glossary.md#held-secret) of its pod, such as a model API key. It puts the
144
151
  value into the requests that go to the hosts of each Held secret.
145
152
 
@@ -154,15 +161,16 @@ names a target. Thus a credential that an Effect uses stays out of the Harness c
154
161
  program, and it runs in a container in which the Agent executes nothing. [Effects](effects.md) tells which Effect runs
155
162
  where. It has two placements:
156
163
 
157
- - **The Sandbox Effector** is a container in a Sandbox pod, named `effector`. It mounts the Worktree read-only. It runs
158
- a `push`, an `http` Effect whose body is a Worktree file, and a `command` that names an Effect secret. Its image is
159
- the Sandbox Image, unless the `workspace()` names a different image.
164
+ - **The Sandbox Effector** is a container in a Sandbox pod, named `effector`. It mounts `/work` read-only. It runs a
165
+ `push`, an `http` Effect whose body is a Worktree file, and a `command` that names an Effect secret. Its image is the
166
+ Sandbox Image, unless the `workspace()` names a different image.
160
167
  - **The Instance Effector** is one pod for each Instance, `jr2-instance-effector`. It runs each `http` Effect that reads
161
168
  no Worktree file, and each `dispatch` that starts a run on a different Orchestrator. It holds the Instance token of
162
169
  that Orchestrator as an Effect secret. It runs in the `jr2-harness` Kit image.
163
170
 
164
171
  Each placement exists only when a Machine of the Instance puts an Effect there. A `command` that names no Effect secret
165
- does not use an Effector. It runs in the Harness container of its Sandbox, with the same tools and access as the Agent.
172
+ does not use an Effector. It runs in the Harness container of its Sandbox, with the same tools and access as the Agent,
173
+ and confined in the same way.
166
174
 
167
175
  The Orchestrator signs each request that it sends to an Effector, and the Effector signs each answer. The Orchestrator
168
176
  only asks, and the Effector holds the credentials.
@@ -188,7 +196,8 @@ the Orchestrator stopped for longer than the Lease allows. [Workspaces](workspac
188
196
 
189
197
  The Repo cache keeps one bare clone of each Repo on each node that needs it. It is a DaemonSet, `jr2-repo-cache`, in the
190
198
  namespace of the Instance. It has one pod on each [Sandbox node](../reference/glossary.md#sandbox-node). `jr2 up`
191
- converges it when a Machine of the Instance uses a Sandbox.
199
+ converges it when a Machine of the Instance uses a Sandbox. Each pod also checks that the kernel of its node has
200
+ Landlock. On a node without Landlock, the pod is not ready, and `jr2 up` names that node and refuses to converge.
192
201
 
193
202
  The operator mounts the cache of each Repo into a Sandbox, read-only, at `/repos`. The clone in the Sandbox gets its
194
203
  objects from that cache. A `git fetch origin` in the Sandbox asks the Repo cache for a fetch. Then the Repo cache
@@ -204,6 +213,8 @@ credential to fetch.
204
213
  - A Harness accepts a call only from the Orchestrator, with the bearer of its own pod. Only its health check needs no
205
214
  bearer.
206
215
  - The Agent executes code only in the Harness container. It executes nothing in the Custodian or in the Effector.
216
+ - In the Harness container, each program that the Harness starts runs confined. It cannot read what the Harness holds.
217
+ - Each Sandbox node needs Landlock in its kernel. `jr2 up` refuses to converge while a Sandbox node has none.
207
218
  - The Orchestrator watches the Sandbox resources and never reads a pod. The operator puts the facts of the pod on the
208
219
  status of the Sandbox resource.
209
220
  - A Sandbox has one pod for its life. A lost pod is never made again.
@@ -226,5 +237,7 @@ credential to fetch.
226
237
  [ADR-0053](../reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md),
227
238
  [ADR-0058](../reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md),
228
239
  [ADR-0059](../reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md),
229
- [ADR-0063](../reference/decisions/0063-the-orchestrator-watches-the-cluster.md) and
230
- [ADR-0068](../reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)
240
+ [ADR-0063](../reference/decisions/0063-the-orchestrator-watches-the-cluster.md),
241
+ [ADR-0068](../reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md),
242
+ [ADR-0076](../reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md) and
243
+ [ADR-0077](../reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md)
@@ -39,8 +39,10 @@ it bad text.
39
39
  and the size of the Chart in bytes.
40
40
 
41
41
  A Chart gets nothing else. A lent Effect keeps its url, its argv and its secrets, so a Chart fills only its input. A
42
- Chart has no url, no secret, no image and no Repo of its own. The Open body also decides where each Turn of the Chart
43
- runs: in the Sandbox of its `workspace()`, or on the [Instance Harness](../reference/glossary.md#instance-harness).
42
+ lent [`worktree()`](../reference/authoring-surface/worktree.md) must state `expect`, because a Chart cannot state it. A
43
+ Chart reads the cut with the kit guard `jr2.ready`, then gives the same literal path as `cwd` and `worktree`. A Chart
44
+ has no url, no secret, no image and no Repo of its own. The Open body also decides where each Turn of the Chart runs: in
45
+ the Sandbox of its `workspace()`, or on the [Instance Harness](../reference/glossary.md#instance-harness).
44
46
 
45
47
  ## What a Chart holds
46
48
 
@@ -48,7 +50,8 @@ A Chart is [xstate](https://xstate.js.org)'s own JSON config, with these limits:
48
50
 
49
51
  - States, transitions, `after` delays, and nested and parallel states. A Chart has 200 states at most, 8 levels deep.
50
52
  - Each `invoke.src` is a lent slot key or an Agent of the Chart. Each guard, action and delay is a lent name. Each Chart
51
- also has the guard `jr2.failure`, which is true on an `onError` when the error is a Failure.
53
+ also has the guard `jr2.failure`, which is true on an `onError` when the error is a Failure, and the guard
54
+ `jr2.ready`, which is true on an `onDone` when the cut is `ready`.
52
55
  - A state that invokes an Agent can route [`agent.fault`](../reference/mechanism-events.md#agent-fault) in its `on`, but
53
56
  it does not have to. When no transition of the Chart takes `agent.fault`, the Open body sends it, unchanged, to the
54
57
  Chart wrapper. The wrapper's own `agent.fault` route takes it, as for an Agent of the wrapper, so one route on the
@@ -83,6 +83,9 @@ the cluster, see [Architecture](architecture.md).
83
83
  12. It applies the operator. For a Repo with an ssh url, it asks where the deploy key comes from.
84
84
  13. It applies the Secrets and the Orchestrator, and waits for the rollout. When the Machines need them, it also waits
85
85
  for the Repo cache, the Instance Harness and the [Instance Effector](../reference/glossary.md#instance-effector).
86
+ The Repo cache agent on each Sandbox node checks that the kernel of its node has Landlock, which jr2 needs. While a
87
+ Sandbox node has no Landlock, the converge fails. The error names each such node, the reason, and the fix: keep the
88
+ node out with `sandbox.nodeSelector` or a taint, or enable Landlock in its kernel. Then run `jr2 up` again.
86
89
  14. It publishes the Assembly to the Orchestrator. When the Orchestrator already has this Assembly, it publishes
87
90
  nothing.
88
91
  15. It removes the images that nothing uses now.