@remnic/core 9.3.735 → 9.3.737

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/access-boundary.d.ts +17 -6
  2. package/dist/access-cli.js +1 -1
  3. package/dist/access-http.d.ts +17 -6
  4. package/dist/access-mcp.d.ts +17 -6
  5. package/dist/access-operations.d.ts +17 -6
  6. package/dist/access-schema.d.ts +4 -4
  7. package/dist/{access-service-CVx7mmvK.d.ts → access-service-DwQ6BIQw.d.ts} +3 -3
  8. package/dist/access-service.d.ts +16 -5
  9. package/dist/access-surface-catalog.d.ts +17 -6
  10. package/dist/action-confidence.d.ts +1 -1
  11. package/dist/active-memory-bridge.d.ts +1 -1
  12. package/dist/active-recall.d.ts +1 -1
  13. package/dist/behavior-learner.d.ts +1 -1
  14. package/dist/behavior-signals.d.ts +1 -1
  15. package/dist/bootstrap.d.ts +16 -4
  16. package/dist/briefing.d.ts +1 -1
  17. package/dist/buffer-surprise-report.d.ts +1 -1
  18. package/dist/buffer.d.ts +1 -1
  19. package/dist/calibration.d.ts +1 -1
  20. package/dist/capabilities.d.ts +1 -1
  21. package/dist/{catalog-BVhDEUhT.d.ts → catalog-_Bap6vfD.d.ts} +1 -1
  22. package/dist/causal-behavior.d.ts +1 -1
  23. package/dist/causal-consolidation.d.ts +1 -1
  24. package/dist/causal-trajectory-graph.d.ts +1 -1
  25. package/dist/{chunk-FLCYY5JX.js → chunk-PB5YON2G.js} +559 -481
  26. package/dist/chunk-PB5YON2G.js.map +1 -0
  27. package/dist/{cli-Cx5Mxwk7.d.ts → cli-BlEIWDQw.d.ts} +3 -3
  28. package/dist/cli.d.ts +9 -7
  29. package/dist/compounding/engine.d.ts +1 -1
  30. package/dist/compounding/preference-consolidator.d.ts +1 -1
  31. package/dist/compression-optimizer.d.ts +1 -1
  32. package/dist/config.d.ts +1 -1
  33. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  34. package/dist/connectors/codex-materialize.d.ts +1 -1
  35. package/dist/connectors/index.d.ts +1 -1
  36. package/dist/consolidation-provenance-check.d.ts +1 -1
  37. package/dist/consolidation-undo.d.ts +1 -1
  38. package/dist/contradiction/index.d.ts +1 -1
  39. package/dist/conversation-index/backend.d.ts +1 -1
  40. package/dist/conversation-index/chunker.d.ts +1 -1
  41. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  42. package/dist/conversation-index/indexer.d.ts +1 -1
  43. package/dist/conversation-index/search.d.ts +1 -1
  44. package/dist/day-summary.d.ts +1 -1
  45. package/dist/delinearize.d.ts +1 -1
  46. package/dist/direct-answer-wiring.d.ts +1 -1
  47. package/dist/direct-answer.d.ts +1 -1
  48. package/dist/embedding-fallback.d.ts +1 -1
  49. package/dist/enrichment/index.d.ts +1 -1
  50. package/dist/entity-retrieval.d.ts +1 -1
  51. package/dist/entity-schema.d.ts +1 -1
  52. package/dist/explicit-capture.d.ts +16 -4
  53. package/dist/extraction-faithfulness.d.ts +1 -1
  54. package/dist/extraction-judge-telemetry.d.ts +1 -1
  55. package/dist/extraction-judge-training.d.ts +1 -1
  56. package/dist/extraction-judge.d.ts +1 -1
  57. package/dist/extraction.d.ts +1 -1
  58. package/dist/fallback-llm.d.ts +1 -1
  59. package/dist/graph-dashboard-diff.d.ts +1 -1
  60. package/dist/graph-dashboard-key.d.ts +1 -1
  61. package/dist/graph-dashboard-parser.d.ts +1 -1
  62. package/dist/graph-edge-reinforcement.d.ts +1 -1
  63. package/dist/graph-snapshot.d.ts +1 -1
  64. package/dist/graph.d.ts +1 -1
  65. package/dist/identity-continuity.d.ts +1 -1
  66. package/dist/importance.d.ts +1 -1
  67. package/dist/index.d.ts +20 -18
  68. package/dist/index.js +1 -1
  69. package/dist/intent.d.ts +1 -1
  70. package/dist/lcm/engine.d.ts +1 -1
  71. package/dist/lcm/index.d.ts +1 -1
  72. package/dist/lcm/tools.d.ts +1 -1
  73. package/dist/lifecycle.d.ts +1 -1
  74. package/dist/live-connectors-runner.d.ts +1 -1
  75. package/dist/local-llm.d.ts +1 -1
  76. package/dist/local-model-endpoint.d.ts +1 -1
  77. package/dist/maintenance/memory-governance.d.ts +1 -1
  78. package/dist/mcp-memory-inspector-app.d.ts +17 -6
  79. package/dist/memory-action-policy.d.ts +1 -1
  80. package/dist/memory-cache.d.ts +1 -1
  81. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  82. package/dist/memory-projection-store.d.ts +1 -1
  83. package/dist/memory-provenance.d.ts +1 -1
  84. package/dist/memory-worth-outcomes.d.ts +1 -1
  85. package/dist/models-json.d.ts +1 -1
  86. package/dist/namespaces/migrate.d.ts +2 -2
  87. package/dist/namespaces/principal.d.ts +1 -1
  88. package/dist/namespaces/search.d.ts +1 -1
  89. package/dist/namespaces/storage.d.ts +2 -2
  90. package/dist/native-knowledge.d.ts +1 -1
  91. package/dist/operator-toolkit.d.ts +1 -1
  92. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  93. package/dist/orchestration/maintenance.d.ts +2 -2
  94. package/dist/{orchestrator-oF0cceBX.d.ts → orchestrator-CJZ21ZiB.d.ts} +139 -14
  95. package/dist/orchestrator.d.ts +16 -4
  96. package/dist/orchestrator.js +1 -3
  97. package/dist/patterns-cli.d.ts +1 -1
  98. package/dist/policy-runtime.d.ts +1 -1
  99. package/dist/provenance.d.ts +1 -1
  100. package/dist/qmd-recall-cache.d.ts +1 -1
  101. package/dist/qmd.d.ts +1 -1
  102. package/dist/recall-disclosure-escalation.d.ts +1 -1
  103. package/dist/recall-explain-renderer.d.ts +1 -1
  104. package/dist/recall-planner-llm.d.ts +1 -1
  105. package/dist/recall-state.d.ts +1 -1
  106. package/dist/recall-tag-filter.d.ts +1 -1
  107. package/dist/recall-xray-cli.d.ts +1 -1
  108. package/dist/recall-xray-renderer.d.ts +1 -1
  109. package/dist/recall-xray.d.ts +1 -1
  110. package/dist/resolve-auth-token.d.ts +1 -1
  111. package/dist/retrieval-agents.d.ts +1 -1
  112. package/dist/retrieval-tiers.d.ts +1 -1
  113. package/dist/routing/engine.d.ts +1 -1
  114. package/dist/routing/store.d.ts +1 -1
  115. package/dist/schemas.d.ts +24 -24
  116. package/dist/search/embed-helper.d.ts +1 -1
  117. package/dist/search/factory.d.ts +1 -1
  118. package/dist/search/index.d.ts +1 -1
  119. package/dist/search/lancedb-backend.d.ts +1 -1
  120. package/dist/search/meilisearch-backend.d.ts +1 -1
  121. package/dist/search/noop-backend.d.ts +1 -1
  122. package/dist/search/orama-backend.d.ts +1 -1
  123. package/dist/search/port.d.ts +1 -1
  124. package/dist/search/remote-backend.d.ts +1 -1
  125. package/dist/{semantic-consolidation-DKGbufYO.d.ts → semantic-consolidation-DsIEiCka.d.ts} +1 -1
  126. package/dist/semantic-consolidation.d.ts +2 -2
  127. package/dist/semantic-rule-verifier.d.ts +1 -1
  128. package/dist/session-observer-bands.d.ts +1 -1
  129. package/dist/session-observer-state.d.ts +1 -1
  130. package/dist/shared-context/manager.d.ts +3 -3
  131. package/dist/signal.d.ts +1 -1
  132. package/dist/storage.d.ts +1 -1
  133. package/dist/summarizer.d.ts +1 -1
  134. package/dist/summary-snapshot.d.ts +1 -1
  135. package/dist/temporal-supersession.d.ts +1 -1
  136. package/dist/temporal-validity.d.ts +1 -1
  137. package/dist/threading.d.ts +1 -1
  138. package/dist/tier-migration.d.ts +1 -1
  139. package/dist/tier-routing.d.ts +1 -1
  140. package/dist/topics.d.ts +1 -1
  141. package/dist/transcript.d.ts +1 -1
  142. package/dist/transfer/types.d.ts +42 -42
  143. package/dist/trust-score-stage.d.ts +2 -2
  144. package/dist/trust-score.d.ts +1 -1
  145. package/dist/{types-C7UCxpuO.d.ts → types-CglNn2V-.d.ts} +1 -1
  146. package/dist/types.d.ts +1 -1
  147. package/dist/utility-runtime.d.ts +1 -1
  148. package/package.json +2 -2
  149. package/src/orchestration/entity-synthesis-coordinator.ts +319 -0
  150. package/src/orchestration/recall-result-formatter.ts +438 -0
  151. package/src/orchestrator.ts +34 -540
  152. package/dist/chunk-FLCYY5JX.js.map +0 -1
