@sema-agent/core 7.2.0 → 7.3.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 (49) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/agents/cross-session-envelope.d.ts +7 -0
  3. package/dist/agents/cross-session-envelope.js +4 -0
  4. package/dist/agents/list-agents-tool.d.ts +55 -0
  5. package/dist/agents/list-agents-tool.js +94 -0
  6. package/dist/agents/peer-admission.d.ts +17 -1
  7. package/dist/agents/peer-admission.js +19 -2
  8. package/dist/agents/peer-directory.d.ts +208 -0
  9. package/dist/agents/peer-directory.js +272 -0
  10. package/dist/agents/peer-session-drain.d.ts +159 -0
  11. package/dist/agents/peer-session-drain.js +245 -0
  12. package/dist/agents/send-message-tool.d.ts +31 -0
  13. package/dist/agents/send-message-tool.js +145 -4
  14. package/dist/agents/subagent-steps.d.ts +11 -0
  15. package/dist/agents/subagent-steps.js +27 -4
  16. package/dist/core/auto-mode-arming.d.ts +11 -0
  17. package/dist/core/auto-mode-arming.js +7 -1
  18. package/dist/core/auto-mode-prompt.d.ts +5 -0
  19. package/dist/core/auto-mode-prompt.js +2 -1
  20. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  21. package/dist/core/auto-mode-rebuild.js +2 -0
  22. package/dist/core/checkpoint-store.d.ts +14 -0
  23. package/dist/core/checkpoint-store.js +4 -3
  24. package/dist/core/governance-codes.d.ts +1 -1
  25. package/dist/core/governance-codes.js +6 -0
  26. package/dist/core/mailbox-store.d.ts +89 -2
  27. package/dist/core/mailbox-store.js +77 -2
  28. package/dist/core/permission-rule-model.d.ts +9 -0
  29. package/dist/core/permission-rule-model.js +4 -1
  30. package/dist/core/runner/prepare-task.d.ts +20 -0
  31. package/dist/core/runner/prepare-task.js +137 -37
  32. package/dist/core/runner/runtask.js +3 -2
  33. package/dist/core/runner/tool-output-projection.js +1 -0
  34. package/dist/core/store-contracts/mailbox-store-contract.d.ts +23 -0
  35. package/dist/core/store-contracts/mailbox-store-contract.js +157 -1
  36. package/dist/core/task-notification.d.ts +38 -9
  37. package/dist/core/task-notification.js +8 -2
  38. package/dist/core/types.d.ts +132 -21
  39. package/dist/core/wiring-manifest.d.ts +21 -0
  40. package/dist/core/wiring-manifest.js +1 -0
  41. package/dist/index.d.ts +9 -3
  42. package/dist/index.js +9 -3
  43. package/dist/stores/cc/mailbox-store.d.ts +1 -1
  44. package/dist/stores/cc/mailbox-store.js +13 -0
  45. package/dist/stores/file/adoption/marker.d.ts +1 -1
  46. package/dist/stores/file/mailbox-store.d.ts +57 -0
  47. package/dist/stores/file/mailbox-store.js +369 -18
  48. package/package.json +1 -1
  49. package/test/export-surface.snapshot.json +109 -1
@@ -12,7 +12,54 @@ export interface MailboxMessage {
12
12
  * has other writers): such records deliver with an empty chain and are never retro-admitted —
13
13
  * cross-engine records sit outside the guard's promise domain by ruling. */
14
14
  hopChain?: string[];
15
+ /** design/385 — typed peer metadata the drain point's authoritative judgments read (from-mode parity,
16
+ * reply routing, admission sender key, record kind, the cross-principal trust anchors). Typed side
17
+ * channel, never model-facing text — encoding it into `content` would let text reach authority.
18
+ * ABSENT = a pre-385 or foreign record (same semantics as an absent `hopChain`): the drain treats it
19
+ * as foreign, never retro-admits it. Validated at `append` ({@link readMailboxPeerMeta}). */
20
+ peerMeta?: MailboxPeerMeta;
15
21
  }
