recallnest 2.3.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +187 -0
- package/README.md +49 -38
- package/README_CN.md +46 -31
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +25 -4
- package/src/__tests__/access-tracker-novelty.test.ts +304 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/api-server-security.test.ts +102 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/belief-history.test.ts +220 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1328 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +116 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +945 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +418 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +430 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +262 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +129 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +780 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/freshness.test.ts +186 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +283 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-client-pivot.test.ts +628 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +189 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +282 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/noise-prototype-bank.test.ts +280 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +333 -0
- package/src/__tests__/pivot-apply.test.ts +373 -0
- package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
- package/src/__tests__/pivot-distill.test.ts +1178 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +282 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-gate.test.ts +266 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/resolve-db-path.test.ts +130 -0
- package/src/__tests__/resolve-tier.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-eval.test.ts +163 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +654 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/stable-context-length-contract.test.ts +62 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-read-consistency.test.ts +122 -0
- package/src/__tests__/store-schema-migration.test.ts +87 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +201 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/ui-server-security.test.ts +93 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +90 -34
- package/src/activity-counter.ts +46 -18
- package/src/adaptive-retrieval.ts +11 -0
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +36 -9
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/auto-recall.ts +61 -1
- package/src/belief-history.ts +164 -0
- package/src/capture-engine.ts +142 -32
- package/src/cli.ts +268 -41
- package/src/confidence-tracker.ts +36 -18
- package/src/conflict-engine.ts +24 -3
- package/src/consolidation-engine.ts +255 -23
- package/src/context-composer-stable-selection.ts +7 -1
- package/src/context-composer.ts +8 -1
- package/src/data-checkup.ts +96 -7
- package/src/decay-engine.ts +59 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +13 -4
- package/src/dream-pipeline.ts +473 -41
- package/src/env-config.ts +118 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +402 -14
- package/src/freshness.ts +192 -0
- package/src/http-request-policy.ts +148 -0
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +308 -12
- package/src/kg-extractor.ts +13 -3
- package/src/kg-store.ts +236 -44
- package/src/llm-client.ts +106 -11
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +87 -1800
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +954 -0
- package/src/mcp-tools-core.ts +637 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +26 -3
- package/src/memory-evolution.ts +73 -1
- package/src/memory-health-rebalance.ts +4 -0
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +129 -4
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +46 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/noise-prototype-bank.ts +408 -0
- package/src/pii-detector.ts +207 -5
- package/src/pivot-apply.ts +399 -0
- package/src/preference-matcher.ts +37 -3
- package/src/query-expander.ts +90 -9
- package/src/recall-gate.ts +305 -0
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retrieval-profiles.ts +46 -4
- package/src/retriever.ts +354 -157
- package/src/retroactive-boost.ts +6 -2
- package/src/runtime-config.ts +78 -6
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +1 -0
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +304 -48
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/store.ts +321 -57
- package/src/term-registry.ts +35 -0
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +9 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/version.ts +19 -0
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -60
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/ingest-2026-04-13.log +0 -87
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
package/src/capture-engine.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Embedder } from "./embedder.js";
|
|
2
2
|
import { detectLang, tokenizeFts } from "./language-hook.js";
|
|
3
3
|
import { generateAnchor } from "./anchor-generator.js";
|
|
4
|
-
import {
|
|
4
|
+
import { extractErrorSignatures } from "./error-signature.js";
|
|
5
5
|
import { verifyWrite } from "./write-verifier.js";
|
|
6
6
|
// batchInternalDedup is available in ingest.ts for large-batch scenarios;
|
|
7
7
|
// persistMemoryBatch relies on per-item conflict detection (A-2) instead.
|
|
8
|
-
import { defaultEvolution, buildSupersedeMetadata, buildSupersedeMetadataForNew, buildPendingReviewMetadata, isActiveMemory, patchEvolution } from "./memory-evolution.js";
|
|
8
|
+
import { defaultEvolution, buildSupersedeMetadata, buildSupersedeMetadataForNew, buildPendingReviewMetadata, isActiveMemory, parseEvolution, patchEvolution } from "./memory-evolution.js";
|
|
9
|
+
import { archiveBeliefVersion, buildSupersedingBeliefMetadata } from "./belief-history.js";
|
|
9
10
|
import { detectTopicTag, injectTopicTag } from "./topic-tag.js";
|
|
10
11
|
import { assignDefaultConfidence, type ConfidenceMetadata } from "./confidence-tracker.js";
|
|
12
|
+
import { parseDependsOnInput } from "./freshness.js";
|
|
11
13
|
import {
|
|
12
14
|
type CaseMemoryInput,
|
|
13
15
|
CaseMemoryInputSchema,
|
|
@@ -56,12 +58,13 @@ import {
|
|
|
56
58
|
import { matchPreference, applyPreferenceMatch } from "./preference-matcher.js";
|
|
57
59
|
import type { LLMClient } from "./llm-client.js";
|
|
58
60
|
import type { KGExtractor } from "./kg-extractor.js";
|
|
59
|
-
import { scanForPII } from "./pii-detector.js";
|
|
61
|
+
import { scanForPII, redactSecrets } from "./pii-detector.js";
|
|
60
62
|
import type { AuditLogger } from "./audit-log.js";
|
|
61
63
|
import { checkAdmission, type ScopeRateLimiter, type AdmissionConfig } from "./admission-control.js";
|
|
64
|
+
import { resolveNoisePrototypeBank, type NoisePrototypeBank } from "./noise-prototype-bank.js";
|
|
62
65
|
import { tagNarrativeIfEnabled } from "./narrative-tagger.js";
|
|
63
66
|
|
|
64
|
-
type StoreDeps = Pick<MemoryStore, "store"> & Partial<Pick<MemoryStore, "list" | "update" | "getById" | "get" | "vectorSearch">>;
|
|
67
|
+
type StoreDeps = Pick<MemoryStore, "store"> & Partial<Pick<MemoryStore, "list" | "update" | "getById" | "get" | "vectorSearch" | "upsert">>;
|
|
65
68
|
type ConflictStoreDeps = Pick<ConflictCandidateStore, "save" | "replace" | "getOpenByFingerprint" | "getLatestByFingerprint">;
|
|
66
69
|
|
|
67
70
|
export interface PersistMemoryDeps {
|
|
@@ -78,9 +81,15 @@ export interface PersistMemoryDeps {
|
|
|
78
81
|
rateLimiter?: ScopeRateLimiter | null;
|
|
79
82
|
/** LME-8: Optional admission config overrides */
|
|
80
83
|
admissionConfig?: Partial<AdmissionConfig>;
|
|
84
|
+
/**
|
|
85
|
+
* Noise-prototype shadow experiment (mlp borrow). undefined → env-governed
|
|
86
|
+
* shared instance (RECALLNEST_NOISE_PROTOTYPE, shadow default); null → off.
|
|
87
|
+
* Shadow-only: it observes and logs, it never rejects.
|
|
88
|
+
*/
|
|
89
|
+
noisePrototypeBank?: NoisePrototypeBank | null;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
interface DurableWriteInput {
|
|
92
|
+
export interface DurableWriteInput {
|
|
84
93
|
text: string;
|
|
85
94
|
vector: number[];
|
|
86
95
|
category: DurableMemoryCategory;
|
|
@@ -140,7 +149,7 @@ function mergeTags(...groups: string[][]): string[] {
|
|
|
140
149
|
return merged.slice(0, 8);
|
|
141
150
|
}
|
|
142
151
|
|
|
143
|
-
function buildStructuredMetadata(params: {
|
|
152
|
+
export function buildStructuredMetadata(params: {
|
|
144
153
|
source: string;
|
|
145
154
|
tags: string[];
|
|
146
155
|
capture: string;
|
|
@@ -437,11 +446,15 @@ async function findCanonicalMatches(
|
|
|
437
446
|
return entries.filter((entry) => {
|
|
438
447
|
const boundary = extractBoundaryMetadata(entry.metadata);
|
|
439
448
|
if (boundary?.layer !== "durable") return false;
|
|
449
|
+
// Belief-history rows carry the same canonicalKey as the live row they were copied
|
|
450
|
+
// from. Without this guard they'd be treated as dedupe/conflict targets — so rewriting
|
|
451
|
+
// a belief back to an older wording would look like a duplicate, not a change.
|
|
452
|
+
if (!isActiveMemory(entry.metadata)) return false;
|
|
440
453
|
return extractCanonicalKey(entry.metadata) === canonicalKey;
|
|
441
454
|
});
|
|
442
455
|
}
|
|
443
456
|
|
|
444
|
-
async function writeDurableEntry(
|
|
457
|
+
export async function writeDurableEntry(
|
|
445
458
|
deps: PersistMemoryDeps,
|
|
446
459
|
params: DurableWriteInput,
|
|
447
460
|
): Promise<{
|
|
@@ -563,13 +576,26 @@ async function writeDurableEntry(
|
|
|
563
576
|
}
|
|
564
577
|
}
|
|
565
578
|
|
|
579
|
+
// latest-wins is where beliefs change. Archive the version being replaced first —
|
|
580
|
+
// overwriting in place used to delete the old text + vector outright, which is what
|
|
581
|
+
// made "what did I believe last month?" unanswerable no matter how retrieval queried.
|
|
582
|
+
const now = Date.now();
|
|
583
|
+
const previousEvo = parseEvolution(latest.metadata, latest.timestamp);
|
|
584
|
+
const { historyId } = await archiveBeliefVersion(
|
|
585
|
+
{ store: deps.store, embedder: deps.embedder },
|
|
586
|
+
latest,
|
|
587
|
+
{ now },
|
|
588
|
+
);
|
|
589
|
+
|
|
566
590
|
const updated = await deps.store.update(latest.id, {
|
|
567
591
|
text: params.text,
|
|
568
592
|
vector: params.vector,
|
|
569
593
|
importance: params.importance,
|
|
570
594
|
category: params.category,
|
|
571
|
-
|
|
572
|
-
|
|
595
|
+
// Merge rather than replace: a bare params.metadata would wipe the evolution block
|
|
596
|
+
// (version, access stats, supersede links) along with the text.
|
|
597
|
+
metadata: buildSupersedingBeliefMetadata(params.metadata, previousEvo, historyId, now),
|
|
598
|
+
timestamp: now,
|
|
573
599
|
...(params.language ? { language: params.language } : {}),
|
|
574
600
|
...(params.fts_text ? { fts_text: params.fts_text } : {}),
|
|
575
601
|
});
|
|
@@ -582,16 +608,38 @@ async function writeDurableEntry(
|
|
|
582
608
|
};
|
|
583
609
|
}
|
|
584
610
|
|
|
611
|
+
// No canonical match was found, so this falls through to store(). With a canonicalKey the
|
|
612
|
+
// id is deterministic and store() upserts on id — so a row already sitting at that id is
|
|
613
|
+
// overwritten here without ever reaching the latest-wins branch above. findCanonicalMatches
|
|
614
|
+
// only scans the most recent CANONICAL_SCAN_LIMIT rows, so any canonical row older than that
|
|
615
|
+
// window lands on this path. Look it up by id directly, which no scan limit can hide.
|
|
616
|
+
const targetId = params.canonicalKey
|
|
617
|
+
? deterministicId(params.scope, params.canonicalKey)
|
|
618
|
+
: undefined;
|
|
619
|
+
let metadataForStore = params.metadata;
|
|
620
|
+
|
|
621
|
+
if (targetId && deps.store.getById) {
|
|
622
|
+
const rowAtTargetId = await deps.store.getById(targetId);
|
|
623
|
+
if (rowAtTargetId && normalizeMemoryText(rowAtTargetId.text) !== normalizeMemoryText(params.text)) {
|
|
624
|
+
const now = Date.now();
|
|
625
|
+
const previousEvo = parseEvolution(rowAtTargetId.metadata, rowAtTargetId.timestamp);
|
|
626
|
+
const { historyId } = await archiveBeliefVersion(
|
|
627
|
+
{ store: deps.store, embedder: deps.embedder },
|
|
628
|
+
rowAtTargetId,
|
|
629
|
+
{ now },
|
|
630
|
+
);
|
|
631
|
+
metadataForStore = buildSupersedingBeliefMetadata(params.metadata, previousEvo, historyId, now);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
585
635
|
const stored = await deps.store.store({
|
|
586
|
-
id:
|
|
587
|
-
? deterministicId(params.scope, params.canonicalKey)
|
|
588
|
-
: undefined,
|
|
636
|
+
id: targetId,
|
|
589
637
|
text: params.text,
|
|
590
638
|
vector: params.vector,
|
|
591
639
|
category: params.category,
|
|
592
640
|
scope: params.scope,
|
|
593
641
|
importance: params.importance,
|
|
594
|
-
metadata:
|
|
642
|
+
metadata: metadataForStore,
|
|
595
643
|
...(params.language ? { language: params.language } : {}),
|
|
596
644
|
...(params.fts_text ? { fts_text: params.fts_text } : {}),
|
|
597
645
|
});
|
|
@@ -699,6 +747,17 @@ function buildCaseMemoryMetadata(
|
|
|
699
747
|
tools: input.tools,
|
|
700
748
|
},
|
|
701
749
|
};
|
|
750
|
+
// A1: error-signature 指纹(读写对称,P3-A 检索复用同一抽取器)。
|
|
751
|
+
// 在 build metadata 前算好作为 extra 写入,不 post-write update(Codex 验证要求)。
|
|
752
|
+
const errorSignature = extractErrorSignatures({
|
|
753
|
+
problem: input.problem,
|
|
754
|
+
context: input.context,
|
|
755
|
+
outcome: input.outcome,
|
|
756
|
+
solutionSteps: input.solutionSteps,
|
|
757
|
+
});
|
|
758
|
+
if (errorSignature.length > 0) cmExtra.error_signature = errorSignature;
|
|
759
|
+
// A1: break-loop 五维(可选,向后兼容;不提供则普通 case)。
|
|
760
|
+
if (input.debugFraming) cmExtra.debugFraming = input.debugFraming;
|
|
702
761
|
const anchor = generateAnchor(input.title + ": " + input.problem, cmExtra);
|
|
703
762
|
if (anchor) cmExtra.anchor = anchor;
|
|
704
763
|
return buildStructuredMetadata({
|
|
@@ -746,7 +805,16 @@ export async function persistMemory(
|
|
|
746
805
|
deps: PersistMemoryDeps,
|
|
747
806
|
rawInput: unknown,
|
|
748
807
|
): Promise<StoredMemoryRecord> {
|
|
749
|
-
|
|
808
|
+
let input = StoreMemoryInputSchema.parse(rawInput);
|
|
809
|
+
|
|
810
|
+
// F-3b: Scan THEN redact, before the text reaches any external surface —
|
|
811
|
+
// LLM summary/importance calls, embedding API, storage, audit log, KG.
|
|
812
|
+
// Scan first so the piiWarning below still reflects what the raw text held.
|
|
813
|
+
const piiScan = scanForPII(input.text);
|
|
814
|
+
const redaction = redactSecrets(input.text);
|
|
815
|
+
if (redaction.redacted > 0) {
|
|
816
|
+
input = { ...input, text: redaction.text };
|
|
817
|
+
}
|
|
750
818
|
|
|
751
819
|
// F3: Extract temporal validity params before Zod strips them
|
|
752
820
|
const rawObj = rawInput as Record<string, unknown> | null;
|
|
@@ -801,6 +869,28 @@ export async function persistMemory(
|
|
|
801
869
|
deps.admissionConfig,
|
|
802
870
|
);
|
|
803
871
|
if (admission.verdict === "rejected") {
|
|
872
|
+
// Gemini #26523 lesson: rejected writes must stay queryable after the fact,
|
|
873
|
+
// not just echo back to the caller — otherwise they become invisible noise.
|
|
874
|
+
try {
|
|
875
|
+
deps.auditLogger?.log({
|
|
876
|
+
operation: "reject",
|
|
877
|
+
scope: admissionScope,
|
|
878
|
+
actor: input.source,
|
|
879
|
+
details: `${admission.reason ?? "unknown"}: ${input.text.slice(0, 100)}`,
|
|
880
|
+
});
|
|
881
|
+
} catch {
|
|
882
|
+
// Audit must never block memory writes
|
|
883
|
+
}
|
|
884
|
+
// Noise-prototype shadow experiment: learn ONLY from the regex noise
|
|
885
|
+
// filter's own verdict (mlp #914 lesson — a heuristic's low-importance
|
|
886
|
+
// self-judgment must not be recycled as a noise label). Embedding only
|
|
887
|
+
// happens on a repeat sighting under the bank cap, so this stays cheap.
|
|
888
|
+
if (admission.reason === "noise_detected") {
|
|
889
|
+
const bank = resolveNoisePrototypeBank(deps.noisePrototypeBank);
|
|
890
|
+
if (bank) {
|
|
891
|
+
await bank.learnFromRejection(input.text, (text) => deps.embedder.embedPassage(text));
|
|
892
|
+
}
|
|
893
|
+
}
|
|
804
894
|
return {
|
|
805
895
|
id: `rejected-${Date.now()}`,
|
|
806
896
|
text: input.text,
|
|
@@ -812,10 +902,20 @@ export async function persistMemory(
|
|
|
812
902
|
resolvedScope: admissionScope,
|
|
813
903
|
storedAt: new Date().toISOString(),
|
|
814
904
|
disposition: "rejected" as const,
|
|
905
|
+
...(admission.reason ? { rejectionReason: admission.reason } : {}),
|
|
815
906
|
};
|
|
816
907
|
}
|
|
817
908
|
|
|
818
909
|
const vector = await deps.embedder.embedPassage(input.text);
|
|
910
|
+
|
|
911
|
+
// Noise-prototype shadow experiment, matching side: reuses the write-path
|
|
912
|
+
// embedding just computed (zero extra embedding). Log-only — the verdict
|
|
913
|
+
// never blocks this write while the experiment is in shadow.
|
|
914
|
+
{
|
|
915
|
+
const bank = resolveNoisePrototypeBank(deps.noisePrototypeBank);
|
|
916
|
+
bank?.matchShadow(input.text, vector);
|
|
917
|
+
}
|
|
918
|
+
|
|
819
919
|
const language = detectLang(input.text);
|
|
820
920
|
const fts_text = tokenizeFts(input.text, language);
|
|
821
921
|
const resolvedScope = resolveScope(input);
|
|
@@ -843,6 +943,7 @@ export async function persistMemory(
|
|
|
843
943
|
matchResult,
|
|
844
944
|
deps.store as MemoryStore,
|
|
845
945
|
resolvedScope,
|
|
946
|
+
{ embedder: deps.embedder },
|
|
846
947
|
);
|
|
847
948
|
if (applied.handled) {
|
|
848
949
|
const entry = applied.entry ?? {
|
|
@@ -937,15 +1038,16 @@ export async function persistMemory(
|
|
|
937
1038
|
}
|
|
938
1039
|
|
|
939
1040
|
// F-3: PII detection — inject warning into metadata if PII found.
|
|
940
|
-
// Non-blocking: PII does not prevent the write.
|
|
1041
|
+
// Non-blocking: PII does not prevent the write. Uses the pre-redaction scan
|
|
1042
|
+
// from the top of persistMemory (scanning here would see scrubbed text).
|
|
941
1043
|
let metadata = buildStoreMemoryMetadata(input, canonicalKey);
|
|
942
|
-
|
|
943
|
-
if (piiResult.hasPII) {
|
|
1044
|
+
if (piiScan.hasPII) {
|
|
944
1045
|
const parsed: Record<string, unknown> = JSON.parse(metadata);
|
|
945
1046
|
parsed.piiWarning = {
|
|
946
|
-
summary:
|
|
947
|
-
detections:
|
|
948
|
-
severity:
|
|
1047
|
+
summary: piiScan.summary,
|
|
1048
|
+
detections: piiScan.detections.length,
|
|
1049
|
+
severity: piiScan.detections.some(d => d.severity === "high") ? "high" : "medium",
|
|
1050
|
+
...(redaction.redacted > 0 ? { redacted: redaction.redacted } : {}),
|
|
949
1051
|
};
|
|
950
1052
|
metadata = JSON.stringify(parsed);
|
|
951
1053
|
}
|
|
@@ -995,6 +1097,15 @@ export async function persistMemory(
|
|
|
995
1097
|
metadata = JSON.stringify(parsed);
|
|
996
1098
|
}
|
|
997
1099
|
|
|
1100
|
+
// Freshness (borrowed 2026-07-21): opt-in dependsOn declarations ride in metadata like
|
|
1101
|
+
// validUntil/confidence do — pulled from raw input so StoreMemoryInputSchema stays untouched.
|
|
1102
|
+
const dependsOn = parseDependsOnInput(rawObj?.dependsOn);
|
|
1103
|
+
if (dependsOn) {
|
|
1104
|
+
const parsed: Record<string, unknown> = JSON.parse(metadata);
|
|
1105
|
+
parsed.dependsOn = dependsOn;
|
|
1106
|
+
metadata = JSON.stringify(parsed);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
998
1109
|
const { entry, disposition, conflictId } = await writeDurableEntry(deps, {
|
|
999
1110
|
text: input.text,
|
|
1000
1111
|
vector,
|
|
@@ -1031,14 +1142,9 @@ export async function persistMemory(
|
|
|
1031
1142
|
// Audit must never block memory writes
|
|
1032
1143
|
}
|
|
1033
1144
|
|
|
1034
|
-
// HP-3
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
incrementWriteCount();
|
|
1038
|
-
} catch {
|
|
1039
|
-
// Activity tracking must never block memory writes
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1145
|
+
// HP-3 activity counter moved to the store layer (store/upsert/storeBatch count per
|
|
1146
|
+
// scope): that is the necessary chokepoint for every write source (capture/ingest/
|
|
1147
|
+
// store_memory/consolidation), and per-scope counts drive per-scope dream activation.
|
|
1042
1148
|
|
|
1043
1149
|
// HP-2: Post-write verification — async, non-blocking
|
|
1044
1150
|
if (disposition !== "deduped" && deps.store.get) {
|
|
@@ -1063,7 +1169,7 @@ export async function persistMemory(
|
|
|
1063
1169
|
input.category !== "preferences" &&
|
|
1064
1170
|
inferImplicitUsageSlot(input.text)
|
|
1065
1171
|
) {
|
|
1066
|
-
const prefCanonicalKey = buildDefaultCanonicalKey("preferences", input.text);
|
|
1172
|
+
const prefCanonicalKey = buildDefaultCanonicalKey({ category: "preferences", text: input.text });
|
|
1067
1173
|
const prefSlot = inferImplicitUsageSlot(input.text);
|
|
1068
1174
|
const prefMetadata = buildStructuredMetadata({
|
|
1069
1175
|
source: input.source,
|
|
@@ -1098,7 +1204,8 @@ export async function persistWorkflowPattern(
|
|
|
1098
1204
|
const input = WorkflowPatternInputSchema.parse(rawInput);
|
|
1099
1205
|
const resolvedScope = resolveScope(input);
|
|
1100
1206
|
const tags = mergeTags(input.tags, ["workflow", "pattern"]);
|
|
1101
|
-
|
|
1207
|
+
// F-3b: scrub before embedding/storage (this path bypasses persistMemory)
|
|
1208
|
+
const text = redactSecrets(buildWorkflowPatternText(input)).text;
|
|
1102
1209
|
const vector = await deps.embedder.embedPassage(text);
|
|
1103
1210
|
const language = detectLang(text);
|
|
1104
1211
|
const fts_text = tokenizeFts(text, language);
|
|
@@ -1142,7 +1249,8 @@ export async function persistCaseMemory(
|
|
|
1142
1249
|
const input = CaseMemoryInputSchema.parse(rawInput);
|
|
1143
1250
|
const resolvedScope = resolveScope(input);
|
|
1144
1251
|
const tags = mergeTags(input.tags, ["case", "solution"]);
|
|
1145
|
-
|
|
1252
|
+
// F-3b: scrub before embedding/storage (this path bypasses persistMemory)
|
|
1253
|
+
const text = redactSecrets(buildCaseMemoryText(input)).text;
|
|
1146
1254
|
const vector = await deps.embedder.embedPassage(text);
|
|
1147
1255
|
const language = detectLang(text);
|
|
1148
1256
|
const fts_text = tokenizeFts(text, language);
|
|
@@ -1202,7 +1310,9 @@ export async function promoteMemory(
|
|
|
1202
1310
|
}
|
|
1203
1311
|
|
|
1204
1312
|
const category = inferPromotedCategory(input, sourceEntry);
|
|
1205
|
-
|
|
1313
|
+
// F-3b: scrub caller-supplied text AND legacy stored text (pre-redaction
|
|
1314
|
+
// era entries may still hold secrets; promotion is a natural wash point)
|
|
1315
|
+
const text = redactSecrets(input.text || sourceEntry.text).text;
|
|
1206
1316
|
const canonicalKey = resolveCanonicalKey({
|
|
1207
1317
|
category,
|
|
1208
1318
|
text,
|