@remnic/core 9.3.737 → 9.3.739
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/dist/access-boundary.d.ts +6 -6
- package/dist/access-boundary.js +10 -10
- package/dist/access-cli.js +26 -26
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +15 -15
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-mcp.js +14 -14
- package/dist/access-operations-batch.js +12 -12
- package/dist/access-operations.d.ts +6 -6
- package/dist/access-operations.js +13 -13
- package/dist/{access-service-DwQ6BIQw.d.ts → access-service-Cbk-Kv_A.d.ts} +1 -1
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +9 -9
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/active-memory-bridge.js +3 -3
- package/dist/bootstrap.d.ts +5 -5
- package/dist/buffer.js +2 -1
- package/dist/capabilities.d.ts +98 -1
- package/dist/capabilities.js +7 -1
- package/dist/{chunk-G54IDCD3.js → chunk-23PLOBWT.js} +30 -27
- package/dist/chunk-23PLOBWT.js.map +1 -0
- package/dist/{chunk-K3Y2SJTN.js → chunk-56LP72AD.js} +6 -6
- package/dist/{chunk-ZBZ4XULO.js → chunk-66RZ4EIT.js} +3 -3
- package/dist/{chunk-IPI45Q4S.js → chunk-75PDC7VT.js} +9 -8
- package/dist/chunk-75PDC7VT.js.map +1 -0
- package/dist/{chunk-GB2FZOWB.js → chunk-7B7IPPN5.js} +5 -5
- package/dist/{chunk-QNTLWH3T.js → chunk-A7INTIC5.js} +3 -3
- package/dist/{chunk-FLPKEMSO.js → chunk-AKKFHN3I.js} +2 -2
- package/dist/{chunk-AAIHDIMD.js → chunk-BHJAMSVC.js} +7 -7
- package/dist/{chunk-BXPG5AVX.js → chunk-BHQQVUEQ.js} +6 -6
- package/dist/{chunk-HFXZIF3V.js → chunk-BVG2IZOB.js} +3 -3
- package/dist/{chunk-NDKW67ER.js → chunk-C53JDEH3.js} +4 -4
- package/dist/{chunk-AESC5OQJ.js → chunk-CKHPUOAP.js} +2 -2
- package/dist/{chunk-OYHYBHHI.js → chunk-DS3FZ7GS.js} +4 -4
- package/dist/{chunk-UO6MPN77.js → chunk-EU543UDC.js} +10 -9
- package/dist/chunk-EU543UDC.js.map +1 -0
- package/dist/{chunk-PB5YON2G.js → chunk-IEWUPZ75.js} +659 -584
- package/dist/chunk-IEWUPZ75.js.map +1 -0
- package/dist/{chunk-E4LYJ7PY.js → chunk-IFCM6OLK.js} +6 -6
- package/dist/{chunk-5BOQNB2K.js → chunk-JQSP7YBJ.js} +3 -3
- package/dist/{chunk-2T2N7ISV.js → chunk-L3TYGUTH.js} +3 -3
- package/dist/{chunk-AB2E5DCN.js → chunk-MAU7DFCJ.js} +2 -2
- package/dist/{chunk-P6VLG7KE.js → chunk-N2O23WYA.js} +2 -2
- package/dist/{chunk-HYNHLBKA.js → chunk-OQQRWPFZ.js} +5 -2
- package/dist/chunk-OQQRWPFZ.js.map +1 -0
- package/dist/{chunk-IXGXALRV.js → chunk-QTJOORGN.js} +2 -2
- package/dist/{chunk-JWDE7OVJ.js → chunk-RJBW4EOU.js} +46 -2
- package/dist/{chunk-JWDE7OVJ.js.map → chunk-RJBW4EOU.js.map} +1 -1
- package/dist/{chunk-AE7NTZ75.js → chunk-TNLXCSXV.js} +3 -3
- package/dist/{chunk-NNJKEAP2.js → chunk-VRBBZH4U.js} +4 -4
- package/dist/{chunk-VOEVVIP3.js → chunk-W2FBVBQ4.js} +4 -4
- package/dist/{chunk-WNGXQMRI.js → chunk-WFNH4UDF.js} +5 -2
- package/dist/chunk-WFNH4UDF.js.map +1 -0
- package/dist/{chunk-CHJIWG2H.js → chunk-WVAEIKUY.js} +2 -2
- package/dist/{chunk-FF36YNKY.js → chunk-XIQAOGAA.js} +11 -10
- package/dist/chunk-XIQAOGAA.js.map +1 -0
- package/dist/{chunk-HLPYL6LC.js → chunk-Y5OOHER4.js} +14 -12
- package/dist/chunk-Y5OOHER4.js.map +1 -0
- package/dist/{chunk-GYICIICQ.js → chunk-YF2MMBKA.js} +9 -7
- package/dist/chunk-YF2MMBKA.js.map +1 -0
- package/dist/{chunk-IX4L7E5G.js → chunk-YXOXAQ5Y.js} +2 -2
- package/dist/{cli-BlEIWDQw.d.ts → cli-BQW2xKdb.d.ts} +2 -2
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +21 -21
- package/dist/compounding/engine.js +2 -2
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-2B4AE4TI.js → contradiction-scan-CWO3LIWV.js} +3 -3
- package/dist/embedding-fallback.js +2 -2
- package/dist/entity-retrieval.js +2 -2
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction.js +3 -3
- package/dist/{first-start-migration-2RQYDIZ7.js → first-start-migration-5DXD3TF2.js} +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +32 -32
- package/dist/local-llm.js +2 -2
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/namespaces/migrate.js +6 -6
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +2 -2
- package/dist/operator-toolkit.js +11 -11
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.js +6 -6
- package/dist/{orchestrator-CJZ21ZiB.d.ts → orchestrator-BOAFN_Yr.d.ts} +186 -56
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +21 -21
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/summarizer.js +3 -3
- package/dist/{tier-stats-KPDIISG7.js → tier-stats-QID73BBB.js} +2 -2
- package/package.json +2 -2
- package/src/access-service.ts +7 -8
- package/src/buffer.ts +2 -1
- package/src/capabilities.test.ts +137 -0
- package/src/capabilities.ts +209 -0
- package/src/cli/creation-ledger-commands.ts +3 -3
- package/src/cli/research-status-commands.ts +4 -4
- package/src/cli.ts +9 -10
- package/src/compounding/engine.ts +7 -7
- package/src/correction/correction-access-wiring.ts +3 -2
- package/src/extraction.ts +8 -10
- package/src/operator-toolkit.ts +6 -7
- package/src/orchestration/conversation-index-coordinator.ts +344 -0
- package/src/orchestration/entity-synthesis-coordinator.ts +2 -1
- package/src/orchestration/maintenance.ts +5 -7
- package/src/orchestration/recall-rerank-coordinator.ts +425 -0
- package/src/orchestration/semantic-consolidation-coordinator.ts +2 -2
- package/src/orchestrator.ts +88 -602
- package/dist/chunk-FF36YNKY.js.map +0 -1
- package/dist/chunk-G54IDCD3.js.map +0 -1
- package/dist/chunk-GYICIICQ.js.map +0 -1
- package/dist/chunk-HLPYL6LC.js.map +0 -1
- package/dist/chunk-HYNHLBKA.js.map +0 -1
- package/dist/chunk-IPI45Q4S.js.map +0 -1
- package/dist/chunk-PB5YON2G.js.map +0 -1
- package/dist/chunk-UO6MPN77.js.map +0 -1
- package/dist/chunk-WNGXQMRI.js.map +0 -1
- /package/dist/{chunk-K3Y2SJTN.js.map → chunk-56LP72AD.js.map} +0 -0
- /package/dist/{chunk-ZBZ4XULO.js.map → chunk-66RZ4EIT.js.map} +0 -0
- /package/dist/{chunk-GB2FZOWB.js.map → chunk-7B7IPPN5.js.map} +0 -0
- /package/dist/{chunk-QNTLWH3T.js.map → chunk-A7INTIC5.js.map} +0 -0
- /package/dist/{chunk-FLPKEMSO.js.map → chunk-AKKFHN3I.js.map} +0 -0
- /package/dist/{chunk-AAIHDIMD.js.map → chunk-BHJAMSVC.js.map} +0 -0
- /package/dist/{chunk-BXPG5AVX.js.map → chunk-BHQQVUEQ.js.map} +0 -0
- /package/dist/{chunk-HFXZIF3V.js.map → chunk-BVG2IZOB.js.map} +0 -0
- /package/dist/{chunk-NDKW67ER.js.map → chunk-C53JDEH3.js.map} +0 -0
- /package/dist/{chunk-AESC5OQJ.js.map → chunk-CKHPUOAP.js.map} +0 -0
- /package/dist/{chunk-OYHYBHHI.js.map → chunk-DS3FZ7GS.js.map} +0 -0
- /package/dist/{chunk-E4LYJ7PY.js.map → chunk-IFCM6OLK.js.map} +0 -0
- /package/dist/{chunk-5BOQNB2K.js.map → chunk-JQSP7YBJ.js.map} +0 -0
- /package/dist/{chunk-2T2N7ISV.js.map → chunk-L3TYGUTH.js.map} +0 -0
- /package/dist/{chunk-AB2E5DCN.js.map → chunk-MAU7DFCJ.js.map} +0 -0
- /package/dist/{chunk-P6VLG7KE.js.map → chunk-N2O23WYA.js.map} +0 -0
- /package/dist/{chunk-IXGXALRV.js.map → chunk-QTJOORGN.js.map} +0 -0
- /package/dist/{chunk-AE7NTZ75.js.map → chunk-TNLXCSXV.js.map} +0 -0
- /package/dist/{chunk-NNJKEAP2.js.map → chunk-VRBBZH4U.js.map} +0 -0
- /package/dist/{chunk-VOEVVIP3.js.map → chunk-W2FBVBQ4.js.map} +0 -0
- /package/dist/{chunk-CHJIWG2H.js.map → chunk-WVAEIKUY.js.map} +0 -0
- /package/dist/{chunk-IX4L7E5G.js.map → chunk-YXOXAQ5Y.js.map} +0 -0
- /package/dist/{contradiction-scan-2B4AE4TI.js.map → contradiction-scan-CWO3LIWV.js.map} +0 -0
- /package/dist/{first-start-migration-2RQYDIZ7.js.map → first-start-migration-5DXD3TF2.js.map} +0 -0
- /package/dist/{tier-stats-KPDIISG7.js.map → tier-stats-QID73BBB.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -102,6 +102,8 @@ import { SemanticConsolidationCoordinator } from "./orchestration/semantic-conso
|
|
|
102
102
|
import { LifecyclePolicyCoordinator } from "./orchestration/lifecycle-policy-coordinator.js";
|
|
103
103
|
import { EntitySynthesisCoordinator } from "./orchestration/entity-synthesis-coordinator.js";
|
|
104
104
|
import { RecallResultFormatter } from "./orchestration/recall-result-formatter.js";
|
|
105
|
+
import { ConversationIndexCoordinator } from "./orchestration/conversation-index-coordinator.js";
|
|
106
|
+
import { RecallRerankCoordinator } from "./orchestration/recall-rerank-coordinator.js";
|
|
105
107
|
export { hasIdentityRecoveryIntent, resolveEffectiveIdentityInjectionMode } from "./orchestration/recall-result-formatter.js";
|
|
106
108
|
import {
|
|
107
109
|
runLiveConnectorsOnce,
|
|
@@ -121,16 +123,8 @@ import {
|
|
|
121
123
|
type ParallelSearchResult,
|
|
122
124
|
} from "./retrieval-agents.js";
|
|
123
125
|
import { RerankCache, rerankLocalOrNoop } from "./rerank.js";
|
|
124
|
-
import {
|
|
125
|
-
applyMemoryWorthFilter,
|
|
126
|
-
buildMemoryWorthCounterMap,
|
|
127
|
-
type MemoryWorthCounters,
|
|
128
|
-
} from "./memory-worth-filter.js";
|
|
129
|
-
import { applyTrustScoreStage, buildTrustSignalsForRerank, projectTrustForXray } from "./trust-score-stage.js";
|
|
126
|
+
import { projectTrustForXray } from "./trust-score-stage.js";
|
|
130
127
|
import type { TrustStageResultItem } from "./trust-score-stage.js";
|
|
131
|
-
import { renderEpistemicHedge, type TrustSignals } from "./trust-score.js";
|
|
132
|
-
import { reorderRecallResultsWithMmr } from "./recall-mmr.js";
|
|
133
|
-
import { applyReasoningTraceBoost } from "./reasoning-trace-recall.js";
|
|
134
128
|
import { buildRetrievedMemoryProvenance } from "./memory-provenance.js";
|
|
135
129
|
import {
|
|
136
130
|
applyTemporalSupersession,
|
|
@@ -267,8 +261,7 @@ import {
|
|
|
267
261
|
type TrustZoneSearchResult,
|
|
268
262
|
} from "./trust-zones.js";
|
|
269
263
|
import { tryDirectAnswer, type DirectAnswerSources } from "./direct-answer-wiring.js";
|
|
270
|
-
import {
|
|
271
|
-
resolveNamespaceCapabilities,
|
|
264
|
+
import { resolveNamespaceCapabilities,
|
|
272
265
|
resolveCapabilities,
|
|
273
266
|
resolveGraphConstructionCapabilities,
|
|
274
267
|
resolveMemoryLifecycleCapabilities,
|
|
@@ -282,7 +275,7 @@ import {
|
|
|
282
275
|
resolveQmdCapabilities,
|
|
283
276
|
resolveIdentityContinuityCapabilities,
|
|
284
277
|
resolveLocalLlmCapabilities,
|
|
285
|
-
resolveSecurityCapabilities, resolveEvalCapabilities, resolveUtilityLearningCapabilities, resolveObjectiveStateCapabilities, resolveCompressionCapabilities} from "./capabilities.js";
|
|
278
|
+
resolveSecurityCapabilities, resolveEvalCapabilities, resolveUtilityLearningCapabilities, resolveObjectiveStateCapabilities, resolveCompressionCapabilities, resolvePresentationCapabilities, resolveConsolidationCapabilities, resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
|
|
286
279
|
import { DEFAULT_TAXONOMY } from "./taxonomy/index.js";
|
|
287
280
|
import {
|
|
288
281
|
searchHarmonicRetrieval,
|
|
@@ -331,9 +324,6 @@ import {
|
|
|
331
324
|
type SemanticConsolidationLlmOperator,
|
|
332
325
|
type SemanticConsolidationResult,
|
|
333
326
|
} from "./semantic-consolidation.js";
|
|
334
|
-
import { chunkTranscriptEntries } from "./conversation-index/chunker.js";
|
|
335
|
-
import { writeConversationChunks } from "./conversation-index/indexer.js";
|
|
336
|
-
import { cleanupConversationChunks } from "./conversation-index/cleanup.js";
|
|
337
327
|
import {
|
|
338
328
|
type ConversationIndexBackend,
|
|
339
329
|
type ConversationIndexBackendInspection,
|
|
@@ -1818,31 +1808,6 @@ export class Orchestrator {
|
|
|
1818
1808
|
readonly lcmEngine: LcmEngine | null = null;
|
|
1819
1809
|
private readonly rerankCache = new RerankCache();
|
|
1820
1810
|
|
|
1821
|
-
/**
|
|
1822
|
-
* Short-TTL cache for Memory Worth counter lookups so interactive recall
|
|
1823
|
-
* doesn't trigger a full `readAllMemories` scan per query. Keyed by
|
|
1824
|
-
* namespace; the filter unions across namespaces at query time. The TTL
|
|
1825
|
-
* is intentionally short (seconds, not minutes) because counters are
|
|
1826
|
-
* mutated by `recordMemoryOutcome` asynchronously and we'd rather serve
|
|
1827
|
-
* a 30-second-stale worth score than a stable-but-wrong one.
|
|
1828
|
-
*/
|
|
1829
|
-
private readonly memoryWorthCounterCache = new Map<
|
|
1830
|
-
string,
|
|
1831
|
-
{ at: number; counters: ReadonlyMap<string, MemoryWorthCounters> }
|
|
1832
|
-
>();
|
|
1833
|
-
private static readonly MEMORY_WORTH_CACHE_TTL_MS = 30_000;
|
|
1834
|
-
/**
|
|
1835
|
-
* Issue #1577 — per-namespace TrustScore signal map cache. Same TTL/shape
|
|
1836
|
-
* discipline as {@link memoryWorthCounterCache}: seconds-scale, evicted on
|
|
1837
|
-
* every call, keyed by namespace. Holds the frontmatter-derived signals
|
|
1838
|
-
* (worth, provenance, faithfulness, corroboration, recency) so the trust
|
|
1839
|
-
* stage doesn't trigger a fresh `readAllMemories` scan per query.
|
|
1840
|
-
*/
|
|
1841
|
-
private readonly trustSignalCache = new Map<
|
|
1842
|
-
string,
|
|
1843
|
-
{ at: number; signals: ReadonlyMap<string, TrustSignals> }
|
|
1844
|
-
>();
|
|
1845
|
-
private static readonly TRUST_SIGNAL_CACHE_TTL_MS = 30_000;
|
|
1846
1811
|
/**
|
|
1847
1812
|
* Per-session workspace selections keyed by sessionKey.
|
|
1848
1813
|
* Set by the before_agent_start hook so recall() uses the correct
|
|
@@ -1910,6 +1875,12 @@ export class Orchestrator {
|
|
|
1910
1875
|
* to RecallResultFormatter.
|
|
1911
1876
|
*/
|
|
1912
1877
|
readonly recallResultFormatter: RecallResultFormatter;
|
|
1878
|
+
/**
|
|
1879
|
+
* Issue #1526: conversation-index subsystem moved to
|
|
1880
|
+
* ConversationIndexCoordinator.
|
|
1881
|
+
*/
|
|
1882
|
+
readonly conversationIndexCoordinator: ConversationIndexCoordinator;
|
|
1883
|
+
readonly recallRerankCoordinator: RecallRerankCoordinator;
|
|
1913
1884
|
private heartbeatObserverChains = new Map<string, Promise<void>>();
|
|
1914
1885
|
private recentExtractionFingerprints = new Map<string, number>();
|
|
1915
1886
|
private readonly consolidationObservers = new Set<
|
|
@@ -1918,7 +1889,6 @@ export class Orchestrator {
|
|
|
1918
1889
|
private wearablesServiceInstance: WearablesService | null = null;
|
|
1919
1890
|
private wearablesAutoSyncHandle: { stop(): Promise<void> } | null = null;
|
|
1920
1891
|
private lastQmdReprobeAtMs = 0;
|
|
1921
|
-
private readonly conversationIndexLastUpdateAtMs = new Map<string, number>();
|
|
1922
1892
|
private lastFileHygieneRunAtMs = 0;
|
|
1923
1893
|
// Pattern-reinforcement cadence gate (issue #687 PR 2/4). Tracks the
|
|
1924
1894
|
// last successful run so `runPatternReinforcement` can short-circuit
|
|
@@ -2565,7 +2535,7 @@ export class Orchestrator {
|
|
|
2565
2535
|
private async contentHashIndexForStorage(
|
|
2566
2536
|
targetStorage: StorageManager,
|
|
2567
2537
|
): Promise<ContentHashIndex | null> {
|
|
2568
|
-
if (!this.config.
|
|
2538
|
+
if (!resolveRecallAuxiliaryCapabilities(this.config).factDeduplication) return null;
|
|
2569
2539
|
|
|
2570
2540
|
if (targetStorage.dir === this.storage.dir) {
|
|
2571
2541
|
if (!this.contentHashIndex) {
|
|
@@ -2828,7 +2798,7 @@ export class Orchestrator {
|
|
|
2828
2798
|
if (config.defaultNamespace) this.storageRouter.bindCatalogWriteHook(this.storage, config.defaultNamespace);
|
|
2829
2799
|
// Wire page-level versioning (issue #371)
|
|
2830
2800
|
this.storage.setVersioningConfig({
|
|
2831
|
-
enabled: config.
|
|
2801
|
+
enabled: resolveRecallAuxiliaryCapabilities(config).versioning,
|
|
2832
2802
|
maxVersionsPerPage: config.versioningMaxPerPage,
|
|
2833
2803
|
sidecarDir: config.versioningSidecarDir,
|
|
2834
2804
|
});
|
|
@@ -2851,7 +2821,7 @@ export class Orchestrator {
|
|
|
2851
2821
|
// Wire at-rest encryption (issue #690 PR 3/4).
|
|
2852
2822
|
// If secureStoreEnabled, check whether the keyring already holds a key
|
|
2853
2823
|
// for this memory dir (e.g. operator unlocked before daemon restart).
|
|
2854
|
-
if (config.
|
|
2824
|
+
if (resolveRecallAuxiliaryCapabilities(config).secureStore) {
|
|
2855
2825
|
// Mark the store as required so writes throw SecureStoreLockedError
|
|
2856
2826
|
// instead of silently falling back to plaintext when locked (P1 finding
|
|
2857
2827
|
// from Cursor review of PR #767).
|
|
@@ -2889,7 +2859,7 @@ export class Orchestrator {
|
|
|
2889
2859
|
this.sharedContext = config.sharedContextEnabled
|
|
2890
2860
|
? new SharedContextManager(config)
|
|
2891
2861
|
: undefined;
|
|
2892
|
-
this.compounding = config.
|
|
2862
|
+
this.compounding = resolveConsolidationCapabilities(config).compounding
|
|
2893
2863
|
? new CompoundingEngine(config, this.storage)
|
|
2894
2864
|
: undefined;
|
|
2895
2865
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
@@ -2899,6 +2869,18 @@ export class Orchestrator {
|
|
|
2899
2869
|
"conversation-index",
|
|
2900
2870
|
"chunks",
|
|
2901
2871
|
);
|
|
2872
|
+
this.conversationIndexCoordinator = new ConversationIndexCoordinator({
|
|
2873
|
+
config,
|
|
2874
|
+
getTranscript: () => this.transcript,
|
|
2875
|
+
getBackend: () => this.conversationIndexBackend,
|
|
2876
|
+
indexDir: this.conversationIndexDir,
|
|
2877
|
+
});
|
|
2878
|
+
this.recallRerankCoordinator = new RecallRerankCoordinator({
|
|
2879
|
+
getConfig: () => this.config,
|
|
2880
|
+
getStorage: (namespace) => this.getStorage(namespace),
|
|
2881
|
+
readQmdResultMemory: (resultPath, fallbackStorage, recallNamespaces) =>
|
|
2882
|
+
this.readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces),
|
|
2883
|
+
});
|
|
2902
2884
|
this.modelRegistry = new ModelRegistry(config.memoryDir);
|
|
2903
2885
|
this.relevance = new RelevanceStore(config.memoryDir);
|
|
2904
2886
|
this.negatives = new NegativeExampleStore(config.memoryDir);
|
|
@@ -3081,7 +3063,7 @@ export class Orchestrator {
|
|
|
3081
3063
|
this.boxBuilders.set(
|
|
3082
3064
|
dir,
|
|
3083
3065
|
new BoxBuilder(dir, {
|
|
3084
|
-
memoryBoxesEnabled: this.config.
|
|
3066
|
+
memoryBoxesEnabled: resolvePresentationCapabilities(this.config).memoryBoxes,
|
|
3085
3067
|
traceWeaverEnabled: this.config.traceWeaverEnabled,
|
|
3086
3068
|
boxTopicShiftThreshold: this.config.boxTopicShiftThreshold,
|
|
3087
3069
|
boxTimeGapMs: this.config.boxTimeGapMs,
|
|
@@ -3427,7 +3409,7 @@ export class Orchestrator {
|
|
|
3427
3409
|
});
|
|
3428
3410
|
|
|
3429
3411
|
// Initialize content-hash dedup index
|
|
3430
|
-
if (this.config.
|
|
3412
|
+
if (resolveRecallAuxiliaryCapabilities(this.config).factDeduplication) {
|
|
3431
3413
|
this.contentHashIndex = this.storage.createContentHashIndex();
|
|
3432
3414
|
await this.contentHashIndex.load();
|
|
3433
3415
|
log.info(
|
|
@@ -3453,7 +3435,7 @@ export class Orchestrator {
|
|
|
3453
3435
|
);
|
|
3454
3436
|
this.buffer.resetToEmpty();
|
|
3455
3437
|
}
|
|
3456
|
-
if (this.config.
|
|
3438
|
+
if (resolveRecallAuxiliaryCapabilities(this.config).compactionReset) {
|
|
3457
3439
|
try {
|
|
3458
3440
|
const wsDir = this.config.workspaceDir || defaultWorkspaceDir();
|
|
3459
3441
|
const files = await readdir(wsDir).catch(() => [] as string[]);
|
|
@@ -3677,7 +3659,7 @@ export class Orchestrator {
|
|
|
3677
3659
|
// Awaited so callers of `deferredReady` can rely on warmups being complete
|
|
3678
3660
|
// and shutdown sequencing does not race with in-flight cache builds.
|
|
3679
3661
|
const cacheWarmups: Promise<void>[] = [];
|
|
3680
|
-
if (this.config.
|
|
3662
|
+
if (resolveRecallAuxiliaryCapabilities(this.config).knowledgeIndex) {
|
|
3681
3663
|
cacheWarmups.push(
|
|
3682
3664
|
(async () => {
|
|
3683
3665
|
try {
|
|
@@ -3996,7 +3978,7 @@ export class Orchestrator {
|
|
|
3996
3978
|
const cadenceKey = options.namespace ?? "";
|
|
3997
3979
|
// Master switch: a disabled feature is never bypassed, even with
|
|
3998
3980
|
// force=true. `force` only relaxes the cadence floor below.
|
|
3999
|
-
if (!this.config.
|
|
3981
|
+
if (!resolveConsolidationCapabilities(this.config).patternReinforcement) {
|
|
4000
3982
|
return { ran: false, skippedReason: "disabled", namespace: cadenceKey };
|
|
4001
3983
|
}
|
|
4002
3984
|
const cadence = this.config.patternReinforcementCadenceMs;
|
|
@@ -4055,7 +4037,7 @@ export class Orchestrator {
|
|
|
4055
4037
|
? { itemCount: result.result.clustersFound }
|
|
4056
4038
|
: { itemCount: 0 };
|
|
4057
4039
|
},
|
|
4058
|
-
{ enabled: this.config.
|
|
4040
|
+
{ enabled: resolveConsolidationCapabilities(this.config).patternReinforcement },
|
|
4059
4041
|
);
|
|
4060
4042
|
}
|
|
4061
4043
|
|
|
@@ -4099,7 +4081,7 @@ export class Orchestrator {
|
|
|
4099
4081
|
});
|
|
4100
4082
|
return { itemCount: result.clustersFound };
|
|
4101
4083
|
},
|
|
4102
|
-
{ enabled: this.config.
|
|
4084
|
+
{ enabled: resolveConsolidationCapabilities(this.config).semanticConsolidation },
|
|
4103
4085
|
);
|
|
4104
4086
|
}
|
|
4105
4087
|
|
|
@@ -5007,63 +4989,21 @@ export class Orchestrator {
|
|
|
5007
4989
|
retrievalQuery: string,
|
|
5008
4990
|
topK: number,
|
|
5009
4991
|
): Promise<Array<{ path: string; snippet: string; score: number }>> {
|
|
5010
|
-
|
|
5011
|
-
return this.conversationIndexBackend.search(retrievalQuery, topK);
|
|
5012
|
-
}
|
|
5013
|
-
return [];
|
|
4992
|
+
return this.conversationIndexCoordinator.search(retrievalQuery, topK);
|
|
5014
4993
|
}
|
|
5015
4994
|
|
|
5016
4995
|
private formatConversationRecallSection(
|
|
5017
4996
|
results: Array<{ path: string; snippet: string; score: number }>,
|
|
5018
4997
|
maxChars: number,
|
|
5019
4998
|
): string | null {
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
if (!r?.snippet) continue;
|
|
5025
|
-
const chunk =
|
|
5026
|
-
`### ${r.path}\n` +
|
|
5027
|
-
`Score: ${r.score.toFixed(3)}\n\n` +
|
|
5028
|
-
`${r.snippet.trim()}\n`;
|
|
5029
|
-
if (used + chunk.length > maxChars) break;
|
|
5030
|
-
lines.push(chunk);
|
|
5031
|
-
used += chunk.length;
|
|
5032
|
-
}
|
|
5033
|
-
return used > 0 ? lines.join("\n") : null;
|
|
5034
|
-
}
|
|
5035
|
-
|
|
5036
|
-
private async countConversationChunkDocs(dir: string): Promise<number> {
|
|
5037
|
-
try {
|
|
5038
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
5039
|
-
let total = 0;
|
|
5040
|
-
for (const entry of entries) {
|
|
5041
|
-
const fullPath = path.join(dir, entry.name);
|
|
5042
|
-
if (entry.isDirectory()) {
|
|
5043
|
-
total += await this.countConversationChunkDocs(fullPath);
|
|
5044
|
-
continue;
|
|
5045
|
-
}
|
|
5046
|
-
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
5047
|
-
total += 1;
|
|
5048
|
-
}
|
|
5049
|
-
}
|
|
5050
|
-
return total;
|
|
5051
|
-
} catch {
|
|
5052
|
-
return 0;
|
|
5053
|
-
}
|
|
4999
|
+
return this.conversationIndexCoordinator.formatRecallSection(
|
|
5000
|
+
results,
|
|
5001
|
+
maxChars,
|
|
5002
|
+
);
|
|
5054
5003
|
}
|
|
5055
5004
|
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
hours: number = 24,
|
|
5059
|
-
): Promise<ReturnType<typeof chunkTranscriptEntries>> {
|
|
5060
|
-
const entries = await this.transcript.readRecent(hours, sessionKey);
|
|
5061
|
-
const effectiveSessionKey = sessionKey ?? "all-sessions";
|
|
5062
|
-
return chunkTranscriptEntries(effectiveSessionKey, entries, {
|
|
5063
|
-
maxChars: this.config.conversationRecallMaxChars * 2,
|
|
5064
|
-
maxTurns: Math.max(10, this.config.hourlySummariesMaxTurnsPerRun),
|
|
5065
|
-
});
|
|
5066
|
-
}
|
|
5005
|
+
// Issue #1526: countConversationChunkDocs / buildConversationIndexChunks moved
|
|
5006
|
+
// to ConversationIndexCoordinator (internal helpers, no orchestrator callers).
|
|
5067
5007
|
|
|
5068
5008
|
async getConversationIndexHealth(): Promise<{
|
|
5069
5009
|
enabled: boolean;
|
|
@@ -5088,37 +5028,7 @@ export class Orchestrator {
|
|
|
5088
5028
|
};
|
|
5089
5029
|
};
|
|
5090
5030
|
}> {
|
|
5091
|
-
|
|
5092
|
-
this.conversationIndexDir,
|
|
5093
|
-
);
|
|
5094
|
-
const lastUpdateAtMs = Math.max(
|
|
5095
|
-
0,
|
|
5096
|
-
...this.conversationIndexLastUpdateAtMs.values(),
|
|
5097
|
-
);
|
|
5098
|
-
const lastUpdateAt =
|
|
5099
|
-
lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
5100
|
-
|
|
5101
|
-
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
5102
|
-
return {
|
|
5103
|
-
enabled: false,
|
|
5104
|
-
backend: this.config.conversationIndexBackend,
|
|
5105
|
-
status: "disabled",
|
|
5106
|
-
chunkDocCount,
|
|
5107
|
-
lastUpdateAt,
|
|
5108
|
-
};
|
|
5109
|
-
}
|
|
5110
|
-
const backendHealth = this.conversationIndexBackend
|
|
5111
|
-
? await this.conversationIndexBackend.health()
|
|
5112
|
-
: {
|
|
5113
|
-
backend: this.config.conversationIndexBackend,
|
|
5114
|
-
status: "degraded" as const,
|
|
5115
|
-
};
|
|
5116
|
-
return {
|
|
5117
|
-
enabled: true,
|
|
5118
|
-
chunkDocCount,
|
|
5119
|
-
lastUpdateAt,
|
|
5120
|
-
...backendHealth,
|
|
5121
|
-
};
|
|
5031
|
+
return this.conversationIndexCoordinator.getHealth();
|
|
5122
5032
|
}
|
|
5123
5033
|
|
|
5124
5034
|
async inspectConversationIndex(): Promise<
|
|
@@ -5128,53 +5038,7 @@ export class Orchestrator {
|
|
|
5128
5038
|
lastUpdateAt: string | null;
|
|
5129
5039
|
}
|
|
5130
5040
|
> {
|
|
5131
|
-
|
|
5132
|
-
this.conversationIndexDir,
|
|
5133
|
-
);
|
|
5134
|
-
const lastUpdateAtMs = Math.max(
|
|
5135
|
-
0,
|
|
5136
|
-
...this.conversationIndexLastUpdateAtMs.values(),
|
|
5137
|
-
);
|
|
5138
|
-
const lastUpdateAt =
|
|
5139
|
-
lastUpdateAtMs > 0 ? new Date(lastUpdateAtMs).toISOString() : null;
|
|
5140
|
-
|
|
5141
|
-
if (!resolveIndexingCapabilities(this.config).conversationIndex) {
|
|
5142
|
-
return {
|
|
5143
|
-
enabled: false,
|
|
5144
|
-
backend: this.config.conversationIndexBackend,
|
|
5145
|
-
status: "disabled",
|
|
5146
|
-
available: false,
|
|
5147
|
-
indexPath: this.conversationIndexDir,
|
|
5148
|
-
supportsIncrementalUpdate: true,
|
|
5149
|
-
message: "Conversation index disabled by config",
|
|
5150
|
-
metadata: {
|
|
5151
|
-
chunkCount: chunkDocCount,
|
|
5152
|
-
},
|
|
5153
|
-
chunkDocCount,
|
|
5154
|
-
lastUpdateAt,
|
|
5155
|
-
};
|
|
5156
|
-
}
|
|
5157
|
-
|
|
5158
|
-
const inspection = this.conversationIndexBackend
|
|
5159
|
-
? await this.conversationIndexBackend.inspect()
|
|
5160
|
-
: {
|
|
5161
|
-
backend: this.config.conversationIndexBackend,
|
|
5162
|
-
status: "degraded" as const,
|
|
5163
|
-
available: false,
|
|
5164
|
-
indexPath: this.conversationIndexDir,
|
|
5165
|
-
supportsIncrementalUpdate: true,
|
|
5166
|
-
message: "Conversation index backend unavailable",
|
|
5167
|
-
metadata: {
|
|
5168
|
-
chunkCount: chunkDocCount,
|
|
5169
|
-
},
|
|
5170
|
-
};
|
|
5171
|
-
|
|
5172
|
-
return {
|
|
5173
|
-
enabled: true,
|
|
5174
|
-
chunkDocCount,
|
|
5175
|
-
lastUpdateAt,
|
|
5176
|
-
...inspection,
|
|
5177
|
-
};
|
|
5041
|
+
return this.conversationIndexCoordinator.inspect();
|
|
5178
5042
|
}
|
|
5179
5043
|
|
|
5180
5044
|
async getRecoverySummary(sessionKey?: string): Promise<{
|
|
@@ -5200,54 +5064,7 @@ export class Orchestrator {
|
|
|
5200
5064
|
retryAfterMs?: number;
|
|
5201
5065
|
embedded?: boolean;
|
|
5202
5066
|
}> {
|
|
5203
|
-
|
|
5204
|
-
return { chunks: 0, skipped: true, reason: "disabled", embedded: false };
|
|
5205
|
-
}
|
|
5206
|
-
const enforceMinInterval = opts?.enforceMinInterval !== false;
|
|
5207
|
-
if (enforceMinInterval) {
|
|
5208
|
-
const minIntervalMs = Math.max(
|
|
5209
|
-
0,
|
|
5210
|
-
this.config.conversationIndexMinUpdateIntervalMs,
|
|
5211
|
-
);
|
|
5212
|
-
const now = Date.now();
|
|
5213
|
-
const last = this.conversationIndexLastUpdateAtMs.get(sessionKey) ?? 0;
|
|
5214
|
-
const elapsed = now - last;
|
|
5215
|
-
if (minIntervalMs > 0 && elapsed < minIntervalMs) {
|
|
5216
|
-
return {
|
|
5217
|
-
chunks: 0,
|
|
5218
|
-
skipped: true,
|
|
5219
|
-
reason: "min_interval",
|
|
5220
|
-
retryAfterMs: minIntervalMs - elapsed,
|
|
5221
|
-
embedded: false,
|
|
5222
|
-
};
|
|
5223
|
-
}
|
|
5224
|
-
}
|
|
5225
|
-
const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
|
|
5226
|
-
await writeConversationChunks(this.conversationIndexDir, chunks);
|
|
5227
|
-
const retentionCutoffMs =
|
|
5228
|
-
Number.isFinite(this.config.conversationIndexRetentionDays) &&
|
|
5229
|
-
this.config.conversationIndexRetentionDays > 0
|
|
5230
|
-
? Date.now() -
|
|
5231
|
-
this.config.conversationIndexRetentionDays * 24 * 60 * 60 * 1000
|
|
5232
|
-
: undefined;
|
|
5233
|
-
await cleanupConversationChunks(
|
|
5234
|
-
this.conversationIndexDir,
|
|
5235
|
-
this.config.conversationIndexRetentionDays,
|
|
5236
|
-
);
|
|
5237
|
-
const shouldEmbed =
|
|
5238
|
-
opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
5239
|
-
let embedded = false;
|
|
5240
|
-
|
|
5241
|
-
if (this.conversationIndexBackend) {
|
|
5242
|
-
const result = await this.conversationIndexBackend.update(chunks, {
|
|
5243
|
-
embed: shouldEmbed,
|
|
5244
|
-
...(retentionCutoffMs !== undefined ? { retentionCutoffMs } : {}),
|
|
5245
|
-
});
|
|
5246
|
-
embedded = result.embedded;
|
|
5247
|
-
}
|
|
5248
|
-
|
|
5249
|
-
this.conversationIndexLastUpdateAtMs.set(sessionKey, Date.now());
|
|
5250
|
-
return { chunks: chunks.length, skipped: false, embedded };
|
|
5067
|
+
return this.conversationIndexCoordinator.update(sessionKey, hours, opts);
|
|
5251
5068
|
}
|
|
5252
5069
|
|
|
5253
5070
|
async rebuildConversationIndex(
|
|
@@ -5261,42 +5078,7 @@ export class Orchestrator {
|
|
|
5261
5078
|
embedded?: boolean;
|
|
5262
5079
|
rebuilt?: boolean;
|
|
5263
5080
|
}> {
|
|
5264
|
-
|
|
5265
|
-
return {
|
|
5266
|
-
chunks: 0,
|
|
5267
|
-
skipped: true,
|
|
5268
|
-
reason: "disabled",
|
|
5269
|
-
embedded: false,
|
|
5270
|
-
rebuilt: false,
|
|
5271
|
-
};
|
|
5272
|
-
}
|
|
5273
|
-
|
|
5274
|
-
const chunks = await this.buildConversationIndexChunks(sessionKey, hours);
|
|
5275
|
-
await writeConversationChunks(this.conversationIndexDir, chunks);
|
|
5276
|
-
await cleanupConversationChunks(
|
|
5277
|
-
this.conversationIndexDir,
|
|
5278
|
-
this.config.conversationIndexRetentionDays,
|
|
5279
|
-
);
|
|
5280
|
-
|
|
5281
|
-
const shouldEmbed =
|
|
5282
|
-
opts?.embed ?? this.config.conversationIndexEmbedOnUpdate;
|
|
5283
|
-
let embedded = false;
|
|
5284
|
-
let rebuilt = false;
|
|
5285
|
-
if (this.conversationIndexBackend) {
|
|
5286
|
-
const result = await this.conversationIndexBackend.rebuild(chunks, {
|
|
5287
|
-
embed: shouldEmbed,
|
|
5288
|
-
});
|
|
5289
|
-
embedded = result.embedded;
|
|
5290
|
-
rebuilt = result.rebuilt;
|
|
5291
|
-
}
|
|
5292
|
-
|
|
5293
|
-
const stamp = Date.now();
|
|
5294
|
-
if (sessionKey) {
|
|
5295
|
-
this.conversationIndexLastUpdateAtMs.set(sessionKey, stamp);
|
|
5296
|
-
} else {
|
|
5297
|
-
this.conversationIndexLastUpdateAtMs.set("__rebuild__", stamp);
|
|
5298
|
-
}
|
|
5299
|
-
return { chunks: chunks.length, skipped: false, embedded, rebuilt };
|
|
5081
|
+
return this.conversationIndexCoordinator.rebuild(sessionKey, hours, opts);
|
|
5300
5082
|
}
|
|
5301
5083
|
|
|
5302
5084
|
/**
|
|
@@ -5430,7 +5212,7 @@ export class Orchestrator {
|
|
|
5430
5212
|
// memory directory, reject recall with a clear human-readable error
|
|
5431
5213
|
// rather than surfacing a cryptic SecureStoreLockedError from deep
|
|
5432
5214
|
// inside the storage layer.
|
|
5433
|
-
if (this.config.
|
|
5215
|
+
if (resolveRecallAuxiliaryCapabilities(this.config).secureStore && !this.storage.isSecureStoreUnlocked()) {
|
|
5434
5216
|
const lockedMsg =
|
|
5435
5217
|
"[secure-store locked] Memory store is encrypted and locked. " +
|
|
5436
5218
|
"Unlock the secure-store inside this daemon process, or restart the daemon through a secure-store aware launcher that installs the key.";
|
|
@@ -5568,7 +5350,7 @@ export class Orchestrator {
|
|
|
5568
5350
|
});
|
|
5569
5351
|
const observationNamespaces = observationScopePlan.readNamespaces;
|
|
5570
5352
|
const observationQueryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
5571
|
-
cronRecallPolicyEnabled: this.config.
|
|
5353
|
+
cronRecallPolicyEnabled: resolveRecallAuxiliaryCapabilities(this.config).cronRecallPolicy,
|
|
5572
5354
|
cronRecallNormalizedQueryMaxChars:
|
|
5573
5355
|
this.config.cronRecallNormalizedQueryMaxChars,
|
|
5574
5356
|
cronRecallInstructionHeavyTokenCap:
|
|
@@ -6994,7 +6776,7 @@ export class Orchestrator {
|
|
|
6994
6776
|
// recall searches, incl. coding `readFallbacks`). See the
|
|
6995
6777
|
// `lcmReadSessionIds` derivation after `recallNamespaces` is built.
|
|
6996
6778
|
const queryPolicy = buildRecallQueryPolicy(prompt, sessionKey, {
|
|
6997
|
-
cronRecallPolicyEnabled: this.config.
|
|
6779
|
+
cronRecallPolicyEnabled: resolveRecallAuxiliaryCapabilities(this.config).cronRecallPolicy,
|
|
6998
6780
|
cronRecallNormalizedQueryMaxChars:
|
|
6999
6781
|
this.config.cronRecallNormalizedQueryMaxChars,
|
|
7000
6782
|
cronRecallInstructionHeavyTokenCap:
|
|
@@ -7157,7 +6939,7 @@ export class Orchestrator {
|
|
|
7157
6939
|
return limit;
|
|
7158
6940
|
})()
|
|
7159
6941
|
: 0;
|
|
7160
|
-
const recallHeadroom = this.config.
|
|
6942
|
+
const recallHeadroom = resolvePresentationCapabilities(this.config).verbatimArtifacts
|
|
7161
6943
|
? Math.max(12, this.config.verbatimArtifactsMaxRecall * 4)
|
|
7162
6944
|
: 12;
|
|
7163
6945
|
const computedFetchLimit =
|
|
@@ -7170,7 +6952,7 @@ export class Orchestrator {
|
|
|
7170
6952
|
const qmdFetchLimit = computedFetchLimit;
|
|
7171
6953
|
const qmdHybridFetchLimit = computeQmdHybridFetchLimit(
|
|
7172
6954
|
qmdFetchLimit,
|
|
7173
|
-
this.config.
|
|
6955
|
+
resolvePresentationCapabilities(this.config).verbatimArtifacts,
|
|
7174
6956
|
this.config.verbatimArtifactsMaxRecall,
|
|
7175
6957
|
);
|
|
7176
6958
|
const embeddingFetchLimit = computedFetchLimit;
|
|
@@ -7862,11 +7644,11 @@ export class Orchestrator {
|
|
|
7862
7644
|
if (
|
|
7863
7645
|
!this.isRecallSectionEnabled(
|
|
7864
7646
|
"entity-retrieval",
|
|
7865
|
-
this.config.
|
|
7647
|
+
resolveRecallAuxiliaryCapabilities(this.config).entityRetrieval,
|
|
7866
7648
|
)
|
|
7867
7649
|
)
|
|
7868
7650
|
return null;
|
|
7869
|
-
if (!this.config.
|
|
7651
|
+
if (!resolveRecallAuxiliaryCapabilities(this.config).entityRetrieval) return null;
|
|
7870
7652
|
const maxChars =
|
|
7871
7653
|
this.getRecallSectionMaxChars("entity-retrieval") ??
|
|
7872
7654
|
this.config.entityRetrievalMaxChars;
|
|
@@ -7939,11 +7721,11 @@ export class Orchestrator {
|
|
|
7939
7721
|
if (
|
|
7940
7722
|
!this.isRecallSectionEnabled(
|
|
7941
7723
|
"knowledge-index",
|
|
7942
|
-
this.config.
|
|
7724
|
+
resolveRecallAuxiliaryCapabilities(this.config).knowledgeIndex,
|
|
7943
7725
|
)
|
|
7944
7726
|
)
|
|
7945
7727
|
return null;
|
|
7946
|
-
if (!this.config.
|
|
7728
|
+
if (!resolveRecallAuxiliaryCapabilities(this.config).knowledgeIndex) return null;
|
|
7947
7729
|
const t0 = Date.now();
|
|
7948
7730
|
try {
|
|
7949
7731
|
const knowledgeIndexMaxChars =
|
|
@@ -8034,11 +7816,11 @@ export class Orchestrator {
|
|
|
8034
7816
|
if (
|
|
8035
7817
|
!this.isRecallSectionEnabled(
|
|
8036
7818
|
"verbatim-artifacts",
|
|
8037
|
-
this.config.
|
|
7819
|
+
resolvePresentationCapabilities(this.config).verbatimArtifacts === true,
|
|
8038
7820
|
)
|
|
8039
7821
|
)
|
|
8040
7822
|
return [];
|
|
8041
|
-
if (!this.config.
|
|
7823
|
+
if (!resolvePresentationCapabilities(this.config).verbatimArtifacts) return [];
|
|
8042
7824
|
const t0 = Date.now();
|
|
8043
7825
|
const targetCount = computeArtifactRecallLimit(
|
|
8044
7826
|
recallMode,
|
|
@@ -8269,10 +8051,10 @@ export class Orchestrator {
|
|
|
8269
8051
|
const calibrationPromise = (async (): Promise<string | null> => {
|
|
8270
8052
|
const t0 = Date.now();
|
|
8271
8053
|
if (
|
|
8272
|
-
!this.config.
|
|
8054
|
+
!resolveConsolidationCapabilities(this.config).calibration ||
|
|
8273
8055
|
!this.isRecallSectionEnabled(
|
|
8274
8056
|
"calibration-rules",
|
|
8275
|
-
this.config.
|
|
8057
|
+
resolveConsolidationCapabilities(this.config).calibration === true,
|
|
8276
8058
|
)
|
|
8277
8059
|
) {
|
|
8278
8060
|
recordRecallSectionMetric({
|
|
@@ -8434,7 +8216,7 @@ export class Orchestrator {
|
|
|
8434
8216
|
query: retrievalQuery,
|
|
8435
8217
|
maxResults,
|
|
8436
8218
|
sessionKey,
|
|
8437
|
-
anchorsEnabled: this.config.
|
|
8219
|
+
anchorsEnabled: resolveConsolidationCapabilities(this.config).abstractionAnchors,
|
|
8438
8220
|
abortSignal: harmonicRetrievalAbort.signal,
|
|
8439
8221
|
}),
|
|
8440
8222
|
),
|
|
@@ -8481,10 +8263,10 @@ export class Orchestrator {
|
|
|
8481
8263
|
const verifiedRecallPromise = (async (): Promise<string | null> => {
|
|
8482
8264
|
const t0 = Date.now();
|
|
8483
8265
|
if (
|
|
8484
|
-
!this.config.
|
|
8266
|
+
!resolveRecallAuxiliaryCapabilities(this.config).verifiedRecall ||
|
|
8485
8267
|
!this.isRecallSectionEnabled(
|
|
8486
8268
|
"verified-episodes",
|
|
8487
|
-
this.config.
|
|
8269
|
+
resolveRecallAuxiliaryCapabilities(this.config).verifiedRecall === true,
|
|
8488
8270
|
)
|
|
8489
8271
|
) {
|
|
8490
8272
|
recordRecallSectionMetric({
|
|
@@ -8570,10 +8352,10 @@ export class Orchestrator {
|
|
|
8570
8352
|
const verifiedRulesPromise = (async (): Promise<string | null> => {
|
|
8571
8353
|
const t0 = Date.now();
|
|
8572
8354
|
if (
|
|
8573
|
-
!this.config.
|
|
8355
|
+
!resolveRecallAuxiliaryCapabilities(this.config).semanticRuleVerification ||
|
|
8574
8356
|
!this.isRecallSectionEnabled(
|
|
8575
8357
|
"verified-rules",
|
|
8576
|
-
this.config.
|
|
8358
|
+
resolveRecallAuxiliaryCapabilities(this.config).semanticRuleVerification === true,
|
|
8577
8359
|
)
|
|
8578
8360
|
) {
|
|
8579
8361
|
recordRecallSectionMetric({
|
|
@@ -8659,10 +8441,10 @@ export class Orchestrator {
|
|
|
8659
8441
|
const t0 = Date.now();
|
|
8660
8442
|
if (
|
|
8661
8443
|
!resolveCreationMemoryCapabilities(this.config).creationMemory ||
|
|
8662
|
-
!this.config.
|
|
8444
|
+
!resolveRecallAuxiliaryCapabilities(this.config).workProductRecall ||
|
|
8663
8445
|
!this.isRecallSectionEnabled(
|
|
8664
8446
|
"work-products",
|
|
8665
|
-
this.config.
|
|
8447
|
+
resolveRecallAuxiliaryCapabilities(this.config).workProductRecall === true,
|
|
8666
8448
|
)
|
|
8667
8449
|
) {
|
|
8668
8450
|
recordRecallSectionMetric({
|
|
@@ -9170,7 +8952,7 @@ export class Orchestrator {
|
|
|
9170
8952
|
const transcriptPromise = (async (): Promise<string | null> => {
|
|
9171
8953
|
const t0 = Date.now();
|
|
9172
8954
|
if (
|
|
9173
|
-
!this.config.
|
|
8955
|
+
!resolvePresentationCapabilities(this.config).transcript ||
|
|
9174
8956
|
!this.isRecallSectionEnabled("transcript", true)
|
|
9175
8957
|
) {
|
|
9176
8958
|
recordRecallSectionMetric({
|
|
@@ -9276,7 +9058,7 @@ export class Orchestrator {
|
|
|
9276
9058
|
this._recallWorkspaceOverrides.get(effectiveSessionKey);
|
|
9277
9059
|
this._recallWorkspaceOverrides.delete(effectiveSessionKey);
|
|
9278
9060
|
|
|
9279
|
-
if (!this.config.
|
|
9061
|
+
if (!resolveRecallAuxiliaryCapabilities(this.config).compactionReset) return null;
|
|
9280
9062
|
|
|
9281
9063
|
const workspaceDir =
|
|
9282
9064
|
compactionWorkspaceDir ||
|
|
@@ -9629,9 +9411,9 @@ export class Orchestrator {
|
|
|
9629
9411
|
const recentBoxesPromise = observeEnrichmentPromise(
|
|
9630
9412
|
this.isRecallSectionEnabled(
|
|
9631
9413
|
"memory-boxes",
|
|
9632
|
-
this.config.
|
|
9414
|
+
resolvePresentationCapabilities(this.config).memoryBoxes === true,
|
|
9633
9415
|
) &&
|
|
9634
|
-
this.config.
|
|
9416
|
+
resolvePresentationCapabilities(this.config).memoryBoxes &&
|
|
9635
9417
|
this.config.boxRecallDays > 0
|
|
9636
9418
|
? Promise.all(
|
|
9637
9419
|
profileStorages.map((storage) =>
|
|
@@ -12734,7 +12516,7 @@ export class Orchestrator {
|
|
|
12734
12516
|
): Promise<void> {
|
|
12735
12517
|
const mode = this.config.correctionCaptureMode;
|
|
12736
12518
|
if (mode === "off") return;
|
|
12737
|
-
if (!this.config.
|
|
12519
|
+
if (!resolveRecallAuxiliaryCapabilities(this.config).correction) return;
|
|
12738
12520
|
|
|
12739
12521
|
try {
|
|
12740
12522
|
const corrections = detectPassiveCorrections(
|
|
@@ -12754,7 +12536,7 @@ export class Orchestrator {
|
|
|
12754
12536
|
const result = await capturePassiveCorrections(
|
|
12755
12537
|
corrections,
|
|
12756
12538
|
{
|
|
12757
|
-
correctionEnabled: this.config.
|
|
12539
|
+
correctionEnabled: resolveRecallAuxiliaryCapabilities(this.config).correction,
|
|
12758
12540
|
isLiveSession: opts.isLiveSession,
|
|
12759
12541
|
bufferKey: opts.bufferKey,
|
|
12760
12542
|
sessionKey: opts.sessionKey,
|
|
@@ -13188,7 +12970,7 @@ export class Orchestrator {
|
|
|
13188
12970
|
}
|
|
13189
12971
|
|
|
13190
12972
|
let threadIdForExtraction: string | null = null;
|
|
13191
|
-
if (this.config.
|
|
12973
|
+
if (resolvePresentationCapabilities(this.config).threading && turns.length > 0) {
|
|
13192
12974
|
const lastTurn = turns[turns.length - 1];
|
|
13193
12975
|
try {
|
|
13194
12976
|
threadIdForExtraction = await this.threading.processTurn(lastTurn, []);
|
|
@@ -13318,7 +13100,7 @@ export class Orchestrator {
|
|
|
13318
13100
|
// Topics are derived from the current extraction's facts and entities only —
|
|
13319
13101
|
// not from readAllMemories() — so box topics accurately reflect the current
|
|
13320
13102
|
// session window and the call is free of expensive full-corpus I/O.
|
|
13321
|
-
if (this.config.
|
|
13103
|
+
if (resolvePresentationCapabilities(this.config).memoryBoxes && persistedIds.length > 0) {
|
|
13322
13104
|
const extractionTopics = deriveTopicsFromExtraction(result);
|
|
13323
13105
|
// Derive episodic metadata from buffer turns (REMem-inspired)
|
|
13324
13106
|
const firstUserTurn = turns.find((t) => t.role === "user");
|
|
@@ -13339,7 +13121,7 @@ export class Orchestrator {
|
|
|
13339
13121
|
// Batch-append persisted IDs so non-fact memories (entities/questions) are
|
|
13340
13122
|
// always attached to the thread. The helper excludes pending_review ids (#1635).
|
|
13341
13123
|
if (
|
|
13342
|
-
this.config.
|
|
13124
|
+
resolvePresentationCapabilities(this.config).threading &&
|
|
13343
13125
|
threadIdForExtraction &&
|
|
13344
13126
|
persistedIds.length > 0
|
|
13345
13127
|
) {
|
|
@@ -13350,7 +13132,7 @@ export class Orchestrator {
|
|
|
13350
13132
|
}
|
|
13351
13133
|
|
|
13352
13134
|
// Thread title update for the already-established thread context.
|
|
13353
|
-
if (this.config.
|
|
13135
|
+
if (resolvePresentationCapabilities(this.config).threading && threadIdForExtraction) {
|
|
13354
13136
|
const conversationContent = turns.map((t) => t.content).join(" ");
|
|
13355
13137
|
try {
|
|
13356
13138
|
await this.threading.updateThreadTitle(
|
|
@@ -15251,7 +15033,7 @@ export class Orchestrator {
|
|
|
15251
15033
|
|
|
15252
15034
|
if (chunkResult.chunked && chunkResult.chunks.length > 1) {
|
|
15253
15035
|
// Classify memory kind (v8.0 Phase 2B: HiMem episode/note dual store)
|
|
15254
|
-
const memoryKind = this.config.
|
|
15036
|
+
const memoryKind = resolvePresentationCapabilities(this.config).episodeNoteMode
|
|
15255
15037
|
? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory)
|
|
15256
15038
|
: undefined;
|
|
15257
15039
|
|
|
@@ -15508,7 +15290,7 @@ export class Orchestrator {
|
|
|
15508
15290
|
}
|
|
15509
15291
|
try {
|
|
15510
15292
|
if (
|
|
15511
|
-
this.config.
|
|
15293
|
+
resolvePresentationCapabilities(this.config).verbatimArtifacts &&
|
|
15512
15294
|
this.config.verbatimArtifactCategories.includes(writeCategory) &&
|
|
15513
15295
|
fact.confidence >= this.config.verbatimArtifactsMinConfidence &&
|
|
15514
15296
|
!postWriteGuard
|
|
@@ -15605,7 +15387,7 @@ export class Orchestrator {
|
|
|
15605
15387
|
const memoryKind =
|
|
15606
15388
|
writeCategory === "procedure"
|
|
15607
15389
|
? undefined
|
|
15608
|
-
: this.config.
|
|
15390
|
+
: resolvePresentationCapabilities(this.config).episodeNoteMode
|
|
15609
15391
|
? classifyMemoryKind(fact.content, fact.tags ?? [], writeCategory)
|
|
15610
15392
|
: undefined;
|
|
15611
15393
|
|
|
@@ -15805,7 +15587,7 @@ export class Orchestrator {
|
|
|
15805
15587
|
}
|
|
15806
15588
|
}
|
|
15807
15589
|
if (
|
|
15808
|
-
this.config.
|
|
15590
|
+
resolvePresentationCapabilities(this.config).verbatimArtifacts &&
|
|
15809
15591
|
this.config.verbatimArtifactCategories.includes(writeCategory) &&
|
|
15810
15592
|
fact.confidence >= this.config.verbatimArtifactsMinConfidence &&
|
|
15811
15593
|
!postWriteGuard
|
|
@@ -16421,7 +16203,7 @@ export class Orchestrator {
|
|
|
16421
16203
|
log.info(`merged ${entitiesMerged} fragmented entity files`);
|
|
16422
16204
|
}
|
|
16423
16205
|
|
|
16424
|
-
if (this.config.
|
|
16206
|
+
if (resolvePresentationCapabilities(this.config).entitySummary) {
|
|
16425
16207
|
try {
|
|
16426
16208
|
const synthesized = await this.processEntitySynthesisQueue(
|
|
16427
16209
|
this.config.defaultNamespace,
|
|
@@ -16565,7 +16347,7 @@ export class Orchestrator {
|
|
|
16565
16347
|
}
|
|
16566
16348
|
|
|
16567
16349
|
// Semantic consolidation pass — find similar memories, synthesize canonical versions
|
|
16568
|
-
if (this.config.
|
|
16350
|
+
if (resolveConsolidationCapabilities(this.config).semanticConsolidation) {
|
|
16569
16351
|
try {
|
|
16570
16352
|
const stateFilePath = path.join(
|
|
16571
16353
|
this.config.memoryDir,
|
|
@@ -17366,156 +17148,15 @@ export class Orchestrator {
|
|
|
17366
17148
|
return matches[0] ?? null;
|
|
17367
17149
|
}
|
|
17368
17150
|
|
|
17151
|
+
// Issue #1526: recall-rerank methods moved to RecallRerankCoordinator.
|
|
17152
|
+
// Thin delegation keeps the private API stable for callers + tests.
|
|
17369
17153
|
private async applyMemoryWorthRerank(
|
|
17370
17154
|
results: QmdSearchResult[],
|
|
17371
17155
|
namespaces: string[],
|
|
17372
17156
|
): Promise<QmdSearchResult[]> {
|
|
17373
|
-
|
|
17374
|
-
// namespace the recall spans — the recall path itself already
|
|
17375
|
-
// aggregates candidates from multiple namespaces, so we must do the
|
|
17376
|
-
// same when looking up counters. Per-namespace results are cached with
|
|
17377
|
-
// a short TTL so interactive recall doesn't trigger a full
|
|
17378
|
-
// `readAllMemories` scan per query (addresses codex P2 on PR 4).
|
|
17379
|
-
const counters = new Map<string, MemoryWorthCounters>();
|
|
17380
|
-
const seenNamespaces = new Set<string>();
|
|
17381
|
-
const nowMs = Date.now();
|
|
17382
|
-
|
|
17383
|
-
// Evict all expired entries on every call so long-running processes
|
|
17384
|
-
// touching a high-cardinality namespace set (coding/project overlays,
|
|
17385
|
-
// per-branch) don't grow the cache unboundedly. Without this, an entry
|
|
17386
|
-
// for a namespace that's never looked up again would pin its full
|
|
17387
|
-
// counter map forever.
|
|
17388
|
-
for (const [key, entry] of this.memoryWorthCounterCache) {
|
|
17389
|
-
if (nowMs - entry.at >= Orchestrator.MEMORY_WORTH_CACHE_TTL_MS) {
|
|
17390
|
-
this.memoryWorthCounterCache.delete(key);
|
|
17391
|
-
}
|
|
17392
|
-
}
|
|
17393
|
-
|
|
17394
|
-
for (const ns of namespaces) {
|
|
17395
|
-
if (seenNamespaces.has(ns)) continue;
|
|
17396
|
-
seenNamespaces.add(ns);
|
|
17397
|
-
try {
|
|
17398
|
-
const cached = this.memoryWorthCounterCache.get(ns);
|
|
17399
|
-
let nsMap: ReadonlyMap<string, MemoryWorthCounters> | undefined;
|
|
17400
|
-
if (
|
|
17401
|
-
cached &&
|
|
17402
|
-
nowMs - cached.at < Orchestrator.MEMORY_WORTH_CACHE_TTL_MS
|
|
17403
|
-
) {
|
|
17404
|
-
nsMap = cached.counters;
|
|
17405
|
-
} else {
|
|
17406
|
-
const storage = await this.getStorage(ns);
|
|
17407
|
-
const memories = await storage.readAllMemories();
|
|
17408
|
-
nsMap = buildMemoryWorthCounterMap(memories);
|
|
17409
|
-
this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
|
|
17410
|
-
}
|
|
17411
|
-
for (const [path, c] of nsMap) counters.set(path, c);
|
|
17412
|
-
} catch (err) {
|
|
17413
|
-
log.debug("memory-worth: failed to read namespace, skipping", {
|
|
17414
|
-
namespace: ns,
|
|
17415
|
-
error: (err as Error).message,
|
|
17416
|
-
});
|
|
17417
|
-
}
|
|
17418
|
-
}
|
|
17419
|
-
|
|
17420
|
-
// For candidates whose path didn't show up in any hot-tier namespace
|
|
17421
|
-
// scan (typical of cold-tier / archive fallback), try a direct
|
|
17422
|
-
// per-path read. Without this, cold-tier candidates always stay at
|
|
17423
|
-
// multiplier 1.0 even when they have outcome history. Errors are
|
|
17424
|
-
// swallowed so a single unreadable archive entry can't break the
|
|
17425
|
-
// whole recall.
|
|
17426
|
-
const missing = results.filter((r) => !counters.has(r.path));
|
|
17427
|
-
if (missing.length > 0) {
|
|
17428
|
-
// Use the first-seen namespace's storage as the reader — all
|
|
17429
|
-
// StorageManagers share the same on-disk format, and
|
|
17430
|
-
// `readMemoryByPath` takes an absolute path so the baseDir doesn't
|
|
17431
|
-
// have to match.
|
|
17432
|
-
let reader: StorageManager | null = null;
|
|
17433
|
-
for (const ns of namespaces) {
|
|
17434
|
-
try {
|
|
17435
|
-
reader = await this.getStorage(ns);
|
|
17436
|
-
break;
|
|
17437
|
-
} catch {
|
|
17438
|
-
// try next namespace
|
|
17439
|
-
}
|
|
17440
|
-
}
|
|
17441
|
-
if (reader) {
|
|
17442
|
-
for (const r of missing) {
|
|
17443
|
-
try {
|
|
17444
|
-
const memory = await this.readQmdResultMemory(r.path, reader, namespaces);
|
|
17445
|
-
if (!memory) continue;
|
|
17446
|
-
const fm = memory.frontmatter;
|
|
17447
|
-
if (fm.mw_success === undefined && fm.mw_fail === undefined) continue;
|
|
17448
|
-
counters.set(r.path, {
|
|
17449
|
-
mw_success: fm.mw_success,
|
|
17450
|
-
mw_fail: fm.mw_fail,
|
|
17451
|
-
lastAccessed: fm.lastAccessed,
|
|
17452
|
-
});
|
|
17453
|
-
} catch (err) {
|
|
17454
|
-
log.debug("memory-worth: direct path lookup failed", {
|
|
17455
|
-
path: r.path,
|
|
17456
|
-
error: (err as Error).message,
|
|
17457
|
-
});
|
|
17458
|
-
}
|
|
17459
|
-
}
|
|
17460
|
-
}
|
|
17461
|
-
}
|
|
17462
|
-
|
|
17463
|
-
// If no memory in the candidate set has any counter data, the filter
|
|
17464
|
-
// would be a no-op — skip the reorder to avoid spurious log spam.
|
|
17465
|
-
if (counters.size === 0) return results;
|
|
17466
|
-
|
|
17467
|
-
// Preserve upstream ordering (reranker, specialized tiers, etc.) for
|
|
17468
|
-
// neutral candidates. The upstream stages set `memoryResults` in their
|
|
17469
|
-
// intended order but often leave `r.score` as the raw QMD score. If we
|
|
17470
|
-
// sorted by `r.score * multiplier` directly, neutral candidates
|
|
17471
|
-
// (multiplier 1.0) would snap back to raw-QMD order and silently undo
|
|
17472
|
-
// the reranker. Feed the filter a synthetic monotone-decreasing rank
|
|
17473
|
-
// score so it uses input position as the baseline, then applies the
|
|
17474
|
-
// multiplier on top. Ties fall back to the stable secondary key in
|
|
17475
|
-
// `applyMemoryWorthFilter`.
|
|
17476
|
-
const rankedInputs = results.map((r, i) => ({
|
|
17477
|
-
path: r.path,
|
|
17478
|
-
// Large positive rank score so multiplier math stays well-scaled and
|
|
17479
|
-
// we never hit zero; descending so earlier items rank higher.
|
|
17480
|
-
score: results.length - i,
|
|
17481
|
-
}));
|
|
17482
|
-
const filtered = applyMemoryWorthFilter(rankedInputs, {
|
|
17483
|
-
counters,
|
|
17484
|
-
now: new Date(),
|
|
17485
|
-
halfLifeMs:
|
|
17486
|
-
this.config.recallMemoryWorthHalfLifeMs > 0
|
|
17487
|
-
? this.config.recallMemoryWorthHalfLifeMs
|
|
17488
|
-
: undefined,
|
|
17489
|
-
});
|
|
17490
|
-
|
|
17491
|
-
// Reconstruct the QmdSearchResult list in the new order. `.score` is
|
|
17492
|
-
// preserved from the upstream pipeline (rerank, tier scoring, etc.) —
|
|
17493
|
-
// we only reorder. Writing the synthetic rank-weighted score back
|
|
17494
|
-
// would contaminate downstream logic (telemetry, confidence gates)
|
|
17495
|
-
// that expects the original QMD/rerank score semantics.
|
|
17496
|
-
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
17497
|
-
const reordered: QmdSearchResult[] = [];
|
|
17498
|
-
for (const item of filtered) {
|
|
17499
|
-
const original = byPath.get(item.path);
|
|
17500
|
-
if (original) reordered.push(original);
|
|
17501
|
-
}
|
|
17502
|
-
return reordered;
|
|
17157
|
+
return this.recallRerankCoordinator.applyMemoryWorthRerank(results, namespaces);
|
|
17503
17158
|
}
|
|
17504
17159
|
|
|
17505
|
-
/**
|
|
17506
|
-
* Issue #1577 — unified TrustScore recall stage. Thin wiring over the pure
|
|
17507
|
-
* {@link applyTrustScoreStage} scorer + the {@link buildTrustSignalsForRerank}
|
|
17508
|
-
* signal builder. The stage subsumes the Memory Worth multiplier — the
|
|
17509
|
-
* orchestrator runs exactly one of the two (mutual exclusion, rule 39; the
|
|
17510
|
-
* double-multiplier test in trust-score-stage.test.ts pins it structurally).
|
|
17511
|
-
*
|
|
17512
|
-
* Returns the admitted results AND the per-path trust map (including
|
|
17513
|
-
* quarantined items) so the caller can: (a) render epistemic hedges, (b)
|
|
17514
|
-
* surface quarantined items in X-ray with a reason (rule 34), and (c) filter
|
|
17515
|
-
* quarantined paths from fallback recall branches. The trust map is a
|
|
17516
|
-
* per-recall local — never instance state — so concurrent recalls cannot
|
|
17517
|
-
* race on it (review: shared-trust-map concurrency).
|
|
17518
|
-
*/
|
|
17519
17160
|
private async applyTrustScoreRerank(
|
|
17520
17161
|
results: QmdSearchResult[],
|
|
17521
17162
|
namespaces: string[],
|
|
@@ -17523,74 +17164,9 @@ export class Orchestrator {
|
|
|
17523
17164
|
results: QmdSearchResult[];
|
|
17524
17165
|
trustByPath: Map<string, TrustStageResultItem> | null;
|
|
17525
17166
|
}> {
|
|
17526
|
-
|
|
17527
|
-
const now = new Date();
|
|
17528
|
-
const halfLifeDays =
|
|
17529
|
-
this.config.recallMemoryWorthHalfLifeMs > 0
|
|
17530
|
-
? this.config.recallMemoryWorthHalfLifeMs / (24 * 60 * 60 * 1000)
|
|
17531
|
-
: undefined;
|
|
17532
|
-
// Cold-tier direct-fallback reader: resolve once, reuse for every missing
|
|
17533
|
-
// candidate (mirrors the memory-worth filter's reader selection).
|
|
17534
|
-
let fallbackReader: StorageManager | null = null;
|
|
17535
|
-
const signals = await buildTrustSignalsForRerank(
|
|
17536
|
-
results.map((r) => r.path),
|
|
17537
|
-
namespaces,
|
|
17538
|
-
{
|
|
17539
|
-
readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
|
|
17540
|
-
readMemoryFrontmatter: async (path) => {
|
|
17541
|
-
if (!fallbackReader) {
|
|
17542
|
-
for (const ns of namespaces) {
|
|
17543
|
-
try {
|
|
17544
|
-
fallbackReader = await this.getStorage(ns);
|
|
17545
|
-
break;
|
|
17546
|
-
} catch {
|
|
17547
|
-
// try next namespace
|
|
17548
|
-
}
|
|
17549
|
-
}
|
|
17550
|
-
}
|
|
17551
|
-
if (!fallbackReader) return null;
|
|
17552
|
-
const memory = await this.readQmdResultMemory(path, fallbackReader, namespaces);
|
|
17553
|
-
return memory ? memory.frontmatter : null;
|
|
17554
|
-
},
|
|
17555
|
-
},
|
|
17556
|
-
{ cache: this.trustSignalCache, ttlMs: Orchestrator.TRUST_SIGNAL_CACHE_TTL_MS },
|
|
17557
|
-
now,
|
|
17558
|
-
{
|
|
17559
|
-
recencyHalfLifeDays: halfLifeDays,
|
|
17560
|
-
logDebug: (message, context) => log.debug(message, context),
|
|
17561
|
-
},
|
|
17562
|
-
);
|
|
17563
|
-
if (signals.size === 0) {
|
|
17564
|
-
return { results, trustByPath: null };
|
|
17565
|
-
}
|
|
17566
|
-
// Synthetic monotone-decreasing rank so the multiplier rebias is applied
|
|
17567
|
-
// on top of upstream ordering, not raw QMD scores (see applyMemoryWorthRerank).
|
|
17568
|
-
const rankedInputs = results.map((r, i) => ({ path: r.path, score: results.length - i }));
|
|
17569
|
-
const stage = applyTrustScoreStage(rankedInputs, {
|
|
17570
|
-
signals,
|
|
17571
|
-
weights: this.config.trustScoreWeights,
|
|
17572
|
-
minMultiplier: this.config.trustScoreMinMultiplier,
|
|
17573
|
-
maxMultiplier: this.config.trustScoreMaxMultiplier,
|
|
17574
|
-
quarantine: this.config.trustScoreQuarantine,
|
|
17575
|
-
});
|
|
17576
|
-
const trustByPath = new Map(stage.all.map((item) => [item.path, item]));
|
|
17577
|
-
const byPath = new Map(results.map((r) => [r.path, r]));
|
|
17578
|
-
const admitted = stage.admitted
|
|
17579
|
-
.map((item) => byPath.get(item.path))
|
|
17580
|
-
.filter((r): r is QmdSearchResult => r !== undefined);
|
|
17581
|
-
return { results: admitted, trustByPath };
|
|
17167
|
+
return this.recallRerankCoordinator.applyTrustScoreRerank(results, namespaces);
|
|
17582
17168
|
}
|
|
17583
17169
|
|
|
17584
|
-
/**
|
|
17585
|
-
* Issue #1577 — apply the TrustScore stage (or, when trust is off, the Memory
|
|
17586
|
-
* Worth multiplier fallback) to ONE recall branch's results, returning the
|
|
17587
|
-
* scored results + the per-path trust map. Thin wiring over
|
|
17588
|
-
* {@link applyTrustScoreRerank} so every recall path — hot QMD, embedding
|
|
17589
|
-
* fallback, recent scan — applies the SAME multiplier gate (rule 41: a
|
|
17590
|
-
* feature gate must apply across ALL parallel recall paths). TrustScore
|
|
17591
|
-
* subsumes Memory Worth; exactly one runs (rule 39). Fail-open on lookup
|
|
17592
|
-
* errors so a storage hiccup never breaks a fallback path.
|
|
17593
|
-
*/
|
|
17594
17170
|
private async applyTrustScoreToBranch(
|
|
17595
17171
|
results: QmdSearchResult[],
|
|
17596
17172
|
namespaces: string[],
|
|
@@ -17600,25 +17176,7 @@ export class Orchestrator {
|
|
|
17600
17176
|
results: QmdSearchResult[];
|
|
17601
17177
|
trustByPath: Map<string, TrustStageResultItem> | null;
|
|
17602
17178
|
}> {
|
|
17603
|
-
|
|
17604
|
-
try {
|
|
17605
|
-
return await this.applyTrustScoreRerank(results, namespaces);
|
|
17606
|
-
} catch (err) {
|
|
17607
|
-
log.debug(`trust-score stage (${label}) failed open`, {
|
|
17608
|
-
error: (err as Error).message,
|
|
17609
|
-
});
|
|
17610
|
-
}
|
|
17611
|
-
} else if (caps.recallMemoryWorthFilter && results.length > 0) {
|
|
17612
|
-
try {
|
|
17613
|
-
const filtered = await this.applyMemoryWorthRerank(results, namespaces);
|
|
17614
|
-
return { results: filtered, trustByPath: null };
|
|
17615
|
-
} catch (err) {
|
|
17616
|
-
log.debug(`memory-worth filter (${label}) failed open`, {
|
|
17617
|
-
error: (err as Error).message,
|
|
17618
|
-
});
|
|
17619
|
-
}
|
|
17620
|
-
}
|
|
17621
|
-
return { results, trustByPath: null };
|
|
17179
|
+
return this.recallRerankCoordinator.applyTrustScoreToBranch(results, namespaces, caps, label);
|
|
17622
17180
|
}
|
|
17623
17181
|
|
|
17624
17182
|
private diversifyAndLimitRecallResults(
|
|
@@ -17626,89 +17184,17 @@ export class Orchestrator {
|
|
|
17626
17184
|
results: QmdSearchResult[],
|
|
17627
17185
|
limit: number,
|
|
17628
17186
|
retrievalQuery?: string,
|
|
17629
|
-
// `caps` is additive AND last (issue #1523) so the positional call shape
|
|
17630
|
-
// stays backward-compatible: the recall pipeline threads a resolved set,
|
|
17631
|
-
// but callers that omit it (e.g. direct unit-test invocations) get an
|
|
17632
|
-
// equivalent set derived from the same config — behavior-preserving.
|
|
17633
17187
|
caps: CapabilitySet = resolveCapabilities(this.config),
|
|
17634
17188
|
): QmdSearchResult[] {
|
|
17635
|
-
|
|
17636
|
-
typeof limit === "number" && Number.isFinite(limit)
|
|
17637
|
-
? Math.max(0, Math.floor(limit))
|
|
17638
|
-
: 0;
|
|
17639
|
-
if (!Array.isArray(results) || results.length === 0) return [];
|
|
17640
|
-
// `recallResultLimit === 0` is a true zero limit (e.g. when
|
|
17641
|
-
// `memoriesSectionEnabled` is false) and must return an empty array so
|
|
17642
|
-
// the memories section is genuinely skipped. This mirrors the
|
|
17643
|
-
// `slice(0, 0)` semantics of every call site this helper replaced.
|
|
17644
|
-
if (safeLimit === 0) return [];
|
|
17645
|
-
// Issue #564 PR 3: when the feature flag is on, boost reasoning_trace
|
|
17646
|
-
// memories for problem-solving asks so they bubble up ahead of ordinary
|
|
17647
|
-
// facts/decisions before MMR picks the final section. No-op when the
|
|
17648
|
-
// flag is off or the query is not a problem-solving ask.
|
|
17649
|
-
const boosted =
|
|
17650
|
-
caps.recallReasoningTraceBoost && typeof retrievalQuery === "string"
|
|
17651
|
-
? applyReasoningTraceBoost(results, {
|
|
17652
|
-
enabled: true,
|
|
17653
|
-
query: retrievalQuery,
|
|
17654
|
-
})
|
|
17655
|
-
: results;
|
|
17656
|
-
const diversified = this.applyMmrToQmdResults(sectionId, boosted, caps);
|
|
17657
|
-
return diversified.slice(0, safeLimit);
|
|
17189
|
+
return this.recallRerankCoordinator.diversifyAndLimitRecallResults(sectionId, results, limit, retrievalQuery, caps);
|
|
17658
17190
|
}
|
|
17659
17191
|
|
|
17660
|
-
/**
|
|
17661
|
-
* Apply Maximal Marginal Relevance to a section's ordered candidate list.
|
|
17662
|
-
*
|
|
17663
|
-
* Operates per-section so one redundant cluster cannot dominate a section,
|
|
17664
|
-
* and so one section's MMR pass cannot starve other sections. Returns the
|
|
17665
|
-
* input unchanged when disabled, when there are fewer than 2 candidates, or
|
|
17666
|
-
* when no budget information is available.
|
|
17667
|
-
*/
|
|
17668
17192
|
private applyMmrToQmdResults(
|
|
17669
17193
|
sectionId: string,
|
|
17670
17194
|
results: QmdSearchResult[],
|
|
17671
|
-
// Additive `caps` (issue #1523); defaults to a config-derived set so direct
|
|
17672
|
-
// callers that omit it behave identically to the threaded recall path.
|
|
17673
17195
|
caps: CapabilitySet = resolveCapabilities(this.config),
|
|
17674
17196
|
): QmdSearchResult[] {
|
|
17675
|
-
|
|
17676
|
-
if (!Array.isArray(results) || results.length < 2) return results;
|
|
17677
|
-
|
|
17678
|
-
// Config is runtime API (see AGENTS.md §4): preserve `0` as a true zero
|
|
17679
|
-
// limit rather than coercing it to a non-zero value. A configured topN of
|
|
17680
|
-
// 0 means "apply MMR over an empty window" — i.e. skip the reorder and
|
|
17681
|
-
// return the upstream candidates unchanged. This keeps read-time
|
|
17682
|
-
// behavior symmetric with the write-time semantics parseConfig exposes.
|
|
17683
|
-
const configuredTopN = this.config.recallMmrTopN;
|
|
17684
|
-
const topN =
|
|
17685
|
-
typeof configuredTopN === "number" && Number.isFinite(configuredTopN)
|
|
17686
|
-
? Math.max(0, Math.floor(configuredTopN))
|
|
17687
|
-
: 40;
|
|
17688
|
-
if (topN === 0) return results;
|
|
17689
|
-
const lambda = this.config.recallMmrLambda ?? 0.7;
|
|
17690
|
-
|
|
17691
|
-
// Delegate to the pure helper so candidate keying (path-first, index
|
|
17692
|
-
// suffixed for uniqueness) and the head-of-list diversity metric are
|
|
17693
|
-
// exercised by the same code path that the unit tests cover.
|
|
17694
|
-
const { reordered, diversity } = reorderRecallResultsWithMmr(results, {
|
|
17695
|
-
lambda,
|
|
17696
|
-
topN,
|
|
17697
|
-
});
|
|
17698
|
-
|
|
17699
|
-
try {
|
|
17700
|
-
log.info(
|
|
17701
|
-
`recall_mmr: section=${sectionId} kept=${diversity.kept}/${diversity.considered} ` +
|
|
17702
|
-
`headReorderCount=${diversity.headReorderCount} ` +
|
|
17703
|
-
`avgSimBefore=${diversity.avgPairwiseSimBefore.toFixed(3)} ` +
|
|
17704
|
-
`avgSimAfter=${diversity.avgPairwiseSimAfter.toFixed(3)} ` +
|
|
17705
|
-
`lambda=${lambda.toFixed(2)}`,
|
|
17706
|
-
);
|
|
17707
|
-
} catch {
|
|
17708
|
-
// Metrics must never break recall.
|
|
17709
|
-
}
|
|
17710
|
-
|
|
17711
|
-
return reordered;
|
|
17197
|
+
return this.recallRerankCoordinator.applyMmrToQmdResults(sectionId, results, caps);
|
|
17712
17198
|
}
|
|
17713
17199
|
|
|
17714
17200
|
private buildLastRecallBudgetSummary(options: {
|