@sema-agent/core 7.0.0 → 7.0.2

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.
@@ -37,10 +37,27 @@ function delegatedCostField(stats) {
37
37
  const total = rollupDelegatedCost(stats, stats.nested);
38
38
  return total !== undefined ? { costMicroUsd: total } : {};
39
39
  }
40
+ export function delegatedGranularityFields(stats, elapsedMs) {
41
+ return { ...(stats.toolCalls !== undefined ? { toolUses: stats.toolCalls } : {}), durationMs: elapsedMs };
42
+ }
43
+ function providerFaultField(child) {
44
+ if (child.apiFailure === undefined)
45
+ return {};
46
+ return {
47
+ apiFailure: {
48
+ ...(typeof child.apiFailure.status === "number" ? { status: child.apiFailure.status } : {}),
49
+ ...(typeof child.apiFailure.requestId === "string" ? { requestId: child.apiFailure.requestId } : {}),
50
+ },
51
+ };
52
+ }
53
+ export function delegatedGranularityFieldsWire(stats, elapsedMs) {
54
+ return { ...(stats.toolCalls !== undefined ? { tool_uses: stats.toolCalls } : {}), duration_ms: elapsedMs };
55
+ }
40
56
  export { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
41
57
  import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
42
58
  import { createPeerInboundChainRef, createPeerSelfRef } from "./peer-admission.js";
43
59
  import { recordRosterSpawn } from "./roster-store.js";
60
+ import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "./launch-receipt-contract.js";
44
61
  import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, ObserverResumeStateError, ObserverStoppedByUserError, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
45
62
  import { SubagentStepRecorder } from "./subagent-steps.js";
46
63
  const BG_AGENT_RESULT_MAX = 4_000;
@@ -326,6 +343,7 @@ function resumabilityClaim(f) {
326
343
  const BG_AGENT_COLLATERAL_REAP_REASON = "its parent run ended";
327
344
  const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME]);
328
345
  export const REPORT_FIELD_MAX = 300;
346
+ const ROSTER_ECHO_MAX = 400;
329
347
  function configError(message, code) {
330
348
  const e = new Error(message);
331
349
  e.code = code;
@@ -364,6 +382,7 @@ export function completedAgentCard(child, extras) {
364
382
  ...(child.blockedReason !== undefined ? { blockedReason: child.blockedReason } : {}),
365
383
  ...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}),
366
384
  ...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}),
385
+ ...providerFaultField(child),
367
386
  ...(child.retryAfterMs !== undefined ? { retryAfterMs: child.retryAfterMs } : {}),
368
387
  ...(child.degraded !== undefined ? { degraded: child.degraded } : {}),
369
388
  ...(extras.handbackWarning !== undefined ? { handbackWarning: extras.handbackWarning } : {}),
@@ -711,9 +730,10 @@ export function createSubagentResume(deps) {
711
730
  const completionIdRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
712
731
  const reviveName = deps.rowDescription ?? `sub-agent ${marker}`;
713
732
  const failReasonRevive = status === "failed" ? child.errorMessage : undefined;
733
+ const reviveElapsedMs = Date.now() - reviveStartedAt;
714
734
  const reviveTerminalSummary = failReasonRevive !== undefined
715
- ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(Date.now() - reviveStartedAt)}`.slice(0, 300) + errorKindClause(errClassRevive)
716
- : `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(Date.now() - reviveStartedAt)}`;
735
+ ? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(reviveElapsedMs)}`.slice(0, 300) + errorKindClause(errClassRevive)
736
+ : `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(reviveElapsedMs)}`;
717
737
  const reviveDurableProbe = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.durableAgentRowProbe(deps.taskId) : undefined;
718
738
  const reviveDurableRow = reviveDurableProbe !== undefined ? await reviveDurableProbe().catch(() => false) : false;
719
739
  const resumableRevive = resumabilityClaim({
@@ -739,7 +759,7 @@ export function createSubagentResume(deps) {
739
759
  summary: reviveTerminalSummary,
740
760
  ...resumeResidual(),
741
761
  resumable: resumableRevive,
742
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
762
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, reviveElapsedMs) },
743
763
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
744
764
  });
745
765
  const resumeFrame = {
@@ -753,11 +773,12 @@ export function createSubagentResume(deps) {
753
773
  summary: reviveTerminalSummary,
754
774
  ...(failReasonRevive !== undefined ? { error: failReasonRevive.slice(0, REPORT_FIELD_MAX) } : {}),
755
775
  ...(status === "failed" && errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
776
+ ...providerFaultField(child),
756
777
  ...(resumeHandbackResult ? { result: notifyResultField(resumeHandbackResult) } : {}),
757
778
  ...(status === "killed" && child.result ? { partial: true } : {}),
758
779
  ...resumeResidual(),
759
780
  resumable: resumableRevive,
760
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
781
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, reviveElapsedMs) },
761
782
  ...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
762
783
  };
763
784
  const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
