@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
@@ -13,11 +13,24 @@ decision generalizes that container. It holds every credential the Harness in it
13
13
 
14
14
  ## Decision
15
15
 
16
- - **A held secret is a credential the Harness uses and never holds.** The Harness container's env holds its
17
- **Stand-in**, `jr2-held-<NAME>`. The Stand-in is deterministic and public. It means something only to the Custodian,
18
- and only toward a host the secret is bound to. It is unique per name, it is a valid header and env value, and it never
19
- contains `sk-ant-oat` (pi reads such a key as an Anthropic OAuth token). The word is not "placeholder", because that
20
- is the word of an Open part.
16
+ - **A held secret is a credential the Harness uses and never holds.** The Harness process holds its **Stand-in**. It
17
+ means something only to the Custodian, and only toward a host the secret is bound to. It is unique per name, it is a
18
+ valid header and env value, and it never contains `sk-ant-oat` (pi reads such a key as an Anthropic OAuth token). The
19
+ word is not "placeholder", because that is the word of an Open part.
20
+ - **Every Stand-in is secret, except the fetch Stand-in.** The pod's maker mints each Stand-in for its pod with the
21
+ signing key, and no process can guess it: the Orchestrator for a Sandbox, and `jr2 up` for the Instance Harness, as it
22
+ writes that pod's other Secrets (the Instance Effector's key too), and for a check pod
23
+ ([ADR-0019](0019-one-converging-command-against-the-current-context.md)). Only the Harness process holds the
24
+ Stand-ins. It holds each one, the control Stand-in included, as a file under `/run/jr2/` that no process it confines
25
+ can read, and never in the Harness container's env. It gives a model key's Stand-in to the engine in-process. A
26
+ process that the Harness confines gets in its env only the Stand-ins of the secrets that its `workspace()`'s `secrets`
27
+ list grants ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). So the
28
+ Stand-in of a model key is in reach of the Harness process alone, unless the author grants that key. Secrecy matters
29
+ because confinement makes it hold: a Stand-in that Workspace code can guess lets that code spend the secret. A call
30
+ that code in `/work` makes to the model directly is outside the Turn's Settlement, so it escapes the usage that jr2
31
+ records ([ADR-0066](0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md)) and each
32
+ ceiling on spend. A project whose own code needs a model gets a key that the author grants on purpose. The fetch
33
+ Stand-in stays public (below): it can only ask for a fetch, for its own Sandbox.
21
34
  - **The Custodian holds them.** It is a container in every Harness pod — every Sandbox and the Instance Harness — beside
22
35
  the Harness, and it holds the held secrets its own pod names (below). The Agent executes no code in it, and
23
36
  `shareProcessNamespace` stays off ([ADR-0005](0005-sandbox-pod-composition.md)). So the Agent cannot read its env, its
@@ -25,16 +38,25 @@ decision generalizes that container. It holds every credential the Harness in it
25
38
  `@jr2/orchestrator`). The pod's maker writes Envoy's whole configuration — the Orchestrator a Sandbox's at provision,
26
39
  `jr2 up` the Instance Harness's — from the manifest `jr2 up` declares (see "Objects, per pod"). There is no admin
27
40
  listener and no xDS, so nothing in the pod can change the configuration or read it back.