22
+ /** design/385 — the typed peer-metadata record on a parked message. ONE optional object, one-time
23
+ * contract extension; every field optional. Bundled backends persist it verbatim and hand back a
24
+ * detached copy. */
25
+ export interface MailboxPeerMeta {
26
+ /** The sending session's id (reply routing). */
27
+ fromSession?: string;
28
+ /** The sender's permission-mode class at send time — the drain judges parity against the RECEIVER. */
29
+ fromMode?: MailboxPeerFromMode;
30
+ /** The admission gate's sender key (rate/dedup axis). */
31
+ senderKey?: string;
32
+ /** Record kind: a peer message runs the parity judgment; notices bypass it and land on the notice face. */
33
+ kind?: MailboxPeerRecordKind;
34
+ /** Cross-principal delivery only — the SENDER's scope, the typed trust anchor for attribution. */
35
+ fromScope?: string;
36
+ /** Cross-principal delivery only — the delivery gate's receipt id (audit back-reference). */
37
+ gateReceiptId?: string;
38
+ }
39
+ export declare const MAILBOX_PEER_FROM_MODES: readonly ["bypass", "prompting"];
40
+ export type MailboxPeerFromMode = (typeof MAILBOX_PEER_FROM_MODES)[number];
41
+ export declare const MAILBOX_PEER_RECORD_KINDS: readonly ["peer_message", "idle_notice", "delivery_notice"];
42
+ export type MailboxPeerRecordKind = (typeof MAILBOX_PEER_RECORD_KINDS)[number];
43
+ /** The `append` refusal code for a malformed `peerMeta` (design/385): a garbage record is refused up
44
+ * front with this code, never silently stripped or stored — a drain that reads a half-typed record
45
+ * would judge on fabricated inputs. */
46
+ export declare const MAILBOX_INVALID_PEER_META_CODE = "mailbox.invalid_peer_meta";
47
+ /** The lane-mount refusal code: the cross-session lane asked for a backend that does not declare
48
+ * cross-process safety ({@link mailboxCrossProcessMountVerdict}). */
49
+ export declare const MAILBOX_CROSS_PROCESS_UNSAFE_CODE = "mailbox.cross_process_unsafe";
50
+ /**
51
+ * Validate + detach an appended `peerMeta`. `undefined` ⇒ `undefined` (absent record). Anything else
52
+ * must be a plain object whose keys are all known, whose present values are non-empty strings, with
53
+ * `fromMode`/`kind` drawn from their closed sets; an `undefined`-valued key counts as absent. Any other
54
+ * shape throws {@link MailboxStoreError} with {@link MAILBOX_INVALID_PEER_META_CODE} — every bundled
55
+ * backend calls this BEFORE touching storage, so a refused append has zero side effects. Unknown keys
56
+ * are refused on purpose: the object is the contract's one typed slot, and a key nobody declared is
57
+ * either a typo or a newer schema this engine cannot judge on — both are loud, not silent.
58
+ */
59
+ export declare function readMailboxPeerMeta(raw: unknown): MailboxPeerMeta | undefined;
60
+ /** A detached copy of a persisted record's `peerMeta` (absent stays absent) — the read-side twin of
61
+ * {@link readMailboxPeerMeta}, so a lease consumer's mutation never reaches the stored record. */
62
+ export declare function cloneMailboxPeerMeta(meta: MailboxPeerMeta | undefined): MailboxPeerMeta | undefined;
16
63
  /** The enqueue refusal code of the pre-delete clause (see {@link MailboxStore} and
17
64
  * {@link MailboxStoreError}) — the ONE place it is spelled, so an out-of-repo store twin imports it
18
65
  * instead of value-copying the string (same posture as `STALE_RUNNING_REAP_ATTRIBUTION`: a shared
@@ -31,9 +78,10 @@ export declare const MAILBOX_TOMBSTONED_RECIPIENT_CODE = "mailbox.recipient_tomb
31
78
  * A backend may raise the same code with a plain `Error` carrying `.code`; consumers branch on the
32
79
  * string, not on this class (a cross-process/out-of-repo store cannot hand back an instance).
33
80
  */
81
+ export type MailboxStoreErrorCode = typeof MAILBOX_TOMBSTONED_RECIPIENT_CODE | typeof MAILBOX_INVALID_PEER_META_CODE | typeof MAILBOX_CROSS_PROCESS_UNSAFE_CODE;
34
82
  export declare class MailboxStoreError extends Error {
35
- readonly code: typeof MAILBOX_TOMBSTONED_RECIPIENT_CODE;
36
- constructor(code: typeof MAILBOX_TOMBSTONED_RECIPIENT_CODE, message: string);
83
+ readonly code: MailboxStoreErrorCode;
84
+ constructor(code: MailboxStoreErrorCode, message: string);
37
85
  }