@@ -1206,7 +1227,7 @@ export function forkWorktreeTranslationNote(parentCwd, worktreeDir) {
1206
1227
  export function asyncLaunchedReceipt(p) {
1207
1228
  const noteLines = (p.notes ?? []).filter((n) => n !== undefined && n !== "");
1208
1229
  const oneShotBlocked = p.notify && p.oneShot === true;
1209
- return (`Async agent launched successfully. (This tool result is internal metadata — never quote or paste any part of it, including the task_id below, into a user-facing reply.)\n` +
1230
+ return (`Async agent launched successfully. (${launchReceiptNoQuoteClause(", including the task_id below,")})\n` +
1210
1231
  `task_id: ${p.taskId} (internal ID - do not mention to user. Use SendMessage with to: '${p.taskId}', summary: '<5-10 word recap>' to continue this agent.)\n` +
1211
1232
  (oneShotBlocked
1212
1233
  ? `${p.workingLine} This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput(task_id: "${p.taskId}", block: true). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.\n`
@@ -1215,10 +1236,18 @@ export function asyncLaunchedReceipt(p) {
1215
1236
  : `${p.workingLine} Its result is NOT pushed automatically — retrieve progress and results with TaskOutput(task_id) where mounted.\n`) +
1216
1237
  noteLines.map((n) => `${n}\n`).join("") +
1217
1238
  (oneShotBlocked
1218
- ? `In your own words, briefly tell the user what you launched — do not echo this tool result. Do not assume a later message will deliver the result: this submission ends after this turn, so retrieve it now with the blocking TaskOutput wait above before writing your final answer.`
1239
+ ? `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} Do not assume a later message will deliver the result: this submission ends after this turn, so retrieve it now with the blocking TaskOutput wait above before writing your final answer.`
1219
1240
  : p.notify
1220
- ? `In your own words, briefly tell the user what you launched — do not echo this tool result. Agent results will arrive in a subsequent message. If the user asks for progress, say the agent is still running.`
1221
- : `In your own words, briefly tell the user what you launched — do not echo this tool result. The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`));
1241
+ ? `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} Agent results will arrive in a subsequent message. If the user asks for progress, say the agent is still running.`
1242
+ : `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`));
1243
+ }
1244
+ function runnerModelCatalog(runner) {
1245
+ try {
1246
+ return runner?.agentCatalog?.models;
1247
+ }
1248
+ catch {
1249
+ return undefined;
1250
+ }
1222
1251
  }
1223
1252
  export function createSubagentTool(opts) {
1224
1253
  const catalog = opts.runner.agentCatalog;
@@ -1227,8 +1256,7 @@ export function createSubagentTool(opts) {
1227
1256
  if (opts.builtinAgents === undefined && catalog?.builtinAgents !== undefined) {
1228
1257
  opts = { ...opts, builtinAgents: catalog.builtinAgents };
1229
1258
  }
1230
- if (opts.models === undefined && catalog?.models)
1231
- opts = { ...opts, models: catalog.models };
1259
+ const assemblyModels = opts.models ?? catalog?.models;
1232
1260
  if (opts.agents?.length) {
1233
1261
  opts = {
1234
1262
  ...opts,
@@ -1252,7 +1280,7 @@ export function createSubagentTool(opts) {
1252
1280
  }
1253
1281
  if (typeof a.model === "string") {
1254
1282
  try {
1255
- resolveModel(a.model, opts.models);
1283
+ resolveModel(a.model, assemblyModels);
1256
1284
  }
1257
1285
  catch {
1258
1286
  throw configError(`createSubagentTool: agent "${a.name}" references unknown model "${a.model}" (pass \`models\` to resolve string refs).`, "config.agent.unknown_model");
@@ -1308,7 +1336,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1308
1336
  ...(forkOffered ? [FORK_SUBAGENT_TYPE] : []),
1309
1337
  ...(generalPurposeShadowed ? [] : [GENERAL_PURPOSE_SUBAGENT_TYPE]),
1310
1338
  ];
1311
- const rosterNames = opts.models ? Object.keys(opts.models) : undefined;
1339
+ const activeModels = () => opts.models ?? runnerModelCatalog(opts.runner);
1340
+ const mountModels = activeModels();
1341
+ const rosterNames = mountModels ? Object.keys(mountModels) : undefined;
1312
1342
  const agentListing = [
1313
1343
  ...(generalPurposeShadowed
1314
1344
  ? []
@@ -1424,7 +1454,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1424
1454
  })),
1425
1455
  model: Type.Optional(Type.String({
1426
1456
  description: rosterNames !== undefined && rosterNames.length > 0
1427
- ? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model, or inherits from the parent. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}" — forks always inherit the parent model.`
1457
+ ? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model, or inherits from the parent. A value outside this deployment's model catalog is REFUSED — the call does not fall back silently. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}" — forks always inherit the parent model.`
1428
1458
  : `Optional model override request. No model catalog is configured here, so a requested name cannot be resolved — the agent runs its definition's model (or inherits from the parent) and the result notes the override was not applied. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}".`,
1429
1459
  })),
1430
1460
  isolation: Type.Optional(Type.Literal("worktree", {
@@ -1560,23 +1590,75 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1560
1590
  };
1561
1591
  }
1562
1592
  }
1593
+ const modelObjectForm = typeof a.model === "object" &&
1594
+ a.model !== null &&
1595
+ !Array.isArray(a.model) &&
1596
+ typeof a.model.id === "string" &&
1597
+ typeof a.model.api === "string";
1598
+ if (a.model !== undefined && typeof a.model !== "string" && !modelObjectForm) {
1599
+ const typeDesc = Array.isArray(a.model) ? "an array" : a.model === null ? "null" : typeof a.model === "object" ? "an object" : `a ${typeof a.model}`;
1600
+ return {
1601
+ isError: true,
1602
+ content: `Sub-agent not started: \`model\` must be a model name (string) from this deployment's catalog — got ${typeDesc}. Omit the parameter to run the agent type's own model.`,
1603
+ details: { error: "model.invalid" },
1604
+ };
1605
+ }
1606
+ if (typeof a.model === "string" && a.model.trim() === "" && !wantsFork && reviveClaim === undefined) {
1607
+ return {
1608
+ isError: true,
1609
+ content: "Sub-agent not started: `model` was empty. Name a model from this deployment's catalog, or omit the parameter to run the agent type's own model.",
1610
+ details: { error: "model.empty" },
1611
+ };
1612
+ }
1563
1613
  const requestedModel = typeof a.model === "string" && a.model.trim() !== "" ? a.model.trim() : undefined;
1564
1614
  let perCallModel;
1565
1615
  let modelNote;
1566
- if (requestedModel !== undefined) {
1616
+ const judgedModels = activeModels();
1617
+ const rosterKeys = judgedModels !== undefined ? Object.keys(judgedModels) : [];
1618
+ if (modelObjectForm) {
1567
1619
  if (wantsFork) {
1568
- modelNote = `note: model "${requestedModel}" was ignored — a fork always runs on the caller's model.`;
1620
+ modelNote = `note: model "${inlineUntrusted(a.model.id, 80)}" was ignored — a fork always runs on the caller's model.`;
1569
1621
  }
1570
- else if (!opts.models) {
1571
- modelNote = `note: model "${requestedModel}" was NOT applied — this delegation tool has no model roster configured; the sub-agent ran on its default (inherited) model.`;
1622
+ else {
1623
+ perCallModel = a.model;
1624
+ }
1625
+ }
1626
+ if (typeof a.model === "string" && requestedModel === undefined) {
1627
+ modelNote = wantsFork
1628
+ ? `note: model "" was ignored — a fork always runs on the caller's model.`
1629
+ : `note: the durable record's model was blank and was NOT applied — the revived agent ran on its default (inherited) model.`;
1630
+ }
1631
+ else if (requestedModel !== undefined) {
1632
+ if (wantsFork) {
1633
+ modelNote = `note: model "${inlineUntrusted(requestedModel, 80)}" was ignored — a fork always runs on the caller's model.`;
1634
+ }
1635
+ else if (rosterKeys.length === 0) {
1636
+ modelNote = `note: model "${inlineUntrusted(requestedModel, 80)}" was NOT applied — this delegation tool has no model roster configured; the sub-agent ran on its default (inherited) model.`;
1572
1637
  }
1573
1638
  else {
1574
1639
  try {
1575
- resolveModel(requestedModel, opts.models);
1576
- perCallModel = requestedModel;
1640
+ perCallModel = resolveModel(requestedModel, judgedModels);
1577
1641
  }
1578
1642
  catch {
1579
- modelNote = `note: model "${requestedModel}" was NOT applied — unknown model (roster: ${Object.keys(opts.models).join(", ")}); the sub-agent ran on its default (inherited) model.`;
1643
+ if (reviveClaim !== undefined) {
1644
+ modelNote = `note: recorded model "${inlineUntrusted(requestedModel, 80)}" is not on the current model roster and was NOT applied — the revived agent ran on its default (inherited) model.`;
1645
+ }
1646
+ else {
1647
+ const shown = [];
1648
+ let budget = ROSTER_ECHO_MAX;
1649
+ for (const k of rosterKeys) {
1650
+ if (budget - k.length < 0)
1651
+ break;
1652
+ shown.push(k);
1653
+ budget -= k.length + 2;
1654
+ }
1655
+ const rosterEcho = shown.length === rosterKeys.length ? shown.join(", ") : `${shown.join(", ")}, … (${rosterKeys.length - shown.length} more)`;
1656
+ return {
1657
+ isError: true,
1658
+ content: `Sub-agent not started: unknown model "${inlineUntrusted(requestedModel, 80)}". Available model values: ${inlineUntrusted(rosterEcho, ROSTER_ECHO_MAX + 32)}. Omit \`model\` to run the agent type's own model, or inherit the caller's.`,
1659
+ details: { error: "model.unknown" },
1660
+ };
1661
+ }
1580
1662
  }
1581
1663
  }
1582
1664
  }
@@ -1690,9 +1772,39 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1690
1772
  const childTools = def
1691
1773
  ? resolveToolSubset(childPool, def.allowTools, def.denyTools)
1692
1774
  : resolveToolSubset(childPool, opts.allowTools, opts.denyTools);
1693
- const childModel = wantsFork
1775
+ let childModel = wantsFork
1694
1776
  ? ctx.model ?? opts.model
1695
1777
  : perCallModel ?? (def ? (def.model ?? ctx.model ?? opts.model) : opts.model);
1778
+ if (reviveClaim !== undefined && typeof childModel === "string") {
1779
+ const resolvable = (ref) => {
1780
+ try {
1781
+ resolveModel(ref, runnerModelCatalog(opts.runner));
1782
+ return true;
1783
+ }
1784
+ catch {
1785
+ return false;
1786
+ }
1787
+ };
1788
+ if (!resolvable(childModel)) {
1789
+ const dead = childModel;
1790
+ const rest = def !== undefined ? [ctx.model, opts.model] : [opts.model];
1791
+ childModel = rest.find((c) => c !== undefined && (typeof c !== "string" || resolvable(c)));
1792
+ if (modelNote === undefined) {
1793
+ modelNote = `note: model "${inlineUntrusted(dead, 80)}" from this agent type's configuration is not on the current model roster and was NOT applied — the revived agent ran on its default (inherited) model.`;
1794
+ }
1795
+ }
1796
+ }
1797
+ const spawnModel = typeof childModel === "string"
1798
+ ? (() => {
1799
+ try {
1800
+ return resolveModel(childModel, judgedModels).id;
1801
+ }
1802
+ catch {
1803
+ return resolveModelDisplayLabel(childModel);
1804
+ }
1805
+ })()
1806
+ : childModel?.id;
1807
+ const rowModelKey = perCallModel !== undefined ? requestedModel ?? perCallModel.id : typeof childModel === "string" ? childModel : childModel?.id;
1696
1808
  const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
1697
1809
  const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
1698
1810
  const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
@@ -1936,6 +2048,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1936
2048
  const provenanceRequest = ctx.delegationProvenanceForChildren?.();
1937
2049
  const childProvenanceRef = provenanceRequest !== undefined ? { current: newDelegationProvenanceAggregate() } : undefined;
1938
2050
  const childAttestation = (status) => childProvenanceRef !== undefined ? reduceDelegationAttestation(childProvenanceRef.current, { completed: status === "completed" }) : undefined;
2051
+ const liveCaptureFloor = reviveClaim === undefined
2052
+ ? { optedOut: (await ctx.memoryCaptureOptedOut) === true, indeterminate: (await ctx.memoryCaptureIndeterminate) === true }
2053
+ : undefined;
1939
2054
  const childInternals = {
1940
2055
  ...(inheritedManifestScope ? { inheritedManifestScope } : {}),
1941
2056
  ...(childProvenanceRef !== undefined && provenanceRequest !== undefined ? { delegationProvenance: { ref: childProvenanceRef, contentSafety: provenanceRequest } } : {}),
@@ -1954,8 +2069,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1954
2069
  ...(ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : {}),
1955
2070
  ...(reviveClaim === undefined
1956
2071
  ? {
1957
- ...(ctx.memoryCaptureOptedOut === true ? { memoryCaptureFloor: true } : {}),
1958
- ...(ctx.memoryCaptureOptedOut !== true && ctx.memoryCaptureIndeterminate === true ? { memoryCaptureFloorIndeterminate: true } : {}),
2072
+ ...(liveCaptureFloor?.optedOut === true ? { memoryCaptureFloor: true } : {}),
2073
+ ...(liveCaptureFloor?.optedOut !== true && liveCaptureFloor?.indeterminate === true ? { memoryCaptureFloorIndeterminate: true } : {}),
1959
2074
  ...(ctx.memoryCaptureControlDir !== undefined ? { memoryCaptureQueryDir: ctx.memoryCaptureControlDir } : {}),
1960
2075
  ...(ctx.memoryCaptureAncestors !== undefined ? { memoryCaptureAncestors: ctx.memoryCaptureAncestors } : {}),
1961
2076
  }
@@ -2342,7 +2457,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2342
2457
  ...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
2343
2458
  ...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
2344
2459
  ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
2345
- ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}),
2460
+ ...(rowModelKey !== undefined ? { model: rowModelKey } : {}),
2346
2461
  deliveryChannel: "attaching",
2347
2462
  toolUseId: ctx.toolCallId,
2348
2463
  ...(bg.agentStore !== undefined ? { store: bg.agentStore } : {}),
@@ -2403,7 +2518,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2403
2518
  }, { priority: "next" }), "subagent.reapTerminalNotify");
2404
2519
  });
2405
2520
  if (agentName !== undefined) {
2406
- recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(modelFallback !== undefined ? { modelFallback } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
2521
+ recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(rowModelKey !== undefined ? { model: rowModelKey } : {}), ...(modelFallback !== undefined ? { modelFallback } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
2407
2522
  }
2408
2523
  const bgSink = ctx.onBackgroundChildEvent;
2409
2524
  const sinkEmit = (event) => {
@@ -2422,6 +2537,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2422
2537
  ...(bgScope !== undefined ? { scope: bgScope } : {}),
2423
2538
  description: shortDesc,
2424
2539
  agentType: spawnAgentType,
2540
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
2425
2541
  ...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
2426
2542
  ...(agentName !== undefined ? { name: agentName } : {}),
2427
2543
  sessionId: forkedId,
@@ -2451,6 +2567,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2451
2567
  parentToolCallId: ctx.toolCallId,
2452
2568
  progressTaskId: forkedId,
2453
2569
  agentType: spawnAgentType,
2570
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
2454
2571
  ...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
2455
2572
  ...(currentAction !== undefined ? { currentAction } : {}),
2456
2573
  ...(currentTool !== undefined ? { currentTool } : {}),
@@ -2485,6 +2602,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2485
2602
  progressTaskId: e.taskId,
2486
2603
  ...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
2487
2604
  agentType: spawnAgentType,
2605
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
2488
2606
  ...(e.name !== undefined ? { name: e.name } : {}),
2489
2607
  ...(currentAction !== undefined ? { currentAction } : {}),
2490
2608
  ...(currentTool !== undefined ? { currentTool } : {}),
@@ -2562,9 +2680,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2562
2680
  const resumableFork = false;
2563
2681
  const forkTranscriptId = child.sessionId ?? forkedId;
2564
2682
  const failReasonFork = settledBg === "failed" ? child.errorMessage : undefined;
2683
+ const forkElapsedMs = Date.now() - forkBgStartedAt;
2565
2684
  const forkTerminalSummary = failReasonFork !== undefined
2566
- ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(Date.now() - forkBgStartedAt)}`.slice(0, 300) + errorKindClause(errClassFork)
2567
- : `${ccCompletionText(shortDesc, settledBg, String(child.status), Date.now() - forkBgStartedAt)}`;
2685
+ ? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(forkElapsedMs)}`.slice(0, 300) + errorKindClause(errClassFork)
2686
+ : `${ccCompletionText(shortDesc, settledBg, String(child.status), forkElapsedMs)}`;
2568
2687
  const ownsTerminalFacesFork = bg.registry.claimAgentTerminalNotify(taskId);
2569
2688
  if (ownsTerminalFacesFork)
2570
2689
  sinkEmit({
@@ -2580,7 +2699,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2580
2699
  summary: forkTerminalSummary,
2581
2700
  ...residualFork,
2582
2701
  resumable: resumableFork,
2583
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2702
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, forkElapsedMs) },
2584
2703
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
2585
2704
  });
2586
2705
  try {
@@ -2594,12 +2713,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2594
2713
  summary: forkTerminalSummary,
2595
2714
  ...(failReasonFork !== undefined ? { error: failReasonFork.slice(0, REPORT_FIELD_MAX) } : {}),
2596
2715
  ...(settledBg === "failed" && errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
2716
+ ...providerFaultField(child),
2597
2717
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
2598
2718
  ...(forkBgResult ? { result: notifyResultField(forkBgResult) } : {}),
2599
2719
  ...(settledBg === "killed" && child.result ? { partial: true } : {}),
2600
2720
  ...residualFork,
2601
2721
  resumable: resumableFork,
2602
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
2722
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, forkElapsedMs) },
2603
2723
  ...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
2604
2724
  }, { priority: "next" });