28
- - **The Custodian replaces the Adapter.** The `@jr2/adapter` package and the `jr2-adapter` image are deleted. The
29
- Sandbox token is a held secret of the kit's own: the Harness container holds
30
- `JR2_SANDBOX_TOKEN=jr2-held-JR2_SANDBOX_TOKEN`, and the Custodian holds the token as a file from the `<sandbox>-token`
31
- Secret (the Instance Harness: the `JR2_INSTANCE_HARNESS_TOKEN` key of `jr2-instance`). No other container mounts it.
41
+ - **The Custodian replaces the Adapter.** The `@jr2/adapter` package and the `jr2-adapter` image are deleted. A
42
+ Sandbox's two tokens are held secrets of the kit's own (ADR-0013): the Custodian holds the control token and the fetch
43
+ token as files from the `<sandbox>-token` Secret (the Instance Harness: its control token, from
44
+ `jr2-instance-harness-token`, which `jr2 up` mints as that pod's maker; it serves no fetch). No other container mounts
45
+ the tokens, and the real tokens never leave the Custodian. The same Secret holds the pod's Stand-ins, which the
46
+ Harness container mounts as files; so it is never `jr2-instance`, which holds the Instance token and the signing key.
47
+ - The **control Stand-in** is secret, as every Stand-in but one. The Harness process alone holds it, as a file under
48
+ `/run/jr2/` that no process it confines can read, and no `secrets` list can grant it: it is in no child's env
49
+ (ADR-0076). The Custodian puts the control token in its place on the agent routes. There is one control Stand-in for
50
+ each Sandbox, whatever the count of its Agents.
51
+ - The **fetch Stand-in** is public, `jr2-held-JR2_SANDBOX_TOKEN`. `jr2-upload-pack` holds it as a constant. The
52
+ Custodian puts the fetch token in its place on the fetch route alone, and refuses it on every agent route.
32
53
  - **The Harness reads its Menu itself.** There is no MCP. For each Submission, `menu.ts` reads
33
- `GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian on
34
- `127.0.0.1:8081`, with the Stand-in as the bearer. `menu-tools.ts` gives pi each accepted event as a tool. The tool
35
- names stay `mcp__jr2__<event>`, because shipped instructions and the printer depend on them; the name is no longer a
36
- transport. `menu-tools.ts` adds the prefix, and `menu.ts` keeps the bare name, because an MCP client adds
37
- `mcp__<server>__` itself. The Harness keeps what the Adapter did: a turn that is over is never prompted
54
+ `GET /agents/<iid>/surface` and delivers a pick with `POST /agents/<iid>/events`, both to the Custodian's control
55
+ socket, `/run/jr2/custodian/control.sock`, with the control Stand-in as the bearer and the slot it states for the
56
+ conversation (ADR-0076). `menu-tools.ts` gives pi each accepted event as a tool. The tool names stay
57
+ `mcp__jr2__<event>`, because shipped instructions and the printer depend on them; the name is no longer a transport.
58
+ `menu-tools.ts` adds the prefix, and `menu.ts` keeps the bare name, because an MCP client adds `mcp__<server>__`
59
+ itself. The Harness keeps what the Adapter did: a turn that is over is never prompted
38
60
  ([ADR-0026](0026-a-turn-that-is-over-is-never-prompted.md)), a receipt is prose
39
61
  ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md),
40
62
  [ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), the surface read retries
@@ -42,32 +64,42 @@ decision generalizes that container. It holds every credential the Harness in it
42
64
  ([ADR-0042](0042-ready-is-not-routable-an-admission-retries-its-connection.md)), and a `poll` event refuses the turn
43
65
  (`deferred` is retired, ADR-0006). The event's own JSON Schema is the tool's parameters, so the model sees the
44
66
  contract the Orchestrator validates against.
45
- - **The control listener forwards four routes and no other.** `127.0.0.1:8081`, plain HTTP, to the Orchestrator's
46
- Service (`jr2-orchestrator.<ns>.svc:4000`, written into the pod's bootstrap by whoever makes the pod — see "Objects,
47
- per pod"):
67
+ - **The control listener forwards five routes and no other.** Plain HTTP, to the Orchestrator's Service
68
+ (`jr2-orchestrator.<ns>.svc:4000`, written into the pod's bootstrap by whoever makes the pod — see "Objects, per
69
+ pod"):
48
70
 
49
71
  ```
50
72
  GET /agents/<iid>/surface → the same path the Menu
51
73
  POST /agents/<iid>/events → the same path a pick
52
74
  GET /agents/<iid>/deliveries/<id> → the same path an outstanding pick's answer (ADR-0024)
75
+ POST /agents/<iid>/notices → the same path a step boundary's notices heard (ADR-0062, ADR-0076)
53
76
  POST /fetch → /sandboxes/<this pod's Sandbox>/fetch the ask (ADR-0053)
54
77
  anything else → 404, from the Custodian
55
78
  ```
56
79
 
57
- A read — the surface, a delivery's poll — is retried on any reset; a pick and the ask only on a reset before they were
58
- sent, so one the Orchestrator may have read is not sent twice. A poll holds up to about 25 s, far below the listener's
59
- 3600 s idle limit, so an outstanding pick never rides one long request (ADR-0002).
80
+ The four agent routes are on a Unix socket, `/run/jr2/custodian/control.sock`, in a folder that the Harness and the
81
+ Custodian share and that no confined program can write (ADR-0076). The fetch is on `127.0.0.1:8081`, because
82
+ `jr2-upload-pack` runs in the domain, and the fetch Stand-in is public. Each listener answers 404 to the other's
83
+ routes. Landlock does not limit a connect to a named socket, so a confined program can connect to the control socket.
84
+ It holds no control Stand-in, so the Custodian refuses each agent route that it asks for there, with the fetch
85
+ Stand-in or with none, and the control socket answers 404 to the fetch route.
60
86
 
61
- On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token and removes every
62
- other credential header. A request without the Stand-in gets 403. The ask's path comes from the Custodian
63
- (`JR2_SANDBOX` in its env, set per pod); on the Instance Harness there is none, and the ask gets 404. The route
64
- allowlist is the second layer. The control stays in the Orchestrator: the token's scope, the live registration, and
65
- the event's name and payload are judged there, as before (ADR-0013).
87
+ A read — the surface, a delivery's poll — and an acknowledgment of notices, which marks them heard however often it
88
+ lands, are retried on any reset; a pick and the ask only on a reset before they were sent, so one the Orchestrator may
89
+ have read is not sent twice. A poll holds up to about 25 s, far below the listener's 3600 s idle limit, so an
90
+ outstanding pick never rides one long request (ADR-0002).
91
+
92
+ On each forwarded request, the Custodian swaps `Authorization: Bearer <Stand-in>` for the token of that route — the
93
+ control token on an agent route, the fetch token on the fetch route — and removes every other credential header. A
94
+ request without the Stand-in of its route gets 403. The ask's path comes from the Custodian (`JR2_SANDBOX` in its env,
95
+ set per pod); on the Instance Harness there is none, and the ask gets 404. The route allowlist is the second layer.
96
+ The control stays in the Orchestrator: the token's scope, the live registration, and the event's name and payload are
97
+ judged there, as before (ADR-0013). The Orchestrator also accepts each token on its own routes only, and checks the
98
+ slot that the Harness states against the registration's (ADR-0076).
66
99
 
67
100
  - **Model keys go the same way, over HTTPS.** When its pod holds a held secret, the Harness container also gets:
68
101
 
69
102
  ```sh
70
- <NAME>=jr2-held-<NAME> one per held secret of the pod
71
103
  HTTPS_PROXY / https_proxy=http://127.0.0.1:15001
72
104
  NO_PROXY / no_proxy=localhost,127.0.0.1,::1
73
105
  NODE_USE_ENV_PROXY=1
@@ -75,11 +107,11 @@ decision generalizes that container. It holds every credential the Harness in it
75
107
  SSL_CERT_FILE / REQUESTS_CA_BUNDLE / GIT_SSL_CAINFO=/etc/jr2/ca/bundle.crt
76
108
  ```
77
109
 
78
- These ride `env`, after `harness.env` and before the wire's digest. In Kubernetes `env` wins over `envFrom`, so a
79
- Stand-in overrides a same-named key of an `envFrom` Secret, even one that changes later. There is no `HTTP_PROXY`: the
80
- Custodian carries TLS only, and plain HTTP (the Custodian's control listener, an in-cluster seed repository) goes
81
- direct. `NO_PROXY` is loopback only, so an in-cluster gateway that a secret is bound to still goes through the
82
- Custodian.
110
+ These ride `env`, after `harness.env` and before the wire's digest. This is the Harness container's env, and it holds
111
+ no Stand-in: the Stand-ins are files (above). A process that the Harness confines gets an explicit env instead, which
112
+ holds only the Stand-ins that its `workspace()` grants (ADR-0076). There is no `HTTP_PROXY`: the Custodian carries TLS
113
+ only, and plain HTTP (the Custodian's control listener, an in-cluster seed repository) goes direct. `NO_PROXY` is
114
+ loopback only, so an in-cluster gateway that a secret is bound to still goes through the Custodian.
83
115
 
84
116
  - **The egress listener: CONNECT only.** `127.0.0.1:15001`. The CONNECT target decides everything (lowercased, without a
85
117
  trailing dot) — never the SNI, never the `Host` header. A target that a held secret is bound to goes to an internal
@@ -143,10 +175,11 @@ decision generalizes that container. It holds every credential the Harness in it
143
175
  (`program.lua`); each pod's own `custodian.lua` is a generated `HELD` table — names, headers, paths and targets, never
144
176
  a value — that ends with `dofile` of the program, so the program runs after the table it reads. At start the program
145
177
  reads each value from `/etc/jr2/custodian/values/<NAME>`, takes off one trailing `\n` or `\r\n`, and stops the
146
- start-up with a line that names the secret when a value is missing, empty, or has a byte outside `0x20`–`0x7E`.
147
- Envoy's `credential_injector` is not used: it is alpha, it breaks on a secret file that ends in a newline (1.39.1),
148
- and it replaces a whole header, where the swap must see the Stand-in. Values never enter a ConfigMap. Envoy runs one
149
- worker, with hot restart off, and flushes its access log twice a second.
178
+ start-up with a line that names the secret when a value is missing, empty, or has a byte outside `0x20`–`0x7E`. Each
179
+ Stand-in but the fetch Stand-in is a value too: the Custodian reads it from its values, never from the table. Envoy's
180
+ `credential_injector` is not used: it is alpha, it breaks on a secret file that ends in a newline (1.39.1), and it
181
+ replaces a whole header, where the swap must see the Stand-in. Values never enter a ConfigMap. Envoy runs one worker,
182
+ with hot restart off, and flushes its access log twice a second.
150
183
  - **The log is a contract.** One line per decision on stdout, with the prefix `jr2.custodian` and `key=value` pairs:
151
184
  `ready secrets=… hosts=N` (at the first probe), `action=intercept target=… secret=… method=… status=…`,
152
185
  `action=tunnel target=…`, `action=refuse target=… reason=…`. Never a header value, a body, a path, a query, a value or
@@ -192,21 +225,26 @@ decision generalizes that container. It holds every credential the Harness in it
192
225
  `jr2 up` alone checks the claimed entries (`heldSecretsOf`), host-side, because `.env` values are absent in-cluster.
193
226
  An entry no pod claims is not checked as a held secret:
194
227
 
195
- | # | Refused |
196
- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
197
- | R1 | A known model key (`MODEL_KEYS`) in `harness.env`, or as a key of a Secret or ConfigMap `harness.envFrom` names. |
198
- | R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
199
- | R3 | One name held twice, or held and set by `harness.env`. |
200
- | R4 | Not exactly one of `value`/`valueFrom`; an empty value; a byte outside `0x20`–`0x7E`; leading or trailing space. |
201
- | R5 | A claimed entry with no `hosts` where no default exists; a wildcard, userinfo, a non-`https` scheme, a bare-host path, a bad port; two secrets on one host in one header. |
202
- | R6 | A `headers` entry that is not a lowercase token, or that frames or routes the request; a duplicate. |
203
- | R7 | `provider.apiKey` with a non-`https` `baseUrl`; a non-`https` catalog `baseUrl`. |
204
- | R8 | A catalog key that is not a known provider, or that is the custom provider's id. |
205
- | R9 | Explicit `hosts` on a known model key that leave out where a carried Agent's provider sends its calls. |
206
- | R10 | A `secretKeyRef` whose Secret or key does not exist. |
207
- | R11 | While a secret is held: `harness.env` sets a proxy or trust variable. |
208
- | R12 | A `paths` entry that does not start with `/`, or holds `..`, `;`, `\`, `%2e`, `%2f` or `%5c`. |
209
- | R13 | A held value that also reaches the Harness: its Secret loaded by `harness.envFrom`, its key read by a `harness.env` `secretKeyRef`, or its literal set by `harness.env`. A kit Secret (`jr2-instance`, `jr2-held-*`, `jr2-instance-effector`, `jr2-effector-*`, `<sandbox>-effector`, `jr2-git-*`) named by either. |
228
+ | # | Refused |
229
+ | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
230
+ | R1 | A known model key (`MODEL_KEYS`) in `harness.env`, or as a key of a Secret or ConfigMap `harness.envFrom` names. |
231
+ | R2 | A held-secret name that is `JR2_*` or a proxy or trust variable. |
232
+ | R3 | One name held twice, or held and set by `harness.env`. |
233
+ | R4 | Not exactly one of `value`/`valueFrom`; an empty value; a byte outside `0x20`–`0x7E`; leading or trailing space. |
234
+ | R5 | A claimed entry with no `hosts` where no default exists; a wildcard, userinfo, a non-`https` scheme, a bare-host path, a bad port; two secrets on one host in one header. |
235
+ | R6 | A `headers` entry that is not a lowercase token, or that frames or routes the request; a duplicate. |
236
+ | R7 | `provider.apiKey` with a non-`https` `baseUrl`; a non-`https` catalog `baseUrl`. |
237
+ | R8 | A catalog key that is not a known provider, or that is the custom provider's id. |
238
+ | R9 | Explicit `hosts` on a known model key that leave out where a carried Agent's provider sends its calls. |
239
+ | R10 | A `secretKeyRef` whose Secret or key does not exist. |
240
+ | R11 | While a secret is held: `harness.env` sets a proxy or trust variable. |
241
+ | R12 | A `paths` entry that does not start with `/`, or holds `..`, `;`, `\`, `%2e`, `%2f` or `%5c`. |
242
+ | R13 | A held value that also reaches the Harness: its Secret loaded by `harness.envFrom`, its key read by a `harness.env` `secretKeyRef`, or its literal set by `harness.env`. A kit Secret (`jr2-instance`, `jr2-held-*`, `jr2-instance-effector`, `jr2-effector-*`, `<sandbox>-effector`, `jr2-git-*`, and each pod's credentials, `jr2-instance-harness-token` and `<sandbox>-token`) named by either. |
243
+ | R14 | A held secret's name that is also a key of a Secret that `harness.envFrom` names. The message names the Secret, the key and the held secret. |
244
+
245
+ R14 exists because the Stand-ins are files, not env: no Stand-in hides such a key, so its real value would reach the
246
+ Harness's env, and the Harness uses a held secret but never holds it. `up` already reads each `envFrom` Secret to
247
+ check that it exists (ADR-0019).
210
248
 
211
249
  Each message names the config path, never a value, and says what to write instead. A SigV4 or ADC credential in
212
250
  `harness.env` is warned about, not refused. A known model key with no `hosts` is bound to the host its provider's
@@ -309,16 +347,18 @@ decision generalizes that container. It holds every credential the Harness in it
309
347
 
310
348
  ## Consequences
311
349
 
312
- - **What this closes, and what it does not.** The Agent cannot take a model key or the Sandbox token out of the pod. It
313
- can still **spend** a key through the Custodian while its pod lives, on any path `paths` allows. A key with admin
314
- rights can mint a new key, and a minted key is one the Agent can take away: use a key with no admin rights, and narrow
315
- `paths`. The User Container shares the pod's loopback, so a human in it can spend a key the same way. That human
316
- cannot read one either.
350
+ - **What this closes, and what it does not.** The Agent cannot take a model key or a Sandbox's token out of the pod. The
351
+ Harness spends the model keys for the Turns. Workspace code can still **spend** a key through the Custodian while its
352
+ pod lives, on any path `paths` allows, when the `workspace()` grants that key. A key with admin rights can mint a new
353
+ key, and a minted key is one the Agent can take away: use a key with no admin rights, and narrow `paths`. The User
354
+ Container shares the pod's loopback, so a human in it can spend a key the same way. That human cannot read one either.
317
355
  - **Response echo.** Responses pass unchanged. An upstream that echoes the full credential in a response hands it to the
318
356
  Agent. Providers mask keys in errors; jr2 does not scrub responses.
319
357
  - **Bypass.** A client that ignores `HTTPS_PROXY` goes direct and sends the Stand-in, which is worth nothing. It is not
320
- blocked: egress control stays the cluster's decision (ADR-0058's open egress gap). The same holds for the control
321
- plane: a caller that skips the Custodian and sends the Stand-in to the Orchestrator gets 401 on every route
358
+ blocked: egress control stays the cluster's decision (ADR-0058's open egress gap), except for the cloud metadata
359
+ addresses, which no Harness pod reaches
360
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The same holds for the
361
+ control plane: a caller that skips the Custodian and sends the Stand-in to the Orchestrator gets 401 on every route
322
362
  (`auth.test.ts`).
323
363
  - **Clients that do not trust the jr2 CA** (Java, a pinned client) fail TLS to a bound host. They fail closed and send
324
364
  nothing. Unbound hosts still work for them.
@@ -338,7 +378,7 @@ decision generalizes that container. It holds every credential the Harness in it
338
378
  - **Rotation of a value** reaches a live Sandbox only when its pod is replaced.
339
379
  - **The walk reads definitions, not Turns.** A pod's model keys come from the models its Agents' definitions name. A
340
380
  Turn whose invocation overrides the model with another provider's (`AgentTurnInput.model`) finds no key for that
341
- provider in its pod: the Harness container gets a Stand-in only for the names in its own pod's manifest, so that
381
+ provider in its pod: the Harness process holds a Stand-in only for the names in its own pod's manifest, so that
342
382
  provider's key variable is not set at all. The call then has no key (a catalog provider) or sends the custom
343
383
  provider's placeholder `unused`, and it fails. This holds in a Workspace and in the Instance Harness alike. The fix is
344
384
  a definition that names the provider; in a Workspace, the key under the `workspace()`'s `secrets` also works. The
@@ -346,16 +386,19 @@ decision generalizes that container. It holds every credential the Harness in it
346
386
  - **TLS-only bindings.** A key for a plain-HTTP endpoint is refused (R7). A LAN `llama-server --api-key` needs TLS and
347
387
  its CA in `caBundle`, or no key.
348
388
  - **No proxy chaining.** The Custodian cannot forward to a corporate proxy. R11 says so rather than fail silently.
349
- - **Two Harness env shapes.** With no held secret in it, a pod gets the Custodian's address and the token's Stand-in and
350
- no proxy, and its HTTPS goes direct. The Custodian runs in both shapes, because it carries the Menu.
351
- - **The dial guard adds no confinement while egress is open.** The Agent can dial those addresses itself. The guard
352
- matters when a cluster closes egress to everything but the Custodian: then the Custodian is not a way around it.
389
+ - **Two Harness env shapes.** With no held secret in it, a pod gets the Custodian's address and no proxy, and its HTTPS
390
+ goes direct. The Harness process holds the control Stand-in in both shapes. The Custodian runs in both shapes, because
391
+ it carries the Menu.
392
+ - **The dial guard adds no confinement while egress is open.** The Agent can dial those addresses itself, except the
393
+ cloud metadata addresses, which jr2's egress policy closes on a CNI that enforces it (ADR-0076). The guard matters
394
+ when a cluster closes egress to everything but the Custodian: then the Custodian is not a way around it.
353
395
  - **One container holds two kinds of credential.** A fault in Envoy's TLS or HTTP parsing is a fault in the container
354
- that holds both the Sandbox token and the model keys. The branch with a separate Adapter keeps them apart.
355
- - **The Harness container now speaks the Orchestrator's two agent routes.** The Agent could always make those calls (the
356
- Adapter would forward any MCP call it asked for); now the Harness makes them itself. Nothing is judged in the pod.
357
- - **`jr2-upload-pack` carries the Stand-in as a constant.** The User Container gets no env (ADR-0005), and asks like any
358
- other seat. A test pins the Go literal to `standIn("JR2_SANDBOX_TOKEN")`.
396
+ that holds both the Sandbox's tokens and the model keys. The branch with a separate Adapter keeps them apart.
397
+ - **The Harness process speaks the Orchestrator's agent routes itself.** Nothing is judged in the pod. No process that
398
+ the Harness confines holds the control Stand-in, so none can make those calls (ADR-0076).
399
+ - **`jr2-upload-pack` carries the fetch Stand-in as a constant.** The User Container gets no env (ADR-0005), and asks
400
+ like any other seat. A test pins the Go literal to `standIn("JR2_SANDBOX_TOKEN")`. The Custodian swaps it on the fetch
401
+ route alone, so the constant reaches no agent route.
359
402
  - **The Custodian's image is a Pinned image.** It is deployed and never built, which
360
403
  [ADR-0038](0038-jr2-up-builds-every-image-it-deploys.md) now states. `kitRegistry` re-homes it and `jr2 kit push`
361
404
  mirrors it ([ADR-0044](0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md)). The image sweep never
@@ -34,16 +34,22 @@ whose processes are gone, with no way to know why.
34
34
  than the Agent's own pick changes them inside the Turn, the Harness appends the Allowed picks again at the next step
35
35
  boundary, in a `<jr2-turn>` block of their own: "The workflow's state changed since you last read the Allowed picks.
36
36
  Allowed now: …" ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). It is
37
- appended, never a rewrite of the first, so the cache argument above holds for it too. It carries no notice: notices
38
- are for the next Turn in their scope. It carries a pick's briefing when the restatement opens that pick.
37
+ appended, never a rewrite of the first, so the cache argument above holds for it too. It carries a pick's briefing
38
+ when the restatement opens that pick. The same read at a step boundary returns the conversation's pending notices, and
39
+ the Harness appends them in the same way (below).
39
40
  - **The Turn part comes last, because a model obeys what it read last.** Measured on the home-lab vLLM model: with a
40
41
  prompt that argued for a pick the Turn did not allow, the Allowed picks placed ahead of the prompt were obeyed 0/10
41
42
  (thinking off) and 5/10 (thinking on); placed after it, 10/10 both. Wording made no difference. The author's task
42
43
  still reads first, as it would with no jr2.
43
44
  - **Always on.** No author can turn it off: every fact in it passes the rule above, so removing one can only make the
44
45
  Agent act worse. An `instructions` that repeats a fact is harmless.
45
- - **A notice is delivered once**, on the next admission in its scope that takes it, then cleared. That is the next Turn,
46
- or, for a guard kill that the Agent of an open Turn was not told, that Turn's nudge or wake (below).
46
+ - **A notice is heard once by each member of its audience.** A member takes it at its next admission, or at a step
47
+ boundary of an open Turn: the surface read of the open Turn returns the conversation's pending notices, and the
48
+ Harness appends them, as it appends a changed Allowed-picks list
49
+ ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md), ADR-0002). For a guard
50
+ kill that the Agent of an open Turn was not told, a member also takes it at that Turn's nudge or wake (below). A
51
+ conversation is an Instance ID ([ADR-0057](0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md)): a fresh
52
+ Turn, a runaway reroll and a burned conversation's successor each start a new one.
47
53
  - **Conversation scope** ("this conversation is new; earlier context is gone") goes to that Agent's next Turn. So does
48
54
  a pick's answer that never reached its call
49
55
  ([ADR-0024](0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md)): the route left the Agent's state, and
@@ -53,18 +59,31 @@ whose processes are gone, with no way to know why.
53
59
  and that stopped it" (`stopped`), with an earlier link's reply when one came. No pick-answer notice says the work
54
60
  ran to its end. A fresh picking Turn has no such conversation, and its answer is no notice.
55
61
  - **Workspace scope** ("the Sandbox's processes were killed at its memory limit during `coder`'s Turn; `/dev/shm` was
56
- cleared. Anything that was running in the Sandbox at that time is gone.") goes to the next admission of any Agent in
57
- that Workspace that takes it. One kill is one notice, however many Turns saw it. Only a kill read on a stream while
58
- its wait is live names the Turn whose Submission ran. Both forms end with the whole Sandbox, because every process
59
- in it died, a shared Workspace's too, and in the past tense, because the Agent may hear it after processes started
60
- again. A kill read from the pod's list names no Turn and says nothing of when or whose: "the Sandbox's processes
61
- were killed at its memory limit (peak X of Y); /dev/shm was cleared. Anything that was running in the Sandbox at
62
- that time is gone." The list cannot say whose Turn a kill ended: the kill may land after a state exits, or end an
63
- attach, a sync or a command. A kernel kill read from the Sandbox's last end (below) says the same, with the limit
64
- and no peak: "the Sandbox's processes were killed at its memory limit of Y; …". A Menu-only Agent outside a
65
- Workspace receives conversation notices only.
62
+ cleared. Anything that was running in the Sandbox at that time is gone.") goes to an **audience** that the ledger
63
+ fixes when it raises the notice. The rule is: an Agent hears a kill if it was there for it.
64
+ 1. Each conversation admitted in that Workspace before the kill that can still take a Turn: the conversation of an
65
+ open Turn, which hears it at its next step boundary, or a continued conversation, which hears it at its next
66
+ Turn. A fresh Turn that already ended is not in the audience.
67
+ 2. When the kill cut a specific Turn — a kill read on that Turn's stream names it — that Agent's next Turn, fresh or
68
+ continued: the retry of the broken work.
69
+ 3. Minus each conversation whose own `bash` answer returned the kill's verdict (`told`, below). That answer told it
70
+ already.
71
+
72
+ Each member hears the notice once. The notice retires when every member has heard it. A conversation that starts
73
+ after the kill does not hear it, except the retry of (2). So a Pool of coders in one Workspace
74
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)) all hear a kill that cut their work.
75
+ Only a kill read on a stream while its wait is live names the Turn whose Submission ran. Both forms end with the
76
+ whole Sandbox, because every process in it died, a shared Workspace's too, and in the past tense, because the Agent
77
+ may hear it after processes started again. A kill read from the pod's list names no Turn and says nothing of when or
78
+ whose: "the Sandbox's processes were killed at its memory limit (peak X of Y); /dev/shm was cleared. Anything that
79
+ was running in the Sandbox at that time is gone." The list cannot say whose Turn a kill ended: the kill may land
80
+ after a state exits, or end an attach, a sync or a command. A kernel kill read from the Sandbox's last end (below)
81
+ says the same, with the limit and no peak: "the Sandbox's processes were killed at its memory limit of Y; …". A
82
+ Menu-only Agent outside a Workspace receives conversation notices only.
83
+
66
84
  - A notice never replaces a fault. The Machine still receives `agent.fault` and decides (ADR-0016); the notice makes
67
85
  sure the next Agent is not working blind.
86
+
68
87
  - **The Orchestrator is the only keeper of notices**: pending notices are plain data in the run's persisted state
69
88
  (ADR-0007). It sees kernel kills and fresh conversations itself. A kernel kill (ADR-0061, layer 5) has two paths,
70
89
  under one source, the Workspace and the time the Harness container ended. A Turn that the kill ended faults on it, and
@@ -89,13 +108,15 @@ whose processes are gone, with no way to know why.
89
108
  the only limit. A read that never left the Orchestrator, or a draining Harness's 503, is sent again in that time. A
90
109
  read that fails or times out is none, and the next admission reads again: the list keeps the kill. The Orchestrator
91
110
  logs one line when the reads from a Harness start to fail. It logs no line when a read works again, and it logs the
92
- failure again only when a read fails after a read that worked. The Agent's uid can write the file, as it can the
93
- conversation records: it can change only what its own Workspace is told. So a `peak` or a `limit` that is not a size
94
- in the guard's form, or is long, is left out and logged, on the Harness's read of the file and on the Orchestrator's
95
- read of the list and of a stream. The kill is kept, so a real kill is never lost, and its notice says no sizes: "the
96
- Sandbox's processes were killed at its memory limit; /dev/shm was cleared. …". Only a kill with no id or no time, or
97
- one outside the bounds below, is dropped. A stream event from an older Harness has no id; its source is then its
98
- place on the stream, exactly as the Orchestrator raised it before.
111
+ failure again only when a read fails after a read that worked. The file is beside the Harness's conversations, which
112
+ no confined child can reach
113
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)). The bounds stay as a
114
+ second layer: a `peak` or a `limit` that is not a size in the guard's form, or is long, is left out and logged, on
115
+ the Harness's read of the file and on the Orchestrator's read of the list and of a stream. The kill is kept, so a
116
+ real kill is never lost, and its notice says no sizes: "the Sandbox's processes were killed at its memory limit;
117
+ /dev/shm was cleared. …". Only a kill with no id or no time, or one outside the bounds below, is dropped. A stream
118
+ event from an older Harness has no id; its source is then its place on the stream, exactly as the Orchestrator
119
+ raised it before.
99
120
 
100
121
  Both paths raise the notice with the same source, the Workspace and the kill's id. The ledger keeps every source it
101
122
  raised (`seen`) in the run's persisted state. So a kill read on both paths, or on two streams, is one notice. Each
@@ -107,62 +128,64 @@ whose processes are gone, with no way to know why.
107
128
  restart of the Orchestrator raises no kill twice. A restart of the Harness container keeps the file, which is on the
108
129
  pod's emptyDir with its conversations (ADR-0031). A lost pod takes the file with it, but then the Workspace is lost
109
130
  (ADR-0021). The list keeps the newest 16 kills, each with at most 32 conversations in `told`, and the Orchestrator
110
- reads at most 256 KiB of it. The Agent's uid can write the file, so the two sides hold the same bounds: an `id` and an
111
- `at` of at most 64, an `agent` and an `instanceId` of at most 128, and an `at` that parses as a time. A kill outside
112
- them is dropped, and a `told` entry outside them is left out. The Harness counts the bytes of the JSON text, and the
113
- Orchestrator counts characters, which are never more, so the Harness keeps no kill that the Orchestrator drops. The
114
- Harness reads at most the last 256 KiB of the file. With these bounds the largest list is 149,259 bytes. When more
115
- come between two admissions, the oldest is lost, and a newer kill tells the same thing. When more than 16 come in one
116
- Turn, the Turn's mark leaves the list too, and every kill in the list then counts as after the mark; a nudge or a wake
117
- of that Turn may then carry a kill from before it. The Instance Harness keeps no kill, because it holds no Workspace.
131
+ reads at most 256 KiB of it. The two sides hold the same bounds: an `id` and an `at` of at most 64, an `agent` and an
132
+ `instanceId` of at most 128, and an `at` that parses as a time. A kill outside them is dropped, and a `told` entry
133
+ outside them is left out. The Harness counts the bytes of the JSON text, and the Orchestrator counts characters, which
134
+ are never more, so the Harness keeps no kill that the Orchestrator drops. The Harness reads at most the last 256 KiB
135
+ of the file. With these bounds the largest list is 149,259 bytes. When more come between two admissions, the oldest is
136
+ lost, and a newer kill tells the same thing. When more than 16 come in one Turn, the Turn's mark leaves the list too,
137
+ and every kill in the list then counts as after the mark; a nudge or a wake of that Turn may then carry a kill from
138
+ before it. The Instance Harness keeps no kill, because it holds no Workspace.
118
139
 
119
140
  A notice counts as delivered when its admission is ledgered, so a restart before that point delivers it again and a
120
- restart after it does not. Delivered is not yet heard: if the Orchestrator ends the Turn before the Harness reads its
121
- surface, the Harness never prompts it (ADR-0026), so the Orchestrator raises that admission's notices again. This
122
- holds for a nudge and a wake too: each one must have its surface read again before its notices count as heard. An
123
- admission that fails leaves the earlier admission's notices heard. A nudge and a wake re-prompt the Turn within
124
- itself, so they take no notice of the kind a Turn takes. They take only the guard kills of the list that come after
125
- the newest kill whose `told` names their conversation. That `bash` answer told the Agent that all its processes died,
126
- so it covers every kill up to it. They also take no kill from before the Turn started. Such a kill would tell the
127
- Agent that processes it started in this Turn died. The Turn's first read of the list sets its mark: the id of the
128
- newest kill it saw, or "none". The mark is ledgered with every admission of the Turn, and a restore reads it back, so
129
- a nudge or a wake takes only kills after it. Only when the first read failed is the Turn's start the mark, ledgered
130
- the same way, with one second allowed for a pod clock that runs behind. A read that failed is "could not tell", never
131
- an empty list, so it sets no mark. The kills they do not take stay pending for the next Turn. The re-admission after a
132
- Harness restart cut the Turn continues the same Turn, so it takes every notice in scope except the guard kills that
133
- this rule leaves out. When its read could not tell, it takes no guard kill at all, as a nudge then takes none: it
134
- cannot know which kills its own `bash` reported. The notices that the Turn's admissions carried since the Harness last
135
- read the Turn's surface are pending again first, each with the source it was raised with, and this re-admission
136
- carries them whatever else its scope leaves out. A notice with no source merges only into a pending one with no
137
- source, so an equal notice of another kill never takes its place. When such an unread admission carried the Turn's
138
- task, the task follows the re-admit text. When the rebuilt Harness lost the conversation, nothing of the Turn is left:
139
- every notice that the Turn's admissions carried is pending again whatever the surface reads said, the re-admission
140
- takes every notice in scope, as a Turn's admission does, and the Turn's task follows the re-admit text. It says once
141
- that the conversation is new. A restore drops the prompt from a Turn's input, so the task rides the ledgered admission
142
- too; a lost conversation with no task to send faults the Turn instead. Whether the Harness still holds the
143
- conversation is asked again for up to 30 seconds while the new container does not listen yet or drains, and only then
144
- is the answer taken as "held". The continue path asks the same way, so a continued Turn after a restart can wait up to
145
- 30 seconds before its admission. A Harness in CrashLoopBackOff can take longer than that: the answer is then "held",
146
- and it can be wrong. So every admission to a conversation that the Harness was taken to keep says so with
147
- `kept: true`: the re-admission after a cut, a continued Turn, and a nudge or a wake. A Harness that keeps no such
148
- conversation refuses it with 404 and `kept: false` in the body, and seats none. The Orchestrator takes only that 404
149
- as the refusal; another 404 is a failed admission. Without the refusal the Harness would seat a new conversation, and
150
- a model on an empty context would read "continue your task" with no task. On the refusal the Turn takes the path of a
151
- lost conversation. The re-admission is admitted as a Turn, with the task after the re-admit text and the notice that
152
- the conversation is new, or faults when it has no task to send. A continued Turn is told that its conversation is new,
153
- and is admitted again. A nudge or a wake is admitted again as a Turn: its prompt is the task alone, because nothing
154
- was cut, with the notice that the conversation is new and every notice in scope. Every notice that the Turn's
155
- admissions carried is pending again first, as for a lost conversation after a cut. With no task to send, the Turn
156
- faults. The Instance Harness's restart is on no watch, so this refusal is the only way a nudge or a wake learns of it.
157
- What the Turn's admissions carried, and whether the Harness read their surface, is kept in the Orchestrator's memory
158
- alone: after a restore of the Orchestrator before a surface read, nothing is raised again. A fresh Turn's "this
159
- conversation is new", which rides an admission alone, is carried again by the next re-admission when the Harness has
160
- not read the one before.
141
+ restart after it does not. A notice at a step boundary has no admission. The surface read of the open Turn reserves it
142
+ for that member, and it counts as delivered, and heard, when the Harness acknowledges the append. When the Turn ends
143
+ with no acknowledgment, the reservation is released, and the notice is pending again for that member. A reservation
144
+ lives in the Orchestrator's memory only, so a restart forgets it: the notice is pending again, and the next read or
145
+ admission carries it. So a conversation can hear a notice twice after a crash, as for an admission. Delivered is not
146
+ yet heard: if the Orchestrator ends the Turn before the Harness reads its surface, the Harness never prompts it
147
+ (ADR-0026), so the Orchestrator raises that admission's notices again. This holds for a nudge and a wake too: each one
148
+ must have its surface read again before its notices count as heard. An admission that fails leaves the earlier
149
+ admission's notices heard. A nudge and a wake re-prompt the Turn within itself. Of the notices, they take only the
150
+ guard kills of the list that come after the newest kill whose `told` names their conversation. That `bash` answer told
151
+ the Agent that all its processes died, so it covers every kill up to it. They also take no kill from before the Turn
152
+ started. Such a kill would tell the Agent that processes it started in this Turn died. The Turn's first read of the
153
+ list sets its mark: the id of the newest kill it saw, or "none". The mark is ledgered with every admission of the
154
+ Turn, and a restore reads it back, so a nudge or a wake takes only kills after it. Only when the first read failed is
155
+ the Turn's start the mark, ledgered the same way, with one second allowed for a pod clock that runs behind. A read
156
+ that failed is "could not tell", never an empty list, so it sets no mark. The kills they do not take stay pending for
157
+ the next Turn. The re-admission after a Harness restart cut the Turn continues the same Turn, so it takes every notice
158
+ in scope except the guard kills that this rule leaves out. When its read could not tell, it takes no guard kill at
159
+ all, as a nudge then takes none: it cannot know which kills its own `bash` reported. The notices that the Turn's
160
+ admissions carried since the Harness last read the Turn's surface are pending again first, each with the source it was
161
+ raised with, and this re-admission carries them whatever else its scope leaves out. A notice with no source merges
162
+ only into a pending one with no source, so an equal notice of another kill never takes its place. When such an unread
163
+ admission carried the Turn's task, the task follows the re-admit text. When the rebuilt Harness lost the conversation,
164
+ nothing of the Turn is left: every notice that the Turn's admissions carried is pending again whatever the surface
165
+ reads said, the re-admission takes every notice in scope, as a Turn's admission does, and the Turn's task follows the
166
+ re-admit text. It says once that the conversation is new. A restore drops the prompt from a Turn's input, so the task
167
+ rides the ledgered admission too; a lost conversation with no task to send faults the Turn instead. Whether the
168
+ Harness still holds the conversation is asked again for up to 30 seconds while the new container does not listen yet
169
+ or drains, and only then is the answer taken as "held". The continue path asks the same way, so a continued Turn after
170
+ a restart can wait up to 30 seconds before its admission. A Harness in CrashLoopBackOff can take longer than that: the
171
+ answer is then "held", and it can be wrong. So every admission to a conversation that the Harness was taken to keep
172
+ says so with `kept: true`: the re-admission after a cut, a continued Turn, and a nudge or a wake. A Harness that keeps
173
+ no such conversation refuses it with 404 and `kept: false` in the body, and seats none. The Orchestrator takes only
174
+ that 404 as the refusal; another 404 is a failed admission. Without the refusal the Harness would seat a new
175
+ conversation, and a model on an empty context would read "continue your task" with no task. On the refusal the Turn
176
+ takes the path of a lost conversation. The re-admission is admitted as a Turn, with the task after the re-admit text
177
+ and the notice that the conversation is new, or faults when it has no task to send. A continued Turn is told that its
178
+ conversation is new, and is admitted again. A nudge or a wake is admitted again as a Turn: its prompt is the task
179
+ alone, because nothing was cut, with the notice that the conversation is new and every notice in scope. Every notice
180
+ that the Turn's admissions carried is pending again first, as for a lost conversation after a cut. With no task to
181
+ send, the Turn faults. The Instance Harness's restart is on no watch, so this refusal is the only way a nudge or a
182
+ wake learns of it. What the Turn's admissions carried, and whether the Harness read their surface, is kept in the
183
+ Orchestrator's memory alone: after a restore of the Orchestrator before a surface read, nothing is raised again. A
184
+ fresh Turn's "this conversation is new", which rides an admission alone, is carried again by the next re-admission
185
+ when the Harness has not read the one before.
161
186
 
162
- A continued Turn of the same conversation is told, as a notice, a kill that its own `bash` call answered in an earlier
163
- Turn. The Agent reads that kill twice, which is true and costs little. The `told` rule could later extend to the first
164
- admission of a continued Turn too; it does not now, because a fresh Turn's conversation holds no earlier answer, and
165
- the rule would then need the conversation in the ledger's scope.
187
+ A conversation whose own `bash` call answered a kill is not in that kill's audience, so it does not hear the kill
188
+ again as a notice, at its next Turn or at a step boundary.
166
189
 
167
190
  - **The Harness writes every word.** The admit body carries typed notices
168
191
  (`notices: [{ kind: "memory-limit", scope, agent?, limit?, peak? } | { kind: "conversation-new", reason } | { kind: "pick-answer", pick, cut, answer?, stopped? } …]`,
@@ -209,4 +232,8 @@ whose processes are gone, with no way to know why.
209
232
  - The Briefing is how a Turn learns its Allowed picks: the tools block is the whole Menu, fixed for a conversation so it
210
233
  does not break the cache
211
234
  ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)).
235
+ - A Workspace notice reaches each conversation that was there for the kill, and the retry of the Turn that it cut; an
236
+ open Turn hears it at its next step boundary, not only at its next admission (ADR-0002,
237
+ [ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)). So a memory kill reaches
238
+ every coder of a Pool that shares one Workspace (ADR-0075).
212
239
  - GLOSSARY.md gains **Briefing**.