@sema-agent/core 7.0.1 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.0.2 — 2026-08-30
4
+
5
+ ### Added
6
+ - **`MemoryEngine.sessionMemoryStatus(sessionId)`** (#511①, design/383 §S-7 — the signed-design read face missed at build): `{captureOptedOut?, committedCount?, foldedCount?, optOutSource?, lastCaptureAt?}`. Every key optional — absence means that fact's source was unreadable, never a coined false/0 stand-in; a store fault answers `optOutSource:"fault"` with `captureOptedOut` ABSENT (the indeterminate axis projected); `foldedCount` counts evidence-only input rows over the full product blocks; the face never throws. New exported type `SessionMemoryStatus` (export surface 1841→1842). Server projects the wire endpoint as a pure derivation ([5786]).
7
+ - **`SessionCaptureRecordStore` dual form** (#511②): the three legs widen to `T | Promise<T>` — a SQL/async backend plugs in with Promise signatures while every existing sync implementation compiles untouched (the additive mechanical proof). The dual form propagates: sync deployments stay sync-transparent end to end; the sync-facing public seats (`MemoryEngineSession.captureOptOut.*`, `ToolExecuteContext.memoryCaptureOptedOut/…Indeterminate`, `parentMemoryCaptureState`) go dual with the await law documented — per-leg failure mapping preserved (mark rejection ⇒ unpersisted, read rejection ⇒ the indeterminate fault axis).
8
+ - **`best` model alias** (#511③, CC 250 $L parity): with `RunnerDeps.tiers` configured, `"best"` resolves to the first DIRECTLY bound tier walking `ultra→max→pro→flash→lite` from the head ("best available", deliberately not a fixed alias-table row). Explicit spellings win over the dynamic walk: a deployment catalog key named `best`, or an explicit `tiers.best` binding. No tiers ⇒ the word stays an unknown ref (refused, never coined).
9
+
10
+ ### Changed — narrowing (named)
11
+ - **Agent tool `model` argument: non-string values on the schema-外 direct-call lane refuse typed** (#511④, test [5781] codex observation): a number/array/boolean/garbage-object now refuses `model.invalid` (closed-set ADDITION beside `model.empty`/`model.unknown` — downstream pinning the details.error set gains a row) instead of crashing or silently no-oping; a legal Model-shape object (string id+api) passes through as the per-call model; strings keep every existing arm byte-identical.
12
+ - **`modelNote` fork-ignored and roster-less arms defuse the model-authored word** (#511⑤, census P2): both arms now run the same `inlineUntrusted` fold (newline fold + fence/envelope-tag neutralization + 80 cap) the family's other three arms already ran — a hostile `model` word can no longer carry raw newlines or fence sentinels into a top-level report line. Clean short words render byte-identical.
13
+
3
14
  ## 7.0.1 — 2026-08-30
4
15
 
5
16
  ### Added
@@ -1590,6 +1590,19 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1590
1590
  };
1591
1591
  }
1592
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
+ }
1593
1606
  if (typeof a.model === "string" && a.model.trim() === "" && !wantsFork && reviveClaim === undefined) {
1594
1607
  return {
1595
1608
  isError: true,
@@ -1602,6 +1615,14 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1602
1615
  let modelNote;
1603
1616
  const judgedModels = activeModels();
1604
1617
  const rosterKeys = judgedModels !== undefined ? Object.keys(judgedModels) : [];
1618
+ if (modelObjectForm) {
1619
+ if (wantsFork) {
1620
+ modelNote = `note: model "${inlineUntrusted(a.model.id, 80)}" was ignored — a fork always runs on the caller's model.`;
1621
+ }
1622
+ else {
1623
+ perCallModel = a.model;
1624
+ }
1625
+ }
1605
1626
  if (typeof a.model === "string" && requestedModel === undefined) {
1606
1627
  modelNote = wantsFork
1607
1628
  ? `note: model "" was ignored — a fork always runs on the caller's model.`
@@ -1609,10 +1630,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1609
1630
  }
1610
1631
  else if (requestedModel !== undefined) {
1611
1632
  if (wantsFork) {
1612
- modelNote = `note: model "${requestedModel}" was ignored — a fork always runs on the caller's model.`;
1633
+ modelNote = `note: model "${inlineUntrusted(requestedModel, 80)}" was ignored — a fork always runs on the caller's model.`;
1613
1634
  }
1614
1635
  else if (rosterKeys.length === 0) {
1615
- modelNote = `note: model "${requestedModel}" was NOT applied — this delegation tool has no model roster configured; the sub-agent ran on its default (inherited) model.`;
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.`;
1616
1637
  }
1617
1638
  else {
1618
1639
  try {
@@ -1783,7 +1804,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
1783
1804
  }
1784
1805
  })()
1785
1806
  : childModel?.id;
1786
- const rowModelKey = perCallModel !== undefined ? requestedModel : typeof childModel === "string" ? childModel : childModel?.id;
1807
+ const rowModelKey = perCallModel !== undefined ? requestedModel ?? perCallModel.id : typeof childModel === "string" ? childModel : childModel?.id;
1787
1808
  const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
1788
1809
  const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
1789
1810
  const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
@@ -2027,6 +2048,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2027
2048
  const provenanceRequest = ctx.delegationProvenanceForChildren?.();
2028
2049
  const childProvenanceRef = provenanceRequest !== undefined ? { current: newDelegationProvenanceAggregate() } : undefined;
2029
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;
2030
2054
  const childInternals = {
2031
2055
  ...(inheritedManifestScope ? { inheritedManifestScope } : {}),
2032
2056
  ...(childProvenanceRef !== undefined && provenanceRequest !== undefined ? { delegationProvenance: { ref: childProvenanceRef, contentSafety: provenanceRequest } } : {}),
@@ -2045,8 +2069,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
2045
2069
  ...(ctx.interactionPosture !== undefined ? { parentInteractionPosture: ctx.interactionPosture } : {}),
2046
2070
  ...(reviveClaim === undefined
2047
2071
  ? {
2048
- ...(ctx.memoryCaptureOptedOut === true ? { memoryCaptureFloor: true } : {}),
2049
- ...(ctx.memoryCaptureOptedOut !== true && ctx.memoryCaptureIndeterminate === true ? { memoryCaptureFloorIndeterminate: true } : {}),
2072
+ ...(liveCaptureFloor?.optedOut === true ? { memoryCaptureFloor: true } : {}),
2073
+ ...(liveCaptureFloor?.optedOut !== true && liveCaptureFloor?.indeterminate === true ? { memoryCaptureFloorIndeterminate: true } : {}),
2050
2074
  ...(ctx.memoryCaptureControlDir !== undefined ? { memoryCaptureQueryDir: ctx.memoryCaptureControlDir } : {}),
2051
2075
  ...(ctx.memoryCaptureAncestors !== undefined ? { memoryCaptureAncestors: ctx.memoryCaptureAncestors } : {}),
2052
2076
  }
@@ -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
@@ -457,37 +457,47 @@ export class MemoryEngine {
457
457
  return "unpersisted";
458
458
  }
459
459
  }
460
- let outcome;
460
+ let raw;
461
461
  try {
462
- outcome = this.captureRecords.mark(sessionId, record);
462
+ raw = this.captureRecords.mark(sessionId, record);
463
463
  }
464
464
  catch {
465
- outcome = "unpersisted";
465
+ raw = "unpersisted";
466
466
  }
467
- if (outcome !== "unpersisted" && !this.captureOptOutSessions.has(sessionId))
468
- this.captureOptOutSessions.set(sessionId, record);
469
- return outcome;
467
+ const settle = (outcome) => {
468
+ if (outcome !== "unpersisted" && !this.captureOptOutSessions.has(sessionId))
469
+ this.captureOptOutSessions.set(sessionId, record);
470
+ return outcome;
471
+ };
472
+ return raw instanceof Promise ? raw.then(settle, () => "unpersisted") : settle(raw);
470
473
  }
471
474
  sessionCaptureOptOut(sessionId) {
472
- return this.sessionCaptureOptOutOrFault(sessionId).record;
475
+ const r = this.sessionCaptureOptOutOrFault(sessionId);
476
+ return r instanceof Promise ? r.then((s) => s.record) : r.record;
473
477
  }
474
478
  sessionCaptureOptOutOrFault(sessionId) {
475
479
  const inProcess = this.captureOptOutSessions.get(sessionId);
476
480
  if (inProcess !== undefined)
477
481
  return { record: inProcess, fault: false };
482
+ let raw;
478
483
  try {
479
- const record = this.captureRecords.read(sessionId);
480
- return record !== undefined ? { record, fault: false } : { fault: false };
484
+ raw = this.captureRecords.read(sessionId);
481
485
  }
482
486
  catch {
483
487
  return { fault: true };
484
488
  }
489
+ const fold = (record) => record !== undefined ? { record, fault: false } : { fault: false };
490
+ return raw instanceof Promise ? raw.then(fold, () => ({ fault: true })) : fold(raw);
485
491
  }
486
492
  listCaptureOptOutSessions() {
487
- const out = new Set(Object.keys(this.captureRecords.list()));
488
- for (const id of this.captureOptOutSessions.keys())
489
- out.add(id);
490
- return out;
493
+ const raw = this.captureRecords.list();
494
+ const fold = (rows) => {
495
+ const out = new Set(Object.keys(rows));
496
+ for (const id of this.captureOptOutSessions.keys())
497
+ out.add(id);
498
+ return out;
499
+ };
500
+ return raw instanceof Promise ? raw.then(fold) : fold(raw);
491
501
  }
492
502
  async sweepSessionCaptureResidue(handle) {
493
503
  const out = { swept: [], restored: [], failures: [] };
@@ -1546,7 +1556,7 @@ export class MemoryEngine {
1546
1556
  warnings: [],
1547
1557
  };
1548
1558
  const writeScope = handle.writeScope;
1549
- const captureRead = opts?.sessionId !== undefined ? this.sessionCaptureOptOutOrFault(opts.sessionId) : { fault: false };
1559
+ const captureRead = opts?.sessionId !== undefined ? await this.sessionCaptureOptOutOrFault(opts.sessionId) : { fault: false };
1550
1560
  const captureOptOut = captureRead.record;
1551
1561
  if (writeScope === null || opts?.admitNothing !== undefined || captureOptOut !== undefined || captureRead.fault) {
1552
1562
  try {
@@ -2194,7 +2204,7 @@ export class MemoryEngine {
2194
2204
  return report;
2195
2205
  }
2196
2206
  }
2197
- const preCommitRead = lineageSessionId !== undefined ? this.sessionCaptureOptOutOrFault(lineageSessionId) : { fault: false, record: undefined };
2207
+ const preCommitRead = lineageSessionId !== undefined ? await this.sessionCaptureOptOutOrFault(lineageSessionId) : { fault: false, record: undefined };
2198
2208
  if (preCommitRead.record !== undefined || preCommitRead.fault) {
2199
2209
  if (lineageArmed) {
2200
2210
  try {
@@ -2796,7 +2806,7 @@ export class MemoryEngine {
2796
2806
  return false;
2797
2807
  return frozenToken === undefined || held.token === frozenToken;
2798
2808
  }
2799
- consolidationEligibility(headers) {
2809
+ async consolidationEligibility(headers) {
2800
2810
  let exclusions;
2801
2811
  try {
2802
2812
  exclusions = this.readChallengeExclusions();
@@ -2823,7 +2833,7 @@ export class MemoryEngine {
2823
2833
  }
2824
2834
  let optOutSessions;
2825
2835
  try {
2826
- optOutSessions = this.listCaptureOptOutSessions();
2836
+ optOutSessions = await this.listCaptureOptOutSessions();
2827
2837
  }
2828
2838
  catch (err) {
2829
2839
  throw new ConsolidationRefusedError("memory.consolidation_governance_unreadable", `memory consolidation refused: the capture opt-out roster cannot be trusted (fail-closed): ${err instanceof Error ? err.message : String(err)}`);
@@ -2874,7 +2884,7 @@ export class MemoryEngine {
2874
2884
  }
2875
2885
  const face = this.committedAuditFace();
2876
2886
  const headers = await face.listHeaders([scope]);
2877
- const { eligible } = this.consolidationEligibility(headers);
2887
+ const { eligible } = await this.consolidationEligibility(headers);
2878
2888
  const fingerprint = opts.full === true ? undefined : row?.fingerprint;
2879
2889
  const candidateIds = [];
2880
2890
  for (const [id, h] of eligible) {
@@ -3040,7 +3050,7 @@ export class MemoryEngine {
3040
3050
  const at = this.now();
3041
3051
  const face = this.committedAuditFace();
3042
3052
  const headers = await face.listHeaders([scope]);
3043
- const { exclusions, superseded } = this.consolidationEligibility(headers);
3053
+ const { exclusions, superseded } = await this.consolidationEligibility(headers);
3044
3054
  const headerById = new Map(headers.map((h) => [h.id, h]));
3045
3055
  const wholeReasons = [];
3046
3056
  for (let i = 0; i < proposal.products.length; i++) {
@@ -3921,6 +3931,67 @@ export class MemoryEngine {
3921
3931
  }
3922
3932
  return await b.listScopes();
3923
3933
  }
3934
+ async sessionMemoryStatus(sessionId) {
3935
+ const out = {};
3936
+ const capture = await this.sessionCaptureOptOutOrFault(sessionId);
3937
+ if (capture.fault) {
3938
+ out.optOutSource = "fault";
3939
+ }
3940
+ else if (capture.record !== undefined) {
3941
+ out.captureOptedOut = true;
3942
+ out.optOutSource = "record";
3943
+ }
3944
+ else {
3945
+ out.captureOptedOut = false;
3946
+ }
3947
+ let mine;
3948
+ try {
3949
+ const committed = readLineageRecord(this.controlDir).committed;
3950
+ const ids = new Set();
3951
+ let lastAt;
3952
+ for (const [entryId, sessions] of Object.entries(committed)) {
3953
+ const c = sessions[sessionId];
3954
+ if (c === undefined)
3955
+ continue;
3956
+ ids.add(entryId);
3957
+ if (lastAt === undefined || c.lastAt > lastAt)
3958
+ lastAt = c.lastAt;
3959
+ }
3960
+ mine = ids;
3961
+ out.committedCount = ids.size;
3962
+ if (lastAt !== undefined)
3963
+ out.lastCaptureAt = lastAt;
3964
+ }
3965
+ catch {
3966
+ }
3967
+ if (mine !== undefined) {
3968
+ if (mine.size === 0) {
3969
+ out.foldedCount = 0;
3970
+ }
3971
+ else {
3972
+ try {
3973
+ const enumeration = await this.listMemoryScopes();
3974
+ if (enumeration.supported) {
3975
+ const face = this.committedAuditFace();
3976
+ const headers = await face.listHeaders(enumeration.scopes);
3977
+ const productIds = headers.filter((h) => h.distilled !== undefined).map((h) => h.id);
3978
+ const folded = new Set();
3979
+ if (productIds.length > 0) {
3980
+ for (const p of await face.getByIds(productIds)) {
3981
+ for (const row of p.frontmatter.distilled?.inputs ?? [])
3982
+ if (mine.has(row.id))
3983
+ folded.add(row.id);
3984
+ }
3985
+ }
3986
+ out.foldedCount = folded.size;
3987
+ }
3988
+ }
3989
+ catch {
3990
+ }
3991
+ }
3992
+ }
3993
+ return out;
3994
+ }
3924
3995
  committedAuditFace() {
3925
3996
  const b = this.backend;
3926
3997
  return b.restrictedAdoptionView?.({ audit: false }) ?? b.retrievalView?.() ?? this.backend;
@@ -1,4 +1,4 @@
1
- export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, } from "./engine.js";
1
+ export { MemoryEngine, buildMemoryInstruction, truncateIndex, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, memoryRecallDisciplineSegment, entryFileHeadCarriesOrigin, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, renderAnnouncements, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, type SessionMemoryStatus, } from "./engine.js";
2
2
  export { MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, } from "./tools.js";
3
3
  export { MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, MEMORY_PROVENANCE_RECALL_SENTENCE, MEMORY_PROVENANCE_SEARCH_SENTENCE, memoryExposureIndexRow, parseMemoryExposureIndexRow, } from "./provenance-wording.js";
4
4
  export { scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE } from "./scan.js";
@@ -407,11 +407,19 @@ export declare function listSessionCaptureOptOut(controlDir: string): Record<str
407
407
  * (`controlDir` + `sessionId` coordinates that outlive any live object — see the settlement handle
408
408
  * doc in prepare-task.ts), and a live store object cannot ride that lane; its migration needs its
409
409
  * own seam design and is registered here rather than half-built.
410
+ *
411
+ * DUAL FORM (#511 件2, additive): each leg may answer `T` OR `Promise<T>` — a SQL/remote carrier is
412
+ * async by nature and could not implement the sync-only trio at all. Every core consumption point
413
+ * awaits (or `.then`-chains where a sync public seat must stay sync-transparent), so a sync store's
414
+ * behavior is unchanged and an async store's Promise legs keep the exact same contract per leg:
415
+ * a rejected `mark` promise is held to the `"unpersisted"` refusal arm, a rejected `read` promise
416
+ * is the INDETERMINATE fault axis, a rejected `list` promise propagates as the enumeration failure.
417
+ * The File trio below keeps its synchronous signatures byte-identical.
410
418
  */
411
419
  export interface SessionCaptureRecordStore {
412
- mark(sessionId: string, record: SessionCaptureOptOutRecord): SessionCaptureOptOutMarkOutcome;
413
- read(sessionId: string): SessionCaptureOptOutRecord | undefined;
414
- list(): Record<string, SessionCaptureOptOutRecord>;
420
+ mark(sessionId: string, record: SessionCaptureOptOutRecord): SessionCaptureOptOutMarkOutcome | Promise<SessionCaptureOptOutMarkOutcome>;
421
+ read(sessionId: string): SessionCaptureOptOutRecord | undefined | Promise<SessionCaptureOptOutRecord | undefined>;
422
+ list(): Record<string, SessionCaptureOptOutRecord> | Promise<Record<string, SessionCaptureOptOutRecord>>;
415
423
  }
416
424
  /** The core-default {@link SessionCaptureRecordStore}: the file trio over `controlDir` (single-
417
425
  * process / single-host deployments keep today's carrier byte-identical). */
@@ -22,7 +22,12 @@ export declare const CC_MODEL_TIER_ALIASES: Readonly<Record<string, string>>;
22
22
  * - an existing catalog key of the same name WINS (deployment SHADOW semantics, mirroring agents):
23
23
  * a deployment that already ships a model literally named "pro" keeps it untouched;
24
24
  * - a binding may itself be a catalog name or a Model object (resolved through `resolveModel`);
25
- * an unknown binding name throws at construction (config error surfaces at boot, not first use).
25
+ * an unknown binding name throws at construction (config error surfaces at boot, not first use);
26
+ * - `"best"` (dynamic head-of-chain alias, CC `$L` parity) resolves to the FIRST tier in
27
+ * {@link DEFAULT_TIER_ORDER} that carries a DIRECT binding — "the strongest model this deployment
28
+ * actually bound", not a fixed tier — under the same SHADOW rule (a deployment catalog key named
29
+ * `best` wins); with no bound known-order tier (or no tiers at all) the key stays absent and
30
+ * `resolveModel("best")` refuses like any unknown ref.
26
31
  */
27
32
  /** the DISPLAY face of a string model ref: a CC tier ALIAS (haiku/sonnet/…) resolves to its
28
33
  * sema tier name (lite/flash/…) for labels — the alias verbatim reads as a strong claim about a
@@ -59,6 +59,15 @@ export function expandTiers(models, tiers) {
59
59
  if (m)
60
60
  out[alias] = m;
61
61
  }
62
+ if (!out["best"]) {
63
+ for (const name of DEFAULT_TIER_ORDER) {
64
+ const m = bound(name);
65
+ if (m) {
66
+ out["best"] = m;
67
+ break;
68
+ }
69
+ }
70
+ }
62
71
  return out;
63
72
  }
64
73
  export function parseModelMention(text, allowedNames) {
@@ -174,8 +174,8 @@ export async function prepareMemory(input) {
174
174
  };
175
175
  const onEngineIncident = (err) => deps.onError?.(err, { phase: "memory", sessionId });
176
176
  const adoptionRestricted = input.memoryPersistenceDeclared === false;
177
- const armCaptureOptOut = (eng) => {
178
- const standingRead = eng.sessionCaptureOptOutOrFault(sessionId);
177
+ const armCaptureOptOut = async (eng) => {
178
+ const standingRead = await eng.sessionCaptureOptOutOrFault(sessionId);
179
179
  if (standingRead.fault) {
180
180
  captureIndeterminate = true;
181
181
  deps.onError?.(new Error(`the capture opt-out record store is unreadable at prepare — this session's capture state is indeterminate, and every memory commit boundary (read-side adoption included) suppresses fail-closed until the store answers.`), { phase: "memory", sessionId });
@@ -199,7 +199,7 @@ export async function prepareMemory(input) {
199
199
  let anyFault = standingRead.fault;
200
200
  let found = false;
201
201
  for (const [ancestorId, ancestorDir] of rows) {
202
- const samePlane = eng.sessionCaptureOptOutOrFault(ancestorId);
202
+ const samePlane = await eng.sessionCaptureOptOutOrFault(ancestorId);
203
203
  if (samePlane.record !== undefined) {
204
204
  found = true;
205
205
  break;
@@ -208,7 +208,7 @@ export async function prepareMemory(input) {
208
208
  anyFault = true;
209
209
  if (ancestorDir !== undefined && ancestorDir !== eng.controlPlaneDir) {
210
210
  try {
211
- const crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
211
+ const crossPlane = deps.memoryCaptureRecordStore !== undefined ? await deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
212
212
  if (crossPlane !== undefined) {
213
213
  found = true;
214
214
  break;
@@ -230,7 +230,7 @@ export async function prepareMemory(input) {
230
230
  }
231
231
  let forkInherited = false;
232
232
  if (!floored && !captureDeclared && standing === undefined && input.captureForkOrigin !== undefined) {
233
- const src = eng.sessionCaptureOptOutOrFault(input.captureForkOrigin);
233
+ const src = await eng.sessionCaptureOptOutOrFault(input.captureForkOrigin);
234
234
  if (src.record !== undefined)
235
235
  forkInherited = true;
236
236
  else if (src.fault && !captureIndeterminate) {
@@ -246,7 +246,7 @@ export async function prepareMemory(input) {
246
246
  throw captureOptOutDeniedError(verdict.detail);
247
247
  }
248
248
  if (standing === undefined) {
249
- const outcome = eng.markSessionCaptureOptOut(sessionId, captureDeclared ? `declared on TaskSpec.memory.capture` : forkInherited ? `inherited capture opt-out from the forked source session` : `inherited capture opt-out floor from the spawning session`);
249
+ const outcome = await eng.markSessionCaptureOptOut(sessionId, captureDeclared ? `declared on TaskSpec.memory.capture` : forkInherited ? `inherited capture opt-out from the forked source session` : `inherited capture opt-out floor from the spawning session`);
250
250
  if (outcome === "unpersisted") {
251
251
  deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "declaration" }));
252
252
  throw captureOptOutUnpersistedError(sessionId, "declaration");
@@ -287,14 +287,14 @@ export async function prepareMemory(input) {
287
287
  let injectFn;
288
288
  let harvestBoth;
289
289
  let toolPlanes;
290
- const admitNothingOptsNow = () => {
290
+ const admitNothingOptsNow = async () => {
291
291
  if (input.memoryPersistenceDeclared === false) {
292
292
  return { admitNothing: { reason: "harvest admitted nothing: memory persistence is declared unavailable for this session (memoryPersistenceCapable: false)" } };
293
293
  }
294
294
  if (captureLineageIndeterminate) {
295
295
  return { admitNothing: { reason: `harvest admitted nothing: this session's fork-lineage metadata is unreadable — whether it continues an opted-out source cannot be established, so commits are suppressed fail-closed` } };
296
296
  }
297
- const own = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
297
+ const own = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : await writeEngine.sessionCaptureOptOutOrFault(sessionId);
298
298
  if (own.record !== undefined) {
299
299
  return { admitNothing: { reason: `harvest admitted nothing: this session declared memory capture opt-out (memory.capture: "off")` } };
300
300
  }
@@ -309,11 +309,11 @@ export async function prepareMemory(input) {
309
309
  if (input.captureForkOrigin !== undefined && !ancestors.has(input.captureForkOrigin))
310
310
  ancestors.set(input.captureForkOrigin, undefined);
311
311
  for (const [ancestorId, ancestorDir] of ancestors) {
312
- const samePlane = writeEngine.sessionCaptureOptOutOrFault(ancestorId);
312
+ const samePlane = await writeEngine.sessionCaptureOptOutOrFault(ancestorId);
313
313
  let crossPlane;
314
314
  if (ancestorDir !== undefined && ancestorDir !== writeEngine.controlPlaneDir) {
315
315
  try {
316
- crossPlane = deps.memoryCaptureRecordStore !== undefined ? deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
316
+ crossPlane = deps.memoryCaptureRecordStore !== undefined ? await deps.memoryCaptureRecordStore({ controlDir: ancestorDir }).read(ancestorId) : readSessionCaptureOptOut(ancestorDir, ancestorId);
317
317
  }
318
318
  catch {
319
319
  return { admitNothing: { reason: `harvest admitted nothing: a spawning session's capture record store is unreadable — the floor state is indeterminate and commits are suppressed fail-closed` } };
@@ -362,7 +362,7 @@ export async function prepareMemory(input) {
362
362
  const personal = createPersonalEngine(personalBackendChosen);
363
363
  const personalEngine = personal.engine;
364
364
  const p = planes;
365
- armCaptureOptOut(p.writePlane === "personal" ? personalEngine : projectEngine);
365
+ await armCaptureOptOut(p.writePlane === "personal" ? personalEngine : projectEngine);
366
366
  const projectHandle = await projectEngine.materialize(p.project, p.writePlane === "project" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
367
367
  materializedResidue.push(...planeScopes(p.project, p.writePlane === "project" ? memorySpec.writeScope : null));
368
368
  const personalHandle = await personalEngine.materialize(p.personal, p.writePlane === "personal" ? memorySpec.writeScope : null, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
@@ -393,7 +393,7 @@ export async function prepareMemory(input) {
393
393
  harvestBoth = async () => {
394
394
  const writeFirst = writeIsPersonal ? [personalEngine, personalHandle] : [projectEngine, projectHandle];
395
395
  const readOther = writeIsPersonal ? [projectEngine, projectHandle] : [personalEngine, personalHandle];
396
- const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...admitNothingOptsNow() });
396
+ const writeReport = await writeFirst[0].harvest(writeFirst[1], { ...pollutedOpts(writeFirst[0]), sessionId, ...(await admitNothingOptsNow()) });
397
397
  let readReport;
398
398
  let readFailure;
399
399
  try {
@@ -411,14 +411,14 @@ export async function prepareMemory(input) {
411
411
  else if (personalOnly) {
412
412
  const personal = createPersonalEngine(choosePersonalBackend());
413
413
  const personalEngine = personal.engine;
414
- armCaptureOptOut(personalEngine);
414
+ await armCaptureOptOut(personalEngine);
415
415
  const handle = await personalEngine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
416
416
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
417
417
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
418
418
  writeEngine = personalEngine;
419
419
  writeHandle = handle;
420
420
  injectFn = () => personalEngine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
421
- harvestBoth = () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...admitNothingOptsNow() });
421
+ harvestBoth = async () => personalEngine.harvest(handle, { ...pollutedOpts(personalEngine), sessionId, ...(await admitNothingOptsNow()) });
422
422
  toolPlanes = [
423
423
  {
424
424
  backend: retrievalBackend(personal.backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -439,14 +439,14 @@ export async function prepareMemory(input) {
439
439
  ...captureStoreOpt(backend, identityKey !== undefined ? deriveProjectControlDir(engineRoot, identityKey) : deriveRepoControlPlaneDir(engineRoot, repoRoot)),
440
440
  ...(input.deps.memoryConsolidation !== undefined ? { consolidation: input.deps.memoryConsolidation } : {}),
441
441
  });
442
- armCaptureOptOut(engine);
442
+ await armCaptureOptOut(engine);
443
443
  const handle = await engine.materialize(memorySpec.scopes, memorySpec.writeScope, { adoptionRestricted: adoptionRestricted || captureOptedOut || captureIndeterminate, sessionId });
444
444
  materializedResidue.push(...planeScopes(memorySpec.scopes, memorySpec.writeScope));
445
445
  memoryMarkedEntriesServed = handle.markedEntriesPresent === true;
446
446
  writeEngine = engine;
447
447
  writeHandle = handle;
448
448
  injectFn = () => engine.inject(handle, { writeToolMounted: input.writeToolsMounted, reminderMark: input.reminderMark });
449
- harvestBoth = () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...admitNothingOptsNow() });
449
+ harvestBoth = async () => engine.harvest(handle, { ...pollutedOpts(engine), sessionId, ...(await admitNothingOptsNow()) });
450
450
  toolPlanes = [
451
451
  {
452
452
  backend: retrievalBackend(backend, adoptionRestricted || captureOptedOut || captureIndeterminate || memorySpec.writeScope === null),
@@ -457,7 +457,7 @@ export async function prepareMemory(input) {
457
457
  },
458
458
  ];
459
459
  }
460
- memoryWriteGateRef.current = (w) => {
460
+ memoryWriteGateRef.current = async (w) => {
461
461
  if (readOnlyEngine !== undefined && readOnlyHandle !== undefined) {
462
462
  const ro = readOnlyEngine.gateWrite(readOnlyHandle, w.key, w.content);
463
463
  if (!ro.ok)
@@ -477,7 +477,7 @@ export async function prepareMemory(input) {
477
477
  {
478
478
  const root = writeHandle.writableRoot;
479
479
  if (w.key === root || w.key.startsWith(`${root}${sep}`)) {
480
- const state = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : writeEngine.sessionCaptureOptOutOrFault(sessionId);
480
+ const state = captureOptedOut ? { record: { at: 0, reason: "in-run state" }, fault: false } : await writeEngine.sessionCaptureOptOutOrFault(sessionId);
481
481
  if (state.record !== undefined || state.fault) {
482
482
  return {
483
483
  ok: false,
@@ -555,12 +555,19 @@ export async function prepareMemory(input) {
555
555
  inject: injectFn,
556
556
  harvest: harvestSafe,
557
557
  captureOptOut: {
558
- optedOut: () => captureOptedOut || writeEngine.sessionCaptureOptOutOrFault(sessionId).record !== undefined,
558
+ optedOut: () => {
559
+ if (captureOptedOut)
560
+ return true;
561
+ const r = writeEngine.sessionCaptureOptOutOrFault(sessionId);
562
+ return r instanceof Promise ? r.then((s) => s.record !== undefined) : r.record !== undefined;
563
+ },
559
564
  indeterminate: () => {
560
565
  if (captureOptedOut)
561
566
  return false;
562
- const s = writeEngine.sessionCaptureOptOutOrFault(sessionId);
563
- return s.record === undefined && (captureIndeterminate || s.fault);
567
+ const r = writeEngine.sessionCaptureOptOutOrFault(sessionId);
568
+ return r instanceof Promise
569
+ ? r.then((s) => s.record === undefined && (captureIndeterminate || s.fault))
570
+ : r.record === undefined && (captureIndeterminate || r.fault);
564
571
  },
565
572
  flip: async (reason) => {
566
573
  const entitlement = await resolveCaptureEntitlementFresh();
@@ -576,7 +583,7 @@ export async function prepareMemory(input) {
576
583
  throw e;
577
584
  }
578
585
  const trimmed = reason?.trim();
579
- const outcome = writeEngine.markSessionCaptureOptOut(sessionId, trimmed !== undefined && trimmed !== "" ? `host flip verb: ${trimmed.slice(0, 200)}` : "host flip verb");
586
+ const outcome = await writeEngine.markSessionCaptureOptOut(sessionId, trimmed !== undefined && trimmed !== "" ? `host flip verb: ${trimmed.slice(0, 200)}` : "host flip verb");
580
587
  if (outcome === "unpersisted") {
581
588
  deliverEngineNotice(deps.onNotice, memoryCaptureOptOutUnpersistedNotice({ sessionId, ingress: "flip-verb" }));
582
589
  throw captureOptOutUnpersistedError(sessionId, "flip-verb");
@@ -550,8 +550,9 @@ export interface Prepared {
550
550
  * the memory session mounted.
551
551
  */
552
552
  captureOptOut?: {
553
- optedOut: () => boolean;
554
- indeterminate: () => boolean;
553
+ /** Dual form: a sync capture store answers synchronously; a Promise-form store answers a Promise. `await` is correct on either arm. */
554
+ optedOut: () => boolean | Promise<boolean>;
555
+ indeterminate: () => boolean | Promise<boolean>;
555
556
  flip: (reason?: string) => Promise<{
556
557
  outcome: "created" | "existed";
557
558
  }>;
@@ -894,6 +894,15 @@ function explicitlyDeferredMemoryTrio(mounted, roster, deferNames) {
894
894
  function memoryGroupRetractionSet(builtinDeferPairNames, engineTrioInPlay) {
895
895
  return new Set([...builtinDeferPairNames, ...(engineTrioInPlay ? MEMORY_ENGINE_TOOL_NAMES : [])]);
896
896
  }
897
+ function assembleParentCaptureState(o, i, ctl, ancestors) {
898
+ const build = (optedOut, indeterminate) => ({
899
+ optedOut: optedOut === true,
900
+ indeterminate: indeterminate === true,
901
+ ...(ctl !== undefined ? { controlDir: ctl } : {}),
902
+ ancestors,
903
+ });
904
+ return o instanceof Promise || i instanceof Promise ? Promise.all([o, i]).then(([ov, iv]) => build(ov, iv)) : build(o, i);
905
+ }
897
906
  async function spliceSessionOverlayRows(overlay, sessionId, persisted, tracer, hostTaskId) {
898
907
  if (overlay === undefined)
899
908
  return persisted;
@@ -1501,10 +1510,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1501
1510
  ...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
1502
1511
  ...(spec.memoryPersistenceCapable !== undefined ? { memoryPersistenceCapable: spec.memoryPersistenceCapable } : {}),
1503
1512
  get memoryCaptureOptedOut() {
1504
- return memoryEngineSession?.captureOptOut?.optedOut() === true;
1513
+ return memoryEngineSession?.captureOptOut?.optedOut() ?? false;
1505
1514
  },
1506
1515
  get memoryCaptureIndeterminate() {
1507
- return memoryEngineSession?.captureOptOut?.indeterminate() === true;
1516
+ return memoryEngineSession?.captureOptOut?.indeterminate() ?? false;
1508
1517
  },
1509
1518
  get memoryCaptureControlDir() {
1510
1519
  return memoryEngineSession?.engine.controlPlaneDir;
@@ -1731,12 +1740,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1731
1740
  ...(resolvedInteractionPosture !== undefined ? { parentInteractionPosture: resolvedInteractionPosture } : {}),
1732
1741
  parentMemoryCaptureState: () => {
1733
1742
  const ctl = memoryEngineSession?.engine.controlPlaneDir;
1734
- return {
1735
- optedOut: memoryEngineSession?.captureOptOut?.optedOut() === true,
1736
- indeterminate: memoryEngineSession?.captureOptOut?.indeterminate() === true,
1737
- ...(ctl !== undefined ? { controlDir: ctl } : {}),
1738
- ancestors: [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }],
1739
- };
1743
+ const co = memoryEngineSession?.captureOptOut;
1744
+ return assembleParentCaptureState(co?.optedOut() ?? false, co?.indeterminate() ?? false, ctl, [...(internals?.memoryCaptureAncestors ?? []), { sessionId, ...(ctl !== undefined ? { controlDir: ctl } : {}) }]);
1740
1745
  },
1741
1746
  autoModeReview: () => (autoModeDecider !== undefined ? { decider: autoModeDecider } : undefined),
1742
1747
  workflowDepth: internals?.workflowDepth,
@@ -934,8 +934,14 @@ export interface ToolExecuteContext {
934
934
  * opted-out session captures nothing, whatever its chosen AgentDefinition says — the opt-out is
935
935
  * a floor no selection loosens (the `memoryPersistenceCapable:false` floor's exact law, on the
936
936
  * privacy axis). Trusted Runner-filled seat, never a model/tool argument.
937
+ *
938
+ * DUAL FORM (#511 件2): on a deployment whose capture record store is synchronous (the default
939
+ * file trio) this is a plain boolean, byte-identical to before; a Promise-form
940
+ * {@link RunnerDeps.memoryCaptureRecordStore} makes the live read answer a `Promise<boolean>`.
941
+ * Consumers must `await` (identity on the boolean arm) — a bare `=== true` on the Promise arm
942
+ * would coin `false`, the exact un-floored escape this seat closes.
937
943
  */
938
- memoryCaptureOptedOut?: boolean;
944
+ memoryCaptureOptedOut?: boolean | Promise<boolean>;
939
945
  /**
940
946
  * design/383 §2.5 (rescan post-6.0.0-RC) — the floor seat's THIRD state: TRUE ⇔ the spawning
941
947
  * session's capture opt-out state is INDETERMINATE at the moment a delegation tool reads this
@@ -947,8 +953,10 @@ export interface ToolExecuteContext {
947
953
  * irreversible record is ever minted off an unreadable state); a readable-and-clean answer
948
954
  * proceeds clean. Never TRUE beside {@link memoryCaptureOptedOut} — a known opt-out is
949
955
  * determinate. Trusted Runner-filled seat, never a model/tool argument.
956
+ * Dual form like its twin (#511 件2): `boolean` over a sync store, `Promise<boolean>` over a
957
+ * Promise-form store — consumers `await`.
950
958
  */
951
- memoryCaptureIndeterminate?: boolean;
959
+ memoryCaptureIndeterminate?: boolean | Promise<boolean>;
952
960
  /** design/383 §2.5 — the spawning session's write-plane control dir (the coordinate its capture
953
961
  * record is keyed under), forwarded beside the floor bit so a cross-plane child's record-query
954
962
  * leg reads the PARENT's carrier, not its own plane's. Trusted Runner-filled seat. */
package/dist/index.d.ts CHANGED
@@ -179,7 +179,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
179
179
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
180
180
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleHitRule, type PersistedRuleUnreadable, type PersistedRuleCoverage, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookInvocationIdentity, type UserPromptSubmitContext, type PostToolBatchContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
181
181
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
182
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, type OriginClearanceShadow, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, type CleanArmLeakFinding, type CleanArmLeakVerdict, type MemoryDistillerPurityContract, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, type SessionCaptureOptOutRecord, type SessionCaptureOptOutMarkOutcome, type SessionCaptureRecordStore, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
182
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_ORIGIN_CAUSES, committedOriginOf, originEquals, consolidationExposedFrontmatter, ambiguousOriginRepresentation, type OriginClearanceRow, type OriginClearanceEvent, type OriginClearanceShadow, committedDistilledOf, distilledEquals, type MemoryEntryDistilled, type MemoryEntryDistilledInput, CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, memoryConsolidationRecommendedNotice, memoryConsolidationCommittedNotice, memoryConsolidationConflictNotice, memoryConsolidationIncompleteNotice, memoryConsolidationRefusedNotice, memoryConsolidationWithheldNotice, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, type ConsolidationCommitReceipt, type ConsolidationReconcileReport, type ConsolidationResolveReceipt, type ConsolidationPlanSummary, type ConsolidationPlanFoldEvidence, type SessionMemoryStatus, DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, MEMORY_DISTILLER_PURITY_CONTRACT_V1, detectCleanArmVerbatimLeak, type CleanArmLeakFinding, type CleanArmLeakVerdict, type MemoryDistillerPurityContract, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, CONSOLIDATION_RUN_STOP_REASONS, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type ConsolidationRunStopReason, type RunMemoryConsolidationOptions, isInstructionEntry, type MemoryEntryOrigin, type MemoryOriginCause, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, SESSION_CAPTURE_OPTOUT_DIR, markSessionCaptureOptOut, readSessionCaptureOptOut, listSessionCaptureOptOut, fileSessionCaptureRecordStore, type SessionCaptureOptOutRecord, type SessionCaptureOptOutMarkOutcome, type SessionCaptureRecordStore, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_INDEX_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type CleanMemorySearchHit, type ExposedMemorySearchHit, MEMORY_EXPOSURE_BANNER, MEMORY_EXPOSURE_HANDLE_TAG, memoryExposureIndexRow, type MemoryGetDetails, type MemoryIndexDetails, type MemoryIndexRow, type CleanMemoryIndexRow, type ExposedMemoryIndexRow, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type MemoryScopeEnumeration, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
183
183
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
184
184
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
185
185
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -245,7 +245,15 @@ export interface RunWorkflowToolDeps {
245
245
  sessionId: string;
246
246
  controlDir?: string;
247
247
  }>;
248
- };
248
+ } | Promise<{
249
+ optedOut: boolean;
250
+ indeterminate: boolean;
251
+ controlDir?: string;
252
+ ancestors?: ReadonlyArray<{
253
+ sessionId: string;
254
+ controlDir?: string;
255
+ }>;
256
+ }>;
249
257
  /** TRUSTED nesting depth from the run's internals (NOT a tool param) — passed to `startWorkflow` so a
250
258
  * cross-process child workflow is rejected by the one-level guard. */
251
259
  workflowDepth?: number;
@@ -515,12 +515,17 @@ export async function createRunWorkflowTool(d) {
515
515
  ...(() => {
516
516
  if ("memoryCaptureOptedOut" in ctx) {
517
517
  return {
518
- parentMemoryCaptureState: () => ({
519
- optedOut: ctx.memoryCaptureOptedOut === true,
520
- indeterminate: ctx.memoryCaptureIndeterminate === true,
521
- ...(ctx.memoryCaptureControlDir !== undefined ? { controlDir: ctx.memoryCaptureControlDir } : {}),
522
- ...(ctx.memoryCaptureAncestors !== undefined ? { ancestors: ctx.memoryCaptureAncestors } : {}),
523
- }),
518
+ parentMemoryCaptureState: () => {
519
+ const o = ctx.memoryCaptureOptedOut ?? false;
520
+ const i = ctx.memoryCaptureIndeterminate ?? false;
521
+ const build = (optedOut, indeterminate) => ({
522
+ optedOut: optedOut === true,
523
+ indeterminate: indeterminate === true,
524
+ ...(ctx.memoryCaptureControlDir !== undefined ? { controlDir: ctx.memoryCaptureControlDir } : {}),
525
+ ...(ctx.memoryCaptureAncestors !== undefined ? { ancestors: ctx.memoryCaptureAncestors } : {}),
526
+ });
527
+ return o instanceof Promise || i instanceof Promise ? Promise.all([o, i]).then(([ov, iv]) => build(ov, iv)) : build(o, i);
528
+ },
524
529
  };
525
530
  }
526
531
  return d.parentMemoryCaptureState !== undefined ? { parentMemoryCaptureState: d.parentMemoryCaptureState } : {};
@@ -405,7 +405,15 @@ export interface RunWorkflowOptions {
405
405
  sessionId: string;
406
406
  controlDir?: string;
407
407
  }>;
408
- };
408
+ } | Promise<{
409
+ optedOut: boolean;
410
+ indeterminate: boolean;
411
+ controlDir?: string;
412
+ ancestors?: ReadonlyArray<{
413
+ sessionId: string;
414
+ controlDir?: string;
415
+ }>;
416
+ }>;
409
417
  /** Call-time getter for the HOST run's RESOLVED Model object. A spawned agent whose
410
418
  * fold chain (script spec → agentType → governance baseline) produced NO model inherits the
411
419
  * parent's full object — baseUrl/key routing included — instead of falling to a string/role
@@ -619,8 +619,8 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
619
619
  ...(opts.placementRoot !== undefined ? { placementRoot: opts.placementRoot } : {}),
620
620
  ...(opts.originatingSessionId !== undefined ? { parentSessionId: opts.originatingSessionId } : {}),
621
621
  };
622
- const captureFloorSeatsNow = () => {
623
- const s = opts.parentMemoryCaptureState?.();
622
+ const captureFloorSeatsNow = async () => {
623
+ const s = await opts.parentMemoryCaptureState?.();
624
624
  if (s === undefined)
625
625
  return {};
626
626
  return {
@@ -1255,7 +1255,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1255
1255
  opts.onForwardEvent(e.type === "task_progress" ? { ...e, workflowRunId: runId, workflowAgentLabel: label } : e);
1256
1256
  }
1257
1257
  : undefined;
1258
- const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...captureFloorSeatsNow(), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1258
+ const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...(await captureFloorSeatsNow()), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
1259
1259
  let attempts = 0;
1260
1260
  let throttleRetried = false;
1261
1261
  let lastAttemptReason;
@@ -1574,7 +1574,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1574
1574
  ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}),
1575
1575
  ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}),
1576
1576
  ...spawnAttribution,
1577
- ...captureFloorSeatsNow(),
1577
+ ...(await captureFloorSeatsNow()),
1578
1578
  ...(enrichedForwardS !== undefined ? { onForwardEvent: enrichedForwardS } : {}),
1579
1579
  delegationTaskType: "workflow",
1580
1580
  agentName: label,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.0.1",
3
+ "version": "7.0.2",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
3
  "_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
4
- "count": 1841,
4
+ "count": 1842,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -996,6 +996,7 @@
996
996
  "SessionCaptureOptOutRecord": "interface",
997
997
  "SessionCaptureRecordStore": "interface",
998
998
  "SessionError": "class",
999
+ "SessionMemoryStatus": "interface",
999
1000
  "SessionMetadata": "interface",
1000
1001
  "SessionPermissionRules": "interface",
1001
1002
  "SessionPlacement": "interface",
@@ -2839,6 +2840,7 @@
2839
2840
  "SessionCaptureOptOutRecord": "advanced",
2840
2841
  "SessionCaptureRecordStore": "advanced",
2841
2842
  "SessionError": "stable",
2843
+ "SessionMemoryStatus": "advanced",
2842
2844
  "SessionMetadata": "stable",
2843
2845
  "SessionPermissionRules": "advanced",
2844
2846
  "SessionPlacement": "advanced",