2605
2725
  }
@@ -2912,7 +3032,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2912
3032
  ...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
2913
3033
  ...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
2914
3034
  ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
2915
- ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}),
3035
+ ...(rowModelKey !== undefined ? { model: rowModelKey } : {}),
2916
3036
  deliveryChannel: "attaching",
2917
3037
  toolUseId: ctx.toolCallId,
2918
3038
  ...(bg.agentStore !== undefined ? { store: bg.agentStore } : {}),
@@ -2936,7 +3056,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2936
3056
  chargeDelegationEntryHandle(bg.registry, capLedgerKey, bg.agentStore, reviveRow === undefined ? taskId : undefined);
2937
3057
  childInternals.peerSelfRef?.addAxis("h", taskId);
2938
3058
  if (agentName !== undefined) {
2939
- recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
3059
+ recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(reviveRow !== undefined ? (reviveRow.model !== undefined ? { model: reviveRow.model } : {}) : rowModelKey !== undefined ? { model: rowModelKey } : {}), ...(modelFallback !== undefined ? { modelFallback } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
2940
3060
  }
2941
3061
  const bgSink = ctx.onBackgroundChildEvent;
2942
3062
  const sinkEmit = (event) => {
@@ -2992,6 +3112,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2992
3112
  ...(bgScope !== undefined ? { scope: bgScope } : {}),
2993
3113
  description: reviveRow !== undefined ? `${shortDesc} (revived)` : shortDesc,
2994
3114
  agentType: spawnAgentType,
3115
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
2995
3116
  ...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
2996
3117
  ...(agentName !== undefined ? { name: agentName } : {}),
2997
3118
  sessionId: bgChildSessionId,
@@ -3065,6 +3186,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3065
3186
  parentToolCallId: ctx.toolCallId,
3066
3187
  progressTaskId: bgChildSessionId,
3067
3188
  agentType: spawnAgentType,
3189
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
3068
3190
  ...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
3069
3191
  ...(currentAction !== undefined ? { currentAction } : {}),
3070
3192
  ...(currentTool !== undefined ? { currentTool } : {}),
@@ -3111,6 +3233,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3111
3233
  progressTaskId: e.taskId,
3112
3234
  ...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
3113
3235
  agentType: spawnAgentType,
3236
+ ...(spawnModel !== undefined ? { model: spawnModel } : {}),
3114
3237
  ...(e.name !== undefined ? { name: e.name } : {}),
3115
3238
  ...(currentAction !== undefined ? { currentAction } : {}),
3116
3239
  ...(currentTool !== undefined ? { currentTool } : {}),
@@ -3399,9 +3522,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3399
3522
  named: agentName !== undefined,
3400
3523
  });
