@sema-agent/core 7.29.0 → 7.30.1

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 (158) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/dist/agents/child-model-seat.d.ts +59 -7
  3. package/dist/agents/child-model-seat.js +33 -7
  4. package/dist/agents/cross-session-judge.d.ts +27 -5
  5. package/dist/agents/cross-session-judge.js +23 -12
  6. package/dist/agents/list-agents-tool.d.ts +31 -6
  7. package/dist/agents/list-agents-tool.js +66 -14
  8. package/dist/agents/peer-admission.d.ts +20 -8
  9. package/dist/agents/peer-admission.js +21 -12
  10. package/dist/agents/peer-directory.d.ts +29 -0
  11. package/dist/agents/peer-directory.js +20 -0
  12. package/dist/agents/peer-held-queue.d.ts +31 -4
  13. package/dist/agents/peer-held-queue.js +32 -1
  14. package/dist/agents/peer-session-drain.d.ts +91 -13
  15. package/dist/agents/peer-session-drain.js +409 -102
  16. package/dist/agents/read-inbox-tool.d.ts +22 -0
  17. package/dist/agents/read-inbox-tool.js +39 -0
  18. package/dist/agents/roster-store.d.ts +2 -4
  19. package/dist/agents/roster-store.js +1 -1
  20. package/dist/agents/send-message-tool.d.ts +4 -4
  21. package/dist/agents/send-message-tool.js +22 -2
  22. package/dist/agents/subagent.js +62 -44
  23. package/dist/agents/team-role.d.ts +34 -0
  24. package/dist/agents/team-role.js +17 -0
  25. package/dist/agents/verify.js +2 -2
  26. package/dist/core/auto-compaction.d.ts +42 -0
  27. package/dist/core/auto-compaction.js +35 -2
  28. package/dist/core/child-clamp-seats.d.ts +11 -0
  29. package/dist/core/child-clamp-seats.js +3 -0
  30. package/dist/core/context-edit.d.ts +3 -2
  31. package/dist/core/context-edit.js +6 -5
  32. package/dist/core/context-guard.d.ts +2 -1
  33. package/dist/core/context-guard.js +3 -2
  34. package/dist/core/context-settings.d.ts +103 -0
  35. package/dist/core/context-settings.js +60 -0
  36. package/dist/core/engine-notice.d.ts +11 -1
  37. package/dist/core/frame-settlement.d.ts +97 -0
  38. package/dist/core/frame-settlement.js +36 -0
  39. package/dist/core/governance-codes.d.ts +1 -1
  40. package/dist/core/governance-codes.js +2 -0
  41. package/dist/core/hooks.d.ts +10 -10
  42. package/dist/core/mailbox-store.d.ts +41 -7
  43. package/dist/core/mailbox-store.js +9 -3
  44. package/dist/core/parent-spec-seat.d.ts +14 -1
  45. package/dist/core/parent-spec-seat.js +6 -0
  46. package/dist/core/permission-rule-model.js +1 -1
  47. package/dist/core/roles.d.ts +20 -0
  48. package/dist/core/roles.js +35 -16
  49. package/dist/core/runner/assemble-result.d.ts +2 -0
  50. package/dist/core/runner/assemble-result.js +1 -1
  51. package/dist/core/runner/compaction-call-options.d.ts +26 -2
  52. package/dist/core/runner/compaction-call-options.js +9 -0
  53. package/dist/core/runner/compaction-seams.d.ts +4 -3
  54. package/dist/core/runner/compaction-seams.js +13 -6
  55. package/dist/core/runner/contracts.d.ts +56 -7
  56. package/dist/core/runner/decide-continuation.d.ts +2 -1
  57. package/dist/core/runner/decide-continuation.js +11 -10
  58. package/dist/core/runner/initial-run-state.js +1 -1
  59. package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
  60. package/dist/core/runner/prepare-config-doors.js +4 -1
  61. package/dist/core/runner/prepare-context-lane.d.ts +9 -2
  62. package/dist/core/runner/prepare-context-lane.js +15 -10
  63. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  64. package/dist/core/runner/prepare-delegation-surface.js +9 -3
  65. package/dist/core/runner/prepare-file-history.js +10 -8
  66. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  67. package/dist/core/runner/prepare-gate-stations.js +3 -1
  68. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  69. package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
  70. package/dist/core/runner/prepare-prompt-inputs.js +2 -1
  71. package/dist/core/runner/prepare-task.js +22 -9
  72. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
  73. package/dist/core/runner/prepare-wiring-manifest.js +6 -1
  74. package/dist/core/runner/retired-runner-deps.d.ts +3 -1
  75. package/dist/core/runner/retired-runner-deps.js +1 -0
  76. package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
  77. package/dist/core/runner/run-compaction-machinery.js +5 -3
  78. package/dist/core/runner/run-harness-handlers.js +1 -1
  79. package/dist/core/runner/run-identity-wiring.js +19 -1
  80. package/dist/core/runner/run-leg.js +1 -1
  81. package/dist/core/runner/run-notification-lane.js +8 -2
  82. package/dist/core/runner/run-recovery-lanes.js +9 -5
  83. package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
  84. package/dist/core/runner/run-settle-and-teardown.js +6 -10
  85. package/dist/core/runner/run-stop-and-final-verify.js +1 -0
  86. package/dist/core/runner/run-terminal-adoption.js +5 -5
  87. package/dist/core/runner/run-turn-boundary.js +32 -5
  88. package/dist/core/runner/runtask.d.ts +8 -1
  89. package/dist/core/runner/runtask.js +32 -1
  90. package/dist/core/runner-deps.d.ts +44 -13
  91. package/dist/core/secret-env.js +2 -1
  92. package/dist/core/swappable-deps.d.ts +20 -4
  93. package/dist/core/swappable-deps.js +6 -1
  94. package/dist/core/task-event.d.ts +5 -0
  95. package/dist/core/task-notification.d.ts +144 -2
  96. package/dist/core/task-notification.js +154 -5
  97. package/dist/core/task-result.d.ts +14 -0
  98. package/dist/core/task-spec.d.ts +7 -0
  99. package/dist/core/tool-catalog-entries.js +2 -1
  100. package/dist/core/tool-conformance.js +1 -0
  101. package/dist/core/tool-policy.d.ts +9 -4
  102. package/dist/core/tool-spec.d.ts +14 -0
  103. package/dist/core/wiring-manifest.d.ts +12 -0
  104. package/dist/core/wiring-manifest.js +5 -1
  105. package/dist/engine/harness/agent-harness.d.ts +14 -1
  106. package/dist/engine/harness/agent-harness.js +28 -3
  107. package/dist/engine/harness/types.d.ts +3 -1
  108. package/dist/engine/llm/types.d.ts +8 -7
  109. package/dist/engine/llm/types.js +1 -1
  110. package/dist/engine/lsp/node-lsp-manager.d.ts +3 -0
  111. package/dist/engine/lsp/node-lsp-manager.js +5 -3
  112. package/dist/engine/session/import-validate.js +4 -0
  113. package/dist/index.d.ts +10 -7
  114. package/dist/index.js +7 -5
  115. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  116. package/dist/orchestration/workflow.d.ts +1 -1
  117. package/dist/orchestration/workflow.js +8 -5
  118. package/dist/shell/lex.d.ts +38 -54
  119. package/dist/shell/lex.js +239 -286
  120. package/dist/stores/cc/mailbox-store.js +3 -2
  121. package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
  122. package/dist/stores/file/file-history-store.d.ts +2 -4
  123. package/dist/stores/file/file-history-store.js +1 -1
  124. package/dist/stores/file/fs-atomic.d.ts +16 -11
  125. package/dist/stores/file/fs-atomic.js +17 -6
  126. package/dist/stores/file/index.d.ts +5 -6
  127. package/dist/stores/file/index.js +1 -1
  128. package/dist/stores/file/mailbox-store.d.ts +4 -9
  129. package/dist/stores/file/mailbox-store.js +29 -30
  130. package/dist/stores/file/session-policy-store.d.ts +2 -0
  131. package/dist/stores/file/session-policy-store.js +1 -1
  132. package/dist/stores/file/session-store.d.ts +11 -7
  133. package/dist/stores/file/session-store.js +23 -16
  134. package/dist/stores/file/shared-ledger.js +3 -14
  135. package/dist/stores/file/tool-result-store.d.ts +2 -0
  136. package/dist/stores/file/tool-result-store.js +5 -2
  137. package/dist/stores/file/workflow-journal-store.js +14 -9
  138. package/dist/tools/fs/bash-program-position.d.ts +15 -24
  139. package/dist/tools/fs/bash-program-position.js +11 -29
  140. package/dist/tools/fs/bash-readonly-classifier.d.ts +131 -117
  141. package/dist/tools/fs/bash-readonly-classifier.js +404 -272
  142. package/dist/tools/fs/shell-read-check.js +3 -3
  143. package/dist/tools/fs/shell-read-establish.d.ts +1 -1
  144. package/dist/tools/fs/shell-read-establish.js +5 -33
  145. package/dist/tools/fs/shell-read-identity.d.ts +13 -0
  146. package/dist/tools/fs/shell-read-identity.js +39 -0
  147. package/dist/tools/fs/shell-read-judge.d.ts +8 -0
  148. package/dist/tools/fs/shell-read-judge.js +7 -0
  149. package/dist/tools/fs/shell-read-plan.d.ts +15 -6
  150. package/dist/tools/fs/shell-read-plan.js +171 -195
  151. package/dist/tools/fs/shell-read-word.d.ts +64 -0
  152. package/dist/tools/fs/shell-read-word.js +94 -0
  153. package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
  154. package/dist/tools/fs/shell-reentry-text.js +73 -0
  155. package/package.json +1 -1
  156. package/test/export-surface.snapshot.json +58 -8
  157. package/wire-changes/core@7.30.0.json +1821 -0
  158. package/wire-changes/core@7.30.1.json +154 -0