38
86
  /** A leased batch: the messages a claim winner owns for delivery, plus the ack cursor. */
39
87
  export interface MailboxLease {
@@ -49,6 +97,8 @@ export interface MailboxAppendMessage {
49
97
  sentAt: number;
50
98
  /** design/176 — see {@link MailboxMessage.hopChain}. */
51
99
  hopChain?: string[];
100
+ /** design/385 — see {@link MailboxMessage.peerMeta}; validated by {@link readMailboxPeerMeta}. */
101
+ peerMeta?: MailboxPeerMeta;
52
102
  }
53
103
  /**
54
104
  * The pluggable mailbox seam (design/151 §7.1). Contract notes for implementations (file/pg):
@@ -92,8 +142,25 @@ export interface MailboxAppendMessage {
92
142
  * lifecycle (the two bundled ones, the CC inbox adapter) has nothing to refuse and keeps accepting
93
143
  * — the clause fixes the SPELLING of the refusal, so a deployment reads one code instead of a
94
144
  * per-backend dialect. Acceptance kit: `mailboxTombstonedRecipientContract`.
145
+ * - PEER METADATA (design/385, additive): `append` takes an optional typed `peerMeta`; a backend
146
+ * persists it and hands it back on `claimLease` byte-for-byte (a detached copy), absent stays absent,
147
+ * and a malformed value is REFUSED with `"mailbox.invalid_peer_meta"` before any side effect
148
+ * (`readMailboxPeerMeta` is the one validator; bundled backends call it first). T1 cases in
149
+ * `mailboxStoreContract` cover the round trip, the refusal and the aliasing.
150
+ * - CROSS-PROCESS SAFETY (design/385, optional capability declared on the store): the session-box
151
+ * drain shares ONE box between several OS processes. A backend that is correct under that sharing —
152
+ * seq minted once across processes, lease/ack never crossing, one process's housekeeping never
153
+ * discarding another's durable append — declares `crossProcessSafe: true` AFTER passing
154
+ * `mailboxCrossProcessContract` (the T2 kit that pins the whole set, not "has a lock"). A backend
155
+ * that does not declare it is refused by the lane at mount time with a named reason
156
+ * ({@link mailboxCrossProcessMountVerdict}) — never silently mounted on luck.
95
157
  */
96
158
  export interface MailboxStore {
159
+ /** design/385 — cross-process safety declaration (see the interface notes). `true` = this backend
160
+ * passed `mailboxCrossProcessContract` over real OS processes; `false` = deliberately process-local
161
+ * (the in-memory reference); absent = undeclared (the CC inbox adapter). Read by
162
+ * {@link mailboxCrossProcessMountVerdict}; any other value is a malformed declaration and refuses. */
163
+ readonly crossProcessSafe?: boolean;
97
164
  /** Durably park one message. Refuses `"mailbox.recipient_tombstoned"` when the backend can see
98
165
  * that its recipient is in the deployment's pre-delete state (see the interface notes above). */
99
166
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
@@ -110,6 +177,21 @@ export interface MailboxStore {
110
177
  maxAgeMs?: number;
111
178
  }): Promise<number>;
112
179
  }
180
+ /** The lane-mount verdict on a backend's cross-process declaration. */
181
+ export type MailboxCrossProcessVerdict = {
182
+ ok: true;
183
+ } | {
184
+ ok: false;
185
+ code: typeof MAILBOX_CROSS_PROCESS_UNSAFE_CODE;
186
+ reason: string;
187
+ };
188
+ /**
189
+ * design/385 — may the cross-session lane (several OS processes draining one session box) mount over
190
+ * this backend? `ok` only for an explicit `crossProcessSafe: true`. Every other shape refuses with a
191
+ * named reason so the host can print it: absent (undeclared), `false` (process-local by design), or a
192
+ * value of the wrong type (a malformed declaration is refused, not read as "probably fine").
193
+ */
194
+ export declare function mailboxCrossProcessMountVerdict(store: MailboxStore): MailboxCrossProcessVerdict;
113
195
  /** RB-250② (2026-07-28) class fix — the box age for `reap` is the MAX `sentAt`, not the
114
196
  * last array element: `sentAt` is caller-supplied, so append order need not be time order, and taking
115
197
  * the tail let a box whose freshest message was mid-array be swept — both bundled backends discarded
@@ -118,12 +200,17 @@ export interface MailboxStore {
118
200
  * adapter's documented posture — treating it as very old would let a malformed row delete a live
119
201
  * box). Module-level export only; NOT re-exported from src/index.ts. */