3401
3524
  const failReasonBg = settled === "failed" ? unparkedPauseReason ?? child.errorMessage : undefined;
3525
+ const bgElapsedMs = Date.now() - bgStartedAt;
3402
3526
  const bgTerminalSummary = failReasonBg !== undefined
3403
- ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(Date.now() - bgStartedAt)}${observerNote}`.slice(0, 300) + errorKindClause(errClassBg)
3404
- : `${ccCompletionText(shortDesc, settled, String(child.status), Date.now() - bgStartedAt)}${observerNote}`;
3527
+ ? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(bgElapsedMs)}${observerNote}`.slice(0, 300) + errorKindClause(errClassBg)
3528
+ : `${ccCompletionText(shortDesc, settled, String(child.status), bgElapsedMs)}${observerNote}`;
3405
3529
  const ownsTerminalFaces = bg.registry.claimAgentTerminalNotify(taskId);
3406
3530
  if (ownsTerminalFaces)
3407
3531
  sinkEmit({
@@ -3416,7 +3540,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3416
3540
  summary: bgTerminalSummary,
3417
3541
  ...residual,
3418
3542
  resumable: resumableBg,
3419
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
3543
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, bgElapsedMs) },
3420
3544
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
3421
3545
  });
3422
3546
  const completionFrame = {
@@ -3429,12 +3553,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
3429
3553
  summary: bgTerminalSummary,
3430
3554
  ...(failReasonBg !== undefined ? { error: failReasonBg.slice(0, REPORT_FIELD_MAX) } : {}),
3431
3555
  ...(settled === "failed" && errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
3556
+ ...providerFaultField(child),
3432
3557
  ...(child.sessionId ? { sessionId: child.sessionId } : {}),
3433
3558
  ...(bgHandbackResult ? { result: notifyResultField(bgHandbackResult) } : {}),
3434
3559
  ...(settled === "killed" && child.result ? { partial: true } : {}),
3435
3560
  ...residual,
3436
3561
  resumable: resumableBg,
3437
- usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
3562
+ usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, bgElapsedMs) },
3438
3563
  ...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