@@ -44,6 +44,10 @@ export interface MailboxPeerMeta {
44
44
  * — a delivery receipt's state (`kind: "delivery_notice"`) or an idle notice's kind
45
45
  * (`kind: "idle_notice"`). The record's `content` is a human-readable mirror; THIS is the authority. */
46
46
  notice?: MailboxPeerNoticeMeta;
47
+ /** `peer_message` only — the SENDER marked it urgent. The recipient's drain presents an urgent record
48
+ * ahead of the non-urgent ones of the same pass and counts it on the inbox notice; it never changes
49
+ * WHEN the recipient reads (the recipient's setting decides that, never the sender). */
50
+ urgent?: boolean;
47
51
  }
48
52
  /** design/385 — the typed body of a notice-kind record. `state` is drawn from the receipt states on a
49
53
  * `delivery_notice` (`held|denied|expired|delivered|refused|dropped`) and from the idle kinds on an
@@ -67,10 +71,23 @@ export interface MailboxPeerNoticeMeta {
67
71
  }
68
72
  export declare const MAILBOX_PEER_FROM_MODES: readonly ["bypass", "prompting"];
69
73
  export type MailboxPeerFromMode = (typeof MAILBOX_PEER_FROM_MODES)[number];
70
- /** design/385: `peer_message` runs the parity judgment; `idle_subscription` registers the sender as an
71
- * idle subscriber of the box owner (§5.2, the store form of CC's `notify_when_idle` frame);
72
- * `idle_notice` / `delivery_notice` land on the notice face (rendered, never enveloped as a peer's words). */
73
- export declare const MAILBOX_PEER_RECORD_KINDS: readonly ["peer_message", "idle_notice", "delivery_notice", "idle_subscription"];
74
+ /**
75
+ * design/385: `peer_message` runs the parity judgment; `idle_subscription` registers the sender as an
76
+ * idle subscriber of the box owner (§5.2, the store form of CC's `notify_when_idle` frame);
77
+ * `idle_notice` / `delivery_notice` land on the notice face (rendered, never enveloped as a peer's words).
78
+ *
79
+ * @contract mailbox.engine_frame_is_not_a_peers_words — an `engine_frame` record is the ENGINE's own
80
+ * task-notification frame parked durably (a child's terminal, a monitor batch, a workflow settle),
81
+ * written by the park seat of the session that owns the box and only over a deployment that vouches
82
+ * every drainer of the box reads the codec (`RunnerDeps.sessionBoxEngineFrames`). Its `content` is the
83
+ * versioned codec text (`encodeEngineFrame`), never a message: the drain decodes it and re-injects the
84
+ * frame through the engine's own notification lane — no parity judgment, no admission gate, no peer
85
+ * envelope — and a codec version this engine does not read STOPS the drain at that record (the box
86
+ * waits for an engine that reads it) rather than rendering engine speech as a peer's words. Only the
87
+ * engine's own lanes (`background_agent` / `monitor` / `workflow`) may be parked this way; an
88
+ * `external` event never is.
89
+ */
90
+ export declare const MAILBOX_PEER_RECORD_KINDS: readonly ["peer_message", "idle_notice", "delivery_notice", "idle_subscription", "engine_frame"];
74
91
  export type MailboxPeerRecordKind = (typeof MAILBOX_PEER_RECORD_KINDS)[number];