@@ -79,12 +79,9 @@ import {
79
79
  } from "./search/factory.js";
80
80
  import { NoopSearchBackend } from "./search/noop-backend.js";
81
81
  import {
82
- compareEntityTimestamps,
83
82
  StorageManager,
84
83
  ContentHashIndex,
85
- fingerprintEntityStructuredFacts,
86
84
  normalizeAttributePairs,
87
- parseEntityFile,
88
85
  } from "./storage.js";
89
86
  import { sanitizeMemoryContent } from "./sanitize.js";
90
87
  import { ThreadingManager } from "./threading.js";
@@ -103,6 +100,9 @@ import { ExtractionQueueCoordinator } from "./orchestration/extraction-queue-coo
103
100
  import { CompressionGuidelineCoordinator } from "./orchestration/compression-guideline-coordinator.js";
104
101
  import { SemanticConsolidationCoordinator } from "./orchestration/semantic-consolidation-coordinator.js";
105
102
  import { LifecyclePolicyCoordinator } from "./orchestration/lifecycle-policy-coordinator.js";
103
+ import { EntitySynthesisCoordinator } from "./orchestration/entity-synthesis-coordinator.js";
104
+ import { RecallResultFormatter } from "./orchestration/recall-result-formatter.js";
105
+ export { hasIdentityRecoveryIntent, resolveEffectiveIdentityInjectionMode } from "./orchestration/recall-result-formatter.js";
106
106
  import {
107
107
  runLiveConnectorsOnce,
108
108
  type LiveConnectorsRunSummary,
@@ -451,8 +451,6 @@ import type {
451
451
  RecallPlanMode,
452
452
  RecallSectionConfig,
453
453
  RecallTierExplain,
454
- EntityStructuredSection,
455
- EntityTimelineEntry,
456
454
  } from "./types.js";
457
455
  import { disposeDefaultArchiveScoring, getDefaultArchiveScoring, memoryFileToScoreItem } from "./recall/archive-scoring.js";
458
456
 
@@ -492,92 +490,6 @@ interface ExtractionRunResult {
492
490
  postPersistMetadataFailed?: boolean;
493
491
  }
494
492
 
495
- export function dedupeEntitySynthesisEvidenceEntries(
496
- entries: EntityTimelineEntry[],
497
- ): EntityTimelineEntry[] {
498
- const dedupedEvidenceEntries: EntityTimelineEntry[] = [];
499
- const evidenceByFact = new Map<string, {
500
- newest: EntityTimelineEntry;
501
- oldest: EntityTimelineEntry;
502
- }>();
503
-
504
- for (const entry of entries) {
505
- const normalizedFact = entry.text.trim();
506
- if (!normalizedFact) continue;
507
- const existing = evidenceByFact.get(normalizedFact);
508
- if (!existing) {
509
- evidenceByFact.set(normalizedFact, { newest: entry, oldest: entry });
510
- continue;
511
- }
512
- if (compareEntityTimestamps(entry.timestamp, existing.newest.timestamp) > 0) {
513
- existing.newest = entry;
514
- }
515
- if (compareEntityTimestamps(entry.timestamp, existing.oldest.timestamp) < 0) {
516
- existing.oldest = entry;
517
- }
518
- }
519
-
520
- for (const { newest, oldest } of evidenceByFact.values()) {
521
- dedupedEvidenceEntries.push(newest);
522
- const newestKey = [
523
- newest.timestamp,
524
- newest.source ?? "",
525
- newest.sessionKey ?? "",
526
- newest.principal ?? "",
527
- newest.text,
528
- ].join("\u0000");
529
- const oldestKey = [
530
- oldest.timestamp,
531
- oldest.source ?? "",
532
- oldest.sessionKey ?? "",
533
- oldest.principal ?? "",
534
- oldest.text,
535
- ].join("\u0000");
536
- if (oldestKey !== newestKey) {
537
- dedupedEvidenceEntries.push(oldest);
538
- }
539
- }
540
-
541
- return dedupedEvidenceEntries;
542
- }
543
-
544
- function flattenStructuredSectionEvidence(
545
- sections: EntityStructuredSection[] | undefined,
546
- ): EntityTimelineEntry[] {
547
- return (sections ?? []).flatMap((section) =>
548
- section.facts
549
- .map((fact) => fact.trim())
550
- .filter((fact) => fact.length > 0)
551
- .map((fact) => ({
552
- timestamp: "",
553
- text: fact,
554
- source: `section:${section.title}`,
555
- })),
556
- );
557
- }
558
-
559
- function fingerprintEntitySynthesisEvidence(entity: {
560
- timeline: EntityTimelineEntry[];
561
- structuredSections?: EntityStructuredSection[];
562
- }): string {
563
- const fingerprint = createHash("sha256");
564
- const timelineEntries = entity.timeline
565
- .map((entry) => [
566
- entry.timestamp,
567
- entry.source ?? "",
568
- entry.sessionKey ?? "",
569
- entry.principal ?? "",
570
- entry.text,
571
- ].join("\u0000"))
572
- .sort();
573
- const timelineEntrySeparator = String.fromCharCode(1);
574
- const structuredFactsSeparator = String.fromCharCode(2);
575
- fingerprint.update(timelineEntries.join(timelineEntrySeparator));
576
- fingerprint.update(structuredFactsSeparator);
577
- fingerprint.update(fingerprintEntityStructuredFacts(entity) ?? "");
578
- return fingerprint.digest("hex");
579
- }
580
-
581
493
  export interface GraphRecallSnapshot {
582
494
  recordedAt: string;
583
495
  mode: RecallPlanMode | string;
@@ -1458,31 +1370,6 @@ export async function resolveRecallModeDecisionAsync(
1458
1370
  };
1459
1371
  }
1460
1372
 
1461
- export function hasIdentityRecoveryIntent(prompt: string): boolean {
1462
- const text = typeof prompt === "string" ? prompt.toLowerCase() : "";
1463
- if (!text) return false;
1464
- return /\b(identity|continuity|recover(?:y|ing|ed)?|incident|drift|restore|regress(?:ion|ed|ing)?)\b/i.test(
1465
- text,
1466
- );
1467
- }
1468
-
1469
- export function resolveEffectiveIdentityInjectionMode(options: {
1470
- configuredMode: IdentityInjectionMode;
1471
- recallMode: RecallPlanMode;
1472
- prompt: string;
1473
- }): { mode: IdentityInjectionMode; shouldInject: boolean } {
1474
- if (
1475
- options.configuredMode === "recovery_only" &&
1476
- !hasIdentityRecoveryIntent(options.prompt)
1477
- ) {
1478
- return { mode: "recovery_only", shouldInject: false };
1479
- }
1480
- if (options.recallMode === "minimal" && options.configuredMode === "full") {
1481
- return { mode: "minimal", shouldInject: true };
1482
- }
1483
- return { mode: options.configuredMode, shouldInject: true };
1484
- }
1485
-
1486
1373
  export function computeArtifactCandidateFetchLimit(
1487
1374
  targetCount: number,
1488
1375
  ): number {
@@ -1904,6 +1791,7 @@ export class Orchestrator {
1904
1791
  readonly compressionGuidelineCoordinator: CompressionGuidelineCoordinator;
1905
1792
  readonly semanticConsolidationCoordinator: SemanticConsolidationCoordinator;
1906
1793
  readonly lifecyclePolicyCoordinator: LifecyclePolicyCoordinator;
1794
+ readonly entitySynthesisCoordinator: EntitySynthesisCoordinator;
1907
1795
  /**
1908
1796
  * In-memory X-ray snapshot from the most recent `recall()` call that
1909
1797
  * was invoked with `xrayCapture: true` (issue #570 PR 1). Scope is
@@ -2017,6 +1905,11 @@ export class Orchestrator {
2017
1905
  * hands it to `enqueue`.
2018
1906
  */
2019
1907
  readonly extractionQueueCoordinator: ExtractionQueueCoordinator;
1908
+ /**
1909
+ * Issue #1526: recall result formatting + identity continuity section moved
1910
+ * to RecallResultFormatter.
1911
+ */
1912
+ readonly recallResultFormatter: RecallResultFormatter;
2020
1913
  private heartbeatObserverChains = new Map<string, Promise<void>>();
2021
1914
  private recentExtractionFingerprints = new Map<string, number>();
2022
1915
  private readonly consolidationObservers = new Set<
@@ -2985,6 +2878,7 @@ export class Orchestrator {
2985
2878
  });
2986
2879
  // Issue #1526: background extraction queue lives on its own coordinator.
2987
2880
  this.extractionQueueCoordinator = new ExtractionQueueCoordinator();
2881
+ this.recallResultFormatter = new RecallResultFormatter(this.config);
2988
2882
  const conversationIndexRuntime = createConversationIndexRuntime(config, {
2989
2883
  getQmd: () => this.conversationQmd,
2990
2884
  getFaiss: () => this.conversationFaiss,
@@ -3027,6 +2921,12 @@ export class Orchestrator {
3027
2921
  fastChatCompletion: (messages, options) =>
3028
2922
  this.fastChatCompletion(messages, options),
3029
2923
  });
2924
+ this.entitySynthesisCoordinator = new EntitySynthesisCoordinator({
2925
+ config,
2926
+ getStorage: (namespace) => this.getStorage(namespace),
2927
+ fastChatCompletion: (messages, options) =>
2928
+ this.fastChatCompletion(messages, options),
2929
+ });
3030
2930
 
3031
2931
  this.sessionObserver = new SessionObserverState({
3032
2932
  memoryDir: config.memoryDir,
@@ -4444,150 +4344,10 @@ export class Orchestrator {
4444
4344
  namespace?: string,
4445
4345
  maxEntities: number = 5,
4446
4346
  ): Promise<number> {
4447
- if (
4448
- !this.config.entitySummaryEnabled
4449
- || maxEntities <= 0
4450
- || this.config.entitySynthesisMaxTokens <= 0
4451
- ) return 0;
4452
- const storage = await this.getStorage(namespace);
4453
- const queued = await storage.refreshEntitySynthesisQueue();
4454
- let processed = 0;
4455
- let attempted = 0;
4456
-
4457
- for (const entityName of queued) {
4458
- if (attempted >= maxEntities) break;
4459
- attempted += 1;
4460
- try {
4461
- const raw = await storage.readEntity(entityName);
4462
- if (!raw) continue;
4463
- const entity = parseEntityFile(raw, this.config.entitySchemas);
4464
- const previousSynthesis = entity.synthesis || entity.summary || "";
4465
- const sortedTimelineEntries = entity.timeline
4466
- .slice()
4467
- .sort((left, right) => compareEntityTimestamps(right.timestamp, left.timestamp));
4468
- const newerTimelineEntries = sortedTimelineEntries.filter(
4469
- (entry) =>
4470
- !entity.synthesisUpdatedAt
4471
- || compareEntityTimestamps(entry.timestamp, entity.synthesisUpdatedAt) > 0,
4472
- );
4473
- const appendedTimelineEntries = entity.synthesisTimelineCount === undefined
4474
- ? []
4475
- : entity.timeline.slice(Math.max(0, entity.synthesisTimelineCount));
4476
- const structuredEvidenceEntries = flattenStructuredSectionEvidence(entity.structuredSections);
4477
- const structuredEvidenceCount = structuredEvidenceEntries.length;
4478
- const structuredEvidenceDigest = fingerprintEntityStructuredFacts(entity);
4479
- const structuredEvidenceDrifted = structuredEvidenceDigest !== (entity.synthesisStructuredFactDigest?.trim() || undefined);
4480
- const appendedStructuredEvidenceEntries = entity.synthesisStructuredFactCount === undefined
4481
- || structuredEvidenceDrifted
4482
- ? structuredEvidenceEntries
4483
- : structuredEvidenceEntries.slice(Math.max(0, entity.synthesisStructuredFactCount));
4484
- const candidateEvidenceEntries = [
4485
- ...newerTimelineEntries,
4486
- ...appendedTimelineEntries,
4487
- ...appendedStructuredEvidenceEntries,
4488
- ]
4489
- .slice()
4490
- .sort((left, right) => compareEntityTimestamps(right.timestamp, left.timestamp));
4491
- const dedupedEvidenceEntries = dedupeEntitySynthesisEvidenceEntries(
4492
- candidateEvidenceEntries.length > 0
4493
- ? candidateEvidenceEntries
4494
- : [...sortedTimelineEntries, ...structuredEvidenceEntries],
4495
- );
4496
- const chronologicalEvidenceEntries = dedupedEvidenceEntries
4497
- .slice()
4498
- .sort((left, right) => compareEntityTimestamps(left.timestamp, right.timestamp));
4499
- if (chronologicalEvidenceEntries.length === 0) continue;
4500
- const latestEvidenceTimestamp = chronologicalEvidenceEntries
4501
- .slice()
4502
- .reverse()
4503
- .map((entry) => entry.timestamp?.trim() || undefined)
4504
- .find((timestamp) => Boolean(timestamp));
4505
- const previousSynthesisUpdatedAt = entity.synthesisUpdatedAt?.trim() || undefined;
4506
- const nextSynthesisUpdatedAt = compareEntityTimestamps(
4507
- latestEvidenceTimestamp,
4508
- previousSynthesisUpdatedAt,
4509
- ) >= 0
4510
- ? latestEvidenceTimestamp
4511
- : previousSynthesisUpdatedAt;
4512
- const evidenceBatches: typeof chronologicalEvidenceEntries[] = [];
4513
- for (let index = 0; index < chronologicalEvidenceEntries.length; index += 8) {
4514
- evidenceBatches.push(chronologicalEvidenceEntries.slice(index, index + 8));
4515
- }
4516
-
4517
- let nextSynthesis = previousSynthesis;
4518
- let batchFailed = false;
4519
- for (const evidenceEntries of evidenceBatches) {
4520
- const evidenceText = evidenceEntries
4521
- .map((entry) => {
4522
- const sectionTitle = entry.source?.startsWith("section:")
4523
- ? entry.source.slice("section:".length)
4524
- : "";
4525
- const metadata = [
4526
- `timestamp=${entry.timestamp}`,
4527
- sectionTitle ? `section=${sectionTitle}` : entry.source ? `source=${entry.source}` : "",
4528
- entry.sessionKey ? `session=${entry.sessionKey}` : "",
4529
- entry.principal ? `principal=${entry.principal}` : "",
4530
- ]
4531
- .filter(Boolean)
4532
- .join(", ");
4533
- return `- ${metadata}: ${entry.text}`;
4534
- })
4535
- .join("\n");
4536
- const response = await this.fastChatCompletion(
4537
- [
4538
- {
4539
- role: "system",
4540
- content:
4541
- "Rewrite the entity synthesis as compact current truth. Preserve uncertainty when evidence conflicts. Return plain text only.",
4542
- },
4543
- {
4544
- role: "user",
4545
- content: [
4546
- `Entity: ${entity.name} (${entity.type})`,
4547
- nextSynthesis ? `Previous synthesis:\n${nextSynthesis}` : "Previous synthesis: none",
4548
- `New evidence:\n${evidenceText}`,
4549
- ].join("\n\n"),
4550
- },
4551
- ],
4552
- {
4553
- temperature: 0.2,
4554
- maxTokens: this.config.entitySynthesisMaxTokens,
4555
- operation: "entity_summary",
4556
- priority: "background",
4557
- },
4558
- );
4559
- const synthesis = response?.content?.trim().replace(/^["']|["']$/g, "");
4560
- const maxSynthesisChars = Math.max(2_000, this.config.entitySynthesisMaxTokens * 8);
4561
- if (!synthesis || synthesis.length < 10 || synthesis.length > maxSynthesisChars) {
4562
- batchFailed = true;
4563
- break;
4564
- }
4565
- nextSynthesis = synthesis;
4566
- }
4567
- if (batchFailed || nextSynthesis.length === 0) continue;
4568
- const latestRaw = await storage.readEntity(entityName);
4569
- if (!latestRaw) continue;
4570
- const latestEntity = parseEntityFile(latestRaw, this.config.entitySchemas);
4571
- if (
4572
- fingerprintEntitySynthesisEvidence(latestEntity)
4573
- !== fingerprintEntitySynthesisEvidence(entity)
4574
- ) {
4575
- continue;
4576
- }
4577
- await storage.updateEntitySynthesis(entityName, nextSynthesis, {
4578
- entityUpdatedAt: new Date().toISOString(),
4579
- synthesisStructuredFactDigest: structuredEvidenceDigest,
4580
- synthesisStructuredFactCount: structuredEvidenceCount,
4581
- synthesisTimelineCount: entity.timeline.length,
4582
- updatedAt: nextSynthesisUpdatedAt,
4583
- });
4584
- processed += 1;
4585
- } catch (err) {
4586
- log.debug(`entity synthesis refresh failed for ${entityName}: ${err}`);
4587
- }
4588
- }
4589
-
4590
- return processed;
4347
+ // Issue #1526: entity synthesis lifecycle moved to EntitySynthesisCoordinator.
4348
+ // Thin delegation keeps all call sites (consolidation pass, access-service)
4349
+ // and tests that exercise the public API working unchanged.
4350
+ return this.entitySynthesisCoordinator.processQueue(namespace, maxEntities);
4591
4351
  }
4592
4352
 
4593
4353
  async generateDaySummary(
@@ -17169,226 +16929,55 @@ export class Orchestrator {
17169
16929
  }
17170
16930
  }
17171
16931
 
16932
+ // Issue #1526: recall result formatting moved to RecallResultFormatter. Thin
16933
+ // delegation keeps the private API stable for callers + tests.
17172
16934
  private formatQmdResults(
17173
16935
  title: string,
17174
16936
  results: QmdSearchResult[],
17175
16937
  sessionKey?: string,
17176
16938
  trustByPath?: Map<string, TrustStageResultItem> | null,
17177
16939
  ): string {
17178
- // Issue #1582 handles are only rendered when a session key is available:
17179
- // resolution requires the handle history to have been recorded for this
17180
- // session, which only happens when sessionKey is present. Rendering handles
17181
- // without recording would show tokens a user can never resolve (cursor
17182
- // review). The rendering logic itself lives in the pure handles module.
17183
- const handleByIndex = buildHandleIndexForResults(
17184
- results,
17185
- this.config.recallMemoryHandles === true && sessionKey != null,
17186
- );
17187
- // Issue #1577 — epistemic hedge. Append a deterministic, component-derived
17188
- // suffix so the downstream model knows each memory's trust status (the
17189
- // cheap lever against confident-stale-answer failures). Gated separately
17190
- // from scoring so rendering can ship after the stage is stable. High-band
17191
- // and neutral items get no suffix (don't waste tokens on the common case).
17192
- const renderHedge = this.config.trustScoreEpistemicRendering && trustByPath !== null && trustByPath !== undefined;
17193
- const hedgeMap = renderHedge ? trustByPath : null;
17194
- const lines = results.map((r, i) => {
17195
- const snippet = r.snippet
17196
- ? r.snippet.slice(0, 500).replace(/\n/g, " ")
17197
- : "(no preview)";
17198
- const source = typeof r.line === "number" ? `${r.path}:${r.line}` : r.path;
17199
- const head = `[${i + 1}] ${source} (score: ${r.score.toFixed(3)})\n${snippet}`;
17200
- const handle = handleByIndex.get(i);
17201
- const hedged = head.replace(/\s+$/, "");
17202
- const withHandle = handle ? `${hedged} ${handle}` : hedged;
17203
- if (hedgeMap) {
17204
- const item = hedgeMap.get(r.path);
17205
- if (item) {
17206
- const hedge = renderEpistemicHedge(item.trust);
17207
- if (hedge.length > 0) return `${withHandle} ${hedge}`;
17208
- }
17209
- }
17210
- return withHandle;
17211
- });
17212
- return `## ${title}\n\n${lines.join("\n\n")}`;
16940
+ return this.recallResultFormatter.formatQmdResults(title, results, sessionKey, trustByPath);
17213
16941
  }
17214
16942
 
17215
16943
  private formatObjectiveStateResults(
17216
16944
  results: ObjectiveStateSearchResult[],
17217
16945
  ): string {
17218
- const lines = results.map(({ snapshot }, index) => {
17219
- const parts = [
17220
- snapshot.recordedAt.replace("T", " ").slice(0, 16),
17221
- `${snapshot.kind}/${snapshot.changeKind}`,
17222
- ];
17223
- if (snapshot.outcome) parts.push(snapshot.outcome);
17224
- const header = `[${index + 1}] ${parts.join(" | ")} | ${snapshot.scope}`;
17225
- const detailParts = [snapshot.summary];
17226
- if (snapshot.command) detailParts.push(`command: ${snapshot.command}`);
17227
- else if (snapshot.toolName)
17228
- detailParts.push(`tool: ${snapshot.toolName}`);
17229
- return `${header}\n${detailParts.join(" | ")}`;
17230
- });
17231
- return `## Objective State\n\n${lines.join("\n\n")}`;
16946
+ return this.recallResultFormatter.formatObjectiveStateResults(results);
17232
16947
  }
17233
16948
 
17234
16949
  private formatCausalTrajectoryResults(
17235
16950
  results: CausalTrajectorySearchResult[],
17236
16951
  ): string {
17237
- const lines = results.map(({ record, matchedFields }, index) => {
17238
- const header = [
17239
- `[${index + 1}] ${record.recordedAt.replace("T", " ").slice(0, 16)}`,
17240
- record.outcomeKind,
17241
- ].join(" | ");
17242
- const details = [
17243
- `goal: ${record.goal}`,
17244
- `action: ${record.actionSummary}`,
17245
- `observation: ${record.observationSummary}`,
17246
- `outcome: ${record.outcomeSummary}`,
17247
- ];
17248
- if (record.followUpSummary)
17249
- details.push(`follow-up: ${record.followUpSummary}`);
17250
- if (matchedFields.length > 0)
17251
- details.push(`matched: ${matchedFields.join(", ")}`);
17252
- return `${header}\n${details.join("\n")}`;
17253
- });
17254
-
17255
- return `## Causal Trajectories\n\n${lines.join("\n\n")}`;
16952
+ return this.recallResultFormatter.formatCausalTrajectoryResults(results);
17256
16953
  }
17257
16954
 
17258
16955
  private formatTrustZoneResults(results: TrustZoneSearchResult[]): string {
17259
- const lines = results.map(({ record, matchedFields }, index) => {
17260
- const header = [
17261
- `[${index + 1}] ${record.recordedAt.replace("T", " ").slice(0, 16)}`,
17262
- record.zone,
17263
- record.kind,
17264
- ].join(" | ");
17265
- const details = [
17266
- record.summary,
17267
- `provenance: ${record.provenance.sourceClass}`,
17268
- ];
17269
- if (record.entityRefs && record.entityRefs.length > 0) {
17270
- details.push(`entities: ${record.entityRefs.join(", ")}`);
17271
- }
17272
- if (record.tags && record.tags.length > 0) {
17273
- details.push(`tags: ${record.tags.join(", ")}`);
17274
- }
17275
- if (matchedFields.length > 0) {
17276
- details.push(`matched: ${matchedFields.join(", ")}`);
17277
- }
17278
- return `${header}\n${details.join("\n")}`;
17279
- });
17280
-
17281
- return `## Trust Zones\n\n${lines.join("\n\n")}`;
16956
+ return this.recallResultFormatter.formatTrustZoneResults(results);
17282
16957
  }
17283
16958
 
17284
16959
  private formatHarmonicRetrievalResults(
17285
16960
  results: HarmonicRetrievalResult[],
17286
16961
  ): string {
17287
- const lines = results.map(
17288
- (
17289
- { node, matchedAnchors, matchedFields, nodeScore, anchorScore },
17290
- index,
17291
- ) => {
17292
- const header = [
17293
- `[${index + 1}] ${node.recordedAt.replace("T", " ").slice(0, 16)}`,
17294
- `${node.kind}/${node.abstractionLevel}`,
17295
- node.sessionKey,
17296
- ].join(" | ");
17297
- const details = [
17298
- node.title,
17299
- node.summary,
17300
- `scores: node=${nodeScore.toFixed(1)} anchor=${anchorScore.toFixed(1)}`,
17301
- ];
17302
- if (matchedAnchors.length > 0) {
17303
- details.push(
17304
- `anchors: ${matchedAnchors.map((anchor) => `${anchor.anchorType}:${anchor.anchorValue}`).join("; ")}`,
17305
- );
17306
- }
17307
- if (matchedFields.length > 0) {
17308
- details.push(`matched: ${matchedFields.join(", ")}`);
17309
- }
17310
- return `${header}\n${details.join("\n")}`;
17311
- },
17312
- );
17313
-
17314
- return `## Harmonic Retrieval\n\n${lines.join("\n\n")}`;
16962
+ return this.recallResultFormatter.formatHarmonicRetrievalResults(results);
17315
16963
  }
17316
16964
 
17317
16965
  private formatWorkProductResults(
17318
16966
  results: WorkProductLedgerSearchResult[],
17319
16967
  ): string {
17320
- const lines = results.map(({ entry, matchedFields }, index) => {
17321
- const header = [
17322
- `[${index + 1}] ${entry.recordedAt.replace("T", " ").slice(0, 16)}`,
17323
- `${entry.kind}/${entry.action}`,
17324
- entry.sessionKey,
17325
- ].join(" | ");
17326
- const details = [entry.summary, `scope: ${entry.scope}`];
17327
- if (entry.artifactPath) details.push(`artifact: ${entry.artifactPath}`);
17328
- if (entry.tags && entry.tags.length > 0)
17329
- details.push(`tags: ${entry.tags.join(", ")}`);
17330
- if (matchedFields.length > 0)
17331
- details.push(`matched: ${matchedFields.join(", ")}`);
17332
- return `${header}\n${details.join("\n")}`;
17333
- });
17334
-
17335
- return `## Work Products\n\n${lines.join("\n\n")}`;
16968
+ return this.recallResultFormatter.formatWorkProductResults(results);
17336
16969
  }
17337
16970
 
17338
16971
  private formatVerifiedEpisodeResults(
17339
16972
  results: VerifiedEpisodeResult[],
17340
16973
  ): string {
17341
- const lines = results.map(
17342
- ({ box, verifiedEpisodeCount, matchedFields }, index) => {
17343
- const header = [
17344
- `[${index + 1}] ${box.sealedAt.replace("T", " ").slice(0, 16)}`,
17345
- box.traceId ? `trace:${box.traceId.slice(0, 12)}` : "trace:none",
17346
- ].join(" | ");
17347
- const details = [
17348
- box.goal ?? `topics: ${box.topics.join(", ")}`,
17349
- `verified episodes: ${verifiedEpisodeCount}`,
17350
- ];
17351
- if (box.toolsUsed && box.toolsUsed.length > 0) {
17352
- details.push(`tools: ${box.toolsUsed.join(", ")}`);
17353
- }
17354
- if (matchedFields.length > 0) {
17355
- details.push(`matched: ${matchedFields.join(", ")}`);
17356
- }
17357
- return `${header}\n${details.join("\n")}`;
17358
- },
17359
- );
17360
-
17361
- return `## Verified Episodes\n\n${lines.join("\n\n")}`;
16974
+ return this.recallResultFormatter.formatVerifiedEpisodeResults(results);
17362
16975
  }
17363
16976
 
17364
16977
  private formatVerifiedSemanticRuleResults(
17365
16978
  results: VerifiedSemanticRuleResult[],
17366
16979
  ): string {
17367
- const lines = results.map(
17368
- (
17369
- {
17370
- rule,
17371
- sourceMemoryId,
17372
- verificationStatus,
17373
- effectiveConfidence,
17374
- matchedFields,
17375
- },
17376
- index,
17377
- ) => {
17378
- const header = [
17379
- `[${index + 1}] ${rule.frontmatter.updated.replace("T", " ").slice(0, 16)}`,
17380
- verificationStatus,
17381
- `confidence:${effectiveConfidence.toFixed(2)}`,
17382
- ].join(" | ");
17383
- const details = [rule.content, `source memory: ${sourceMemoryId}`];
17384
- if (matchedFields.length > 0) {
17385
- details.push(`matched: ${matchedFields.join(", ")}`);
17386
- }
17387
- return `${header}\n${details.join("\n")}`;
17388
- },
17389
- );
17390
-
17391
- return `## Verified Rules\n\n${lines.join("\n\n")}`;
16980
+ return this.recallResultFormatter.formatVerifiedSemanticRuleResults(results);
17392
16981
  }
17393
16982
 
17394
16983
  private summarizeIdentityText(
@@ -17396,42 +16985,21 @@ export class Orchestrator {
17396
16985
  maxLines: number,
17397
16986
  maxChars: number,
17398
16987
  ): string {
17399
- const lines = raw
17400
- .replace(/\r/g, "")
17401
- .split("\n")
17402
- .map((line) => line.trim())
17403
- .filter((line) => line.length > 0 && !line.startsWith("#"));
17404
- const compact = lines.slice(0, Math.max(1, maxLines)).join(" ");
17405
- if (compact.length <= maxChars) return compact;
17406
- return `${compact.slice(0, Math.max(0, maxChars - 1))}…`;
16988
+ return this.recallResultFormatter.summarizeIdentityText(raw, maxLines, maxChars);
17407
16989
  }
17408
16990
 
17409
16991
  private formatOpenIncidentLine(
17410
16992
  incident: ContinuityIncidentRecord,
17411
16993
  includeDetails: boolean,
17412
16994
  ): string {
17413
- const base = `[${incident.id}] ${incident.symptom.trim()}`;
17414
- if (!includeDetails) return `- ${base}`;
17415
- const parts = [base];
17416
- if (incident.suspectedCause)
17417
- parts.push(`cause: ${incident.suspectedCause.trim()}`);
17418
- if (incident.triggerWindow)
17419
- parts.push(`window: ${incident.triggerWindow.trim()}`);
17420
- return `- ${parts.join(" | ")}`;
16995
+ return this.recallResultFormatter.formatOpenIncidentLine(incident, includeDetails);
17421
16996
  }
17422
16997
 
17423
16998
  private trimIdentitySection(
17424
16999
  content: string,
17425
17000
  maxChars: number,
17426
17001
  ): { text: string; truncated: boolean } {
17427
- if (maxChars <= 0) return { text: "", truncated: false };
17428
- if (content.length <= maxChars) return { text: content, truncated: false };
17429
- const suffix = "\n\n...(identity continuity trimmed)";
17430
- if (maxChars <= suffix.length) {
17431
- return { text: content.slice(0, maxChars), truncated: true };
17432
- }
17433
- const headroom = Math.max(0, maxChars - suffix.length);
17434
- return { text: `${content.slice(0, headroom)}${suffix}`, truncated: true };
17002
+ return this.recallResultFormatter.trimIdentitySection(content, maxChars);
17435
17003
  }
17436
17004
 
17437
17005
  private async buildIdentityContinuitySection(options: {
@@ -17444,81 +17012,7 @@ export class Orchestrator {
17444
17012
  injectedChars: number;
17445
17013
  truncated: boolean;
17446
17014
  } | null> {
17447
- if (!resolveIdentityContinuityCapabilities(this.config).identityContinuity) return null;
17448
- if (this.config.identityMaxInjectChars <= 0) return null;
17449
-
17450
- const resolved = resolveEffectiveIdentityInjectionMode({
17451
- configuredMode: this.config.identityInjectionMode,
17452
- recallMode: options.recallMode,
17453
- prompt: options.prompt,
17454
- });
17455
- if (!resolved.shouldInject) return null;
17456
-
17457
- const [anchorRaw, loopsRaw, incidents] = await Promise.all([
17458
- options.storage.readIdentityAnchor(),
17459
- options.storage.readIdentityImprovementLoops(),
17460
- options.storage.readContinuityIncidents(200),
17461
- ]);
17462
- const openIncidents = incidents.filter(
17463
- (incident) => incident.state === "open",
17464
- );
17465
-
17466
- const lines: string[] = [];
17467
- if (resolved.mode === "full") {
17468
- lines.push("## Identity Continuity");
17469
- if (anchorRaw && anchorRaw.trim().length > 0) {
17470
- lines.push("", "### Anchor", "", anchorRaw.trim());
17471
- }
17472
- if (loopsRaw && loopsRaw.trim().length > 0) {
17473
- lines.push("", "### Improvement Loops", "", loopsRaw.trim());
17474
- }
17475
- lines.push("", "### Open Incidents", "");
17476
- if (openIncidents.length === 0) {
17477
- lines.push("- none");
17478
- } else {
17479
- lines.push(
17480
- ...openIncidents
17481
- .slice(0, 5)
17482
- .map((incident) => this.formatOpenIncidentLine(incident, true)),
17483
- );
17484
- }
17485
- } else {
17486
- const anchorSummary = anchorRaw
17487
- ? this.summarizeIdentityText(anchorRaw, 3, 320)
17488
- : "";
17489
- const loopsSummary = loopsRaw
17490
- ? this.summarizeIdentityText(loopsRaw, 2, 240)
17491
- : "";
17492
- lines.push("## Identity Continuity Signals", "");
17493
- if (anchorSummary) lines.push(`- anchor: ${anchorSummary}`);
17494
- if (loopsSummary) lines.push(`- loops: ${loopsSummary}`);
17495
- if (openIncidents.length === 0) {
17496
- lines.push("- incidents: 0 open");
17497
- } else {
17498
- lines.push(`- incidents: ${openIncidents.length} open`);
17499
- lines.push(
17500
- ...openIncidents
17501
- .slice(0, 2)
17502
- .map((incident) => this.formatOpenIncidentLine(incident, false)),
17503
- );
17504
- }
17505
- }
17506
-
17507
- const body = lines.join("\n").trim();
17508
- if (!body) return null;
17509
-
17510
- const { text, truncated } = this.trimIdentitySection(
17511
- body,
17512
- this.config.identityMaxInjectChars,
17513
- );
17514
- if (!text) return null;
17515
-
17516
- return {
17517
- section: text,
17518
- mode: resolved.mode,
17519
- injectedChars: text.length,
17520
- truncated,
17521
- };
17015
+ return this.recallResultFormatter.buildIdentityContinuitySection(options);
17522
17016
  }
17523
17017
 
17524
17018
  private emitTrace(event: EngramTraceEvent): void {