3439
3564
  };
3440
3565
  const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
@@ -166,6 +166,14 @@ export function validateFileHistoryExport(data) {
166
166
  return bad(`tracked row ${t.relOrAbsPath} carries a non-integer version number`);
167
167
  if (v.blobHash !== null && (typeof v.blobHash !== "string" || !/^[0-9a-f]{64}$/.test(v.blobHash)))
168
168
  return bad(`tracked row ${t.relOrAbsPath} carries a blob reference that is not a sha256 digest`);
169
+ if (v.mode !== undefined) {
170
+ if (typeof v.mode !== "number" || !Number.isInteger(v.mode) || v.mode < 0 || v.mode > 0o7777) {
171
+ return bad(`tracked row ${t.relOrAbsPath} carries a mode outside the permission-bit domain [0, 0o7777] — refused, never coerced`);
172
+ }
173
+ if ((v.mode & 0o6000) !== 0) {
174
+ return bad(`tracked row ${t.relOrAbsPath} carries a set-ID mode (0o${v.mode.toString(8)}) — a restore replaying setuid/setgid bits would mint a privilege-escalation primitive; refused`);
175
+ }
176
+ }
169
177
  }
170
178
  if (new Set(t.versions.map((v) => v.version)).size !== t.versions.length)
171
179
  return bad(`tracked row ${t.relOrAbsPath} carries the same version number twice — a chain whose versions are ambiguous cannot be published`);