75
92
  /** The `notice.state` vocabulary the validator admits (the union of both notice families; the drain
76
93
  * checks the kind↔state pairing). Spelled here, beside the record kinds, so the store contract is
@@ -183,7 +200,7 @@ export interface MailboxAppendMessage {
183
200
  * persists it and hands it back on `claimLease` byte-for-byte (a detached copy), absent stays absent,
184
201
  * and a malformed value is REFUSED with `"mailbox.invalid_peer_meta"` before any side effect
185
202
  * (`readMailboxPeerMeta` is the one validator; bundled backends call it first). T1 cases in
186
- * `mailboxStoreContract` cover the round trip, the refusal and the aliasing.
203
+ * `mailboxStoreContract` cover the pass trip, the refusal and the aliasing.
187
204
  * - CROSS-PROCESS SAFETY (design/385, optional capability declared on the store): the session-box
188
205
  * drain shares ONE box between several OS processes. A backend that is correct under that sharing —
189
206
  * seq minted once across processes, lease/ack never crossing, one process's housekeeping never
@@ -192,6 +209,12 @@ export interface MailboxAppendMessage {
192
209
  * that does not declare it is refused by the lane at mount time with a named reason
193
210
  * ({@link mailboxCrossProcessMountVerdict}) — never silently mounted on luck.
194
211
  */
