@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
package/src/run-host.ts CHANGED
@@ -34,7 +34,7 @@ import { SENDER_KEY, type EventDef, type EventSemantics, type Sender } from "@jr
34
34
  import { partiesAt, seatOf, senderText } from "./party.ts";
35
35
  import { placeAnswers, type AnswerSeat } from "./pick.ts";
36
36
  import { inputSchemaOf } from "./vocabulary.ts";
37
- import type { EchoEvent, EchoStatusChild, LiveConversation, PickAnswer } from "./wire.ts";
37
+ import type { EchoEvent, EchoStatusChild, LiveConversation, Notice, PickAnswer } from "./wire.ts";
38
38
  import { answerOf, childErrorOf, replyOf, type Hold } from "./holds.ts";
39
39
  import { PickCauses } from "./cause.ts";
40
40
  import { restOf, runningBelow } from "./rest.ts";
@@ -404,7 +404,7 @@ type Sending = {
404
404
 
405
405
  /**
406
406
  * Whose pick started a child (ADR-0067): the Agent's slot, and the invoke id of the Agent's state the
407
- * pick was made at, which tells one slot's two regions apart. `invoke` is absent when the event that
407
+ * pick was made at, which tells apart two states that invoke one slot, one after the other. `invoke` is absent when the event that
408
408
  * started the child named the Agent and not where (a pick taken outside its own send).
409
409
  */
410
410
  type StartedBy = { agent: string; invoke?: string };
@@ -536,8 +536,17 @@ export type RunStatus = RunRecord & {
536
536
  * direction, read off the caller's own ledger rows. A foreign dispatch's run is a run of the
537
537
  * Orchestrator its target names. Absent when it started none. */
538
538
  dispatched?: Dispatched[];
539
+ /** The Worktrees this run's cuts made, oldest first (ADR-0075), read off its ledger's settled
540
+ * `ready` rows, so an Agent's `git switch` does not change them. A re-cut of one path and branch
541
+ * is one entry. Behind the token, like `context`: a branch name is the workflow's data. Absent
542
+ * when the run cut none. */
543
+ worktrees?: RunWorktree[];
539
544
  };
540
545
 
546
+ /** One Worktree a `worktree()` Effect of this run cut (ADR-0075): its Repo Slot, its path, and the
547
+ * branch of the cut. `branch` is `null` for a detached Worktree. */
548
+ export type RunWorktree = { slot: string; path: string; branch: string | null };
549
+
541
550
  /** One run a `dispatch` Effect of this run started (ADR-0071): its slot, the run, and when the
542
551
  * ledger row was written. */
543
552
  export type Dispatched = { slot: string; run: string; at: string };
@@ -668,6 +677,9 @@ export type AgentSurfaceView = {
668
677
  instanceId: string;
669
678
  runId: string;
670
679
  sandbox?: string;
680
+ /** The slot this surface belongs to (ADR-0076): its Agent's actor path and agent name — the slot
681
+ * the Harness must state to speak here. Absent on a registration no pod hosts. */
682
+ slot?: string;
671
683
  menu: SurfaceEvent[];
672
684
  allowed: string[];
673
685
  };
@@ -961,8 +973,10 @@ type RunBlob = {
961
973
  * The pending NOTICES (ADR-0062): what the next Turn in a scope must be told — a memory kill in
962
974
  * its Workspace, a conversation that is new. The Orchestrator is their only keeper, so they ride
963
975
  * this blob beside the two ledgers, for the same reasons: the run's exact lifetime, and the
964
- * snapshot's atomicity. A notice leaves it in the save that ledgers the admission that carried
965
- * it, so a restart before that save delivers it again and a restart after does not.
976
+ * snapshot's atomicity. A notice is cleared for a conversation in the save that ledgers the
977
+ * admission that carried it there, so a restart before that save delivers it again and a restart
978
+ * after does not. A conversation notice then leaves it; a Workspace notice stays, with the
979
+ * conversations that heard it, for the Workspace's other conversations.
966
980
  */
967
981
  notices?: NoticeLedgerState;
968
982
  /**
@@ -1740,6 +1754,7 @@ export class RunHost {
1740
1754
  instanceId,
1741
1755
  runId: reg.runId,
1742
1756
  sandbox: reg.sandbox,
1757
+ ...(reg.slot !== undefined ? { slot: reg.slot } : {}),
1743
1758
  menu: [...menuOf(reg).values()].map((def) => ({
1744
1759
  name: def.name,
1745
1760
  description: def.description,
@@ -1757,6 +1772,19 @@ export class RunHost {
1757
1772
  this.table.lookup(agentAddress(instanceId))?.served?.();
1758
1773
  }
1759
1774
 
1775
+ /** The notices this Turn's conversation hears at a step boundary (ADR-0062): its pending notices
1776
+ * in their audience, reserved for this read until the Harness acknowledges them
1777
+ * ({@link noticesHeard}) or the Turn ends. Empty for a registration that hears none. */
1778
+ async boundaryNotices(instanceId: string): Promise<Array<{ id: string; notice: Notice }>> {
1779
+ return (await this.table.lookup(agentAddress(instanceId))?.boundaryNotices?.()) ?? [];
1780
+ }
1781
+
1782
+ /** The Harness appended these step-boundary notices to the Turn (ADR-0062): they are heard in its
1783
+ * conversation, ledgered in one save. Answers how many were this conversation's reservations. */
1784
+ noticesHeard(instanceId: string, ids: readonly string[]): number {
1785
+ return this.table.lookup(agentAddress(instanceId))?.noticesHeard?.(ids) ?? 0;
1786
+ }
1787
+
1760
1788
  /**
1761
1789
  * Deliver one event from an Agent's Harness (`POST /agents/:iid/events` — ADR-0013). Validation
1762
1790
  * and delivery are the table's; this only agent-scopes the address and mints the receipt. (A
@@ -1890,6 +1918,7 @@ export class RunHost {
1890
1918
  agent: invoking.sender?.id ?? "",
1891
1919
  since,
1892
1920
  ...(invoking.sandbox !== undefined ? { sandbox: invoking.sandbox } : {}),
1921
+ ...(invoking.slot !== undefined ? { slot: invoking.slot } : {}),
1893
1922
  ...(invoking.invokeId !== undefined ? { invokeId: invoking.invokeId } : {}),
1894
1923
  ...(conversation !== undefined ? { conversation } : {}),
1895
1924
  ...(sending && sending.chain.length > 0 ? { chain: [...sending.chain] } : {}),
@@ -2073,8 +2102,8 @@ export class RunHost {
2073
2102
  * its own send, else the one outstanding. On a transition below no Agent's state (`null`): the
2074
2103
  * event that took the transition names the Agent — the sender of a pick, or the Agent whose pick
2075
2104
  * started the child whose done or error event it is — and that Agent's pick is the one shaped, and
2076
- * no other; none when that Agent has no pick being answered. When one slot is invoked in two
2077
- * regions, the event names an invoke too, and only the pick at that invoke is shaped: the sending
2105
+ * no other; none when that Agent has no pick being answered. When one slot is invoked by two
2106
+ * states, the event names an invoke too, and only the pick at that invoke is shaped: the sending
2078
2107
  * pick, or the pick at the invoke whose pick started the child; none when that pick is not being
2079
2108
  * answered (its Turn ended, and its child runs on). An event that names no Agent (a timer,
2080
2109
  * a Gate's answer, an outside send) shapes the pick in its own send. Outside one, it shapes the one
@@ -2106,7 +2135,7 @@ export class RunHost {
2106
2135
  const { agent } = named;
2107
2136
  const own = sending && sending.reg.sender?.id === agent ? sending : undefined;
2108
2137
  // That Agent's picks being answered: the one in its own send, and the ones outstanding at
2109
- // its other invokes (one slot invoked in two regions). The event names the invoke of the
2138
+ // its other invokes (one slot invoked by two states). The event names the invoke of the
2110
2139
  // pick that sent it or started its child, and only the pick at that invoke is shaped; none
2111
2140
  // when that pick is not being answered, and each other pick keeps its default answer.
2112
2141
  const held = open.filter((hold) => hold.agent === agent && hold.instanceId !== own?.reg.id);
@@ -2177,10 +2206,16 @@ export class RunHost {
2177
2206
  return this.parked.has(`${instanceId} ${deliveryId}`);
2178
2207
  }
2179
2208
 
2180
- /** The Sandbox that may read a delivery's answer (ADR-0013), or undefined for an unknown one. */
2181
- deliveryScope(instanceId: string, deliveryId: string): { sandbox?: string } | undefined {
2209
+ /** The pod and slot that may read a delivery's answer (ADR-0013, ADR-0076), or undefined for an
2210
+ * unknown one. */
2211
+ deliveryScope(instanceId: string, deliveryId: string): { sandbox?: string; slot?: string } | undefined {
2182
2212
  const found = this.holdAt(instanceId, deliveryId);
2183
- return found && { ...(found.hold.sandbox !== undefined ? { sandbox: found.hold.sandbox } : {}) };
2213
+ return (
2214
+ found && {
2215
+ ...(found.hold.sandbox !== undefined ? { sandbox: found.hold.sandbox } : {}),
2216
+ ...(found.hold.slot !== undefined ? { slot: found.hold.slot } : {}),
2217
+ }
2218
+ );
2184
2219
  }
2185
2220
 
2186
2221
  /**
@@ -2507,6 +2542,7 @@ export class RunHost {
2507
2542
  ? { output: snap.output }
2508
2543
  : {}),
2509
2544
  ...dispatchedField(blob.effects ?? {}),
2545
+ ...worktreesField(blob.effects ?? {}),
2510
2546
  };
2511
2547
  }
2512
2548
 
@@ -2762,6 +2798,7 @@ export class RunHost {
2762
2798
  ? { output: snap.output }
2763
2799
  : {}),
2764
2800
  ...dispatchedField(run.effects),
2801
+ ...worktreesField(run.effects),
2765
2802
  };
2766
2803
  }
2767
2804
 
@@ -2830,10 +2867,20 @@ export class RunHost {
2830
2867
  // moment the Harness admits it, and the ledger hits the store in the same RunBlob save. An
2831
2868
  // admission arriving around stop/untrack still lands in `agents` but skips the save,
2832
2869
  // exactly like the persist scheduler's tracked-run guard.
2833
- recordAdmission: (instanceId, admission, delivered = []) => {
2870
+ recordAdmission: (instanceId, admission, delivered = [], taker, member) => {
2834
2871
  agents[instanceId] = admission;
2835
- // The notices this admission carried are delivered by THIS save (ADR-0062).
2836
- notices.delivered(delivered);
2872
+ // The notices this admission carried are delivered by THIS save, in the conversation it
2873
+ // rode (ADR-0062): a Workspace notice stays pending for the rest of its audience. And the
2874
+ // conversation is a member of its Workspace's audiences from now on, in the same save.
2875
+ notices.delivered(delivered, taker);
2876
+ if (member !== undefined && taker !== undefined) {
2877
+ notices.admitted(member.workspace, {
2878
+ taker,
2879
+ slot: member.slot,
2880
+ continued: member.continued,
2881
+ since: new Date().toISOString(),
2882
+ });
2883
+ }
2837
2884
  const run = this.runs.get(record.runId);
2838
2885
  if (run && run.agents === agents) this.persist(run);
2839
2886
  },
@@ -2845,6 +2892,9 @@ export class RunHost {
2845
2892
  // invoke would otherwise restore into the burned conversation.
2846
2893
  epochOf: (conversation) => epochs[conversation] ?? 0,
2847
2894
  bumpEpoch: (conversation, reason) => {
2895
+ // The burned conversation can take no Turn again: it leaves every audience (ADR-0062).
2896
+ const burned = epochs[conversation] ? `${conversation}/${epochs[conversation]}` : conversation;
2897
+ notices.forget(burned);
2848
2898
  epochs[conversation] = (epochs[conversation] ?? 0) + 1;
2849
2899
  // The next `continue` lands on a virgin conversation and is told so, with the fault's own
2850
2900
  // words (ADR-0062) — raised in the SAME save as the bump, so no restart can split them.
@@ -2854,18 +2904,36 @@ export class RunHost {
2854
2904
  },
2855
2905
  // The notices' other halves (ADR-0062). A raise is saved at the write, like the ledgers; a
2856
2906
  // take is a reservation in memory, delivered by `recordAdmission` or released on failure.
2857
- raiseNotice: (notice, to, source, older) => {
2858
- if (!notices.raise(notice, to, source, older)) return;
2907
+ raiseNotice: (notice, to, source, older, raiseOpts) => {
2908
+ if (!notices.raise(notice, to, source, older, raiseOpts)) return;
2859
2909
  const run = this.runs.get(record.runId);
2860
2910
  if (run && run.notices === notices) this.persist(run);
2861
2911
  },
2862
- restoreNotice: (notice, to, source) => {
2863
- if (!notices.restore(notice, to, source)) return;
2912
+ restoreNotice: (notice, to, source, taker) => {
2913
+ if (!notices.restore(notice, to, source, taker)) return;
2864
2914
  const run = this.runs.get(record.runId);
2865
2915
  if (run && run.notices === notices) this.persist(run);
2866
2916
  },
2867
2917
  takeNotices: (scope) => notices.take(scope),
2868
- releaseNotices: (ids) => notices.release(ids),
2918
+ releaseNotices: (ids, taker) => notices.release(ids, taker),
2919
+ peekNotices: (scope) => notices.peek(scope),
2920
+ acknowledgeNotices: (ids, taker) => {
2921
+ const heard = notices.acknowledge(ids, taker);
2922
+ const run = this.runs.get(record.runId);
2923
+ if (heard > 0 && run && run.notices === notices) this.persist(run);
2924
+ return heard;
2925
+ },
2926
+ noticesTold: (told) => {
2927
+ let changed = false;
2928
+ for (const { source, takers } of told) changed = notices.told(source, takers) || changed;
2929
+ const run = this.runs.get(record.runId);
2930
+ if (changed && run && run.notices === notices) this.persist(run);
2931
+ },
2932
+ noticeTurnEnded: (workspace, takers) => {
2933
+ if (!notices.ended(workspace, takers)) return;
2934
+ const run = this.runs.get(record.runId);
2935
+ if (run && run.notices === notices) this.persist(run);
2936
+ },
2869
2937
  // Absorbed-retry attempts go straight to the run's observers (SSE/CLI watch) — they are
2870
2938
  // feed events, not machine events (ADR-0016: the workflow sees only the terminal fault).
2871
2939
  telemetry: (event) => {
@@ -3331,8 +3399,6 @@ function atOrBelow(actor: AnyActorRef, ref: AnyActorRef): boolean {
3331
3399
  return false;
3332
3400
  }
3333
3401
 
3334
- /** A status's bound fields (ADR-0070): `bound`, and the hashes of the Charts bound, distinct and
3335
- * sorted. Both absent when the snapshot binds no Chart. */
3336
3402
  /** The runs a run's `dispatch` Effects started (ADR-0071), from its ledger: an intent row the
3337
3403
  * start's answer re-wrote with its run, a settled row whose output names one (`started`,
3338
3404
  * `settled`), and an aborted row that kept it. A row that never learned its run names none. */
@@ -3350,6 +3416,26 @@ function dispatchedField(effects: Record<string, EffectRow>): { dispatched?: Dis
3350
3416
  return out.length ? { dispatched: out } : {};
3351
3417
  }
3352
3418
 
3419
+ /** The Worktrees a run's cuts made (ADR-0075), from the settled `ready` rows of its ledger, in the
3420
+ * order of each Worktree's first cut. A cut also reads a Worktree, so one path and branch has many
3421
+ * rows and one entry. */
3422
+ function worktreesField(effects: Record<string, EffectRow>): { worktrees?: RunWorktree[] } {
3423
+ const rows = Object.values(effects).flatMap((row) =>
3424
+ row.state === "settled" && row.cut !== undefined && row.repo !== undefined
3425
+ ? [{ at: row.at, slot: row.repo, ...row.cut }]
3426
+ : [],
3427
+ );
3428
+ rows.sort((a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : 0));
3429
+ const out = new Map<string, RunWorktree>();
3430
+ for (const { slot, path, branch } of rows) {
3431
+ const key = JSON.stringify([path, branch]);
3432
+ if (!out.has(key)) out.set(key, { slot, path, branch });
3433
+ }
3434
+ return out.size ? { worktrees: [...out.values()] } : {};
3435
+ }
3436
+
3437
+ /** A status's bound fields (ADR-0070): `bound`, and the hashes of the Charts bound, distinct and
3438
+ * sorted. Both absent when the snapshot binds no Chart. */
3353
3439
  function boundFields(snap: unknown): { bound?: true; chartHashes?: string[] } {
3354
3440
  const hashes = [...new Set(boundChartRoutesOf(snap).map((route) => route.chartHash))].sort();
3355
3441
  return hashes.length ? { bound: true, chartHashes: hashes } : {};