@@ -284,13 +292,15 @@ export async function applyFileRestoreViaEnv(env, absPath, target, opts) {
284
292
  return { outcome: "identical" };
285
293
  if (modeOnly === "applied")
286
294
  return { outcome: "applied" };
295
+ if (modeOnly === "applied_unpreserved")
296
+ return { outcome: "applied", modeNotPreserved: true };
287
297
  return { outcome: "identical", modeNotPreserved: true };
288
298
  }
289
299
  const w = await writeRestoredBytes(env, absPath, target.bytes, signal);
290
300
  if (!w.ok)
291
301
  return { outcome: w.refused ? "refused" : "failed", reason: `write: ${w.code}` };
292
302
  const modeAfter = await applyRecordedMode(env, absPath, opts, undefined);
293
- return { outcome: "applied", ...(modeAfter === "unpreserved" ? { modeNotPreserved: true } : {}) };
303
+ return { outcome: "applied", ...(modeAfter === "unpreserved" || modeAfter === "applied_unpreserved" ? { modeNotPreserved: true } : {}) };
294
304
  }
295
305
  async function restorePreflight(env, absPath, opts) {
296
306
  const info = await env.fileInfo(absPath, opts.signal);
@@ -337,7 +347,10 @@ export async function previewFileDelta(env, absPath, targetHash, targetBytes, ta
337
347
  return undefined;
338
348
  const info = await env.fileInfo(absPath);
339
349
  const currentMode = info.ok ? info.value.mode : undefined;
340
- return modeProvenDifferent(targetMode, currentMode) ? { insertions: 0, deletions: 0 } : undefined;
350
+ const replayMode = (targetMode & 0o6000) !== 0 ? targetMode & 0o1777 : targetMode;
351
+ return modeProvenDifferent(targetMode, currentMode) && modeProvenDifferent(replayMode, currentMode)
352
+ ? { insertions: 0, deletions: 0 }
353
+ : undefined;
341
354
  }
342
355
  return countRestoreLineDiff(curBytes, targetBytes);
343
356
  }