120
202
  export declare function newestSentAt(messages: readonly MailboxMessage[]): number | undefined;
203
+ /** A lease-facing copy of a stored record: every mutable member (hopChain, peerMeta) detached, so a
204
+ * consumer's mutation can never reach the record a redelivery serves. Shared by the bundled backends. */
205
+ export declare function detachMailboxMessage(m: MailboxMessage): MailboxMessage;
121
206
  /**
122
207
  * In-process reference implementation (single-instance / tests). Same posture as
123
208
  * {@link import("./background-agent-store.js").InMemoryBackgroundAgentStore}: not default-mounted,
124
209
  * detached copies at both boundaries, single-event-loop atomicity.
125
210
  */
126
211
  export declare class InMemoryMailboxStore implements MailboxStore {
212
+ /** Process-local by construction (a Map): never a cross-process box, said so explicitly. */
213
+ readonly crossProcessSafe = false;
127
214
  private boxes;
128
215
  private key;
129
216
  private box;
@@ -1,4 +1,52 @@
1
1
  import { assertRetentionPolicy } from "./retention-policy.js";
2
+ export const MAILBOX_PEER_FROM_MODES = ["bypass", "prompting"];
3
+ export const MAILBOX_PEER_RECORD_KINDS = ["peer_message", "idle_notice", "delivery_notice"];
4
+ export const MAILBOX_INVALID_PEER_META_CODE = "mailbox.invalid_peer_meta";
5
+ export const MAILBOX_CROSS_PROCESS_UNSAFE_CODE = "mailbox.cross_process_unsafe";
6
+ const PEER_META_STRING_KEYS = ["fromSession", "senderKey", "fromScope", "gateReceiptId"];
7
+ export function readMailboxPeerMeta(raw) {
8
+ if (raw === undefined)
9
+ return undefined;
10
+ const refuse = (why) => {
11
+ throw new MailboxStoreError(MAILBOX_INVALID_PEER_META_CODE, `MailboxStore.append: peerMeta ${why}`);
12
+ };
13
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
14
+ return refuse("must be a plain object");
15
+ const proto = Object.getPrototypeOf(raw);
16
+ if (proto !== Object.prototype && proto !== null)
17
+ return refuse("must be a plain object (a class instance, Date, Map or similar is not)");
18
+ const out = {};
19
+ for (const key of Reflect.ownKeys(raw)) {
20
+ if (typeof key !== "string")
21
+ return refuse("carries a symbol-keyed member");
22
+ const k = key;
23
+ const v = raw[k];
24
+ if (v === undefined)
25
+ continue;
26
+ if (k === "fromMode") {
27
+ if (!MAILBOX_PEER_FROM_MODES.includes(v))
28
+ return refuse(`fromMode must be one of ${MAILBOX_PEER_FROM_MODES.join("|")}`);
29
+ out.fromMode = v;
30
+ }
31
+ else if (k === "kind") {
32
+ if (!MAILBOX_PEER_RECORD_KINDS.includes(v))
33
+ return refuse(`kind must be one of ${MAILBOX_PEER_RECORD_KINDS.join("|")}`);
34
+ out.kind = v;
35
+ }
36
+ else if (PEER_META_STRING_KEYS.includes(k)) {
37
+ if (typeof v !== "string" || v === "")
38
+ return refuse(`${k} must be a non-empty string`);
39
+ out[k] = v;
40
+ }
41
+ else {
42
+ return refuse(`carries an unknown key ${JSON.stringify(k)}`);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ export function cloneMailboxPeerMeta(meta) {
48
+ return meta === undefined ? undefined : { ...meta };
49
+ }
2
50
  export const MAILBOX_TOMBSTONED_RECIPIENT_CODE = "mailbox.recipient_tombstoned";
3
51
  export class MailboxStoreError extends Error {
4
52
  code;
@@ -8,6 +56,17 @@ export class MailboxStoreError extends Error {
8
56
  this.name = "MailboxStoreError";
9
57
  }
10
58
  }
59
+ export function mailboxCrossProcessMountVerdict(store) {
60
+ const declared = store.crossProcessSafe;
61
+ if (declared === true)
62
+ return { ok: true };
63
+ const reason = declared === undefined
64
+ ? "the mailbox backend does not declare cross-process safety (crossProcessSafe is absent): several terminal sessions would share one session box on luck"
65
+ : declared === false
66
+ ? "the mailbox backend declares crossProcessSafe: false (process-local by design): it cannot serve a session box shared across OS processes"
67
+ : `the mailbox backend's crossProcessSafe declaration is malformed (${typeof declared}, expected a boolean)`;
68
+ return { ok: false, code: MAILBOX_CROSS_PROCESS_UNSAFE_CODE, reason };
69
+ }
11
70
  export function newestSentAt(messages) {
12
71
  let newest;
13
72
  for (const m of messages) {
@@ -18,7 +77,15 @@ export function newestSentAt(messages) {
18
77
  }
19
78
  return newest;
20
79
  }
80
+ export function detachMailboxMessage(m) {
81
+ return {
82
+ ...m,
83
+ ...(m.hopChain !== undefined ? { hopChain: [...m.hopChain] } : {}),
84
+ ...(m.peerMeta !== undefined ? { peerMeta: cloneMailboxPeerMeta(m.peerMeta) } : {}),
85
+ };
86
+ }
21
87
  export class InMemoryMailboxStore {
88
+ crossProcessSafe = false;
22
89
  boxes = new Map();
23
90
  key(scope, handle) {
24
91
  if (handle.includes("\u0000")) {
@@ -38,9 +105,17 @@ export class InMemoryMailboxStore {
38
105
  async append(scope, handle, msg) {
39
106
  if (scope === undefined || scope === "")
40
107
  throw new Error("MailboxStore.append: refusing a message without a scope");
108
+ const peerMeta = readMailboxPeerMeta(msg.peerMeta);
41
109
  const b = this.box(scope, handle);
42
110
  const seq = b.nextSeq++;
43
- b.messages.push({ seq, ...(msg.from !== undefined ? { from: msg.from } : {}), content: msg.content, sentAt: msg.sentAt, ...(msg.hopChain !== undefined ? { hopChain: [...msg.hopChain] } : {}) });
111
+ b.messages.push({
112
+ seq,
113
+ ...(msg.from !== undefined ? { from: msg.from } : {}),
114
+ content: msg.content,
115
+ sentAt: msg.sentAt,
116
+ ...(msg.hopChain !== undefined ? { hopChain: [...msg.hopChain] } : {}),
117
+ ...(peerMeta !== undefined ? { peerMeta } : {}),
118
+ });
44
119
  return seq;
45
120
  }
46
121
  async claimLease(scope, handle, owner, ttlMs, now = Date.now()) {
@@ -51,7 +126,7 @@ export class InMemoryMailboxStore {
51
126
  return null;
52
127
  const maxSeq = b.messages[b.messages.length - 1].seq;
53
128
  b.lease = { owner, expiresAt: now + ttlMs, maxSeq };
54
- return { messages: b.messages.map((m) => ({ ...m, ...(m.hopChain !== undefined ? { hopChain: [...m.hopChain] } : {}) })), maxSeq };
129
+ return { messages: b.messages.map((m) => detachMailboxMessage(m)), maxSeq };
55
130
  }
56
131
  async ack(scope, handle, owner, upToSeq) {
57
132
  const b = this.boxes.get(this.key(scope, handle));
@@ -331,6 +331,15 @@ export declare const SUGGESTION_LEXICON: readonly string[];
331
331
  * through, so an ordinary rule text reads normally. The result is length-bounded.
332
332
  */
333
333
  export declare function escapeForDisclosure(value: unknown): string;
334
+ /**
335
+ * The zero-width half of {@link renderUntrustedCommandText} on its own: every `\p{Cf}` character
336
+ * REMOVED, nothing else touched. Exposed for a seat that must run a PATTERN pass (a secret scanner)
337
+ * before the display render — a credential the text splits with a format character is invisible to
338
+ * the scanner while it runs, and the render's own strip would then glue it back together in clear
339
+ * AFTER the scanner missed it. Strip first, scan, then render: the render's strip is idempotent over
340
+ * text this already stripped, so the final bytes are the same as a single render, minus the leak.
341
+ */
342
+ export declare function stripFormatCharacters(text: string): string;
334
343
  /**
335
344
  * Render raw, untrusted COMMAND text for a display surface — the minimal safe baseline for the seats
336
345
  * that carry post-rewrite command bytes verbatim ({@link SegmentRuleSuggestion.segment} is the
@@ -171,6 +171,9 @@ export function escapeForDisclosure(value) {
171
171
  return escaped.length <= DISCLOSED_RULE_TEXT_MAX_CHARS ? escaped : `${escaped.slice(0, DISCLOSED_RULE_TEXT_MAX_CHARS)}…`;
172
172
  }
173
173
  const DISPLAY_STRIP_FORMAT_RE = /\p{Cf}/gu;
174
+ export function stripFormatCharacters(text) {
175
+ return text.replace(DISPLAY_STRIP_FORMAT_RE, "");
176
+ }
174
177
  export function renderUntrustedCommandText(text, maxLen = DISCLOSED_RULE_TEXT_MAX_CHARS) {
175
178
  let raw;
176
179
  try {
@@ -179,7 +182,7 @@ export function renderUntrustedCommandText(text, maxLen = DISCLOSED_RULE_TEXT_MA
179
182
  catch {
180
183
  return "<unprintable>";
181
184
  }
182
- return inlineUntrusted(raw.replace(DISPLAY_STRIP_FORMAT_RE, ""), maxLen);
185
+ return inlineUntrusted(stripFormatCharacters(raw), maxLen);
183
186
  }
184
187
  export function hasUnrenderableCharacters(text) {
185
188
  return CONTROL_CHARS_RE.test(text);
@@ -41,6 +41,13 @@ export declare function __resetMaterializeEnvAnnouncements(): void;
41
41
  * seam below (console latch only): tests here legitimately reuse ONE sink across prepares to pin
42
42
  * the per-sink dedup itself, so the seam must be able to re-arm a still-referenced sink. */
43
43
  export declare function __resetToolModelGateAnnouncements(): void;
44
+ /**
45
+ * The model gate's per-session READ face ({@link WiringManifest.modelGate}) — projected from the SAME
46
+ * gate decision {@link announceToolModelGate} reads (one source; the notice is the operator-sink
47
+ * dialect, this the manifest dialect). `undefined` when no default-mounted tool was trimmed on this
48
+ * run. The restore sentence is the notice's, verbatim.
49
+ */
50
+ export declare const TOOL_MODEL_GATE_RESTORE_SENTENCE = "restore via TaskSpec.restoreGatedTools, SEMA_TOOL_MODEL_GATE=off, or RunnerDeps.toolModelGate: false";
44
51
  export { __resetReadFaceClampAnnouncement } from "./prepare-hands-readface.js";
45
52
  /**
46
53
  * design/164 件四 — how long before an execution environment's declared `lifetimeMs` expires the engine
@@ -1177,6 +1184,19 @@ export interface InheritedGate {
1177
1184
  }>;
1178
1185
  /** The chain's effective shell-gate doctrine — the child folds it by max-rank with its own spec. */
1179
1186
  shellGate?: "off" | "always" | "classify";
1187
+ /**
1188
+ * The chain's AUTO-MODE INTENT (`TaskSpec.autoModeRequested`, session-wide like a permission mode):
1189
+ * emitted when the spawning task carried the intent itself or inherited it, so an engine-spawned
1190
+ * child of an auto-mode task arms its OWN per-run classifier exactly as its parent did — the
1191
+ * intent half only; the child's deny bit (`RuntimeCaps.autoMode === false`) and deployment face
1192
+ * (`RunnerDeps.autoMode`) are evaluated for the child. Trusted chain data (Runner-assembled, never a
1193
+ * model-authored argument); absent ⇒ the child is an auto-mode task only if its own spec says so.
1194
+ * Persists on the checkpoint's data half (`CheckpointState.inheritedGate.autoModeRequested`) so a
1195
+ * redemption in another process — no seat re-passed, no waking chain — reads the same intent the
1196
+ * suspend leg had; the intent folds by OR across seat, live chain and seed, while the deny bit and
1197
+ * the deployment face stay per-leg.
1198
+ */
1199
+ autoModeRequested?: true;
1180
1200
  /**
1181
1201
  * Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
1182
1202
  * set — every org memory scope the parent actually mounted (deployment-origin + admitted request).