212
+ /** #1036 — what {@link MailboxStore.ack} answers: did this call actually consume the prefix?
213
+ * `false` = the lease fence refused it (this caller does not hold the box), so nothing was deleted
214
+ * and the records are still parked for whoever does. */
215
+ export interface MailboxAckResult {
216
+ readonly acked: boolean;
217
+ }
195
218
  export interface MailboxStore {
196
219
  /** design/385 — cross-process safety declaration (see the interface notes). `true` = this backend
197
220
  * passed `mailboxCrossProcessContract` over real OS processes; `false` = deliberately process-local
@@ -202,7 +225,18 @@ export interface MailboxStore {
202
225
  * that its recipient is in the deployment's pre-delete state (see the interface notes above). */
203
226
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
204
227
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
205
- ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
228
+ /**
229
+ * Consume the leased prefix up to `upToSeq` (the records are DELETED). Fenced by the current lease:
230
+ * a caller that does not hold it consumes nothing.
231
+ *
232
+ * #1036 — the fence's verdict is REPORTED (`{acked:false}`), not silent. The fence itself is
233
+ * older (MB1): it exists so a stale owner whose lease a live rival already took over cannot delete
234
+ * the rival's messages. What was missing is that a no-op and a success were the same `void` to the
235
+ * caller, so a drain that had lost its lease would clear its own watermark as if the store had
236
+ * obeyed it — the one shape where "the records stay parked" turns into "the records are forgotten".
237
+ * Implementations report `{acked:true}` only when they actually consumed the prefix.
238
+ */
239
+ ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
206
240
  releaseLease(scope: string, handle: string, owner: string): Promise<void>;
207
241
  /** Observation face (the "N pending" receipt figure) — count of currently-visible messages. */
208
242
  peekCount(scope: string, handle: string): Promise<number>;
@@ -253,7 +287,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
253
287
  private box;
254
288
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
255
289
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
256
- ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
290
+ ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
257
291
  releaseLease(scope: string, handle: string, owner: string): Promise<void>;
258
292
  peekCount(scope: string, handle: string): Promise<number>;
259
293
  drop(scope: string, handle: string): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { assertRetentionPolicy } from "./retention-policy.js";
2
2
  export const MAILBOX_PEER_FROM_MODES = ["bypass", "prompting"];
3
- export const MAILBOX_PEER_RECORD_KINDS = ["peer_message", "idle_notice", "delivery_notice", "idle_subscription"];
3
+ export const MAILBOX_PEER_RECORD_KINDS = ["peer_message", "idle_notice", "delivery_notice", "idle_subscription", "engine_frame"];
4
4
  export const MAILBOX_PEER_NOTICE_STATES = ["held", "denied", "expired", "delivered", "refused", "dropped", "idle", "exited", "unavailable"];
5
5
  export const MAILBOX_INVALID_PEER_META_CODE = "mailbox.invalid_peer_meta";
6
6
  export const MAILBOX_CROSS_PROCESS_UNSAFE_CODE = "mailbox.cross_process_unsafe";
@@ -42,6 +42,11 @@ export function readMailboxPeerMeta(raw) {
42
42
  else if (k === "notice") {
43
43
  out.notice = readMailboxPeerNoticeMeta(v, refuse);
44
44
  }
45
+ else if (k === "urgent") {
46
+ if (typeof v !== "boolean")
47
+ return refuse("urgent must be a boolean");
48
+ out.urgent = v;
49
+ }
45
50
  else {
46
51
  return refuse(`carries an unknown key ${JSON.stringify(k)}`);
47
52
  }
@@ -171,12 +176,13 @@ export class InMemoryMailboxStore {
171
176
  async ack(scope, handle, owner, upToSeq) {
172
177
  const b = this.boxes.get(this.key(scope, handle));
173
178
  if (!b)
174
- return;
179
+ return { acked: false };
175
180
  if (b.lease === undefined || b.lease.owner !== owner)
176
- return;
181
+ return { acked: false };
177
182
  b.messages = b.messages.filter((m) => m.seq > upToSeq);
178
183
  if (b.lease.maxSeq <= upToSeq)
179
184
  b.lease = undefined;
185
+ return { acked: true };
180
186
  }
181
187
  async releaseLease(scope, handle, owner) {
182
188
  const b = this.boxes.get(this.key(scope, handle));
@@ -13,7 +13,7 @@ import type { Hooks } from "./hooks.js";
13
13
  import type { ToolExecuteContext } from "./tool-spec.js";
14
14
  import type { TaskSpec } from "./types.js";
15
15
  /** The fields of a spec the seat carries — one `Pick`, shared by the mint's input and the apply's target. */
16
- export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "clientContext" | "onQuestion" | "hooks" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "memory">;
16
+ export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "clientContext" | "onQuestion" | "hooks" | "additionalDirectories" | "additionalReadDirectories" | "envFacts" | "memoryPersistenceCapable" | "memory" | "compaction" | "skillListingBudgetFraction" | "skillListingMaxDescChars">;
17
17
  /**
18
18
  * The seat a run hands its children. Every member is the parent's own value, frozen at mint (arrays copied and
19
19
  * frozen; the facts object copied). Members present iff the parent carries them — an absent member is "nothing to
@@ -33,6 +33,16 @@ export type ParentSpecSeatFields = Pick<TaskSpec, "principal" | "oneShot" | "cli
33
33
  * `true` a default a child's own declaration may narrow.
34
34
  * · `memoryOff` — the request-level "memory is off" FACT (`spec.memory.enabled === false`): a REMOVAL
35
35
  * (floor) that REWRITES the child's own memory declaration instead of travelling as a value.
36
+ * · `compaction` — the request's CONTEXT-MANAGEMENT settings (enabled / withinTask / reserve / keepRecent /
37
+ * instructions / attachWorkingFiles / staleToolResultOffload / clampTolerance). One set of settings for the
38
+ * whole delegation tree, measured per leg against the leg's OWN window: the numbers are repaired against the
39
+ * child's model by the same sanitizer every leg runs (`sanitizeCompactionSettings`), so a parent's 128k-tuned
40
+ * reserve does not have to be re-tuned to be legal on a 6k child. `enabled: false` is NOT a removal — the
41
+ * reference implementation shares one settings object down the tree rather than flooring children, and a
42
+ * child that authors its own settings wins the whole object (an ORDINARY seat member).
43
+ * · `skillListingBudgetFraction` / `skillListingMaxDescChars` — the listing-budget knobs, the same shape for the
44
+ * same reason: a fraction the parent tuned is a fraction OF EACH LEG'S OWN window, so it travels as a number
45
+ * and every leg multiplies it by its own.
36
46
  */
37
47
  export interface ParentSpecSeat {
38
48
  readonly principal?: string;
@@ -45,6 +55,9 @@ export interface ParentSpecSeat {
45
55
  readonly envFacts?: TaskSpec["envFacts"];
46
56
  readonly memoryPersistenceCapable?: boolean;
47
57
  readonly memoryOff?: true;
58
+ readonly compaction?: TaskSpec["compaction"];
59
+ readonly skillListingBudgetFraction?: number;
60
+ readonly skillListingMaxDescChars?: number;
48
61
  }
49
62
  /** Mint the seat off the parent's REBOUND spec and its frozen question face (spec over deps — the caller resolves it once,
50
63
  * the same value the parent's own question routing reads). Absent fields stay absent; `oneShot` only when `true`. */
@@ -9,6 +9,9 @@ export function parentSpecSeatOf(spec, frozenOnQuestion) {
9
9
  ...(spec.additionalReadDirectories !== undefined ? { additionalReadDirectories: Object.freeze([...spec.additionalReadDirectories]) } : {}),
10
10
  ...(spec.envFacts !== undefined ? { envFacts: Object.freeze({ ...spec.envFacts }) } : {}),
11
11
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
12
+ ...(spec.compaction !== undefined ? { compaction: Object.freeze({ ...spec.compaction }) } : {}),
13
+ ...(spec.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: spec.skillListingBudgetFraction } : {}),
14
+ ...(spec.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: spec.skillListingMaxDescChars } : {}),
12
15
  ...(spec.memory?.enabled === false ? { memoryOff: true } : {}),
13
16
  });
14
17
  }
@@ -25,6 +28,9 @@ export function applyParentSpecSeat(child, seat) {
25
28
  ...fill("additionalDirectories", (seat.additionalDirectories !== undefined ? [...seat.additionalDirectories] : undefined)),
26
29
  ...fill("additionalReadDirectories", (seat.additionalReadDirectories !== undefined ? [...seat.additionalReadDirectories] : undefined)),
27
30
  ...fill("envFacts", (seat.envFacts !== undefined ? { ...seat.envFacts } : undefined)),
31
+ ...fill("compaction", (seat.compaction !== undefined ? { ...seat.compaction } : undefined)),
32
+ ...fill("skillListingBudgetFraction", seat.skillListingBudgetFraction),
33
+ ...fill("skillListingMaxDescChars", seat.skillListingMaxDescChars),
28
34
  ...(seat.oneShot === true ? { oneShot: true } : {}),
29
35
  ...(seat.memoryPersistenceCapable === false ? { memoryPersistenceCapable: false } : fill("memoryPersistenceCapable", seat.memoryPersistenceCapable)),
30
36
  ...(seat.memoryOff === true ? { memory: memoryTurnedOff(child.memory) } : {}),
@@ -263,7 +263,7 @@ export function parseRuleText(text, behavior) {
263
263
  }
264
264
  const shape = ruleLaneShapeOf(body, MATCH_READING);
265
265
  if ("reject" in shape) {
266
- if (body.trim() === "")
266
+ if (body.trim() === "" || shape.reject === "empty command")
267
267
  return reject("invalid.empty_command", `rule "${text}" names no command`);
268
268
  if (foldSpacing(body) === undefined)
269
269
  return reject("invalid.unbalanced_quotes", `rule "${text}" has unbalanced quoting, so the command it names is not determinable`);
@@ -37,6 +37,13 @@ export declare function resolveModelDisplayLabel(ref: string): string;
37
37
  * `resolveModel("best")` refuses like any unknown ref.
38
38
  */
39
39
  export declare function expandTiers(models: Record<string, Model> | undefined, tiers: Record<string, ModelRef> | undefined): Record<string, Model> | undefined;
40
+ /**
41
+ * The words a catalog word stands in for under the tier layer, nearest first — what {@link expandTiers}
42
+ * recorded for a key IT wrote: the tier words its resolution passed through and the binding's own catalog
43
+ * word, each naming the model the key resolves to. A deployment's own key (a shadowing one included), and any
44
+ * catalog that was not tier-expanded, stands in for nothing.
45
+ */
46
+ export declare function tierAliasVia(word: string, models: Record<string, Model> | undefined): readonly string[];
40
47
  export interface ModelMention {
41
48
  /** The matched model name — always one of `allowedNames` — or undefined if none was found. */
42
49
  model?: string;
@@ -98,6 +105,19 @@ export declare const ROLE_TIER_DEFAULTS: Readonly<Partial<Record<ModelRole, stri
98
105
  export declare function roleSpecNamesModel(spec: RoleSpec | undefined): boolean;
99
106
  export interface ResolvedRole {
100
107
  model: Model;
108
+ /**
109
+ * WHICH REF CHOSE {@link model} — the word, when a word chose it (the explicit `spec.model` string, the
110
+ * role's own `model` ref, or the role→tier default's catalog key), else the `Model` object itself (an
111
+ * explicit object, or one a `select` criteria picked: neither has a catalog word).
112
+ *
113
+ * The distinction is load-bearing wherever a LATER tick must ask the catalog the same question again: a
114
+ * word re-resolves against the catalog generation in force at that tick (a hot swap redirects it, a
115
+ * retirement refuses it loudly), while an object is pinned and never re-resolved. The engine-spawned
116
+ * child that inherits its parent's model inherits THIS, not the resolved object, so it lands on the
117
+ * generation its own prepare sees rather than on the one its parent started under; the same word keys
118
+ * the deployment's per-model context-window overlay.
119
+ */
120
+ modelRef: ModelRef;
101
121
  /** The effective thinking level: explicit `spec.thinking` wins, else the role's default. */
102
122
  thinking?: ThinkingLevel;
103
123
  /** The requested role's preset system prompt, if it set one (independent of model fallback). */
@@ -22,59 +22,75 @@ export function resolveModelDisplayLabel(ref) {
22
22
  return Object.hasOwn(CC_MODEL_TIER_ALIASES, ref) ? CC_MODEL_TIER_ALIASES[ref] : ref;
23
23
  }
24
24
  const TIERS_ACTIVE = Symbol.for("sema.tiersActive");
25
+ const TIER_ALIAS_VIA = Symbol.for("sema.tierAliasVia");
25
26
  export function expandTiers(models, tiers) {
26
27
  if (!tiers || Object.keys(tiers).length === 0)
27
28
  return models;
28
29
  const out = { ...models };
29
30
  out[TIERS_ACTIVE] = true;
31
+ const aliasVia = new Map();
32
+ out[TIER_ALIAS_VIA] = aliasVia;
30
33
  const bound = (name) => {
31
34
  const b = Object.hasOwn(tiers, name) ? tiers[name] : undefined;
32
35
  return b === undefined ? undefined : resolveModel(b, models);
33
36
  };
34
- const resolveTier = (name) => {
35
- const direct = bound(name);
37
+ const ownKey = (k) => models !== undefined && Object.hasOwn(models, k) && Boolean(models[k]);
38
+ const landing = (t, self) => {
39
+ const m = bound(t);
40
+ if (!m)
41
+ return undefined;
42
+ const b = Object.hasOwn(tiers, t) ? tiers[t] : undefined;
43
+ return { model: m, via: [...(t !== self && !ownKey(t) ? [t] : []), ...(typeof b === "string" ? [b] : [])] };
44
+ };
45
+ const resolveTier = (name, self = name) => {
46
+ const direct = landing(name, self);
36
47
  if (direct)
37
48
  return direct;
38
49
  const at = DEFAULT_TIER_ORDER.indexOf(name);
39
50
  if (at === -1)
40
51
  return undefined;
41
52
  for (let i = at + 1; i < DEFAULT_TIER_ORDER.length; i++) {
42
- const next = bound(DEFAULT_TIER_ORDER[i]);
53
+ const next = landing(DEFAULT_TIER_ORDER[i], self);
43
54
  if (next)
44
- return next;
55
+ return { model: next.model, via: [...(name !== self && !ownKey(name) ? [name] : []), ...next.via] };
45
56
  }
46
57
  return undefined;
47
58
  };
48
59
  const shadowed = (key) => Object.hasOwn(out, key) && Boolean(out[key]);
49
- const put = (key, m) => {
60
+ const put = (key, m, via) => {
50
61
  Object.defineProperty(out, key, { value: m, writable: true, enumerable: true, configurable: true });
62
+ aliasVia.set(key, via);
51
63
  };
52
64
  const tierNames = new Set([...DEFAULT_TIER_ORDER, ...Object.keys(tiers)]);
53
65
  for (const name of tierNames) {
54
66
  if (shadowed(name))
55
67
  continue;
56
- const m = resolveTier(name);
57
- if (m)
58
- put(name, m);
68
+ const r = resolveTier(name);
69
+ if (r)
70
+ put(name, r.model, r.via);
59
71
  }
60
72
  for (const [alias, tier] of Object.entries(CC_MODEL_TIER_ALIASES)) {
61
73
  if (shadowed(alias))
62
74
  continue;
63
- const m = resolveTier(tier);
64
- if (m)
65
- put(alias, m);
75
+ const r = resolveTier(tier, alias);
76
+ if (r)
77
+ put(alias, r.model, r.via);
66
78
  }
67
79
  if (!shadowed("best")) {
68
80
  for (const name of DEFAULT_TIER_ORDER) {
69
- const m = bound(name);
70
- if (m) {
71
- put("best", m);
81
+ const r = landing(name, "best");
82
+ if (r) {
83
+ put("best", r.model, r.via);
72
84
  break;
73
85
  }
74
86
  }
75
87
  }
76
88
  return out;
77
89
  }
90
+ export function tierAliasVia(word, models) {
91
+ const via = models?.[TIER_ALIAS_VIA];
92
+ return via?.get(word) ?? [];
93
+ }
78
94
  export function parseModelMention(text, allowedNames) {
79
95
  const allowed = new Map();
80
96
  for (const n of allowedNames)
@@ -181,6 +197,7 @@ export function resolveTaskModel(spec, deps) {
181
197
  const startParts = specParts(mergeRoles(deps.roles, spec.roles)[start]);
182
198
  return {
183
199
  model: resolveModel(spec.model, deps.models),
200
+ modelRef: spec.model,
184
201
  thinking: spec.thinking ?? startParts.thinking,
185
202
  systemPrompt: startParts.systemPrompt,
186
203
  };
@@ -190,9 +207,11 @@ export function resolveTaskModel(spec, deps) {
190
207
  const startSystemPrompt = specParts(merged[start]).systemPrompt;
191
208
  for (const role of [start, ...FALLBACK[start]]) {
192
209
  const parts = specParts(merged[role]);
193
- const model = partsModel(parts, deps.models) ?? roleTierModel(role, deps.models);
210
+ const fromParts = partsModel(parts, deps.models);
211
+ const model = fromParts ?? roleTierModel(role, deps.models);
194
212
  if (model) {
195
- return { model, thinking: spec.thinking ?? parts.thinking, systemPrompt: startSystemPrompt };
213
+ const modelRef = fromParts !== undefined ? (parts.model ?? model) : (ROLE_TIER_DEFAULTS[role] ?? model);
214
+ return { model, modelRef, thinking: spec.thinking ?? parts.thinking, systemPrompt: startSystemPrompt };
196
215
  }
197
216
  }
198
217
  throw new Error(`No model for role "${start}": set TaskSpec.model, or configure RunnerDeps.roles / TaskSpec.roles["${start}"] (or a "default" role). A "select" RoleSpec also fails here if no catalog model matches.`);
@@ -165,6 +165,8 @@ export interface ResultFlags {
165
165
  * `reasoning.resolved` trace frame carried (runtask resolves once per leg — two faces, one mint);
166
166
  * absent when thinking was off/unset for the leg, so the key is omitted. */
167
167
  effectiveReasoning?: TaskResult["effectiveReasoning"];
168
+ /** The run's compaction record (see `TaskResult.compaction`); assembly omits the key when absent. */
169
+ compaction?: TaskResult["compaction"];
168
170
  /** ruled 2026-08-04 — the usage-governance wait hint carried by the platform terminal the run adopted
169
171
  * (`undefined` for every other cause: an expiring environment has no return time to give, and a store
170
172
  * failure is not a window). Passed as data rather than read back off `threw` so the seat has a typed
@@ -178,5 +178,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
178
178
  if (flags.unpricedSpend)
179
179
  delete publicStats.costMicroUsd;
180
180
  const stampHaltedByUser = haltConsumed(flags.haltConsumption);
181
- return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), ...(responseModel !== undefined ? { responseModel } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveWriteFace !== undefined ? { effectiveWriteFace: flags.effectiveWriteFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
181
+ return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), ...(responseModel !== undefined ? { responseModel } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveWriteFace !== undefined ? { effectiveWriteFace: flags.effectiveWriteFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), ...(flags.compaction !== undefined ? { compaction: flags.compaction } : {}), stats: publicStats };
182
182
  }
@@ -3,7 +3,9 @@ import type { MaybeCompactOptions } from "../auto-compaction.js";
3
3
  import type { StaleToolResultOffloadOptions, TaskSpec } from "../types.js";
4
4
  import type { Context } from "../../internal/llm.js";
5
5
  import { type ToolResultStore } from "../tool-result-store.js";
6
- import type { Prepared } from "./contracts.js";
6
+ import type { Prepared, RunnerDepsSeat } from "./contracts.js";
7
+ import { type ContextWindowOverlay } from "../context-settings.js";
8
+ import { type CompactionSettings } from "../../internal/harness.js";
7
9
  /**
8
10
  * The maybeCompact call arguments that MUST be assembled identically on all three
9
11
  * of runtask's compaction lanes (turn boundary / prompt-too-long recovery / end-of-task).
@@ -17,7 +19,8 @@ import type { Prepared } from "./contracts.js";
17
19
  *
18
20
  * Every export is a free function: the turn-boundary lane assembles its call inside `createTurnBoundary`
19
21
  * (a module-level function with no `this`), so a method would not be reachable from all three sites.
20
- * Nothing here reads or writes runner state — pure assembly over `spec` + `Prepared`.
22
+ * Nothing here WRITES runner state; one export ({@link legContextOverlayOption}) READS the live deps seat,
23
+ * because reading it at the moment of the pass — not at prepare — is the whole of what it states.
21
24
  */
22
25
  /**
23
26
  * Opt-in working-file attachments (LONGRUN-2 / CC 198 post-compact restore parity).
@@ -142,3 +145,24 @@ writtenRefs: Set<string>): Promise<Context>;
142
145
  * scope inside `finish()`.
143
146
  */
144
147
  export declare function emitInputTruncated(tracer: TracerHook | undefined, taskId: string): NonNullable<MaybeCompactOptions["onInputTruncated"]>;
148
+ /**
149
+ * The deployment's per-model window overlay in force for THIS pass — the one option of the three lanes that
150
+ * is deliberately a LIVE read rather than a prepared value.
151
+ *
152
+ * Two reads, both at the moment of the pass: the leg's own catalog word (the harness's current ref, so a
153
+ * mid-task degrade moves the key along with the model) and the Runner's current deps seat (so a `swapDeps`
154
+ * reaches a leg already running). An object-pinned leg has no word and therefore no overlay — the key is
155
+ * simply absent, which `maybeCompact` reads as "the model card's own declarations", the pre-seat behavior.
156
+ */
157
+ export declare function legContextOverlayOption(prepared: Prepared, runner: RunnerDepsSeat): Pick<MaybeCompactOptions, "contextOverlay">;
158
+ /**
159
+ * The compaction SETTINGS a pass runs under — the assembly all three lanes share.
160
+ *
161
+ * `{ ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction }` was hand-copied at each lane, and it is where the
162
+ * deployment's live `enabled` declaration belongs: `maybeCompact` has exactly one enablement gate (it reads
163
+ * `settings.enabled`), so folding the overlay in HERE makes the deployment's switch reach the boundary pass,
164
+ * the recovery pass and the end-of-task summary by one edit instead of three, and keeps "who decides whether
165
+ * compaction runs" a single expression ({@link compactionEnabled}: the request authored it > the deployment
166
+ * declared it > on).
167
+ */
168
+ export declare function legCompactionSettings(spec: TaskSpec, overlay: ContextWindowOverlay | undefined): CompactionSettings;
@@ -1,5 +1,7 @@
1
1
  import { emitTrace } from "../trace.js";
2
2
  import { buildToolResultRef, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX, toolResultContentSegment, toolResultProvenanceOf, } from "../tool-result-store.js";
3
+ import { compactionEnabled, contextOverlayFor } from "../context-settings.js";
4
+ import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
3
5
  export function buildWorkingFileAttachments(spec, prepared) {
4
6
  if (spec.compaction?.attachWorkingFiles === false || !prepared.readTaskFile)
5
7
  return undefined;
@@ -115,3 +117,10 @@ export function emitInputTruncated(tracer, taskId) {
115
117
  ts: Date.now(),
116
118
  }));
117
119
  }
120
+ export function legContextOverlayOption(prepared, runner) {
121
+ const overlay = contextOverlayFor(runner.deps.contextSettings, prepared.harness.getModelRef(), runner.deps);
122
+ return overlay !== undefined ? { contextOverlay: overlay } : {};
123
+ }
124
+ export function legCompactionSettings(spec, overlay) {
125
+ return { ...DEFAULT_COMPACTION_SETTINGS, ...spec.compaction, enabled: compactionEnabled(spec.compaction?.enabled, overlay) };
126
+ }
@@ -8,10 +8,11 @@ import type { Prepared, RunnerDepsSeat } from "./contracts.js";
8
8
  * Runner OWNS the consecutive-reuse counter (`prepared.compactionReuseRef`) so the
9
9
  * `maxConsecutiveProviderReuse` drift guard spans the whole task across both sites: it FEEDS the current
10
10
  * count in as `consecutiveProviderReuse`, and {@link recordCompactionReuse} updates it from the result.
11
- * Returns `undefined` when no provider is wired (so the call site spreads nothing → byte-identical to
12
- * the prior behavior).
11
+ * The provider half is absent when no provider is wired (byte-identical to the prior behavior at every
12
+ * call site); the #1036 `onCommitted` effect sink rides unconditionally, which is why the
13
+ * function itself always returns an object.
13
14
  */
14
- export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared: Prepared): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse"> | undefined;
15
+ export declare function seamCCompactionOptions(runner: RunnerDepsSeat, prepared: Prepared): Pick<MaybeCompactOptions, "summaryProvider" | "onCompaction" | "maxConsecutiveProviderReuse" | "consecutiveProviderReuse" | "onCommitted">;
15
16
  /** Resolve the pre/postCompact lifecycle callbacks (whole-slot `spec.hooks ??
16
17
  * deps.hooks`, same resolution as the stop hook) and wrap each in a SWALLOW+TRACE shell before
17
18
  * threading them into maybeCompact. The wrapper owns the observability half of the R3 MED contract
@@ -1,13 +1,20 @@
1
1
  import { hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
2
2
  import { asThrownError } from "../thrown-value.js";
3
3
  export function seamCCompactionOptions(runner, prepared) {
4
- if (!runner.deps.summaryProvider)
5
- return undefined;
6
4
  return {
7
- summaryProvider: runner.deps.summaryProvider,
8
- onCompaction: runner.deps.onCompaction,
9
- maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
10
- consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
5
+ ...(runner.deps.summaryProvider
6
+ ? {
7
+ summaryProvider: runner.deps.summaryProvider,
8
+ onCompaction: runner.deps.onCompaction,
9
+ maxConsecutiveProviderReuse: runner.deps.maxConsecutiveProviderReuse,
10
+ consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
11
+ }
12
+ : {}),
13
+ onCommitted: (effect) => {
14
+ if (prepared.frameSettlement.wantsEffects?.() !== true)
15
+ return;
16
+ prepared.frameSettlement.compactions.push(effect);
17
+ },
11
18
  };
12
19
  }
13
20
  export function compactionHookOptions(runner, spec, sessionId, trigger, identity, seatBound) {
@@ -47,7 +47,7 @@ import type { SystemInjectionPriority, TaskNotificationPayload } from "../task-n
47
47
  import type { OnAsk, PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
48
48
  import type { RemoteExecutionEnv, SnapshotId, WorkspaceHandle } from "../remote-env.js";
49
49
  import type { ToolDisclosureManifest } from "../trace.js";
50
- import type { AgentDefinition, ApproverPosture, Brain, CompactOutcome, ModelRoles, NestedUsageAccum, RunnerDeps, ShellGateDoctrine, TaskEvent, TaskEventIdentity, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
50
+ import type { AgentDefinition, ApproverPosture, Brain, CompactOutcome, ModelRef, ModelRoles, NestedUsageAccum, RunnerDeps, ShellGateDoctrine, TaskEvent, TaskEventIdentity, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
51
51
  import type { WiringManifest } from "../wiring-manifest.js";
52
52
  import type { ActiveSkillFrame } from "./active-skill-scope.js";
53
53
  import type { BudgetAxis } from "./assemble-result.js";
@@ -248,6 +248,11 @@ export interface Prepared {
248
248
  * prepare emits run-scoped disclosures of its own and each must have a terminal that names it.
249
249
  */
250
250
  runId: string;
251
+ /** The (owner, scope) key this run's registry-facing tools register rows under — (host task id, mount domain), minted once at prepare; the teardown sweeps read it and never re-derive it. */
252
+ registryAccess: {
253
+ readonly owner: string;
254
+ readonly scope: string;
255
+ };
251
256
  /** The session's reminder provenance mark this run mints under (adopted
252
257
  * through the prepare adoption ladder: checkpoint seed → trusted fork channel → session entry →
253
258
  * fresh mint). Every engine-authored `<system-reminder>` open tag in the run carries it, and the
@@ -257,6 +262,8 @@ export interface Prepared {
257
262
  * disclosure outlets at tool-execute time; read once at result assembly into
258
263
  * `stats.mechanisms.reminderDisclosures` when any key is non-zero). Always present. */
259
264
  reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
265
+ /** Whether the full shell is on this leg's roster (the inherited-gate phase's bit, asserted against the mount). */
266
+ fullShellReachable: boolean;
260
267
  /** The run's RESOLVED approval-posture facts — the auto-mode intent and the declared approver posture,
261
268
  * folded once by the chain assembly. The first user turn renders them into the engine's own marked
262
269
  * reminder; nothing else reads them, and no gate decision keys on either. */
@@ -471,6 +478,28 @@ export interface Prepared {
471
478
  * drains — one mount judgment, the drain and the park read it alike. Absent ⇒ in-process parking.
472
479
  */
473
480
  sessionBox?: import("../task-notification.js").SessionBoxBinding;
481
+ /**
482
+ * #1036 — the leg's TWO SETTLEMENT LEDGERS, one cell shared by the three beats that fill and
483
+ * read them: the harness's transcript-append sink writes receipts, the compaction commit beat
484
+ * writes effects, and the session-box drain's settlement half-pass reads both and clears what it
485
+ * consumed. Always present (the cell is minted with the leg); empty on a run with no session box,
486
+ * because nothing registers an interest and the receipt sink only records awaited keys.
487
+ */
488
+ frameSettlement: import("../frame-settlement.js").FrameSettlement;
489
+ /**
490
+ * #1036 — the session-box drain's BOUNDARY PASS, called by the turn-boundary lane as its tail
491
+ * (in a `finally`, so every early exit of the boundary still settles). Present iff this leg bound
492
+ * the drain. `continues` = this leg will issue another request after this boundary, which is what
493
+ * decides whether the pass's NEW-delivery half runs; the settlement half runs either way.
494
+ *
495
+ * It is a tail call and not a `turn_boundary` hook of the drain's own because the ORDER matters and
496
+ * a hook's position is registration-order luck: settlement must run AFTER this boundary's
497
+ * compaction, or a record whose frame the compaction just summarized away is already acked (deleted
498
+ * from the box) by the time anyone could notice.
499
+ */
500
+ peerBoundaryRound?: (o: {
501
+ continues: boolean;
502
+ }) => Promise<void>;
474
503
  /**
475
504
  * The per-invocation TIME BOUND every hook seat of this leg runs under (`Hooks.timeoutMs`, already
476
505
  * validated — a garbage value was refused to the default and disclosed ONCE, here, rather than on
@@ -1699,6 +1728,19 @@ export interface RunInternals {
1699
1728
  * TRUSTED run-scoped channel (NOT a {@link TaskSpec} field), mirroring `insideFork`.
1700
1729
  */
1701
1730
  defaultSystemPrompt?: string;
1731
+ /**
1732
+ * The catalog WORD that chose the model this engine-spawned child was handed on its spec.
1733
+ *
1734
+ * The two halves of one fact travel on two channels on purpose: the spec carries the RESOLVED `Model`
1735
+ * object, so nothing re-resolves between the spawn's judgement and this child's prepare (the model the
1736
+ * spawn frames name is the model that runs); this carries the word that object came from, so the child
1737
+ * is not PINNED by having been handed an object — its own children inherit the word in turn, and the
1738
+ * deployment's per-model window overlay (keyed by word) still reaches it. Absent ⇒ no word exists for
1739
+ * this child's model (a caller-pinned object, a `select` criteria), and such a leg is deliberately never
1740
+ * re-resolved and never overlaid. Trusted channel, never a `TaskSpec` field: it is the engine's own
1741
+ * statement about a model the engine itself resolved, and a caller cannot author it.
1742
+ */
1743
+ inheritedModelRef?: ModelRef;
1702
1744
  /**
1703
1745
  * GOAL MODE flag, a TRUSTED internal channel set ONLY by `runGoal` (a core caller),
1704
1746
  * NEVER a {@link TaskSpec} field. It drives `featureFlags.goalEnabled` → injects `GOAL_COMPLETION_GUIDANCE`.
@@ -2105,7 +2147,7 @@ export interface RunInternals {
2105
2147
  * `leg` is the LEG IDENTITY, and the engine mints NONE of it. It is present only where a
2106
2148
  * composition labelled the leg it dispatched: the verify entries hand each leg a per-dispatch
2107
2149
  * copy of this object whose sink closes over that leg's identity, so a consumer can tell the
2108
- * implementation leg from round 2's verifier without inferring it from arrival order (the
2150
+ * implementation leg from pass 2's verifier without inferring it from arrival order (the
2109
2151
  * ordering is all the sibling sinks have — see the attribution paragraph on `verifyCompleted`).
2110
2152
  * ABSENT therefore means "nobody labelled this leg", never "implementation": a plain `runTask`,
2111
2153
  * a workflow agent call and a cascade rung all reach this sink with no identity, and reading one
@@ -2113,13 +2155,13 @@ export interface RunInternals {
2113
2155
  * OWN `leg` field ({@link WiringManifest.leg}, `root` / `child` / `resume`), which is a fact about
2114
2156
  * how the ENGINE reached this run; this one is a fact about the composition that dispatched it, and
2115
2157
  * a resumed fix leg carries both at once — `manifest.leg.kind === "resume"` and `leg.kind === "fix"`
2116
- * are both true of it. `round` is 1-based over the gate's verification rounds: a `verifier` leg
2158
+ * are both true of it. `pass` is 1-based over the gate's verification rounds: a `verifier` leg
2117
2159
  * carries the one it verifies, a `fix` leg the one whose findings it answers, and the entry
2118
2160
  * implementation leg carries 0 — before the first. The axis is relative to the gate INVOCATION,
2119
2161
  * not to the session's history: a resume entry's work leg is THAT invocation's implementation leg
2120
- * (`impl`, round 0) even when the checkpoint it resumes was minted inside a previous invocation's
2121
- * fix round, because the invocation's own round counter (`VerificationResult.rounds`) restarts with
2122
- * it — one axis for both fields, rather than a leg that reads "fix round 1" inside a result whose
2162
+ * (`impl`, pass 0) even when the checkpoint it resumes was minted inside a previous invocation's
2163
+ * fix pass, because the invocation's own pass counter (`VerificationResult.rounds`) restarts with
2164
+ * it — one axis for both fields, rather than a leg that reads "fix pass 1" inside a result whose
2123
2165
  * rounds begin at 1 again.
2124
2166
  *
2125
2167
  * Observe-only and failure-isolated through the SAME per-run notifier as {@link onActivity} (one
@@ -2357,6 +2399,9 @@ export interface RunState {
2357
2399
  /** B15 三期终件补遗:压缩地板/背压 latch — rs.counters.compactionFloor 跨 drainManualCompact(写)/R9(读写)/收尾(读)三方共享,rs.counters.trimForceBackoff 为 R9 跨调用 latch(同款只改绑定不改时机)。 */
2358
2400
  compactionFloor: number;
2359
2401
  trimForceBackoff: boolean;
2402
+ /** Landed compactions (boundary + prompt-too-long lanes) and the last one's kind — the `TaskResult.compaction` source (the breaker state is read off the breaker itself). */
2403
+ compactionPasses: number;
2404
+ compactionKind?: import("../task-notification.js").CompactionKind;
2360
2405
  repetitionCuts: number;
2361
2406
  repetitionSpared: number;
2362
2407
  repetitionEvents: Array<{
@@ -2544,7 +2589,7 @@ export interface TurnBoundaryDeps {
2544
2589
  }, ctx?: import("../hooks.js").PostToolBatchContext) => PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>) | undefined;
2545
2590
  batchArgs: Map<string, unknown> | undefined;
2546
2591
  compactionBrain: Brain;
2547
- withinTaskCompaction: boolean;
2592
+ withinTaskCompaction: () => boolean;
2548
2593
  compactionBreaker: {
2549
2594
  failures: number;
2550
2595
  };
@@ -2658,6 +2703,10 @@ export interface RunnerSelfSeat {
2658
2703
  models?: Record<string, Model>;
2659
2704
  roles?: ModelRoles;
2660
2705
  };
2706
+ /** The context-window overlay a leg on `ref` reads RIGHT NOW (`RunnerDeps.contextSettings`, tier aliases followed
2707
+ * against the catalog in force): prepare hands its leg a call-time reader over it, because this is the one seat a
2708
+ * running leg re-reads (geometry, not route — see `context-settings.ts`). */
2709
+ contextOverlayOf?(ref: import("../model-seat.js").ModelRef | undefined): import("../context-settings.js").ContextWindowOverlay | undefined;
2661
2710
  /** The deployment-level gating baseline (deps toolPolicy / hooks) as delegate wrappers — for assemblers that compose with it. */
2662
2711
  readonly gateBaseline: {
2663
2712
  toolPolicy?: RunnerDeps["toolPolicy"];
@@ -92,5 +92,6 @@ export declare function assertOutcomeText(value: string | undefined, field: "edi
92
92
  * `prompt(text)` (it always appends a user message — there is no "continue from tool results" entry), so
93
93
  * resume re-enters via a `<system-reminder>`-wrapped user message that tells the model the gated decision
94
94
  * was applied. The "user message wart" is accepted (API-valid, and clearer than a silent continuation).
95
+ * `fullShellReachable` is the resumed leg's own roster fact, read by {@link reorientClause}.
95
96
  */
96
- export declare function resumeContinuation(resume: ResumeRun, mark: string | undefined): string;
97
+ export declare function resumeContinuation(resume: ResumeRun, mark: string | undefined, fullShellReachable: boolean): string;