@@ -347,6 +360,15 @@ async function applyRecordedMode(env, absPath, opts, currentMode) {
347
360
  return "unchanged";
348
361
  if (currentMode === (want & 0o7777))
349
362
  return "unchanged";
363
+ if ((want & 0o6000) !== 0) {
364
+ const stripped = want & 0o1777;
365
+ if (currentMode === stripped)
366
+ return "unpreserved";
367
+ if (env.setFileMode === undefined)
368
+ return "unpreserved";
369
+ const r = await env.setFileMode(absPath, stripped, opts.signal);
370
+ return r.ok ? "applied_unpreserved" : "unpreserved";
371
+ }
350
372
  if (env.setFileMode === undefined)
351
373
  return "unpreserved";
352
374
  const r = await env.setFileMode(absPath, want & 0o7777, opts.signal);
@@ -796,6 +796,27 @@ export declare function memoryConsolidationRefusedNotice(input: {
796
796
  reason?: string;
797
797
  occurrenceId?: string;
798
798
  }): EngineNotice;
799
+ /**
800
+ * design/383 §S-7 (#511 件1) — {@link MemoryEngine.sessionMemoryStatus}'s answer. Every key is
801
+ * optional; ABSENCE means the fact's source could not be read (a fault never coins a `false`/`0`
802
+ * stand-in) — see the method doc for each key's exact absence law.
803
+ */
804
+ export interface SessionMemoryStatus {
805
+ /** TRUE = a standing capture opt-out record; FALSE = store readable, no record (capture on).
806
+ * ABSENT = the record store faulted — indeterminate (`optOutSource: "fault"` accompanies). */
807
+ captureOptedOut?: boolean;
808
+ /** Committed entries carrying this session's lineage contribution. Absent = ledger unreadable. */
809
+ committedCount?: number;
810
+ /** Of those, entries already folded into consolidation products (lineage × `distilled.inputs`).
811
+ * Absent = ledger / scope enumeration / product read unreadable. */
812
+ foldedCount?: number;
813
+ /** WHY `captureOptedOut` reads as it does: `"record"` = a standing one-way record;
814
+ * `"fault"` = the store faulted and the capture state is INDETERMINATE (no boolean is coined). */
815
+ optOutSource?: "record" | "fault";
816
+ /** Newest lineage `lastAt` for this session (ms epoch) — rides the same single ledger read as
817
+ * `committedCount`. Absent = ledger unreadable, or no committed contribution exists at all. */
818
+ lastCaptureAt?: number;
819
+ }
799
820
  export declare class MemoryEngine {
800
821
  private readonly backend;
801
822
  private readonly memoryDir;
@@ -910,13 +931,17 @@ export declare class MemoryEngine {
910
931
  * first cross-process resume, and "私密 only until the next resume" is a promise this engine
911
932
  * refuses to imply — the deliberate divergence from the pollution marker's best-effort arm.
912
933
  */
913
- markSessionCaptureOptOut(sessionId: string, reason: string): SessionCaptureOptOutMarkOutcome;
934
+ markSessionCaptureOptOut(sessionId: string, reason: string): SessionCaptureOptOutMarkOutcome | Promise<SessionCaptureOptOutMarkOutcome>;
914
935
  /** The session's capture opt-out record (in-process first, then the durable store) — undefined =
915
936
  * capture is on. Side-effect-free OBSERVER face; a THROWING store read degrades to the
916
937
  * in-process answer here. Every consumer whose answer decides whether bytes COMMIT must use
917
938
  * {@link sessionCaptureOptOutOrFault} instead — this face cannot distinguish "no record" from
918
- * "store outage", and on that distinction the fail direction flips. */
919
- sessionCaptureOptOut(sessionId: string): SessionCaptureOptOutRecord | undefined;
939
+ * "store outage", and on that distinction the fail direction flips.
940
+ * DUAL FORM (#511 件2, the whole capture-face family — this one, `sessionCaptureOptOutOrFault`,
941
+ * `markSessionCaptureOptOut`, `listCaptureOptOutSessions`): over a sync store the answer is the
942
+ * same synchronous value as always; over a Promise-form {@link SessionCaptureRecordStore} the
943
+ * face answers a Promise of the identical shape. `await` is always correct on either arm. */
944
+ sessionCaptureOptOut(sessionId: string): SessionCaptureOptOutRecord | undefined | Promise<SessionCaptureOptOutRecord | undefined>;
920
945
  /** {@link sessionCaptureOptOut} with the FAULT axis preserved (codex review, 亲核 adopted):
921
946
  * `fault: true` ⇔ the durable store THREW — the record state is INDETERMINATE, which the
922
947
  * commit-deciding consumers (harvest opening read, pre-commit re-read, the runner's read
@@ -926,11 +951,14 @@ export declare class MemoryEngine {
926
951
  sessionCaptureOptOutOrFault(sessionId: string): {
927
952
  record?: SessionCaptureOptOutRecord;
928
953
  fault: boolean;
929
- };
954
+ } | Promise<{
955
+ record?: SessionCaptureOptOutRecord;
956
+ fault: boolean;
957
+ }>;
930
958
  /** Every capture-opted-out session id (durable roster ∪ in-process marks). THROWS on an
931
959
  * enumeration failure — the one consumer (the consolidation eligibility arm, §2.4) is
932
960
  * fail-closed by design: "roster unknown" must refuse the run, never read as "no one opted out". */
933
- listCaptureOptOutSessions(): Set<string>;
961
+ listCaptureOptOutSessions(): Set<string> | Promise<Set<string>>;
934
962
  /**
935
963
  * design/383 §2.3 — the mid-session flip's BOUNDARY-ISOLATION SWEEP over the write plane: files
936
964
  * under the writable root that this session window added or changed (vs the materialize baseline)
@@ -1445,6 +1473,35 @@ export declare class MemoryEngine {
1445
1473
  * registry THROWS fail-closed — the same reason an unsupported one never reads as empty.
1446
1474
  */
1447
1475
  listMemoryScopes(): Promise<MemoryScopeEnumeration>;
1476
+ /**
1477
+ * design/383 §S-7 (#511 件1) — the HOST's per-session memory-status read face: the data supply
1478
+ * for the "memory capture is off / N committed, M folded" disclosure family and the
1479
+ * resume-visibility answer (state face, not event face — notice dedup semantics are untouched).
1480
+ * A server projects it onto a wire endpoint as a PURE derivation of these keys.
1481
+ *
1482
+ * EVERY key is optional and its ABSENCE means "that fact's source could not be read" — a fault
1483
+ * never coins a `false`/`0` stand-in (the §3.1 fault-transit law):
1484
+ * - `captureOptedOut` — the capture opt-out state. Present `true` (a standing record — with
1485
+ * `optOutSource: "record"`) or present `false` (the store answered and no one-way record
1486
+ * exists — genuine "capture on"). ABSENT ⇔ the record store faulted: the capture state is
1487
+ * INDETERMINATE and `optOutSource: "fault"` says so (the 383 片2/3 `captureIndeterminate`
1488
+ * axis, projected — never a boolean).
1489
+ * - `committedCount` — how many committed entries carry this session's lineage contribution
1490
+ * (the lineage ledger's committed set). Absent ⇔ the ledger is unreadable.
1491
+ * - `foldedCount` — of those, how many appear among the `distilled.inputs` of committed
1492
+ * consolidation products (the lineage × distilled.inputs intersection — "already folded into
1493
+ * long-term memory"). Absent ⇔ the ledger, the scope enumeration (a backend without
1494
+ * `listScopes` cannot name the header universe), or the product read is unreadable.
1495
+ * - `lastCaptureAt` — the newest `lastAt` over this session's lineage contributions (ms epoch).
1496
+ * CHEAP by construction — it rides the SAME single ledger read as `committedCount`, so it is
1497
+ * carried rather than dropped; absent ⇔ the ledger is unreadable OR the session has no
1498
+ * committed contribution at all (there is no such moment to name — the one key whose absence
1499
+ * also covers "no fact exists", stated here so consumers need not guess).
1500
+ *
1501
+ * Side-effect-free committed reads throughout (capture face + lineage sidecar + audit-face
1502
+ * headers/getByIds); never throws — an unreadable source is an absent key, which IS the answer.
1503
+ */
1504
+ sessionMemoryStatus(sessionId: string): Promise<SessionMemoryStatus>;
1448
1505
  /** The committed, side-effect-free audit read: the adoption-restricted committed view when the
1449
1506
  * backend offers one (zero-copy File — ledger+shadow, no disk adoption), else the non-adopting
1450
1507
  * retrieval view (copy-out File), else the backend itself (Pg/TiDB shapes — naturally committed