@anuma/sdk 5.1.1 → 6.0.1
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/expo/{chunk-WV7C7VHF.mjs → chunk-DT6QNRZL.mjs} +8 -2
- package/dist/expo/{consolidate-T6GZ5YQ4.mjs → consolidate-3MEWQNM3.mjs} +38 -4
- package/dist/expo/index.cjs +46 -6
- package/dist/expo/index.d.mts +33 -6
- package/dist/expo/index.d.ts +33 -6
- package/dist/expo/index.mjs +2 -2
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/react/{chunk-3NPDZZGO.mjs → chunk-VMI74IUX.mjs} +8 -2
- package/dist/react/{consolidate-POPTUI54.mjs → consolidate-AMPKH6YJ.mjs} +38 -4
- package/dist/react/index.cjs +46 -6
- package/dist/react/index.d.mts +33 -6
- package/dist/react/index.d.ts +33 -6
- package/dist/react/index.mjs +2 -2
- package/dist/server/{chunk-RT75PNSJ.mjs → chunk-KF4HV3QE.mjs} +8 -2
- package/dist/server/{consolidate-LMIDJFR6.mjs → consolidate-I6TOH7CB.mjs} +38 -4
- package/dist/server/index.cjs +46 -6
- package/dist/server/index.d.mts +30 -5
- package/dist/server/index.d.ts +30 -5
- package/dist/server/index.mjs +2 -2
- package/package.json +3 -3
|
@@ -643,10 +643,16 @@ function createStreamingDeAnonymizer(redactor, emit) {
|
|
|
643
643
|
|
|
644
644
|
// src/lib/memory/consolidationFallback.ts
|
|
645
645
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
646
|
+
const refused = reason === "subject_mismatch";
|
|
647
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
648
|
+
const log = getLogger();
|
|
646
649
|
if (detail !== void 0) {
|
|
647
|
-
|
|
650
|
+
if (refused) log.info(message, detail);
|
|
651
|
+
else log.warn(message, detail);
|
|
652
|
+
} else if (refused) {
|
|
653
|
+
log.info(message);
|
|
648
654
|
} else {
|
|
649
|
-
|
|
655
|
+
log.warn(message);
|
|
650
656
|
}
|
|
651
657
|
try {
|
|
652
658
|
onFallback?.(reason);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
notifyConsolidationFallback,
|
|
3
3
|
resolvePiiRedactor
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DT6QNRZL.mjs";
|
|
5
5
|
import {
|
|
6
6
|
callPortalJsonCompletion
|
|
7
7
|
} from "./chunk-ILAWDQDE.mjs";
|
|
@@ -39,12 +39,15 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
39
39
|
"action": "create" | "update" | "supersede" | "noop",
|
|
40
40
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
41
41
|
"targetIds": ["<id>", ...],
|
|
42
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
42
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
43
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
44
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
45
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
46
49
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
47
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
50
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
48
51
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
49
52
|
async function consolidateMemory(newContent, candidates, options) {
|
|
50
53
|
const fallback = {
|
|
@@ -101,7 +104,13 @@ ${candidateText}`;
|
|
|
101
104
|
if (restored.unresolved) {
|
|
102
105
|
return degrade("invalid_response", fallback, options);
|
|
103
106
|
}
|
|
104
|
-
return { ...result, content: restored.text };
|
|
107
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
108
|
+
}
|
|
109
|
+
return reportRefusal(result, options);
|
|
110
|
+
}
|
|
111
|
+
function reportRefusal(result, options) {
|
|
112
|
+
if (result.fallbackReason) {
|
|
113
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
105
114
|
}
|
|
106
115
|
return result;
|
|
107
116
|
}
|
|
@@ -109,6 +118,19 @@ function degrade(reason, fallback, options, detail) {
|
|
|
109
118
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
110
119
|
return { ...fallback, fallbackReason: reason };
|
|
111
120
|
}
|
|
121
|
+
function normalizeSubject(raw) {
|
|
122
|
+
if (typeof raw !== "string") return "";
|
|
123
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
124
|
+
s = s.replace(/^the\s+/u, "");
|
|
125
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
126
|
+
return s.trim();
|
|
127
|
+
}
|
|
128
|
+
var USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
129
|
+
function subjectKey(raw) {
|
|
130
|
+
const s = normalizeSubject(raw);
|
|
131
|
+
if (s.length === 0) return "";
|
|
132
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
133
|
+
}
|
|
112
134
|
function validate(parsed, newContent, validIds) {
|
|
113
135
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
114
136
|
const obj = parsed;
|
|
@@ -136,6 +158,18 @@ function validate(parsed, newContent, validIds) {
|
|
|
136
158
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
137
159
|
];
|
|
138
160
|
if (targetIds.length === 0) return null;
|
|
161
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
162
|
+
if (newSubject.length > 0) {
|
|
163
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
164
|
+
const statedSubjects = [
|
|
165
|
+
...positional.map((s) => subjectKey(s)),
|
|
166
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
167
|
+
subjectKey(obj.targetSubject)
|
|
168
|
+
];
|
|
169
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
170
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
139
173
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
140
174
|
}
|
|
141
175
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
package/dist/expo/index.cjs
CHANGED
|
@@ -8723,10 +8723,16 @@ var init_searchTool = __esm({
|
|
|
8723
8723
|
|
|
8724
8724
|
// src/lib/memory/consolidationFallback.ts
|
|
8725
8725
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
8726
|
+
const refused = reason === "subject_mismatch";
|
|
8727
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
8728
|
+
const log = getLogger();
|
|
8726
8729
|
if (detail !== void 0) {
|
|
8727
|
-
|
|
8730
|
+
if (refused) log.info(message, detail);
|
|
8731
|
+
else log.warn(message, detail);
|
|
8732
|
+
} else if (refused) {
|
|
8733
|
+
log.info(message);
|
|
8728
8734
|
} else {
|
|
8729
|
-
|
|
8735
|
+
log.warn(message);
|
|
8730
8736
|
}
|
|
8731
8737
|
try {
|
|
8732
8738
|
onFallback?.(reason);
|
|
@@ -8801,7 +8807,13 @@ ${candidateText}`;
|
|
|
8801
8807
|
if (restored.unresolved) {
|
|
8802
8808
|
return degrade("invalid_response", fallback, options);
|
|
8803
8809
|
}
|
|
8804
|
-
return { ...result, content: restored.text };
|
|
8810
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
8811
|
+
}
|
|
8812
|
+
return reportRefusal(result, options);
|
|
8813
|
+
}
|
|
8814
|
+
function reportRefusal(result, options) {
|
|
8815
|
+
if (result.fallbackReason) {
|
|
8816
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
8805
8817
|
}
|
|
8806
8818
|
return result;
|
|
8807
8819
|
}
|
|
@@ -8809,6 +8821,18 @@ function degrade(reason, fallback, options, detail) {
|
|
|
8809
8821
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
8810
8822
|
return { ...fallback, fallbackReason: reason };
|
|
8811
8823
|
}
|
|
8824
|
+
function normalizeSubject(raw) {
|
|
8825
|
+
if (typeof raw !== "string") return "";
|
|
8826
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
8827
|
+
s = s.replace(/^the\s+/u, "");
|
|
8828
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
8829
|
+
return s.trim();
|
|
8830
|
+
}
|
|
8831
|
+
function subjectKey(raw) {
|
|
8832
|
+
const s = normalizeSubject(raw);
|
|
8833
|
+
if (s.length === 0) return "";
|
|
8834
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
8835
|
+
}
|
|
8812
8836
|
function validate2(parsed, newContent, validIds) {
|
|
8813
8837
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
8814
8838
|
const obj = parsed;
|
|
@@ -8836,13 +8860,25 @@ function validate2(parsed, newContent, validIds) {
|
|
|
8836
8860
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
8837
8861
|
];
|
|
8838
8862
|
if (targetIds.length === 0) return null;
|
|
8863
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
8864
|
+
if (newSubject.length > 0) {
|
|
8865
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
8866
|
+
const statedSubjects = [
|
|
8867
|
+
...positional.map((s) => subjectKey(s)),
|
|
8868
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
8869
|
+
subjectKey(obj.targetSubject)
|
|
8870
|
+
];
|
|
8871
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
8872
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8839
8875
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
8840
8876
|
}
|
|
8841
8877
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
|
8842
8878
|
if (!targetId || !validIds.has(targetId)) return null;
|
|
8843
8879
|
return { action: "update", targetId, content: c };
|
|
8844
8880
|
}
|
|
8845
|
-
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3;
|
|
8881
|
+
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3, USER_SUBJECT_ALIASES;
|
|
8846
8882
|
var init_consolidate = __esm({
|
|
8847
8883
|
"src/lib/memory/consolidate.ts"() {
|
|
8848
8884
|
"use strict";
|
|
@@ -8880,13 +8916,17 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
8880
8916
|
"action": "create" | "update" | "supersede" | "noop",
|
|
8881
8917
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
8882
8918
|
"targetIds": ["<id>", ...],
|
|
8883
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
8919
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
8920
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
8921
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
8922
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
8884
8923
|
}
|
|
8885
8924
|
|
|
8886
8925
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
8887
8926
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
8888
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
8927
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
8889
8928
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
8929
|
+
USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
8890
8930
|
}
|
|
8891
8931
|
});
|
|
8892
8932
|
|
package/dist/expo/index.d.mts
CHANGED
|
@@ -5528,9 +5528,9 @@ interface RecallDiagnostics {
|
|
|
5528
5528
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
5529
5529
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
5530
5530
|
/**
|
|
5531
|
-
* Why a retain
|
|
5532
|
-
*
|
|
5533
|
-
*
|
|
5531
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
5532
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
5533
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
5534
5534
|
*
|
|
5535
5535
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
5536
5536
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -5545,8 +5545,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
5545
5545
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
5546
5546
|
* manual write racing over the same vault), which quietly costs you the
|
|
5547
5547
|
* dedup that decision would have performed.
|
|
5548
|
-
|
|
5549
|
-
|
|
5548
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
5549
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
5550
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
5551
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
5552
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
5553
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
5554
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
5555
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
5556
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
5557
|
+
*/
|
|
5558
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
5559
|
+
/**
|
|
5560
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
5561
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
5562
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
5563
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
5564
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
5565
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
5566
|
+
* reporting degraded quality.
|
|
5567
|
+
*/
|
|
5568
|
+
| "subject_mismatch";
|
|
5550
5569
|
interface RetainOptions {
|
|
5551
5570
|
source?: RetainSource;
|
|
5552
5571
|
sourceChunkIds?: string[];
|
|
@@ -5582,6 +5601,12 @@ interface RetainOptions {
|
|
|
5582
5601
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
5583
5602
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
5584
5603
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
5604
|
+
*
|
|
5605
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
5606
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
5607
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
5608
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
5609
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
5585
5610
|
*/
|
|
5586
5611
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
5587
5612
|
/**
|
|
@@ -9533,7 +9558,9 @@ declare function ensureDefaultFoldersOp(ctx: VaultFolderOperationsContext): Prom
|
|
|
9533
9558
|
*/
|
|
9534
9559
|
interface Logger {
|
|
9535
9560
|
debug: (...args: unknown[]) => void;
|
|
9536
|
-
/**
|
|
9561
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
9562
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
9563
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
9537
9564
|
info: (...args: unknown[]) => void;
|
|
9538
9565
|
warn: (...args: unknown[]) => void;
|
|
9539
9566
|
error: (...args: unknown[]) => void;
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -5528,9 +5528,9 @@ interface RecallDiagnostics {
|
|
|
5528
5528
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
5529
5529
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
5530
5530
|
/**
|
|
5531
|
-
* Why a retain
|
|
5532
|
-
*
|
|
5533
|
-
*
|
|
5531
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
5532
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
5533
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
5534
5534
|
*
|
|
5535
5535
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
5536
5536
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -5545,8 +5545,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
5545
5545
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
5546
5546
|
* manual write racing over the same vault), which quietly costs you the
|
|
5547
5547
|
* dedup that decision would have performed.
|
|
5548
|
-
|
|
5549
|
-
|
|
5548
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
5549
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
5550
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
5551
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
5552
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
5553
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
5554
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
5555
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
5556
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
5557
|
+
*/
|
|
5558
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
5559
|
+
/**
|
|
5560
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
5561
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
5562
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
5563
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
5564
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
5565
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
5566
|
+
* reporting degraded quality.
|
|
5567
|
+
*/
|
|
5568
|
+
| "subject_mismatch";
|
|
5550
5569
|
interface RetainOptions {
|
|
5551
5570
|
source?: RetainSource;
|
|
5552
5571
|
sourceChunkIds?: string[];
|
|
@@ -5582,6 +5601,12 @@ interface RetainOptions {
|
|
|
5582
5601
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
5583
5602
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
5584
5603
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
5604
|
+
*
|
|
5605
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
5606
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
5607
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
5608
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
5609
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
5585
5610
|
*/
|
|
5586
5611
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
5587
5612
|
/**
|
|
@@ -9533,7 +9558,9 @@ declare function ensureDefaultFoldersOp(ctx: VaultFolderOperationsContext): Prom
|
|
|
9533
9558
|
*/
|
|
9534
9559
|
interface Logger {
|
|
9535
9560
|
debug: (...args: unknown[]) => void;
|
|
9536
|
-
/**
|
|
9561
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
9562
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
9563
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
9537
9564
|
info: (...args: unknown[]) => void;
|
|
9538
9565
|
warn: (...args: unknown[]) => void;
|
|
9539
9566
|
error: (...args: unknown[]) => void;
|
package/dist/expo/index.mjs
CHANGED
|
@@ -129,7 +129,7 @@ import {
|
|
|
129
129
|
isPiiRedactor,
|
|
130
130
|
notifyConsolidationFallback,
|
|
131
131
|
resolvePiiRedactor
|
|
132
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-DT6QNRZL.mjs";
|
|
133
133
|
import {
|
|
134
134
|
callPortalJsonCompletion,
|
|
135
135
|
consoleLogger,
|
|
@@ -6705,7 +6705,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
6705
6705
|
content: m.content,
|
|
6706
6706
|
similarity: m.similarity
|
|
6707
6707
|
}));
|
|
6708
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
6708
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-3MEWQNM3.mjs");
|
|
6709
6709
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
6710
6710
|
if (decision.action === "create") return null;
|
|
6711
6711
|
if (decision.action === "supersede" && decision.content) {
|
package/dist/index.d.mts
CHANGED
|
@@ -8449,7 +8449,9 @@ interface AgentConfig {
|
|
|
8449
8449
|
*/
|
|
8450
8450
|
interface Logger {
|
|
8451
8451
|
debug: (...args: unknown[]) => void;
|
|
8452
|
-
/**
|
|
8452
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
8453
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
8454
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
8453
8455
|
info: (...args: unknown[]) => void;
|
|
8454
8456
|
warn: (...args: unknown[]) => void;
|
|
8455
8457
|
error: (...args: unknown[]) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -8449,7 +8449,9 @@ interface AgentConfig {
|
|
|
8449
8449
|
*/
|
|
8450
8450
|
interface Logger {
|
|
8451
8451
|
debug: (...args: unknown[]) => void;
|
|
8452
|
-
/**
|
|
8452
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
8453
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
8454
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
8453
8455
|
info: (...args: unknown[]) => void;
|
|
8454
8456
|
warn: (...args: unknown[]) => void;
|
|
8455
8457
|
error: (...args: unknown[]) => void;
|
|
@@ -643,10 +643,16 @@ function createStreamingDeAnonymizer(redactor, emit) {
|
|
|
643
643
|
|
|
644
644
|
// src/lib/memory/consolidationFallback.ts
|
|
645
645
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
646
|
+
const refused = reason === "subject_mismatch";
|
|
647
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
648
|
+
const log = getLogger();
|
|
646
649
|
if (detail !== void 0) {
|
|
647
|
-
|
|
650
|
+
if (refused) log.info(message, detail);
|
|
651
|
+
else log.warn(message, detail);
|
|
652
|
+
} else if (refused) {
|
|
653
|
+
log.info(message);
|
|
648
654
|
} else {
|
|
649
|
-
|
|
655
|
+
log.warn(message);
|
|
650
656
|
}
|
|
651
657
|
try {
|
|
652
658
|
onFallback?.(reason);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
notifyConsolidationFallback,
|
|
3
3
|
resolvePiiRedactor
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VMI74IUX.mjs";
|
|
5
5
|
import {
|
|
6
6
|
callPortalJsonCompletion
|
|
7
7
|
} from "./chunk-ILAWDQDE.mjs";
|
|
@@ -39,12 +39,15 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
39
39
|
"action": "create" | "update" | "supersede" | "noop",
|
|
40
40
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
41
41
|
"targetIds": ["<id>", ...],
|
|
42
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
42
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
43
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
44
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
45
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
46
49
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
47
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
50
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
48
51
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
49
52
|
async function consolidateMemory(newContent, candidates, options) {
|
|
50
53
|
const fallback = {
|
|
@@ -101,7 +104,13 @@ ${candidateText}`;
|
|
|
101
104
|
if (restored.unresolved) {
|
|
102
105
|
return degrade("invalid_response", fallback, options);
|
|
103
106
|
}
|
|
104
|
-
return { ...result, content: restored.text };
|
|
107
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
108
|
+
}
|
|
109
|
+
return reportRefusal(result, options);
|
|
110
|
+
}
|
|
111
|
+
function reportRefusal(result, options) {
|
|
112
|
+
if (result.fallbackReason) {
|
|
113
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
105
114
|
}
|
|
106
115
|
return result;
|
|
107
116
|
}
|
|
@@ -109,6 +118,19 @@ function degrade(reason, fallback, options, detail) {
|
|
|
109
118
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
110
119
|
return { ...fallback, fallbackReason: reason };
|
|
111
120
|
}
|
|
121
|
+
function normalizeSubject(raw) {
|
|
122
|
+
if (typeof raw !== "string") return "";
|
|
123
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
124
|
+
s = s.replace(/^the\s+/u, "");
|
|
125
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
126
|
+
return s.trim();
|
|
127
|
+
}
|
|
128
|
+
var USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
129
|
+
function subjectKey(raw) {
|
|
130
|
+
const s = normalizeSubject(raw);
|
|
131
|
+
if (s.length === 0) return "";
|
|
132
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
133
|
+
}
|
|
112
134
|
function validate(parsed, newContent, validIds) {
|
|
113
135
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
114
136
|
const obj = parsed;
|
|
@@ -136,6 +158,18 @@ function validate(parsed, newContent, validIds) {
|
|
|
136
158
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
137
159
|
];
|
|
138
160
|
if (targetIds.length === 0) return null;
|
|
161
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
162
|
+
if (newSubject.length > 0) {
|
|
163
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
164
|
+
const statedSubjects = [
|
|
165
|
+
...positional.map((s) => subjectKey(s)),
|
|
166
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
167
|
+
subjectKey(obj.targetSubject)
|
|
168
|
+
];
|
|
169
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
170
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
139
173
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
140
174
|
}
|
|
141
175
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
package/dist/react/index.cjs
CHANGED
|
@@ -9006,10 +9006,16 @@ var init_searchTool = __esm({
|
|
|
9006
9006
|
|
|
9007
9007
|
// src/lib/memory/consolidationFallback.ts
|
|
9008
9008
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
9009
|
+
const refused = reason === "subject_mismatch";
|
|
9010
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
9011
|
+
const log = getLogger();
|
|
9009
9012
|
if (detail !== void 0) {
|
|
9010
|
-
|
|
9013
|
+
if (refused) log.info(message, detail);
|
|
9014
|
+
else log.warn(message, detail);
|
|
9015
|
+
} else if (refused) {
|
|
9016
|
+
log.info(message);
|
|
9011
9017
|
} else {
|
|
9012
|
-
|
|
9018
|
+
log.warn(message);
|
|
9013
9019
|
}
|
|
9014
9020
|
try {
|
|
9015
9021
|
onFallback?.(reason);
|
|
@@ -9084,7 +9090,13 @@ ${candidateText}`;
|
|
|
9084
9090
|
if (restored.unresolved) {
|
|
9085
9091
|
return degrade("invalid_response", fallback, options);
|
|
9086
9092
|
}
|
|
9087
|
-
return { ...result, content: restored.text };
|
|
9093
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
9094
|
+
}
|
|
9095
|
+
return reportRefusal(result, options);
|
|
9096
|
+
}
|
|
9097
|
+
function reportRefusal(result, options) {
|
|
9098
|
+
if (result.fallbackReason) {
|
|
9099
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
9088
9100
|
}
|
|
9089
9101
|
return result;
|
|
9090
9102
|
}
|
|
@@ -9092,6 +9104,18 @@ function degrade(reason, fallback, options, detail) {
|
|
|
9092
9104
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
9093
9105
|
return { ...fallback, fallbackReason: reason };
|
|
9094
9106
|
}
|
|
9107
|
+
function normalizeSubject(raw) {
|
|
9108
|
+
if (typeof raw !== "string") return "";
|
|
9109
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
9110
|
+
s = s.replace(/^the\s+/u, "");
|
|
9111
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
9112
|
+
return s.trim();
|
|
9113
|
+
}
|
|
9114
|
+
function subjectKey(raw) {
|
|
9115
|
+
const s = normalizeSubject(raw);
|
|
9116
|
+
if (s.length === 0) return "";
|
|
9117
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
9118
|
+
}
|
|
9095
9119
|
function validate2(parsed, newContent, validIds) {
|
|
9096
9120
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
9097
9121
|
const obj = parsed;
|
|
@@ -9119,13 +9143,25 @@ function validate2(parsed, newContent, validIds) {
|
|
|
9119
9143
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
9120
9144
|
];
|
|
9121
9145
|
if (targetIds.length === 0) return null;
|
|
9146
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
9147
|
+
if (newSubject.length > 0) {
|
|
9148
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
9149
|
+
const statedSubjects = [
|
|
9150
|
+
...positional.map((s) => subjectKey(s)),
|
|
9151
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
9152
|
+
subjectKey(obj.targetSubject)
|
|
9153
|
+
];
|
|
9154
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
9155
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
9156
|
+
}
|
|
9157
|
+
}
|
|
9122
9158
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
9123
9159
|
}
|
|
9124
9160
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
|
9125
9161
|
if (!targetId || !validIds.has(targetId)) return null;
|
|
9126
9162
|
return { action: "update", targetId, content: c };
|
|
9127
9163
|
}
|
|
9128
|
-
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3;
|
|
9164
|
+
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3, USER_SUBJECT_ALIASES;
|
|
9129
9165
|
var init_consolidate = __esm({
|
|
9130
9166
|
"src/lib/memory/consolidate.ts"() {
|
|
9131
9167
|
"use strict";
|
|
@@ -9163,13 +9199,17 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
9163
9199
|
"action": "create" | "update" | "supersede" | "noop",
|
|
9164
9200
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
9165
9201
|
"targetIds": ["<id>", ...],
|
|
9166
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
9202
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
9203
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
9204
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
9205
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
9167
9206
|
}
|
|
9168
9207
|
|
|
9169
9208
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
9170
9209
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
9171
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
9210
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
9172
9211
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
9212
|
+
USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
9173
9213
|
}
|
|
9174
9214
|
});
|
|
9175
9215
|
|
package/dist/react/index.d.mts
CHANGED
|
@@ -3238,7 +3238,9 @@ declare function createWebSearchPreProcessor(options?: WebSearchPreProcessorOpti
|
|
|
3238
3238
|
*/
|
|
3239
3239
|
interface Logger {
|
|
3240
3240
|
debug: (...args: unknown[]) => void;
|
|
3241
|
-
/**
|
|
3241
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
3242
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
3243
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
3242
3244
|
info: (...args: unknown[]) => void;
|
|
3243
3245
|
warn: (...args: unknown[]) => void;
|
|
3244
3246
|
error: (...args: unknown[]) => void;
|
|
@@ -6800,9 +6802,9 @@ interface RecallDiagnostics {
|
|
|
6800
6802
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
6801
6803
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
6802
6804
|
/**
|
|
6803
|
-
* Why a retain
|
|
6804
|
-
*
|
|
6805
|
-
*
|
|
6805
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
6806
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
6807
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
6806
6808
|
*
|
|
6807
6809
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
6808
6810
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -6817,8 +6819,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
6817
6819
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
6818
6820
|
* manual write racing over the same vault), which quietly costs you the
|
|
6819
6821
|
* dedup that decision would have performed.
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
6823
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
6824
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
6825
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
6826
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
6827
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
6828
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
6829
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
6830
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
6831
|
+
*/
|
|
6832
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
6833
|
+
/**
|
|
6834
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
6835
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
6836
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
6837
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
6838
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
6839
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
6840
|
+
* reporting degraded quality.
|
|
6841
|
+
*/
|
|
6842
|
+
| "subject_mismatch";
|
|
6822
6843
|
interface RetainOptions {
|
|
6823
6844
|
source?: RetainSource;
|
|
6824
6845
|
sourceChunkIds?: string[];
|
|
@@ -6854,6 +6875,12 @@ interface RetainOptions {
|
|
|
6854
6875
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
6855
6876
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
6856
6877
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
6878
|
+
*
|
|
6879
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
6880
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
6881
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
6882
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
6883
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
6857
6884
|
*/
|
|
6858
6885
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
6859
6886
|
/**
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3238,7 +3238,9 @@ declare function createWebSearchPreProcessor(options?: WebSearchPreProcessorOpti
|
|
|
3238
3238
|
*/
|
|
3239
3239
|
interface Logger {
|
|
3240
3240
|
debug: (...args: unknown[]) => void;
|
|
3241
|
-
/**
|
|
3241
|
+
/** Used for outcomes that are noteworthy but expected — a consolidation
|
|
3242
|
+
* refusal that preserved a memory, for example. Keep genuinely-wrong things at
|
|
3243
|
+
* `warn` so the two stay distinguishable in a log search. */
|
|
3242
3244
|
info: (...args: unknown[]) => void;
|
|
3243
3245
|
warn: (...args: unknown[]) => void;
|
|
3244
3246
|
error: (...args: unknown[]) => void;
|
|
@@ -6800,9 +6802,9 @@ interface RecallDiagnostics {
|
|
|
6800
6802
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
6801
6803
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
6802
6804
|
/**
|
|
6803
|
-
* Why a retain
|
|
6804
|
-
*
|
|
6805
|
-
*
|
|
6805
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
6806
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
6807
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
6806
6808
|
*
|
|
6807
6809
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
6808
6810
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -6817,8 +6819,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
6817
6819
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
6818
6820
|
* manual write racing over the same vault), which quietly costs you the
|
|
6819
6821
|
* dedup that decision would have performed.
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
6823
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
6824
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
6825
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
6826
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
6827
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
6828
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
6829
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
6830
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
6831
|
+
*/
|
|
6832
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
6833
|
+
/**
|
|
6834
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
6835
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
6836
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
6837
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
6838
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
6839
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
6840
|
+
* reporting degraded quality.
|
|
6841
|
+
*/
|
|
6842
|
+
| "subject_mismatch";
|
|
6822
6843
|
interface RetainOptions {
|
|
6823
6844
|
source?: RetainSource;
|
|
6824
6845
|
sourceChunkIds?: string[];
|
|
@@ -6854,6 +6875,12 @@ interface RetainOptions {
|
|
|
6854
6875
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
6855
6876
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
6856
6877
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
6878
|
+
*
|
|
6879
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
6880
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
6881
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
6882
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
6883
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
6857
6884
|
*/
|
|
6858
6885
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
6859
6886
|
/**
|
package/dist/react/index.mjs
CHANGED
|
@@ -173,7 +173,7 @@ import {
|
|
|
173
173
|
isPiiRedactor,
|
|
174
174
|
notifyConsolidationFallback,
|
|
175
175
|
resolvePiiRedactor
|
|
176
|
-
} from "./chunk-
|
|
176
|
+
} from "./chunk-VMI74IUX.mjs";
|
|
177
177
|
import {
|
|
178
178
|
callPortalJsonCompletion,
|
|
179
179
|
consoleLogger,
|
|
@@ -7522,7 +7522,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
7522
7522
|
content: m.content,
|
|
7523
7523
|
similarity: m.similarity
|
|
7524
7524
|
}));
|
|
7525
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
7525
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-AMPKH6YJ.mjs");
|
|
7526
7526
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
7527
7527
|
if (decision.action === "create") return null;
|
|
7528
7528
|
if (decision.action === "supersede" && decision.content) {
|
|
@@ -643,10 +643,16 @@ function createStreamingDeAnonymizer(redactor, emit) {
|
|
|
643
643
|
|
|
644
644
|
// src/lib/memory/consolidationFallback.ts
|
|
645
645
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
646
|
+
const refused = reason === "subject_mismatch";
|
|
647
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
648
|
+
const log = getLogger();
|
|
646
649
|
if (detail !== void 0) {
|
|
647
|
-
|
|
650
|
+
if (refused) log.info(message, detail);
|
|
651
|
+
else log.warn(message, detail);
|
|
652
|
+
} else if (refused) {
|
|
653
|
+
log.info(message);
|
|
648
654
|
} else {
|
|
649
|
-
|
|
655
|
+
log.warn(message);
|
|
650
656
|
}
|
|
651
657
|
try {
|
|
652
658
|
onFallback?.(reason);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
notifyConsolidationFallback,
|
|
3
3
|
resolvePiiRedactor
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KF4HV3QE.mjs";
|
|
5
5
|
import {
|
|
6
6
|
callPortalJsonCompletion
|
|
7
7
|
} from "./chunk-OBDRKQQI.mjs";
|
|
@@ -39,12 +39,15 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
39
39
|
"action": "create" | "update" | "supersede" | "noop",
|
|
40
40
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
41
41
|
"targetIds": ["<id>", ...],
|
|
42
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
42
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
43
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
44
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
45
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
46
49
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
47
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
50
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
48
51
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
49
52
|
async function consolidateMemory(newContent, candidates, options) {
|
|
50
53
|
const fallback = {
|
|
@@ -101,7 +104,13 @@ ${candidateText}`;
|
|
|
101
104
|
if (restored.unresolved) {
|
|
102
105
|
return degrade("invalid_response", fallback, options);
|
|
103
106
|
}
|
|
104
|
-
return { ...result, content: restored.text };
|
|
107
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
108
|
+
}
|
|
109
|
+
return reportRefusal(result, options);
|
|
110
|
+
}
|
|
111
|
+
function reportRefusal(result, options) {
|
|
112
|
+
if (result.fallbackReason) {
|
|
113
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
105
114
|
}
|
|
106
115
|
return result;
|
|
107
116
|
}
|
|
@@ -109,6 +118,19 @@ function degrade(reason, fallback, options, detail) {
|
|
|
109
118
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
110
119
|
return { ...fallback, fallbackReason: reason };
|
|
111
120
|
}
|
|
121
|
+
function normalizeSubject(raw) {
|
|
122
|
+
if (typeof raw !== "string") return "";
|
|
123
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
124
|
+
s = s.replace(/^the\s+/u, "");
|
|
125
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
126
|
+
return s.trim();
|
|
127
|
+
}
|
|
128
|
+
var USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
129
|
+
function subjectKey(raw) {
|
|
130
|
+
const s = normalizeSubject(raw);
|
|
131
|
+
if (s.length === 0) return "";
|
|
132
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
133
|
+
}
|
|
112
134
|
function validate(parsed, newContent, validIds) {
|
|
113
135
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
114
136
|
const obj = parsed;
|
|
@@ -136,6 +158,18 @@ function validate(parsed, newContent, validIds) {
|
|
|
136
158
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
137
159
|
];
|
|
138
160
|
if (targetIds.length === 0) return null;
|
|
161
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
162
|
+
if (newSubject.length > 0) {
|
|
163
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
164
|
+
const statedSubjects = [
|
|
165
|
+
...positional.map((s) => subjectKey(s)),
|
|
166
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
167
|
+
subjectKey(obj.targetSubject)
|
|
168
|
+
];
|
|
169
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
170
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
139
173
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
140
174
|
}
|
|
141
175
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
package/dist/server/index.cjs
CHANGED
|
@@ -23548,10 +23548,16 @@ var init_redactor = __esm({
|
|
|
23548
23548
|
|
|
23549
23549
|
// src/lib/memory/consolidationFallback.ts
|
|
23550
23550
|
function notifyConsolidationFallback(reason, onFallback, detail) {
|
|
23551
|
+
const refused = reason === "subject_mismatch";
|
|
23552
|
+
const message = refused ? `memory/consolidate: refused a cross-subject supersede, created instead (${reason})` : `memory/consolidate: degraded to create (${reason})`;
|
|
23553
|
+
const log = getLogger();
|
|
23551
23554
|
if (detail !== void 0) {
|
|
23552
|
-
|
|
23555
|
+
if (refused) log.info(message, detail);
|
|
23556
|
+
else log.warn(message, detail);
|
|
23557
|
+
} else if (refused) {
|
|
23558
|
+
log.info(message);
|
|
23553
23559
|
} else {
|
|
23554
|
-
|
|
23560
|
+
log.warn(message);
|
|
23555
23561
|
}
|
|
23556
23562
|
try {
|
|
23557
23563
|
onFallback?.(reason);
|
|
@@ -23626,7 +23632,13 @@ ${candidateText}`;
|
|
|
23626
23632
|
if (restored.unresolved) {
|
|
23627
23633
|
return degrade("invalid_response", fallback, options);
|
|
23628
23634
|
}
|
|
23629
|
-
return { ...result, content: restored.text };
|
|
23635
|
+
return reportRefusal({ ...result, content: restored.text }, options);
|
|
23636
|
+
}
|
|
23637
|
+
return reportRefusal(result, options);
|
|
23638
|
+
}
|
|
23639
|
+
function reportRefusal(result, options) {
|
|
23640
|
+
if (result.fallbackReason) {
|
|
23641
|
+
notifyConsolidationFallback(result.fallbackReason, options.onFallback);
|
|
23630
23642
|
}
|
|
23631
23643
|
return result;
|
|
23632
23644
|
}
|
|
@@ -23634,6 +23646,18 @@ function degrade(reason, fallback, options, detail) {
|
|
|
23634
23646
|
notifyConsolidationFallback(reason, options.onFallback, detail);
|
|
23635
23647
|
return { ...fallback, fallbackReason: reason };
|
|
23636
23648
|
}
|
|
23649
|
+
function normalizeSubject(raw) {
|
|
23650
|
+
if (typeof raw !== "string") return "";
|
|
23651
|
+
let s = raw.trim().toLowerCase().replace(/[.,;:!?]+$/u, "").trim();
|
|
23652
|
+
s = s.replace(/^the\s+/u, "");
|
|
23653
|
+
s = s.replace(/^users'?\s+/u, "").replace(/^user's\s+/u, "");
|
|
23654
|
+
return s.trim();
|
|
23655
|
+
}
|
|
23656
|
+
function subjectKey(raw) {
|
|
23657
|
+
const s = normalizeSubject(raw);
|
|
23658
|
+
if (s.length === 0) return "";
|
|
23659
|
+
return USER_SUBJECT_ALIASES.has(s) ? "user" : s;
|
|
23660
|
+
}
|
|
23637
23661
|
function validate2(parsed, newContent, validIds) {
|
|
23638
23662
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
23639
23663
|
const obj = parsed;
|
|
@@ -23661,13 +23685,25 @@ function validate2(parsed, newContent, validIds) {
|
|
|
23661
23685
|
...new Set(rawIds.filter((id) => typeof id === "string" && validIds.has(id)))
|
|
23662
23686
|
];
|
|
23663
23687
|
if (targetIds.length === 0) return null;
|
|
23688
|
+
const newSubject = subjectKey(obj.newSubject);
|
|
23689
|
+
if (newSubject.length > 0) {
|
|
23690
|
+
const positional = Array.isArray(obj.targetSubjects) ? obj.targetSubjects : [];
|
|
23691
|
+
const statedSubjects = [
|
|
23692
|
+
...positional.map((s) => subjectKey(s)),
|
|
23693
|
+
// The singular pair, which is the whole story for a one-target supersede.
|
|
23694
|
+
subjectKey(obj.targetSubject)
|
|
23695
|
+
];
|
|
23696
|
+
if (statedSubjects.some((s) => s.length > 0 && s !== newSubject)) {
|
|
23697
|
+
return { action: "create", content: c, fallbackReason: "subject_mismatch" };
|
|
23698
|
+
}
|
|
23699
|
+
}
|
|
23664
23700
|
return { action: "supersede", targetId: targetIds[0], targetIds, content: c };
|
|
23665
23701
|
}
|
|
23666
23702
|
const targetId = typeof obj.targetId === "string" ? obj.targetId : null;
|
|
23667
23703
|
if (!targetId || !validIds.has(targetId)) return null;
|
|
23668
23704
|
return { action: "update", targetId, content: c };
|
|
23669
23705
|
}
|
|
23670
|
-
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3;
|
|
23706
|
+
var DEFAULT_CONSOLIDATION_MODEL, DEFAULT_CONSOLIDATE_ATTEMPTS, DEFAULT_CONSOLIDATE_TOTAL_TIMEOUT_MS, SYSTEM_PROMPT3, USER_SUBJECT_ALIASES;
|
|
23671
23707
|
var init_consolidate = __esm({
|
|
23672
23708
|
"src/lib/memory/consolidate.ts"() {
|
|
23673
23709
|
"use strict";
|
|
@@ -23705,13 +23741,17 @@ OUTPUT \u2014 strict JSON, no prose:
|
|
|
23705
23741
|
"action": "create" | "update" | "supersede" | "noop",
|
|
23706
23742
|
"targetId": "<existing memory id \u2014 required for update/noop, omit for create/supersede>",
|
|
23707
23743
|
"targetIds": ["<id>", ...],
|
|
23708
|
-
"content": "<content \u2014 required for create/update/supersede, omit for noop>"
|
|
23744
|
+
"content": "<content \u2014 required for create/update/supersede, omit for noop>",
|
|
23745
|
+
"newSubject": "<required for supersede: WHO the new memory is about>",
|
|
23746
|
+
"targetSubject": "<required for supersede: WHO the memory in targetId is about>",
|
|
23747
|
+
"targetSubjects": ["<required for supersede: WHO each memory you are retiring is about \u2014 one entry per retired id>"]
|
|
23709
23748
|
}
|
|
23710
23749
|
|
|
23711
23750
|
For "create": content is the new memory verbatim (or a slight refinement); omit targetId/targetIds.
|
|
23712
23751
|
For "update": content is the merged richest-version, \u226480 words; targetId is the single memory to update.
|
|
23713
|
-
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute).
|
|
23752
|
+
For "supersede": content is the NEW fact only (\u226480 words); "targetIds" lists EVERY stale memory being retired (all candidates describing the same now-changed attribute). You MUST also name the subjects: "newSubject" for the new memory, and one entry in "targetSubjects" for EACH memory you are retiring (use "targetSubject" as well when you retire a single memory). List a subject ONLY for the memories you are actually retiring, not for every candidate you were shown. Write the plain subject, not a sentence: "the user", "the user's sister", "the user's manager", "Biscuit the dog". Rule 1a means a supersede is only valid when every one of those subjects is the SAME as "newSubject"; name them and check them against each other before you commit to the action. If ANY of them differs, the answer is "create" \u2014 do not retire the ones that match and keep the rest, because a group that mixes subjects was grouped wrongly in the first place. A subjectless fact ("Lives in Denver", "Works at Riverbend") is about "the user" \u2014 the extractor omits the subject when it is the user, so treat the absence of a subject as the user, never as unknown.
|
|
23714
23753
|
For "noop": no content (existing memory is already correct); targetId is that memory.`;
|
|
23754
|
+
USER_SUBJECT_ALIASES = /* @__PURE__ */ new Set(["user", "themselves", "themself", "they", "me", "self"]);
|
|
23715
23755
|
}
|
|
23716
23756
|
});
|
|
23717
23757
|
|
package/dist/server/index.d.mts
CHANGED
|
@@ -5469,9 +5469,9 @@ interface RecallDiagnostics {
|
|
|
5469
5469
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
5470
5470
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
5471
5471
|
/**
|
|
5472
|
-
* Why a retain
|
|
5473
|
-
*
|
|
5474
|
-
*
|
|
5472
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
5473
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
5474
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
5475
5475
|
*
|
|
5476
5476
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
5477
5477
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -5486,8 +5486,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
5486
5486
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
5487
5487
|
* manual write racing over the same vault), which quietly costs you the
|
|
5488
5488
|
* dedup that decision would have performed.
|
|
5489
|
-
|
|
5490
|
-
|
|
5489
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
5490
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
5491
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
5492
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
5493
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
5494
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
5495
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
5496
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
5497
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
5498
|
+
*/
|
|
5499
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
5500
|
+
/**
|
|
5501
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
5502
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
5503
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
5504
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
5505
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
5506
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
5507
|
+
* reporting degraded quality.
|
|
5508
|
+
*/
|
|
5509
|
+
| "subject_mismatch";
|
|
5491
5510
|
interface RetainOptions {
|
|
5492
5511
|
source?: RetainSource;
|
|
5493
5512
|
sourceChunkIds?: string[];
|
|
@@ -5523,6 +5542,12 @@ interface RetainOptions {
|
|
|
5523
5542
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
5524
5543
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
5525
5544
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
5545
|
+
*
|
|
5546
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
5547
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
5548
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
5549
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
5550
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
5526
5551
|
*/
|
|
5527
5552
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
5528
5553
|
/**
|
package/dist/server/index.d.ts
CHANGED
|
@@ -5469,9 +5469,9 @@ interface RecallDiagnostics {
|
|
|
5469
5469
|
type RetainAction = "create" | "merge" | "update" | "skip" | "suppressed" | "supersede";
|
|
5470
5470
|
type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
5471
5471
|
/**
|
|
5472
|
-
* Why a retain
|
|
5473
|
-
*
|
|
5474
|
-
*
|
|
5472
|
+
* Why a retain returned "create" instead of applying a consolidation decision.
|
|
5473
|
+
* Each value names a DIFFERENT thing to go fix, which is the point of keeping
|
|
5474
|
+
* them apart — and one of them is not a fault at all, see `subject_mismatch`:
|
|
5475
5475
|
*
|
|
5476
5476
|
* - `llm_error` — the consolidation call never produced a response (network,
|
|
5477
5477
|
* timeout, 5xx, 429, empty completion, or missing credentials). Look at the
|
|
@@ -5486,8 +5486,27 @@ type RetainSource = "manual" | "auto-extracted" | "capsule";
|
|
|
5486
5486
|
* rate points at write contention (for example an auto-extraction worker and a
|
|
5487
5487
|
* manual write racing over the same vault), which quietly costs you the
|
|
5488
5488
|
* dedup that decision would have performed.
|
|
5489
|
-
|
|
5490
|
-
|
|
5489
|
+
* - `subject_mismatch` — NOT a fault. The model returned a well-formed
|
|
5490
|
+
* `supersede` and we refused it because the two subjects it named were
|
|
5491
|
+
* different people, so retiring the target would have hidden a memory that is
|
|
5492
|
+
* still true (#822 — "User's sister lives in Denver" retiring "User lives in
|
|
5493
|
+
* Denver"). The refusal is the feature; do not alarm on it. What the rate does
|
|
5494
|
+
* tell you is how often the model reaches for a cross-subject supersede, and —
|
|
5495
|
+
* because the guard is inert unless the model fills in its subject fields — a
|
|
5496
|
+
* rate of exactly zero is as likely to mean "the fields are being omitted" as
|
|
5497
|
+
* "the mistake never happens". Check compliance before reading zero as health.
|
|
5498
|
+
*/
|
|
5499
|
+
type ConsolidationFallbackReason = "llm_error" | "invalid_response" | "target_vanished"
|
|
5500
|
+
/**
|
|
5501
|
+
* A `supersede` was downgraded to `create` because the model's own stated
|
|
5502
|
+
* subjects disagreed: the new fact is about someone other than the fact it
|
|
5503
|
+
* wanted to retire (#822 — "User's sister lives in Denver" retiring "User
|
|
5504
|
+
* lives in Denver"). NOT an error; the decision was refused on purpose, and
|
|
5505
|
+
* the refusal preserved a true memory. Retiring a superseded row hides it from
|
|
5506
|
+
* recall, so this is the one fallback that prevents data loss rather than
|
|
5507
|
+
* reporting degraded quality.
|
|
5508
|
+
*/
|
|
5509
|
+
| "subject_mismatch";
|
|
5491
5510
|
interface RetainOptions {
|
|
5492
5511
|
source?: RetainSource;
|
|
5493
5512
|
sourceChunkIds?: string[];
|
|
@@ -5523,6 +5542,12 @@ interface RetainOptions {
|
|
|
5523
5542
|
* JSON that violates the schema (unknown action, bad targetId).
|
|
5524
5543
|
* A flaky consolidator silently accumulates duplicate memories;
|
|
5525
5544
|
* wire this to logging/metrics so the fallback rate is observable.
|
|
5545
|
+
*
|
|
5546
|
+
* `subject_mismatch` is the odd one out and worth reading separately: the
|
|
5547
|
+
* model answered fine and we REFUSED its supersede because its own stated
|
|
5548
|
+
* subjects disagreed (#822). A non-zero rate is the guard doing its job, not
|
|
5549
|
+
* a problem — the rate to watch is how often it fires, which is also the
|
|
5550
|
+
* only signal for how reliably the model fills the subject fields in at all.
|
|
5526
5551
|
*/
|
|
5527
5552
|
onFallback?: (reason: ConsolidationFallbackReason) => void;
|
|
5528
5553
|
/**
|
package/dist/server/index.mjs
CHANGED
|
@@ -142,7 +142,7 @@ import {
|
|
|
142
142
|
createStreamingDeAnonymizer,
|
|
143
143
|
notifyConsolidationFallback,
|
|
144
144
|
resolvePiiRedactor
|
|
145
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-KF4HV3QE.mjs";
|
|
146
146
|
import {
|
|
147
147
|
callPortalJsonCompletion,
|
|
148
148
|
extractJsonCandidate,
|
|
@@ -5503,7 +5503,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
5503
5503
|
content: m.content,
|
|
5504
5504
|
similarity: m.similarity
|
|
5505
5505
|
}));
|
|
5506
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
5506
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-I6TOH7CB.mjs");
|
|
5507
5507
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
5508
5508
|
if (decision.action === "create") return null;
|
|
5509
5509
|
if (decision.action === "supersede" && decision.content) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anuma/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -275,8 +275,8 @@
|
|
|
275
275
|
"js-yaml@>=4 <4.2.0": ">=4.2.0 <5.0.0",
|
|
276
276
|
"js-yaml@>=3 <3.15.0": ">=3.15.0 <4.0.0",
|
|
277
277
|
"markdown-it@<14.2.0": ">=14.2.0",
|
|
278
|
-
"brace-expansion@1": ">=1.1.
|
|
279
|
-
"brace-expansion@2": ">=2.1.
|
|
278
|
+
"brace-expansion@1": ">=1.1.18 <2.0.0",
|
|
279
|
+
"brace-expansion@2": ">=2.1.4 <3.0.0"
|
|
280
280
|
},
|
|
281
281
|
"patchedDependencies": {
|
|
282
282
|
"tesseract.js@6.0.1": "patches/tesseract.js@6.0.1.patch",
|