@remnic/core 9.3.655 → 9.3.656
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-cli.js +22 -22
- package/dist/access-http.d.ts +4 -4
- package/dist/access-http.js +10 -10
- package/dist/access-mcp.d.ts +4 -4
- package/dist/access-mcp.js +9 -9
- package/dist/access-schema.d.ts +10 -10
- package/dist/{access-service-BEJvriUt.d.ts → access-service-D_nbpexW.d.ts} +33 -2
- package/dist/access-service.d.ts +4 -4
- package/dist/access-service.js +8 -8
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +3 -3
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-PVE7KSQP.js → chunk-2BD7DG37.js} +2 -2
- package/dist/{chunk-54LOUIBE.js → chunk-2MXEVL75.js} +2 -2
- package/dist/{chunk-55ZMNKMQ.js → chunk-4UL7VPTD.js} +276 -57
- package/dist/chunk-4UL7VPTD.js.map +1 -0
- package/dist/{chunk-COVZLGMR.js → chunk-54XF2FY7.js} +17 -17
- package/dist/{chunk-UYNFWZWG.js → chunk-AGJKWOKV.js} +2 -2
- package/dist/{chunk-TDZSSJV4.js → chunk-AZBV4RRY.js} +1 -1
- package/dist/chunk-AZBV4RRY.js.map +1 -0
- package/dist/{chunk-KOI765XP.js → chunk-CTAV55JM.js} +241 -1
- package/dist/chunk-CTAV55JM.js.map +1 -0
- package/dist/{chunk-A3Y37UWI.js → chunk-DIBWFCLA.js} +3 -3
- package/dist/{chunk-QDVQ4AN2.js → chunk-DR67OK4E.js} +5 -5
- package/dist/{chunk-XBIACVCO.js → chunk-EC2AYKRX.js} +2 -2
- package/dist/{chunk-IQ53ZSXV.js → chunk-GCYFUTUC.js} +2 -2
- package/dist/{chunk-YYN3LIYA.js → chunk-GSHW5VVD.js} +5 -5
- package/dist/chunk-GYSYLGNE.js +650 -0
- package/dist/chunk-GYSYLGNE.js.map +1 -0
- package/dist/{chunk-NRBGRZW4.js → chunk-IOZ5WBWD.js} +2 -2
- package/dist/{chunk-NCSJKK23.js → chunk-JSVFEHLL.js} +7 -5
- package/dist/chunk-JSVFEHLL.js.map +1 -0
- package/dist/{chunk-7LWRCOP7.js → chunk-LZTFCAKE.js} +2 -2
- package/dist/{chunk-TEO46GMM.js → chunk-NXCK7DO7.js} +2 -2
- package/dist/{chunk-XOFXKASO.js → chunk-PEPHBH2W.js} +2 -2
- package/dist/{chunk-WDTUYOLS.js → chunk-QZRKNA5F.js} +2 -2
- package/dist/{chunk-PS3SYNHP.js → chunk-R5DB26G6.js} +2 -2
- package/dist/{chunk-5QD3QD76.js → chunk-RDW5G6DO.js} +659 -123
- package/dist/chunk-RDW5G6DO.js.map +1 -0
- package/dist/{chunk-BGKXTVNG.js → chunk-SWDHVH2P.js} +2 -2
- package/dist/{chunk-67G4T7KI.js → chunk-SXYCVRLK.js} +3 -3
- package/dist/{chunk-UCEABZZN.js → chunk-TFFZUFEP.js} +7 -5
- package/dist/chunk-TFFZUFEP.js.map +1 -0
- package/dist/{chunk-UCEDY5M7.js → chunk-TIJYQXDI.js} +2 -2
- package/dist/{chunk-2RCGZ67B.js → chunk-VAEAGTEQ.js} +3 -3
- package/dist/{chunk-XRKQOQLY.js → chunk-WIKMCJUR.js} +2 -2
- package/dist/{chunk-KZZ4YAEC.js → chunk-WWMHAMAY.js} +2 -2
- package/dist/{chunk-OKW6F5S5.js → chunk-YEZHZCUO.js} +4 -4
- package/dist/{chunk-5FOCXX5E.js → chunk-YVVQUAOO.js} +3 -3
- package/dist/{chunk-5FOCXX5E.js.map → chunk-YVVQUAOO.js.map} +1 -1
- package/dist/{chunk-3XGWCZ63.js → chunk-YXLT4EMM.js} +2 -2
- package/dist/{chunk-PTMJ2FH2.js → chunk-Z6UDTNY6.js} +2 -2
- package/dist/{cli-BGahB_d3.d.ts → cli-aYxSuPvP.d.ts} +3 -3
- package/dist/cli.d.ts +5 -5
- package/dist/cli.js +22 -22
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +3 -3
- package/dist/explicit-cue-recall.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +121 -121
- package/dist/index.js +32 -32
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lcm-fallback-read.js +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +4 -4
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +7 -7
- package/dist/{orchestrator-BgzZlWxH.d.ts → orchestrator-D1wcmPNj.d.ts} +8 -2
- package/dist/orchestrator.d.ts +3 -3
- package/dist/orchestrator.js +18 -18
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-Z8d_uMq8.d.ts → semantic-consolidation-MWOdNtSE.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +3 -2
- package/dist/semantic-rule-verifier.js +5 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/{types-2OPlQWJG.d.ts → types-CgcCpUrf.d.ts} +39 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.d.ts +2 -1
- package/dist/verified-recall.js +5 -3
- package/package.json +1 -1
- package/src/access-service-observe-lcm-parity.test.ts +86 -1
- package/src/access-service-observe-scope.test.ts +283 -1
- package/src/access-service-raw-excerpt-read-gate.test.ts +53 -0
- package/src/access-service.ts +391 -93
- package/src/coding/coding-namespace.ts +0 -3
- package/src/config.ts +282 -0
- package/src/lcm-fallback-read.ts +2 -6
- package/src/namespaces/scope-profiles.test.ts +1074 -0
- package/src/namespaces/scope-profiles.ts +456 -0
- package/src/orchestrator-flush.test.ts +142 -0
- package/src/orchestrator-source-attribution.test.ts +73 -0
- package/src/orchestrator.ts +835 -163
- package/src/semantic-rule-verifier.ts +13 -6
- package/src/types.ts +52 -0
- package/src/verified-recall.ts +10 -6
- package/dist/chunk-55ZMNKMQ.js.map +0 -1
- package/dist/chunk-5QD3QD76.js.map +0 -1
- package/dist/chunk-KOI765XP.js.map +0 -1
- package/dist/chunk-MMJANTJX.js +0 -339
- package/dist/chunk-MMJANTJX.js.map +0 -1
- package/dist/chunk-NCSJKK23.js.map +0 -1
- package/dist/chunk-TDZSSJV4.js.map +0 -1
- package/dist/chunk-UCEABZZN.js.map +0 -1
- /package/dist/{chunk-PVE7KSQP.js.map → chunk-2BD7DG37.js.map} +0 -0
- /package/dist/{chunk-54LOUIBE.js.map → chunk-2MXEVL75.js.map} +0 -0
- /package/dist/{chunk-COVZLGMR.js.map → chunk-54XF2FY7.js.map} +0 -0
- /package/dist/{chunk-UYNFWZWG.js.map → chunk-AGJKWOKV.js.map} +0 -0
- /package/dist/{chunk-A3Y37UWI.js.map → chunk-DIBWFCLA.js.map} +0 -0
- /package/dist/{chunk-QDVQ4AN2.js.map → chunk-DR67OK4E.js.map} +0 -0
- /package/dist/{chunk-XBIACVCO.js.map → chunk-EC2AYKRX.js.map} +0 -0
- /package/dist/{chunk-IQ53ZSXV.js.map → chunk-GCYFUTUC.js.map} +0 -0
- /package/dist/{chunk-YYN3LIYA.js.map → chunk-GSHW5VVD.js.map} +0 -0
- /package/dist/{chunk-NRBGRZW4.js.map → chunk-IOZ5WBWD.js.map} +0 -0
- /package/dist/{chunk-7LWRCOP7.js.map → chunk-LZTFCAKE.js.map} +0 -0
- /package/dist/{chunk-TEO46GMM.js.map → chunk-NXCK7DO7.js.map} +0 -0
- /package/dist/{chunk-XOFXKASO.js.map → chunk-PEPHBH2W.js.map} +0 -0
- /package/dist/{chunk-WDTUYOLS.js.map → chunk-QZRKNA5F.js.map} +0 -0
- /package/dist/{chunk-PS3SYNHP.js.map → chunk-R5DB26G6.js.map} +0 -0
- /package/dist/{chunk-BGKXTVNG.js.map → chunk-SWDHVH2P.js.map} +0 -0
- /package/dist/{chunk-67G4T7KI.js.map → chunk-SXYCVRLK.js.map} +0 -0
- /package/dist/{chunk-UCEDY5M7.js.map → chunk-TIJYQXDI.js.map} +0 -0
- /package/dist/{chunk-2RCGZ67B.js.map → chunk-VAEAGTEQ.js.map} +0 -0
- /package/dist/{chunk-XRKQOQLY.js.map → chunk-WIKMCJUR.js.map} +0 -0
- /package/dist/{chunk-KZZ4YAEC.js.map → chunk-WWMHAMAY.js.map} +0 -0
- /package/dist/{chunk-OKW6F5S5.js.map → chunk-YEZHZCUO.js.map} +0 -0
- /package/dist/{chunk-3XGWCZ63.js.map → chunk-YXLT4EMM.js.map} +0 -0
- /package/dist/{chunk-PTMJ2FH2.js.map → chunk-Z6UDTNY6.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -249,10 +249,12 @@ import {
|
|
|
249
249
|
type HarmonicRetrievalResult,
|
|
250
250
|
} from "./harmonic-retrieval.js";
|
|
251
251
|
import {
|
|
252
|
+
compareVerifiedEpisodeResults,
|
|
252
253
|
searchVerifiedEpisodes,
|
|
253
254
|
type VerifiedEpisodeResult,
|
|
254
255
|
} from "./verified-recall.js";
|
|
255
256
|
import {
|
|
257
|
+
compareVerifiedSemanticRuleResults,
|
|
256
258
|
searchVerifiedSemanticRules,
|
|
257
259
|
type VerifiedSemanticRuleResult,
|
|
258
260
|
} from "./semantic-rule-verifier.js";
|
|
@@ -320,6 +322,11 @@ import {
|
|
|
320
322
|
recallNamespacesForPrincipal,
|
|
321
323
|
resolvePrincipal,
|
|
322
324
|
} from "./namespaces/principal.js";
|
|
325
|
+
import {
|
|
326
|
+
expandScopeProfileReadNamespaces,
|
|
327
|
+
resolveScopeProfilePlan,
|
|
328
|
+
type ResolvedScopeProfilePlan,
|
|
329
|
+
} from "./namespaces/scope-profiles.js";
|
|
323
330
|
import {
|
|
324
331
|
combineNamespaces,
|
|
325
332
|
lcmReadSessionIdsForNamespaces,
|
|
@@ -1889,6 +1896,7 @@ export class Orchestrator {
|
|
|
1889
1896
|
private readonly _peerIdBySession = new Map<string, string>();
|
|
1890
1897
|
private routingRulesStore: RoutingRulesStore | null = null;
|
|
1891
1898
|
private contentHashIndex: ContentHashIndex | null = null;
|
|
1899
|
+
private readonly contentHashIndexesByStorageDir = new Map<string, ContentHashIndex>();
|
|
1892
1900
|
private readonly artifactSourceStatusCache = new WeakMap<
|
|
1893
1901
|
StorageManager,
|
|
1894
1902
|
{
|
|
@@ -2480,6 +2488,13 @@ export class Orchestrator {
|
|
|
2480
2488
|
searchOptions?: SearchQueryOptions;
|
|
2481
2489
|
execution?: SearchExecutionOptions;
|
|
2482
2490
|
}): Promise<QmdSearchResult[]> {
|
|
2491
|
+
if (
|
|
2492
|
+
this.config.namespacesEnabled &&
|
|
2493
|
+
options.namespaces !== undefined &&
|
|
2494
|
+
options.namespaces.length === 0
|
|
2495
|
+
) {
|
|
2496
|
+
return [];
|
|
2497
|
+
}
|
|
2483
2498
|
const namespaces = this.config.namespacesEnabled
|
|
2484
2499
|
? Array.from(
|
|
2485
2500
|
new Set(
|
|
@@ -2551,6 +2566,79 @@ export class Orchestrator {
|
|
|
2551
2566
|
|
|
2552
2567
|
invalidateLiveContentHashIndex(): void {
|
|
2553
2568
|
this.contentHashIndex = null;
|
|
2569
|
+
this.contentHashIndexesByStorageDir.clear();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
private async contentHashIndexForStorage(
|
|
2573
|
+
targetStorage: StorageManager,
|
|
2574
|
+
): Promise<ContentHashIndex | null> {
|
|
2575
|
+
if (!this.config.factDeduplicationEnabled) return null;
|
|
2576
|
+
|
|
2577
|
+
if (targetStorage.dir === this.storage.dir) {
|
|
2578
|
+
if (!this.contentHashIndex) {
|
|
2579
|
+
this.contentHashIndex = this.storage.createContentHashIndex();
|
|
2580
|
+
await this.contentHashIndex.load();
|
|
2581
|
+
}
|
|
2582
|
+
return this.contentHashIndex;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
const cached = this.contentHashIndexesByStorageDir.get(targetStorage.dir);
|
|
2586
|
+
if (cached) return cached;
|
|
2587
|
+
|
|
2588
|
+
const index = targetStorage.createContentHashIndex();
|
|
2589
|
+
await index.load();
|
|
2590
|
+
this.contentHashIndexesByStorageDir.set(targetStorage.dir, index);
|
|
2591
|
+
log.info(
|
|
2592
|
+
`content-hash dedup: loaded ${index.size} hashes for storage ${targetStorage.dir}`,
|
|
2593
|
+
);
|
|
2594
|
+
return index;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
private async hasContentHashDedup(
|
|
2598
|
+
targetStorage: StorageManager,
|
|
2599
|
+
content: string,
|
|
2600
|
+
): Promise<boolean> {
|
|
2601
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2602
|
+
return index ? index.has(content) : false;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
private async addContentHashDedup(
|
|
2606
|
+
targetStorage: StorageManager,
|
|
2607
|
+
content: string,
|
|
2608
|
+
): Promise<void> {
|
|
2609
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2610
|
+
if (!index) return;
|
|
2611
|
+
index.add(content);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
private async removeContentHashForMemory(
|
|
2615
|
+
targetStorage: StorageManager,
|
|
2616
|
+
memory: MemoryFile,
|
|
2617
|
+
context: string,
|
|
2618
|
+
): Promise<void> {
|
|
2619
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2620
|
+
if (!index) return;
|
|
2621
|
+
|
|
2622
|
+
if (memory.frontmatter.contentHash) {
|
|
2623
|
+
index.removeByHash(memory.frontmatter.contentHash);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
log.warn(
|
|
2628
|
+
`[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`,
|
|
2629
|
+
);
|
|
2630
|
+
index.remove(memory.content);
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
private async saveContentHashIndexes(): Promise<void> {
|
|
2634
|
+
const indexes = new Set<ContentHashIndex>();
|
|
2635
|
+
if (this.contentHashIndex) indexes.add(this.contentHashIndex);
|
|
2636
|
+
for (const index of this.contentHashIndexesByStorageDir.values()) {
|
|
2637
|
+
indexes.add(index);
|
|
2638
|
+
}
|
|
2639
|
+
for (const index of indexes) {
|
|
2640
|
+
await index.save();
|
|
2641
|
+
}
|
|
2554
2642
|
}
|
|
2555
2643
|
|
|
2556
2644
|
constructor(config: PluginConfig) {
|
|
@@ -4320,28 +4408,13 @@ export class Orchestrator {
|
|
|
4320
4408
|
relatedMemoryIds: [canonicalId],
|
|
4321
4409
|
});
|
|
4322
4410
|
if (archiveResult) {
|
|
4323
|
-
// Remove from content-hash index
|
|
4324
|
-
//
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
// Modern memory: frontmatter.contentHash is already a SHA-256
|
|
4331
|
-
// hex string — use removeByHash to avoid double-hashing.
|
|
4332
|
-
this.contentHashIndex.removeByHash(m.frontmatter.contentHash);
|
|
4333
|
-
} else {
|
|
4334
|
-
// Legacy memory written before contentHash was stored on the
|
|
4335
|
-
// frontmatter. Pre-#369 facts were stored without inline
|
|
4336
|
-
// citations, so m.content is the raw fact text and we can
|
|
4337
|
-
// remove the hash directly from the content. This clears
|
|
4338
|
-
// stale dedup entries so the fact can be re-extracted.
|
|
4339
|
-
log.warn(
|
|
4340
|
-
`[semantic-consolidation] removing hash for legacy memory ${m.frontmatter.id ?? "(unknown)"} via content fallback — no contentHash in frontmatter`,
|
|
4341
|
-
);
|
|
4342
|
-
this.contentHashIndex.remove(m.content);
|
|
4343
|
-
}
|
|
4344
|
-
}
|
|
4411
|
+
// Remove from the same storage-scoped content-hash index that
|
|
4412
|
+
// originally deduped this memory.
|
|
4413
|
+
await this.removeContentHashForMemory(
|
|
4414
|
+
targetStorage,
|
|
4415
|
+
m,
|
|
4416
|
+
"semantic-consolidation",
|
|
4417
|
+
);
|
|
4345
4418
|
// Best-effort index cleanup: a failure here (e.g. on-disk index save
|
|
4346
4419
|
// under disk-full) must NOT abort the archival loop and thereby skip
|
|
4347
4420
|
// the catalog write touch below for an already-durable canonical write
|
|
@@ -4395,15 +4468,13 @@ export class Orchestrator {
|
|
|
4395
4468
|
}
|
|
4396
4469
|
}
|
|
4397
4470
|
|
|
4398
|
-
// Save hash
|
|
4399
|
-
if (result.memoriesArchived > 0
|
|
4400
|
-
await this.
|
|
4401
|
-
.
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
),
|
|
4406
|
-
);
|
|
4471
|
+
// Save hash indexes if we modified them.
|
|
4472
|
+
if (result.memoriesArchived > 0) {
|
|
4473
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
4474
|
+
log.warn(
|
|
4475
|
+
`[semantic-consolidation] content-hash index save failed: ${err}`,
|
|
4476
|
+
),
|
|
4477
|
+
);
|
|
4407
4478
|
}
|
|
4408
4479
|
|
|
4409
4480
|
log.info(
|
|
@@ -5746,6 +5817,7 @@ export class Orchestrator {
|
|
|
5746
5817
|
prompt,
|
|
5747
5818
|
sessionKey,
|
|
5748
5819
|
options.namespace?.trim() || undefined,
|
|
5820
|
+
options.principalOverride,
|
|
5749
5821
|
);
|
|
5750
5822
|
} catch (err) {
|
|
5751
5823
|
log.debug(`direct-answer observation setup failed: ${err}`);
|
|
@@ -5813,12 +5885,13 @@ export class Orchestrator {
|
|
|
5813
5885
|
prompt: string,
|
|
5814
5886
|
sessionKey: string,
|
|
5815
5887
|
namespaceOverride: string | undefined,
|
|
5888
|
+
principalOverride: string | undefined,
|
|
5816
5889
|
): void {
|
|
5817
5890
|
const expectedSnapshot = this.lastRecall.get(sessionKey);
|
|
5818
5891
|
if (expectedSnapshot === null) return;
|
|
5819
5892
|
if (expectedSnapshot.plannerMode === "no_recall") return;
|
|
5820
5893
|
|
|
5821
|
-
const principal = resolvePrincipal(sessionKey, this.config);
|
|
5894
|
+
const principal = principalOverride ?? resolvePrincipal(sessionKey, this.config);
|
|
5822
5895
|
// Coding-agent overlay (issue #569) is applied when the session has a
|
|
5823
5896
|
// coding context and there is no explicit namespaceOverride — mirrors
|
|
5824
5897
|
// the main recall path above.
|
|
@@ -5830,9 +5903,29 @@ export class Orchestrator {
|
|
|
5830
5903
|
const observationCodingSelf = observationCodingOverlay
|
|
5831
5904
|
? combineNamespaces(observationPrincipalSelf, observationCodingOverlay.namespace)
|
|
5832
5905
|
: null;
|
|
5906
|
+
const observationScopeProfilePlan =
|
|
5907
|
+
namespaceOverride && canReadNamespace(principal, namespaceOverride, this.config)
|
|
5908
|
+
? null
|
|
5909
|
+
: resolveScopeProfilePlan({
|
|
5910
|
+
config: this.config,
|
|
5911
|
+
principal,
|
|
5912
|
+
codingContext: sessionKey
|
|
5913
|
+
? this.getCodingContextForSession(sessionKey)
|
|
5914
|
+
: null,
|
|
5915
|
+
codingOverlay: observationCodingOverlay,
|
|
5916
|
+
});
|
|
5833
5917
|
let observationNamespaces: string[];
|
|
5834
5918
|
if (namespaceOverride && canReadNamespace(principal, namespaceOverride, this.config)) {
|
|
5835
5919
|
observationNamespaces = [namespaceOverride];
|
|
5920
|
+
} else if (observationScopeProfilePlan) {
|
|
5921
|
+
observationNamespaces = expandScopeProfileReadNamespaces({
|
|
5922
|
+
profilePlan: observationScopeProfilePlan,
|
|
5923
|
+
principalSelfNamespace: observationScopeProfilePlan.baseNamespace,
|
|
5924
|
+
config: this.config,
|
|
5925
|
+
principal,
|
|
5926
|
+
codingOverlay: observationCodingOverlay,
|
|
5927
|
+
legacyRecallNamespaces: recallNamespacesForPrincipal(principal, this.config),
|
|
5928
|
+
});
|
|
5836
5929
|
} else if (observationCodingOverlay && observationCodingSelf) {
|
|
5837
5930
|
// Rule 42 / parity with the main recall path: substitute the self
|
|
5838
5931
|
// namespace within the principal's recall list rather than
|
|
@@ -7449,13 +7542,34 @@ export class Orchestrator {
|
|
|
7449
7542
|
const codingSelfNamespace = codingOverlay
|
|
7450
7543
|
? combineNamespaces(principalSelfNamespace, codingOverlay.namespace)
|
|
7451
7544
|
: null;
|
|
7545
|
+
const scopeProfilePlan = namespaceOverride
|
|
7546
|
+
? null
|
|
7547
|
+
: resolveScopeProfilePlan({
|
|
7548
|
+
config: this.config,
|
|
7549
|
+
principal,
|
|
7550
|
+
codingContext: sessionKey
|
|
7551
|
+
? this.getCodingContextForSession(sessionKey)
|
|
7552
|
+
: null,
|
|
7553
|
+
codingOverlay,
|
|
7554
|
+
});
|
|
7555
|
+
const profileEffectiveNamespace = scopeProfilePlan?.writeNamespace || scopeProfilePlan?.readNamespaces[0];
|
|
7452
7556
|
const selfNamespace =
|
|
7453
7557
|
namespaceOverride ??
|
|
7558
|
+
profileEffectiveNamespace ??
|
|
7454
7559
|
codingSelfNamespace ??
|
|
7455
7560
|
principalSelfNamespace;
|
|
7456
7561
|
let recallNamespaces: string[];
|
|
7457
7562
|
if (namespaceOverride) {
|
|
7458
7563
|
recallNamespaces = [namespaceOverride];
|
|
7564
|
+
} else if (scopeProfilePlan) {
|
|
7565
|
+
recallNamespaces = expandScopeProfileReadNamespaces({
|
|
7566
|
+
profilePlan: scopeProfilePlan,
|
|
7567
|
+
principalSelfNamespace: scopeProfilePlan.baseNamespace,
|
|
7568
|
+
config: this.config,
|
|
7569
|
+
principal,
|
|
7570
|
+
codingOverlay,
|
|
7571
|
+
legacyRecallNamespaces: readableRecallNamespaces,
|
|
7572
|
+
});
|
|
7459
7573
|
} else if (codingOverlay && codingSelfNamespace) {
|
|
7460
7574
|
// Substitute the principal's self namespace with the coding-scoped
|
|
7461
7575
|
// one, and append any read fallbacks (branch→project, PR 3) combined
|
|
@@ -7515,11 +7629,18 @@ export class Orchestrator {
|
|
|
7515
7629
|
// so the prior round's authorization invariant is preserved.
|
|
7516
7630
|
const codingOverlaySelfReadable =
|
|
7517
7631
|
codingOverlay !== null &&
|
|
7518
|
-
|
|
7632
|
+
(scopeProfilePlan
|
|
7633
|
+
? scopeProfilePlan.layers.some((layer) => layer.id === "userProject" && layer.readable)
|
|
7634
|
+
: readableRecallNamespaces.includes(principalSelfNamespace));
|
|
7519
7635
|
let lcmReadNamespaces: string[];
|
|
7520
7636
|
if (namespaceOverride) {
|
|
7521
7637
|
// Explicit namespace already read-authorized above (canReadNamespace gate).
|
|
7522
7638
|
lcmReadNamespaces = [namespaceOverride];
|
|
7639
|
+
} else if (scopeProfilePlan) {
|
|
7640
|
+
// Scope profiles define a layered read stack; LCM-backed evidence uses the
|
|
7641
|
+
// same namespace set as QMD/file recall so team/global/shared observations
|
|
7642
|
+
// are not silently skipped.
|
|
7643
|
+
lcmReadNamespaces = recallNamespaces;
|
|
7523
7644
|
} else if (codingOverlay && codingSelfNamespace && codingOverlaySelfReadable) {
|
|
7524
7645
|
// Self base readable → overlay rows authorized. Read the primary overlay
|
|
7525
7646
|
// key first, then each coding read fallback (project → root), combined with
|
|
@@ -7539,11 +7660,14 @@ export class Orchestrator {
|
|
|
7539
7660
|
// session_id set. Single-user / no-overlay recall passes a single-namespace
|
|
7540
7661
|
// set that collapses to the raw `sessionKey`, so this is `[sessionKey]` —
|
|
7541
7662
|
// byte-for-byte the pre-#1495 single-key behavior.
|
|
7542
|
-
const lcmReadSessionIds =
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7663
|
+
const lcmReadSessionIds =
|
|
7664
|
+
scopeProfilePlan && !sessionKey
|
|
7665
|
+
? []
|
|
7666
|
+
: lcmReadSessionIdsForNamespaces(
|
|
7667
|
+
lcmReadNamespaces,
|
|
7668
|
+
sessionKey,
|
|
7669
|
+
this.config.defaultNamespace,
|
|
7670
|
+
);
|
|
7547
7671
|
// Query an LCM-backed read across the ordered read key set and return the
|
|
7548
7672
|
// FIRST non-empty result (#1505 fallback-namespace unification). The primary
|
|
7549
7673
|
// overlay key is tried first; if a branch-scoped session has no rows under its
|
|
@@ -7561,9 +7685,12 @@ export class Orchestrator {
|
|
|
7561
7685
|
//
|
|
7562
7686
|
// When the set is a single key (single-user / no-overlay / explicit-namespace),
|
|
7563
7687
|
// this is exactly one call — unchanged. `lcmSessionId` is `string | undefined`:
|
|
7564
|
-
// a SESSIONLESS recall yields the single `undefined` key so the read
|
|
7565
|
-
// archive-wide read with no `session_id` filter (pre-#1505 behavior).
|
|
7566
|
-
//
|
|
7688
|
+
// a legacy SESSIONLESS recall yields the single `undefined` key so the read
|
|
7689
|
+
// runs ONE archive-wide read with no `session_id` filter (pre-#1505 behavior).
|
|
7690
|
+
// Hosted scope profiles are stricter: without a session key there is no
|
|
7691
|
+
// namespace-scoped LCM key to query, so the key set stays empty and LCM cannot
|
|
7692
|
+
// bypass the profile read stack via an archive-wide read. NEVER the literal
|
|
7693
|
+
// "default" session id (codex P2).
|
|
7567
7694
|
const firstNonEmptyLcmRead = async <T>(
|
|
7568
7695
|
read: (lcmSessionId: string | undefined) => Promise<T>,
|
|
7569
7696
|
isEmpty: (value: T) => boolean,
|
|
@@ -7761,7 +7888,158 @@ export class Orchestrator {
|
|
|
7761
7888
|
return "";
|
|
7762
7889
|
}
|
|
7763
7890
|
|
|
7764
|
-
const
|
|
7891
|
+
const profileStorageNamespaces = scopeProfilePlan ? recallNamespaces : [selfNamespace];
|
|
7892
|
+
const profileStorages = await Promise.all(
|
|
7893
|
+
profileStorageNamespaces.map((namespace) => this.storageRouter.storageFor(namespace)),
|
|
7894
|
+
);
|
|
7895
|
+
const emptyProfileStorage = new Proxy(
|
|
7896
|
+
{ dir: path.join(this.config.memoryDir, ".empty-scope-profile") } as any,
|
|
7897
|
+
{
|
|
7898
|
+
get(target, prop: string | symbol) {
|
|
7899
|
+
if (prop in target) return target[prop];
|
|
7900
|
+
if (prop === "readProfile") return async () => "";
|
|
7901
|
+
if (
|
|
7902
|
+
prop === "readQuestions" ||
|
|
7903
|
+
prop === "listEntityNames" ||
|
|
7904
|
+
prop === "readContinuityIncidents"
|
|
7905
|
+
)
|
|
7906
|
+
return async () => [];
|
|
7907
|
+
if (
|
|
7908
|
+
prop === "readIdentityAnchor" ||
|
|
7909
|
+
prop === "readIdentityImprovementLoops"
|
|
7910
|
+
)
|
|
7911
|
+
return async () => "";
|
|
7912
|
+
if (prop === "readEntity" || prop === "readMemoryByPath")
|
|
7913
|
+
return async () => null;
|
|
7914
|
+
return async () => [];
|
|
7915
|
+
},
|
|
7916
|
+
},
|
|
7917
|
+
);
|
|
7918
|
+
const profileStorage =
|
|
7919
|
+
profileStorages.length <= 1
|
|
7920
|
+
? profileStorages[0] ?? emptyProfileStorage
|
|
7921
|
+
: new Proxy(profileStorages[0] as any, {
|
|
7922
|
+
get(target, prop: string | symbol) {
|
|
7923
|
+
if (prop === "readProfile") {
|
|
7924
|
+
return async () => {
|
|
7925
|
+
for (const storage of profileStorages) {
|
|
7926
|
+
const profile = await storage.readProfile();
|
|
7927
|
+
if (profile.trim().length > 0) return profile;
|
|
7928
|
+
}
|
|
7929
|
+
return "";
|
|
7930
|
+
};
|
|
7931
|
+
}
|
|
7932
|
+
if (prop === "readQuestions") {
|
|
7933
|
+
return async (...args: any[]) => {
|
|
7934
|
+
const merged: any[] = [];
|
|
7935
|
+
const seen = new Set<string>();
|
|
7936
|
+
const priorityOf = (question: any): number => {
|
|
7937
|
+
const priority = Number(question?.priority ?? 0);
|
|
7938
|
+
return Number.isFinite(priority) ? priority : 0;
|
|
7939
|
+
};
|
|
7940
|
+
for (const storage of profileStorages) {
|
|
7941
|
+
const questions = await (storage.readQuestions as any)(...args);
|
|
7942
|
+
for (const question of questions) {
|
|
7943
|
+
const key = typeof question === "string" ? question : JSON.stringify(question);
|
|
7944
|
+
if (seen.has(key)) continue;
|
|
7945
|
+
seen.add(key);
|
|
7946
|
+
merged.push(question);
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
return merged.sort(
|
|
7950
|
+
(left, right) =>
|
|
7951
|
+
priorityOf(right) - priorityOf(left) ||
|
|
7952
|
+
String(left?.id ?? "").localeCompare(String(right?.id ?? "")),
|
|
7953
|
+
);
|
|
7954
|
+
};
|
|
7955
|
+
}
|
|
7956
|
+
if (prop === "readIdentityAnchor") {
|
|
7957
|
+
return async () => {
|
|
7958
|
+
for (const storage of profileStorages) {
|
|
7959
|
+
const anchor = (await storage.readIdentityAnchor()) ?? "";
|
|
7960
|
+
if (anchor.trim().length > 0) return anchor;
|
|
7961
|
+
}
|
|
7962
|
+
return "";
|
|
7963
|
+
};
|
|
7964
|
+
}
|
|
7965
|
+
if (prop === "readIdentityImprovementLoops") {
|
|
7966
|
+
return async () => {
|
|
7967
|
+
const sections: string[] = [];
|
|
7968
|
+
const seen = new Set<string>();
|
|
7969
|
+
for (const storage of profileStorages) {
|
|
7970
|
+
const loops = ((await storage.readIdentityImprovementLoops()) ?? "").trim();
|
|
7971
|
+
if (!loops || seen.has(loops)) continue;
|
|
7972
|
+
seen.add(loops);
|
|
7973
|
+
sections.push(loops);
|
|
7974
|
+
}
|
|
7975
|
+
return sections.join("\n\n");
|
|
7976
|
+
};
|
|
7977
|
+
}
|
|
7978
|
+
if (prop === "readContinuityIncidents") {
|
|
7979
|
+
return async (...args: any[]) => {
|
|
7980
|
+
const limit = typeof args[0] === "number" && Number.isFinite(args[0]) ? Math.max(0, args[0]) : undefined;
|
|
7981
|
+
const incidents: any[] = [];
|
|
7982
|
+
const seen = new Set<string>();
|
|
7983
|
+
const incidentTime = (incident: any): number => {
|
|
7984
|
+
const raw = incident?.updatedAt ?? incident?.openedAt ?? incident?.createdAt;
|
|
7985
|
+
const parsed = typeof raw === "string" ? Date.parse(raw) : Number.NaN;
|
|
7986
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7987
|
+
};
|
|
7988
|
+
for (const storage of profileStorages) {
|
|
7989
|
+
for (const incident of await (storage.readContinuityIncidents as any)(...args)) {
|
|
7990
|
+
const key = JSON.stringify(incident);
|
|
7991
|
+
if (seen.has(key)) continue;
|
|
7992
|
+
seen.add(key);
|
|
7993
|
+
incidents.push(incident);
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
incidents.sort(
|
|
7997
|
+
(left, right) =>
|
|
7998
|
+
incidentTime(right) - incidentTime(left) ||
|
|
7999
|
+
String(left?.id ?? "").localeCompare(String(right?.id ?? "")),
|
|
8000
|
+
);
|
|
8001
|
+
return limit === undefined ? incidents : incidents.slice(0, limit);
|
|
8002
|
+
};
|
|
8003
|
+
}
|
|
8004
|
+
if (prop === "listEntityNames") {
|
|
8005
|
+
return async (...args: any[]) => {
|
|
8006
|
+
const names = new Set<string>();
|
|
8007
|
+
for (const storage of profileStorages) {
|
|
8008
|
+
for (const name of await (storage.listEntityNames as any)(...args)) names.add(name);
|
|
8009
|
+
}
|
|
8010
|
+
return [...names];
|
|
8011
|
+
};
|
|
8012
|
+
}
|
|
8013
|
+
if (prop === "readEntity" || prop === "readMemoryByPath") {
|
|
8014
|
+
return async (...args: any[]) => {
|
|
8015
|
+
for (const storage of profileStorages) {
|
|
8016
|
+
const value = await (storage as any)[prop](...args);
|
|
8017
|
+
if (value) return value;
|
|
8018
|
+
}
|
|
8019
|
+
return null;
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
if (prop === "readAllMemories") {
|
|
8023
|
+
return async (...args: any[]) => {
|
|
8024
|
+
const memories: any[] = [];
|
|
8025
|
+
const seen = new Set<string>();
|
|
8026
|
+
for (const storage of profileStorages) {
|
|
8027
|
+
for (const memory of await (storage.readAllMemories as any)(...args)) {
|
|
8028
|
+
const key = String(memory?.path ?? memory?.frontmatter?.id ?? JSON.stringify(memory));
|
|
8029
|
+
if (seen.has(key)) continue;
|
|
8030
|
+
seen.add(key);
|
|
8031
|
+
memories.push(memory);
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
return memories;
|
|
8035
|
+
};
|
|
8036
|
+
}
|
|
8037
|
+
return target[prop];
|
|
8038
|
+
},
|
|
8039
|
+
});
|
|
8040
|
+
const profileStorageDirs = Array.from(
|
|
8041
|
+
new Set(profileStorages.map((storage) => storage.dir).filter((dir): dir is string => typeof dir === "string" && dir.length > 0)),
|
|
8042
|
+
);
|
|
7765
8043
|
|
|
7766
8044
|
// --- Phase 1: Launch ALL independent data fetches in parallel ---
|
|
7767
8045
|
throwIfRecallAborted(options.abortSignal);
|
|
@@ -7791,6 +8069,14 @@ export class Orchestrator {
|
|
|
7791
8069
|
)
|
|
7792
8070
|
return null;
|
|
7793
8071
|
if (!this.sharedContext) return null;
|
|
8072
|
+
if (
|
|
8073
|
+
scopeProfilePlan &&
|
|
8074
|
+
!(
|
|
8075
|
+
scopeProfilePlan.profile.readOrder.includes("serverShared") &&
|
|
8076
|
+
scopeProfilePlan.readNamespaces.includes(this.config.sharedNamespace)
|
|
8077
|
+
)
|
|
8078
|
+
)
|
|
8079
|
+
return null;
|
|
7794
8080
|
const t0 = Date.now();
|
|
7795
8081
|
const [priorities, roundtable, crossSignals] = await Promise.all([
|
|
7796
8082
|
this.sharedContext.readPriorities(),
|
|
@@ -8100,13 +8386,64 @@ export class Orchestrator {
|
|
|
8100
8386
|
if (!this.config.knowledgeIndexEnabled) return null;
|
|
8101
8387
|
const t0 = Date.now();
|
|
8102
8388
|
try {
|
|
8103
|
-
const
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
)
|
|
8108
|
-
|
|
8109
|
-
|
|
8389
|
+
const knowledgeIndexMaxChars =
|
|
8390
|
+
this.getRecallSectionNumber("knowledge-index", "maxChars") ??
|
|
8391
|
+
this.config.knowledgeIndexMaxChars;
|
|
8392
|
+
const knowledgeIndexMaxEntities =
|
|
8393
|
+
this.getRecallSectionNumber("knowledge-index", "maxEntities") ??
|
|
8394
|
+
this.config.knowledgeIndexMaxEntities;
|
|
8395
|
+
const knowledgeIndexOptions = {
|
|
8396
|
+
maxEntities: knowledgeIndexMaxEntities,
|
|
8397
|
+
maxChars: knowledgeIndexMaxChars,
|
|
8398
|
+
};
|
|
8399
|
+
const ki = scopeProfilePlan
|
|
8400
|
+
? await (async () => {
|
|
8401
|
+
const perLayerOptions = {
|
|
8402
|
+
...knowledgeIndexOptions,
|
|
8403
|
+
maxEntities: Number.MAX_SAFE_INTEGER,
|
|
8404
|
+
maxChars: Number.MAX_SAFE_INTEGER,
|
|
8405
|
+
};
|
|
8406
|
+
const results = await Promise.all(
|
|
8407
|
+
profileStorages.map((storage) =>
|
|
8408
|
+
storage.buildKnowledgeIndex(this.config, perLayerOptions),
|
|
8409
|
+
),
|
|
8410
|
+
);
|
|
8411
|
+
const sections = results
|
|
8412
|
+
.map((result) => result.result.trim())
|
|
8413
|
+
.filter((section) => section.length > 0);
|
|
8414
|
+
const maxRows = Math.max(0, Math.floor(knowledgeIndexMaxEntities));
|
|
8415
|
+
const rows: string[] = [];
|
|
8416
|
+
let header: string[] | null = null;
|
|
8417
|
+
for (const section of sections) {
|
|
8418
|
+
const lines = section
|
|
8419
|
+
.split("\n")
|
|
8420
|
+
.map((line) => line.trimEnd())
|
|
8421
|
+
.filter((line) => line.length > 0);
|
|
8422
|
+
const tableHeaderIndex = lines.findIndex((line) =>
|
|
8423
|
+
line.startsWith("| Entity |"),
|
|
8424
|
+
);
|
|
8425
|
+
if (tableHeaderIndex === -1) continue;
|
|
8426
|
+
header ??= lines.slice(0, tableHeaderIndex + 2);
|
|
8427
|
+
for (const row of lines.slice(tableHeaderIndex + 2)) {
|
|
8428
|
+
if (!row.startsWith("|")) continue;
|
|
8429
|
+
if (rows.length >= maxRows) break;
|
|
8430
|
+
rows.push(row);
|
|
8431
|
+
}
|
|
8432
|
+
if (rows.length >= maxRows) break;
|
|
8433
|
+
}
|
|
8434
|
+
const merged =
|
|
8435
|
+
header && rows.length > 0
|
|
8436
|
+
? `${header.join("\n")}\n${rows.join("\n")}\n`
|
|
8437
|
+
: "";
|
|
8438
|
+
return {
|
|
8439
|
+
result: this.truncateRecallSectionToBudget(
|
|
8440
|
+
merged,
|
|
8441
|
+
knowledgeIndexMaxChars,
|
|
8442
|
+
),
|
|
8443
|
+
cached: results.every((result) => result.cached),
|
|
8444
|
+
};
|
|
8445
|
+
})()
|
|
8446
|
+
: await this.storage.buildKnowledgeIndex(this.config, knowledgeIndexOptions);
|
|
8110
8447
|
recordRecallSectionMetric({
|
|
8111
8448
|
section: "ki",
|
|
8112
8449
|
priority: "core",
|
|
@@ -8528,15 +8865,36 @@ export class Orchestrator {
|
|
|
8528
8865
|
return null;
|
|
8529
8866
|
}
|
|
8530
8867
|
|
|
8531
|
-
const
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8868
|
+
const harmonicSearchDirs = scopeProfilePlan ? profileStorageDirs : [this.config.memoryDir];
|
|
8869
|
+
const harmonicResultsByDir = await Promise.all(
|
|
8870
|
+
harmonicSearchDirs.map((memoryDir) =>
|
|
8871
|
+
searchHarmonicRetrieval({
|
|
8872
|
+
memoryDir,
|
|
8873
|
+
abstractionNodeStoreDir: scopeProfilePlan ? undefined : this.config.abstractionNodeStoreDir,
|
|
8874
|
+
query: retrievalQuery,
|
|
8875
|
+
maxResults,
|
|
8876
|
+
sessionKey,
|
|
8877
|
+
anchorsEnabled: this.config.abstractionAnchorsEnabled,
|
|
8878
|
+
abortSignal: harmonicRetrievalAbort.signal,
|
|
8879
|
+
}),
|
|
8880
|
+
),
|
|
8881
|
+
);
|
|
8882
|
+
const harmonicByNodeId = new Map<string, HarmonicRetrievalResult>();
|
|
8883
|
+
for (const result of harmonicResultsByDir.flat()) {
|
|
8884
|
+
const existing = harmonicByNodeId.get(result.node.nodeId);
|
|
8885
|
+
if (!existing || result.score > existing.score) {
|
|
8886
|
+
harmonicByNodeId.set(result.node.nodeId, result);
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
const results = [...harmonicByNodeId.values()]
|
|
8890
|
+
.sort(
|
|
8891
|
+
(left, right) =>
|
|
8892
|
+
right.score - left.score ||
|
|
8893
|
+
right.anchorScore - left.anchorScore ||
|
|
8894
|
+
right.node.recordedAt.localeCompare(left.node.recordedAt) ||
|
|
8895
|
+
left.node.nodeId.localeCompare(right.node.nodeId),
|
|
8896
|
+
)
|
|
8897
|
+
.slice(0, maxResults);
|
|
8540
8898
|
|
|
8541
8899
|
recordRecallSectionMetric({
|
|
8542
8900
|
section: "harmonicRetrieval",
|
|
@@ -8598,11 +8956,28 @@ export class Orchestrator {
|
|
|
8598
8956
|
const VERIFIED_RECALL_TIMEOUT_MS = 15_000;
|
|
8599
8957
|
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
8600
8958
|
const results = await Promise.race([
|
|
8601
|
-
|
|
8602
|
-
memoryDir
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8959
|
+
Promise.all(
|
|
8960
|
+
profileStorageDirs.map((memoryDir) =>
|
|
8961
|
+
searchVerifiedEpisodes({
|
|
8962
|
+
memoryDir,
|
|
8963
|
+
query: retrievalQuery,
|
|
8964
|
+
maxResults,
|
|
8965
|
+
boxRecallDays: this.config.boxRecallDays,
|
|
8966
|
+
}).catch((err) => {
|
|
8967
|
+
log.debug(`verified recall directory scan failed: ${err}`);
|
|
8968
|
+
return [] as VerifiedEpisodeResult[];
|
|
8969
|
+
}),
|
|
8970
|
+
),
|
|
8971
|
+
).then((groups) => {
|
|
8972
|
+
const merged: VerifiedEpisodeResult[] = [];
|
|
8973
|
+
const seen = new Set<string>();
|
|
8974
|
+
for (const result of groups.flat()) {
|
|
8975
|
+
const key = result.box.id || JSON.stringify(result);
|
|
8976
|
+
if (seen.has(key)) continue;
|
|
8977
|
+
seen.add(key);
|
|
8978
|
+
merged.push(result);
|
|
8979
|
+
}
|
|
8980
|
+
return merged.sort(compareVerifiedEpisodeResults).slice(0, maxResults);
|
|
8606
8981
|
}),
|
|
8607
8982
|
new Promise<[]>((resolve) => {
|
|
8608
8983
|
timeoutHandle = setTimeout(
|
|
@@ -8670,10 +9045,27 @@ export class Orchestrator {
|
|
|
8670
9045
|
const VERIFIED_RULES_TIMEOUT_MS = 15_000;
|
|
8671
9046
|
let rulesTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
8672
9047
|
const results = await Promise.race([
|
|
8673
|
-
|
|
8674
|
-
memoryDir
|
|
8675
|
-
|
|
8676
|
-
|
|
9048
|
+
Promise.all(
|
|
9049
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9050
|
+
searchVerifiedSemanticRules({
|
|
9051
|
+
memoryDir,
|
|
9052
|
+
query: retrievalQuery,
|
|
9053
|
+
maxResults,
|
|
9054
|
+
}).catch((err) => {
|
|
9055
|
+
log.debug(`verified rules directory scan failed: ${err}`);
|
|
9056
|
+
return [] as VerifiedSemanticRuleResult[];
|
|
9057
|
+
}),
|
|
9058
|
+
),
|
|
9059
|
+
).then((groups) => {
|
|
9060
|
+
const merged: VerifiedSemanticRuleResult[] = [];
|
|
9061
|
+
const seen = new Set<string>();
|
|
9062
|
+
for (const result of groups.flat()) {
|
|
9063
|
+
const key = result.rule.frontmatter.id || result.rule.path || JSON.stringify(result);
|
|
9064
|
+
if (seen.has(key)) continue;
|
|
9065
|
+
seen.add(key);
|
|
9066
|
+
merged.push(result);
|
|
9067
|
+
}
|
|
9068
|
+
return merged.sort(compareVerifiedSemanticRuleResults).slice(0, maxResults);
|
|
8677
9069
|
}),
|
|
8678
9070
|
new Promise<[]>((resolve) => {
|
|
8679
9071
|
rulesTimeoutHandle = setTimeout(
|
|
@@ -8739,13 +9131,33 @@ export class Orchestrator {
|
|
|
8739
9131
|
return null;
|
|
8740
9132
|
}
|
|
8741
9133
|
|
|
8742
|
-
const
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
9134
|
+
const workProductSearchDirs = scopeProfilePlan ? profileStorageDirs : [this.config.memoryDir];
|
|
9135
|
+
const workProductResultsByDir = await Promise.all(
|
|
9136
|
+
workProductSearchDirs.map((memoryDir) =>
|
|
9137
|
+
searchWorkProductLedgerEntries({
|
|
9138
|
+
memoryDir,
|
|
9139
|
+
workProductLedgerDir: scopeProfilePlan ? undefined : this.config.workProductLedgerDir,
|
|
9140
|
+
query: retrievalQuery,
|
|
9141
|
+
maxResults,
|
|
9142
|
+
sessionKey,
|
|
9143
|
+
}),
|
|
9144
|
+
),
|
|
9145
|
+
);
|
|
9146
|
+
const workProductByEntryId = new Map<string, WorkProductLedgerSearchResult>();
|
|
9147
|
+
for (const result of workProductResultsByDir.flat()) {
|
|
9148
|
+
const existing = workProductByEntryId.get(result.entry.entryId);
|
|
9149
|
+
if (!existing || result.score > existing.score) {
|
|
9150
|
+
workProductByEntryId.set(result.entry.entryId, result);
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
const results = [...workProductByEntryId.values()]
|
|
9154
|
+
.sort(
|
|
9155
|
+
(left, right) =>
|
|
9156
|
+
right.score - left.score ||
|
|
9157
|
+
right.entry.recordedAt.localeCompare(left.entry.recordedAt) ||
|
|
9158
|
+
left.entry.entryId.localeCompare(right.entry.entryId),
|
|
9159
|
+
)
|
|
9160
|
+
.slice(0, maxResults);
|
|
8749
9161
|
|
|
8750
9162
|
recordRecallSectionMetric({
|
|
8751
9163
|
section: "workProducts",
|
|
@@ -8958,24 +9370,56 @@ export class Orchestrator {
|
|
|
8958
9370
|
this.config.parallelRetrievalEnabled && maxPerAgent > 0
|
|
8959
9371
|
? Promise.all([
|
|
8960
9372
|
shouldRunAgent("direct", retrievalQuery, 0)
|
|
8961
|
-
?
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
9373
|
+
? Promise.all(
|
|
9374
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9375
|
+
runDirectAgent(
|
|
9376
|
+
retrievalQuery,
|
|
9377
|
+
memoryDir,
|
|
9378
|
+
maxPerAgent,
|
|
9379
|
+
).catch((err) => {
|
|
9380
|
+
log.debug(`DirectAgent pre-start failed: ${err}`);
|
|
9381
|
+
return [] as ParallelSearchResult[];
|
|
9382
|
+
}),
|
|
9383
|
+
),
|
|
9384
|
+
).then((groups) => {
|
|
9385
|
+
const merged: ParallelSearchResult[] = [];
|
|
9386
|
+
const seen = new Set<string>();
|
|
9387
|
+
for (const result of groups.flat()) {
|
|
9388
|
+
const key = (result as any).path ?? JSON.stringify(result);
|
|
9389
|
+
if (seen.has(key)) continue;
|
|
9390
|
+
seen.add(key);
|
|
9391
|
+
merged.push(result);
|
|
9392
|
+
}
|
|
9393
|
+
return merged
|
|
9394
|
+
.sort((a, b) => b.score - a.score)
|
|
9395
|
+
.slice(0, maxPerAgent);
|
|
8968
9396
|
})
|
|
8969
9397
|
: Promise.resolve([] as ParallelSearchResult[]),
|
|
8970
9398
|
shouldRunAgent("temporal", retrievalQuery, 0)
|
|
8971
|
-
?
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
9399
|
+
? Promise.all(
|
|
9400
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9401
|
+
runTemporalAgent(
|
|
9402
|
+
retrievalQuery,
|
|
9403
|
+
memoryDir,
|
|
9404
|
+
maxPerAgent,
|
|
9405
|
+
queryAwarePrefilter.candidatePaths,
|
|
9406
|
+
).catch((err) => {
|
|
9407
|
+
log.debug(`TemporalAgent pre-start failed for ${memoryDir}: ${err}`);
|
|
9408
|
+
return [] as ParallelSearchResult[];
|
|
9409
|
+
}),
|
|
9410
|
+
),
|
|
9411
|
+
).then((groups) => {
|
|
9412
|
+
const merged: ParallelSearchResult[] = [];
|
|
9413
|
+
const seen = new Set<string>();
|
|
9414
|
+
for (const result of groups.flat()) {
|
|
9415
|
+
const key = (result as any).path ?? JSON.stringify(result);
|
|
9416
|
+
if (seen.has(key)) continue;
|
|
9417
|
+
seen.add(key);
|
|
9418
|
+
merged.push(result);
|
|
9419
|
+
}
|
|
9420
|
+
return merged
|
|
9421
|
+
.sort((a, b) => b.score - a.score)
|
|
9422
|
+
.slice(0, maxPerAgent);
|
|
8979
9423
|
})
|
|
8980
9424
|
: Promise.resolve([] as ParallelSearchResult[]),
|
|
8981
9425
|
])
|
|
@@ -9568,9 +10012,29 @@ export class Orchestrator {
|
|
|
9568
10012
|
) &&
|
|
9569
10013
|
this.config.memoryBoxesEnabled &&
|
|
9570
10014
|
this.config.boxRecallDays > 0
|
|
9571
|
-
?
|
|
9572
|
-
.
|
|
9573
|
-
|
|
10015
|
+
? Promise.all(
|
|
10016
|
+
profileStorages.map((storage) =>
|
|
10017
|
+
this.boxBuilderFor(storage)
|
|
10018
|
+
.readRecentBoxes(this.config.boxRecallDays)
|
|
10019
|
+
.catch(() => [] as BoxFrontmatter[]),
|
|
10020
|
+
),
|
|
10021
|
+
).then((groups) => {
|
|
10022
|
+
const boxes: BoxFrontmatter[] = [];
|
|
10023
|
+
const seen = new Set<string>();
|
|
10024
|
+
for (const box of groups.flat()) {
|
|
10025
|
+
const key = JSON.stringify(box);
|
|
10026
|
+
if (seen.has(key)) continue;
|
|
10027
|
+
seen.add(key);
|
|
10028
|
+
boxes.push(box);
|
|
10029
|
+
}
|
|
10030
|
+
return boxes.sort((a, b) => {
|
|
10031
|
+
const aTime = Date.parse(a.sealedAt ?? "");
|
|
10032
|
+
const bTime = Date.parse(b.sealedAt ?? "");
|
|
10033
|
+
const aRank = Number.isFinite(aTime) ? aTime : 0;
|
|
10034
|
+
const bRank = Number.isFinite(bTime) ? bTime : 0;
|
|
10035
|
+
return bRank - aRank;
|
|
10036
|
+
});
|
|
10037
|
+
})
|
|
9574
10038
|
: Promise.resolve([] as BoxFrontmatter[]),
|
|
9575
10039
|
);
|
|
9576
10040
|
|
|
@@ -12663,18 +13127,57 @@ export class Orchestrator {
|
|
|
12663
13127
|
options.principalOverride.length > 0
|
|
12664
13128
|
? options.principalOverride
|
|
12665
13129
|
: resolvePrincipal(sessionKey, this.config);
|
|
12666
|
-
// Write path —
|
|
12667
|
-
//
|
|
12668
|
-
//
|
|
12669
|
-
//
|
|
12670
|
-
const
|
|
13130
|
+
// Write path — explicit callers still win. Otherwise, an active hosted
|
|
13131
|
+
// scope profile owns the extraction write target so hook-captured turns land
|
|
13132
|
+
// in the same layer that profile recall searches. Without a profile, preserve
|
|
13133
|
+
// the existing coding-agent overlay behavior (issue #569).
|
|
13134
|
+
const explicitWriteNamespace =
|
|
12671
13135
|
typeof options.writeNamespaceOverride === "string" &&
|
|
12672
13136
|
options.writeNamespaceOverride.length > 0
|
|
12673
13137
|
? options.writeNamespaceOverride
|
|
12674
|
-
:
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
13138
|
+
: undefined;
|
|
13139
|
+
const codingContextForWrite = sessionKey
|
|
13140
|
+
? this.getCodingContextForSession(sessionKey)
|
|
13141
|
+
: null;
|
|
13142
|
+
const codingOverlayForWrite = resolveCodingNamespaceOverlay(
|
|
13143
|
+
codingContextForWrite,
|
|
13144
|
+
this.config.codingMode,
|
|
13145
|
+
this.config.defaultNamespace,
|
|
13146
|
+
);
|
|
13147
|
+
const scopeProfileGatePlan = resolveScopeProfilePlan({
|
|
13148
|
+
config: this.config,
|
|
13149
|
+
principal,
|
|
13150
|
+
codingContext: codingContextForWrite,
|
|
13151
|
+
codingOverlay: codingOverlayForWrite,
|
|
13152
|
+
});
|
|
13153
|
+
const scopeProfileWritePlan = explicitWriteNamespace ? null : scopeProfileGatePlan;
|
|
13154
|
+
if (scopeProfileWritePlan) {
|
|
13155
|
+
const selectedLayer = scopeProfileWritePlan.layers.find(
|
|
13156
|
+
(layer) => layer.id === scopeProfileWritePlan.writeLayer,
|
|
13157
|
+
);
|
|
13158
|
+
const writeNamespaceReadable = scopeProfileWritePlan.readNamespaces.includes(
|
|
13159
|
+
scopeProfileWritePlan.writeNamespace,
|
|
13160
|
+
);
|
|
13161
|
+
if (!selectedLayer?.writable || !writeNamespaceReadable) {
|
|
13162
|
+
log.warn(
|
|
13163
|
+
`runExtraction: skipping scope profile ${scopeProfileWritePlan.profileId} because write layer ${scopeProfileWritePlan.writeLayer} is not writable inside the profile read stack`,
|
|
13164
|
+
);
|
|
13165
|
+
await clearBuffer();
|
|
13166
|
+
return {
|
|
13167
|
+
status: "skipped",
|
|
13168
|
+
reason: "scope_profile_no_writable_layer",
|
|
13169
|
+
persistedCount: 0,
|
|
13170
|
+
durableOutputCount: 0,
|
|
13171
|
+
};
|
|
13172
|
+
}
|
|
13173
|
+
}
|
|
13174
|
+
const selfNamespace =
|
|
13175
|
+
explicitWriteNamespace ??
|
|
13176
|
+
scopeProfileWritePlan?.writeNamespace ??
|
|
13177
|
+
this.applyCodingNamespaceOverlay(
|
|
13178
|
+
sessionKey,
|
|
13179
|
+
defaultNamespaceForPrincipal(principal, this.config),
|
|
13180
|
+
);
|
|
12678
13181
|
const storage = await this.storageRouter.storageFor(selfNamespace);
|
|
12679
13182
|
const shouldPersistProcessedFingerprint = targetTurns.some(
|
|
12680
13183
|
(turn) => turn.persistProcessedFingerprint === true,
|
|
@@ -12834,6 +13337,7 @@ export class Orchestrator {
|
|
|
12834
13337
|
// Pass the KNOWN base namespace (NHIdx) so the catalog write touch records the
|
|
12835
13338
|
// real namespace rather than a guess decoded from the storage dir.
|
|
12836
13339
|
selfNamespace,
|
|
13340
|
+
scopeProfileGatePlan,
|
|
12837
13341
|
);
|
|
12838
13342
|
let postPersistMetadataFailed = false;
|
|
12839
13343
|
meta ??= await storage.loadMeta();
|
|
@@ -13404,6 +13908,7 @@ export class Orchestrator {
|
|
|
13404
13908
|
threadIdForExtraction?: string | null,
|
|
13405
13909
|
sourceContext?: { sessionKey?: string; principal?: string; validAt?: string },
|
|
13406
13910
|
baseNamespace?: string,
|
|
13911
|
+
scopeProfileWritePlan?: ResolvedScopeProfilePlan | null,
|
|
13407
13912
|
): Promise<string[]> {
|
|
13408
13913
|
// Inline source attribution (issue #369). When enabled, every extracted
|
|
13409
13914
|
// fact is rewritten to carry a compact provenance tag inside its body so
|
|
@@ -13498,6 +14003,60 @@ export class Orchestrator {
|
|
|
13498
14003
|
"inferred",
|
|
13499
14004
|
"speculative",
|
|
13500
14005
|
] as const;
|
|
14006
|
+
const sharedProfileLayer = scopeProfileWritePlan?.layers.find(
|
|
14007
|
+
(layer) =>
|
|
14008
|
+
layer.id === "serverShared" &&
|
|
14009
|
+
layer.namespace === this.config.sharedNamespace,
|
|
14010
|
+
);
|
|
14011
|
+
const sharedPromotionTarget = scopeProfileWritePlan?.promotionTargets.find(
|
|
14012
|
+
(target) =>
|
|
14013
|
+
target.target === "serverShared" &&
|
|
14014
|
+
target.namespace === this.config.sharedNamespace,
|
|
14015
|
+
);
|
|
14016
|
+
const profileAllowsSharedWrites =
|
|
14017
|
+
!scopeProfileWritePlan ||
|
|
14018
|
+
Boolean(
|
|
14019
|
+
scopeProfileWritePlan.profile.readOrder.includes("serverShared") &&
|
|
14020
|
+
scopeProfileWritePlan.readNamespaces.includes(this.config.sharedNamespace) &&
|
|
14021
|
+
sharedProfileLayer?.readable &&
|
|
14022
|
+
sharedProfileLayer.writable &&
|
|
14023
|
+
sharedPromotionTarget?.authorized,
|
|
14024
|
+
);
|
|
14025
|
+
const profileAutoPromotionAllows = (
|
|
14026
|
+
category: string,
|
|
14027
|
+
confidence: number,
|
|
14028
|
+
): boolean => {
|
|
14029
|
+
if (!scopeProfileWritePlan) return false;
|
|
14030
|
+
const actualTier = confidenceTier(confidence);
|
|
14031
|
+
const actualRank = confidenceTierOrder.indexOf(actualTier);
|
|
14032
|
+
if (actualRank === -1) return false;
|
|
14033
|
+
const autoPromote = scopeProfileWritePlan.profile.autoPromote;
|
|
14034
|
+
if (!autoPromote.enabled) return false;
|
|
14035
|
+
if (!autoPromote.categories.includes(category as any)) return false;
|
|
14036
|
+
const minimumRank = confidenceTierOrder.indexOf(autoPromote.minConfidenceTier);
|
|
14037
|
+
return minimumRank !== -1 && actualRank <= minimumRank;
|
|
14038
|
+
};
|
|
14039
|
+
const sharedAutoPromotionAllows = (
|
|
14040
|
+
category: string,
|
|
14041
|
+
confidence: number,
|
|
14042
|
+
): boolean => {
|
|
14043
|
+
if (!scopeProfileWritePlan) {
|
|
14044
|
+
const actualTier = confidenceTier(confidence);
|
|
14045
|
+
const actualRank = confidenceTierOrder.indexOf(actualTier);
|
|
14046
|
+
if (actualRank === -1) return false;
|
|
14047
|
+
if (!this.config.autoPromoteToSharedEnabled) return false;
|
|
14048
|
+
if (!this.config.autoPromoteToSharedCategories.includes(category as any))
|
|
14049
|
+
return false;
|
|
14050
|
+
const minimumRank = confidenceTierOrder.indexOf(
|
|
14051
|
+
this.config.autoPromoteMinConfidenceTier,
|
|
14052
|
+
);
|
|
14053
|
+
return minimumRank !== -1 && actualRank <= minimumRank;
|
|
14054
|
+
}
|
|
14055
|
+
return (
|
|
14056
|
+
scopeProfileWritePlan.profile.autoPromote.targets.includes("serverShared") &&
|
|
14057
|
+
profileAutoPromotionAllows(category, confidence)
|
|
14058
|
+
);
|
|
14059
|
+
};
|
|
13501
14060
|
const shouldPromoteToShared = (
|
|
13502
14061
|
targetStorage: StorageManager,
|
|
13503
14062
|
category: string,
|
|
@@ -13505,7 +14064,8 @@ export class Orchestrator {
|
|
|
13505
14064
|
): boolean => {
|
|
13506
14065
|
if (
|
|
13507
14066
|
!this.config.namespacesEnabled ||
|
|
13508
|
-
!
|
|
14067
|
+
!profileAllowsSharedWrites ||
|
|
14068
|
+
!sharedAutoPromotionAllows(category, confidence)
|
|
13509
14069
|
)
|
|
13510
14070
|
return false;
|
|
13511
14071
|
if (
|
|
@@ -13513,15 +14073,124 @@ export class Orchestrator {
|
|
|
13513
14073
|
this.config.sharedNamespace
|
|
13514
14074
|
)
|
|
13515
14075
|
return false;
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
14076
|
+
return true;
|
|
14077
|
+
};
|
|
14078
|
+
const promoteMemoryToProfileTargets = async (options: {
|
|
14079
|
+
sourceStorage: StorageManager;
|
|
14080
|
+
category: string;
|
|
14081
|
+
content: string;
|
|
14082
|
+
confidence: number;
|
|
14083
|
+
tags: string[];
|
|
14084
|
+
entityRef?: string;
|
|
14085
|
+
structuredAttributes?: Record<string, string>;
|
|
14086
|
+
sourceMemoryId: string;
|
|
14087
|
+
importance?: ReturnType<typeof scoreImportance>;
|
|
14088
|
+
intentGoal?: string;
|
|
14089
|
+
intentActionType?: string;
|
|
14090
|
+
intentEntityTypes?: string[];
|
|
14091
|
+
memoryKind?: MemoryFrontmatter["memoryKind"];
|
|
14092
|
+
validAt?: string;
|
|
14093
|
+
source: string;
|
|
14094
|
+
}): Promise<void> => {
|
|
14095
|
+
if (
|
|
14096
|
+
!scopeProfileWritePlan ||
|
|
14097
|
+
!profileAutoPromotionAllows(options.category, options.confidence)
|
|
14098
|
+
)
|
|
14099
|
+
return;
|
|
14100
|
+
const autoTargets = new Set(scopeProfileWritePlan.profile.autoPromote.targets);
|
|
14101
|
+
const targets = scopeProfileWritePlan.promotionTargets.filter(
|
|
14102
|
+
(target) =>
|
|
14103
|
+
target.target !== "serverShared" &&
|
|
14104
|
+
autoTargets.has(target.target) &&
|
|
14105
|
+
target.authorized &&
|
|
14106
|
+
target.namespace,
|
|
13522
14107
|
);
|
|
13523
|
-
if (
|
|
13524
|
-
|
|
14108
|
+
if (targets.length === 0) return;
|
|
14109
|
+
const rawContent =
|
|
14110
|
+
citationEnabled && hasCitationForTemplate(options.content, citationTemplate)
|
|
14111
|
+
? stripCitationForTemplate(options.content, citationTemplate)
|
|
14112
|
+
: options.content;
|
|
14113
|
+
const citedContent = applyInlineCitation(rawContent);
|
|
14114
|
+
const sanitizedBase = sanitizeMemoryContent(rawContent);
|
|
14115
|
+
const dedupContent =
|
|
14116
|
+
options.category === "fact" &&
|
|
14117
|
+
options.structuredAttributes &&
|
|
14118
|
+
Object.keys(options.structuredAttributes).length > 0
|
|
14119
|
+
? `${sanitizedBase.text}\n[Attributes: ${normalizeAttributePairs(options.structuredAttributes)}]`
|
|
14120
|
+
: sanitizedBase.text;
|
|
14121
|
+
for (const target of targets) {
|
|
14122
|
+
if (!target.namespace) continue;
|
|
14123
|
+
try {
|
|
14124
|
+
const targetStorage = await this.storageRouter.storageFor(target.namespace);
|
|
14125
|
+
if (targetStorage.dir === options.sourceStorage.dir) continue;
|
|
14126
|
+
if (
|
|
14127
|
+
options.category === "fact" &&
|
|
14128
|
+
(await targetStorage.hasFactContentHash(dedupContent))
|
|
14129
|
+
) {
|
|
14130
|
+
continue;
|
|
14131
|
+
}
|
|
14132
|
+
const promotedId = await targetStorage.writeMemory(
|
|
14133
|
+
options.category as any,
|
|
14134
|
+
citedContent,
|
|
14135
|
+
{
|
|
14136
|
+
confidence: options.confidence,
|
|
14137
|
+
tags: [...options.tags, `${target.target}-promotion`],
|
|
14138
|
+
entityRef: options.entityRef,
|
|
14139
|
+
structuredAttributes: options.structuredAttributes,
|
|
14140
|
+
source: `${options.source}-${target.target}-promotion`,
|
|
14141
|
+
importance: options.importance,
|
|
14142
|
+
lineage: [options.sourceMemoryId],
|
|
14143
|
+
sourceMemoryId: options.sourceMemoryId,
|
|
14144
|
+
intentGoal: options.intentGoal,
|
|
14145
|
+
intentActionType: options.intentActionType,
|
|
14146
|
+
intentEntityTypes: options.intentEntityTypes,
|
|
14147
|
+
memoryKind: options.memoryKind,
|
|
14148
|
+
validAt: options.validAt,
|
|
14149
|
+
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
14150
|
+
},
|
|
14151
|
+
);
|
|
14152
|
+
if (
|
|
14153
|
+
this.config.temporalSupersessionEnabled &&
|
|
14154
|
+
options.category === "fact" &&
|
|
14155
|
+
options.entityRef &&
|
|
14156
|
+
options.structuredAttributes &&
|
|
14157
|
+
Object.keys(options.structuredAttributes).length > 0
|
|
14158
|
+
) {
|
|
14159
|
+
try {
|
|
14160
|
+
await applyTemporalSupersession({
|
|
14161
|
+
storage: targetStorage,
|
|
14162
|
+
newMemoryId: promotedId,
|
|
14163
|
+
entityRef: options.entityRef,
|
|
14164
|
+
structuredAttributes: options.structuredAttributes,
|
|
14165
|
+
createdAt: supersessionOrderingAt(options.validAt),
|
|
14166
|
+
enabled: true,
|
|
14167
|
+
});
|
|
14168
|
+
} catch (profileSupersessionErr) {
|
|
14169
|
+
log.warn(
|
|
14170
|
+
`persistExtraction: ${target.target} promotion temporal supersession failed open for promoted ${promotedId}: ${profileSupersessionErr}`,
|
|
14171
|
+
);
|
|
14172
|
+
}
|
|
14173
|
+
}
|
|
14174
|
+
this.markCatalogWrite(target.namespace, targetStorage.dir);
|
|
14175
|
+
trackPersistedId(targetStorage, promotedId, { includeReturnedIds: false });
|
|
14176
|
+
await this.indexPersistedMemory(targetStorage, promotedId);
|
|
14177
|
+
trackBehaviorSignals(
|
|
14178
|
+
targetStorage,
|
|
14179
|
+
buildBehaviorSignalsForMemory({
|
|
14180
|
+
memoryId: promotedId,
|
|
14181
|
+
category: options.category as any,
|
|
14182
|
+
content: options.content,
|
|
14183
|
+
namespace: target.namespace,
|
|
14184
|
+
confidence: options.confidence,
|
|
14185
|
+
source: "extraction",
|
|
14186
|
+
}),
|
|
14187
|
+
);
|
|
14188
|
+
} catch (err) {
|
|
14189
|
+
log.warn(
|
|
14190
|
+
`persistExtraction: ${target.target} promotion failed open for ${options.sourceMemoryId}: ${err}`,
|
|
14191
|
+
);
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
13525
14194
|
};
|
|
13526
14195
|
const promoteMemoryToShared = async (options: {
|
|
13527
14196
|
sourceStorage: StorageManager;
|
|
@@ -13540,6 +14209,7 @@ export class Orchestrator {
|
|
|
13540
14209
|
validAt?: string;
|
|
13541
14210
|
source: string;
|
|
13542
14211
|
}): Promise<void> => {
|
|
14212
|
+
await promoteMemoryToProfileTargets(options);
|
|
13543
14213
|
if (
|
|
13544
14214
|
!shouldPromoteToShared(
|
|
13545
14215
|
options.sourceStorage,
|
|
@@ -13764,11 +14434,10 @@ export class Orchestrator {
|
|
|
13764
14434
|
intentEntityTypes: options.intentEntityTypes,
|
|
13765
14435
|
memoryKind: options.memoryKind,
|
|
13766
14436
|
validAt: options.validAt,
|
|
13767
|
-
// Index the
|
|
13768
|
-
//
|
|
13769
|
-
//
|
|
13770
|
-
|
|
13771
|
-
contentHashSource: rawContent,
|
|
14437
|
+
// Index the same canonical body used by hasFactContentHash above.
|
|
14438
|
+
// For structured facts this includes the normalized Attributes
|
|
14439
|
+
// suffix, matching StorageManager.writeMemory enrichment.
|
|
14440
|
+
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
13772
14441
|
},
|
|
13773
14442
|
);
|
|
13774
14443
|
// PR #402 Finding 3 fix: run temporal supersession against the shared
|
|
@@ -14222,7 +14891,7 @@ export class Orchestrator {
|
|
|
14222
14891
|
!routedNamespaceExplicit
|
|
14223
14892
|
) {
|
|
14224
14893
|
const currentNs = this.namespaceFromStorageDir(targetStorage.dir);
|
|
14225
|
-
if (currentNs !== this.config.sharedNamespace) {
|
|
14894
|
+
if (currentNs !== this.config.sharedNamespace && profileAllowsSharedWrites) {
|
|
14226
14895
|
try {
|
|
14227
14896
|
targetStorage = await this.storageRouter.storageFor(
|
|
14228
14897
|
this.config.sharedNamespace,
|
|
@@ -14236,6 +14905,10 @@ export class Orchestrator {
|
|
|
14236
14905
|
`scope-routing: failed to resolve shared namespace storage; writing to session namespace (fail-open): ${scopeRouteErr}`,
|
|
14237
14906
|
);
|
|
14238
14907
|
}
|
|
14908
|
+
} else if (currentNs !== this.config.sharedNamespace) {
|
|
14909
|
+
log.debug(
|
|
14910
|
+
`scope-routing: skipped shared namespace for global fact because active scope profile ${scopeProfileWritePlan?.profileId ?? "none"} does not authorize serverShared writes`,
|
|
14911
|
+
);
|
|
14239
14912
|
}
|
|
14240
14913
|
}
|
|
14241
14914
|
|
|
@@ -14250,9 +14923,20 @@ export class Orchestrator {
|
|
|
14250
14923
|
writeCategory === "procedure"
|
|
14251
14924
|
? buildProcedurePersistBody(fact.content, fact.procedureSteps)
|
|
14252
14925
|
: canonicalContentForHash;
|
|
14253
|
-
|
|
14926
|
+
let exactDuplicate = false;
|
|
14927
|
+
try {
|
|
14928
|
+
exactDuplicate = await this.hasContentHashDedup(
|
|
14929
|
+
targetStorage,
|
|
14930
|
+
contentHashDedupKey,
|
|
14931
|
+
);
|
|
14932
|
+
} catch (err) {
|
|
14933
|
+
log.warn(
|
|
14934
|
+
`content-hash dedup lookup failed for storage ${targetStorage.dir}; writing fact fail-open: ${err}`,
|
|
14935
|
+
);
|
|
14936
|
+
}
|
|
14937
|
+
if (exactDuplicate) {
|
|
14254
14938
|
log.debug(
|
|
14255
|
-
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}…"`,
|
|
14939
|
+
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}…" in storage ${targetStorage.dir}`,
|
|
14256
14940
|
);
|
|
14257
14941
|
dedupedCount++;
|
|
14258
14942
|
continue;
|
|
@@ -14700,17 +15384,20 @@ export class Orchestrator {
|
|
|
14700
15384
|
validAt: sourceContext?.validAt,
|
|
14701
15385
|
source: extractionWriteSource,
|
|
14702
15386
|
});
|
|
14703
|
-
// Register chunked content in hash index too.
|
|
15387
|
+
// Register chunked content in the target storage hash index too.
|
|
14704
15388
|
// Thread 3 fix: canonicalize by stripping any pre-existing citation
|
|
14705
|
-
// so the stored hash matches what the dedup check computes
|
|
14706
|
-
|
|
14707
|
-
if (this.contentHashIndex) {
|
|
15389
|
+
// so the stored hash matches what the dedup check computes.
|
|
15390
|
+
try {
|
|
14708
15391
|
const canonicalChunkedContent =
|
|
14709
15392
|
citationEnabled &&
|
|
14710
15393
|
hasCitationForTemplate(fact.content, citationTemplate)
|
|
14711
15394
|
? stripCitationForTemplate(fact.content, citationTemplate)
|
|
14712
15395
|
: fact.content;
|
|
14713
|
-
this.
|
|
15396
|
+
await this.addContentHashDedup(targetStorage, canonicalChunkedContent);
|
|
15397
|
+
} catch (err) {
|
|
15398
|
+
log.warn(
|
|
15399
|
+
`content-hash dedup registration failed for chunked memory ${parentId}: ${err}`,
|
|
15400
|
+
);
|
|
14714
15401
|
}
|
|
14715
15402
|
|
|
14716
15403
|
for (const chunk of chunkResult.chunks) {
|
|
@@ -14988,11 +15675,10 @@ export class Orchestrator {
|
|
|
14988
15675
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
14989
15676
|
});
|
|
14990
15677
|
}
|
|
14991
|
-
// Register in content-hash index after successful
|
|
14992
|
-
// Thread 3 fix: canonicalize by stripping any pre-existing
|
|
14993
|
-
// the stored hash matches what the dedup check computes
|
|
14994
|
-
|
|
14995
|
-
if (this.contentHashIndex) {
|
|
15678
|
+
// Register in the target storage content-hash index after successful
|
|
15679
|
+
// write. Thread 3 fix: canonicalize by stripping any pre-existing
|
|
15680
|
+
// citation so the stored hash matches what the dedup check computes.
|
|
15681
|
+
try {
|
|
14996
15682
|
const canonicalFactContent =
|
|
14997
15683
|
citationEnabled &&
|
|
14998
15684
|
hasCitationForTemplate(fact.content, citationTemplate)
|
|
@@ -15002,7 +15688,11 @@ export class Orchestrator {
|
|
|
15002
15688
|
writeCategory === "procedure"
|
|
15003
15689
|
? buildProcedurePersistBody(fact.content, fact.procedureSteps)
|
|
15004
15690
|
: canonicalFactContent;
|
|
15005
|
-
this.
|
|
15691
|
+
await this.addContentHashDedup(targetStorage, hashRegisterKey);
|
|
15692
|
+
} catch (err) {
|
|
15693
|
+
log.warn(
|
|
15694
|
+
`content-hash dedup registration failed for memory ${memoryId}: ${err}`,
|
|
15695
|
+
);
|
|
15006
15696
|
}
|
|
15007
15697
|
} finally {
|
|
15008
15698
|
// Catalog touch (issue #1499): record AFTER every synchronous
|
|
@@ -15158,12 +15848,10 @@ export class Orchestrator {
|
|
|
15158
15848
|
touchBaseNonFactNamespace();
|
|
15159
15849
|
}
|
|
15160
15850
|
|
|
15161
|
-
// Save content-hash
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
.catch((err) => log.warn(`content-hash index save failed: ${err}`));
|
|
15166
|
-
}
|
|
15851
|
+
// Save any content-hash indexes touched during the batch.
|
|
15852
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
15853
|
+
log.warn(`content-hash index save failed: ${err}`),
|
|
15854
|
+
);
|
|
15167
15855
|
|
|
15168
15856
|
for (const {
|
|
15169
15857
|
storage: targetStorage,
|
|
@@ -16501,27 +17189,13 @@ export class Orchestrator {
|
|
|
16501
17189
|
// All criteria met — archive
|
|
16502
17190
|
const result = await this.storage.archiveMemory(memory);
|
|
16503
17191
|
if (result) {
|
|
16504
|
-
// Remove from content-hash index since it
|
|
16505
|
-
//
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
// hex string — use removeByHash to avoid double-hashing.
|
|
16512
|
-
this.contentHashIndex.removeByHash(memory.frontmatter.contentHash);
|
|
16513
|
-
} else {
|
|
16514
|
-
// Legacy memory written before contentHash was stored on the
|
|
16515
|
-
// frontmatter. Pre-#369 facts were stored without inline
|
|
16516
|
-
// citations, so memory.content is the raw fact text and we can
|
|
16517
|
-
// remove the hash directly from the content. This clears
|
|
16518
|
-
// stale dedup entries so the fact can be re-extracted.
|
|
16519
|
-
log.warn(
|
|
16520
|
-
`[fact-archival] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback — no contentHash in frontmatter`,
|
|
16521
|
-
);
|
|
16522
|
-
this.contentHashIndex.remove(memory.content);
|
|
16523
|
-
}
|
|
16524
|
-
}
|
|
17192
|
+
// Remove from the same storage-scoped content-hash index since it is
|
|
17193
|
+
// no longer in hot search.
|
|
17194
|
+
await this.removeContentHashForMemory(
|
|
17195
|
+
this.storage,
|
|
17196
|
+
memory,
|
|
17197
|
+
"fact-archival",
|
|
17198
|
+
);
|
|
16525
17199
|
await this.embeddingFallback.removeFromIndex(memory.frontmatter.id);
|
|
16526
17200
|
if (
|
|
16527
17201
|
this.config.queryAwareIndexingEnabled &&
|
|
@@ -16539,13 +17213,11 @@ export class Orchestrator {
|
|
|
16539
17213
|
}
|
|
16540
17214
|
}
|
|
16541
17215
|
|
|
16542
|
-
// Save hash
|
|
16543
|
-
if (archivedCount > 0
|
|
16544
|
-
await this.
|
|
16545
|
-
.save
|
|
16546
|
-
|
|
16547
|
-
log.warn(`content-hash index save failed during archival: ${err}`),
|
|
16548
|
-
);
|
|
17216
|
+
// Save hash indexes if we removed any entries.
|
|
17217
|
+
if (archivedCount > 0) {
|
|
17218
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
17219
|
+
log.warn(`content-hash index save failed during archival: ${err}`),
|
|
17220
|
+
);
|
|
16549
17221
|
}
|
|
16550
17222
|
|
|
16551
17223
|
return archivedCount;
|