@psiclawops/hypermem 0.6.2 → 0.8.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/ARCHITECTURE.md +31 -39
- package/README.md +20 -14
- package/bin/hypermem-status.mjs +1 -1
- package/dist/background-indexer.d.ts +14 -3
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +135 -27
- package/dist/budget-policy.d.ts +22 -0
- package/dist/budget-policy.d.ts.map +1 -0
- package/dist/budget-policy.js +27 -0
- package/dist/cache.d.ts +11 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/compositor-utils.d.ts +31 -0
- package/dist/compositor-utils.d.ts.map +1 -0
- package/dist/compositor-utils.js +47 -0
- package/dist/compositor.d.ts +163 -1
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +862 -130
- package/dist/content-hash.d.ts +43 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +75 -0
- package/dist/context-store.d.ts +54 -0
- package/dist/context-store.d.ts.map +1 -1
- package/dist/context-store.js +102 -0
- package/dist/contradiction-audit-store.d.ts +54 -0
- package/dist/contradiction-audit-store.d.ts.map +1 -0
- package/dist/contradiction-audit-store.js +88 -0
- package/dist/contradiction-detector.d.ts +78 -0
- package/dist/contradiction-detector.d.ts.map +1 -0
- package/dist/contradiction-detector.js +362 -0
- package/dist/contradiction-resolution-policy.d.ts +21 -0
- package/dist/contradiction-resolution-policy.d.ts.map +1 -0
- package/dist/contradiction-resolution-policy.js +17 -0
- package/dist/cross-agent.d.ts +1 -1
- package/dist/cross-agent.js +17 -17
- package/dist/degradation.d.ts +102 -0
- package/dist/degradation.d.ts.map +1 -0
- package/dist/degradation.js +141 -0
- package/dist/dreaming-promoter.d.ts +39 -1
- package/dist/dreaming-promoter.d.ts.map +1 -1
- package/dist/dreaming-promoter.js +70 -4
- package/dist/expertise-store.d.ts +129 -0
- package/dist/expertise-store.d.ts.map +1 -0
- package/dist/expertise-store.js +342 -0
- package/dist/fact-store.d.ts +15 -0
- package/dist/fact-store.d.ts.map +1 -1
- package/dist/fact-store.js +52 -5
- package/dist/index.d.ts +74 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +407 -29
- package/dist/knowledge-lint.d.ts +2 -0
- package/dist/knowledge-lint.d.ts.map +1 -1
- package/dist/knowledge-lint.js +40 -1
- package/dist/library-schema.d.ts +7 -2
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +307 -2
- package/dist/message-store.d.ts +64 -1
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +137 -1
- package/dist/proactive-pass.d.ts +2 -2
- package/dist/proactive-pass.d.ts.map +1 -1
- package/dist/proactive-pass.js +66 -12
- package/dist/replay-recovery.d.ts +29 -0
- package/dist/replay-recovery.d.ts.map +1 -0
- package/dist/replay-recovery.js +82 -0
- package/dist/reranker.d.ts +95 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +308 -0
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +46 -1
- package/dist/seed.d.ts +1 -1
- package/dist/seed.js +1 -1
- package/dist/session-flusher.d.ts +4 -4
- package/dist/session-flusher.d.ts.map +1 -1
- package/dist/session-flusher.js +3 -3
- package/dist/spawn-context.d.ts +1 -1
- package/dist/spawn-context.js +1 -1
- package/dist/temporal-store.d.ts +1 -0
- package/dist/temporal-store.d.ts.map +1 -1
- package/dist/tool-artifact-store.d.ts +98 -0
- package/dist/tool-artifact-store.d.ts.map +1 -0
- package/dist/tool-artifact-store.js +244 -0
- package/dist/topic-detector.js +2 -2
- package/dist/topic-store.d.ts +6 -0
- package/dist/topic-store.d.ts.map +1 -1
- package/dist/topic-store.js +39 -0
- package/dist/topic-synthesizer.js +1 -1
- package/dist/trigger-registry.d.ts +1 -1
- package/dist/trigger-registry.js +4 -4
- package/dist/types.d.ts +239 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +2 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +3 -0
- package/dist/version.d.ts +10 -10
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +10 -10
- package/package.json +6 -4
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contradiction Detector — heuristic-based contradiction detection for the fact store.
|
|
3
|
+
*
|
|
4
|
+
* Detects when a newly ingested fact contradicts existing active facts using
|
|
5
|
+
* vector similarity (when available) and FTS candidate retrieval, scored by
|
|
6
|
+
* pattern-based heuristics (negation, numeric conflict, state conflict, temporal).
|
|
7
|
+
*
|
|
8
|
+
* No LLM calls — v1 is purely heuristic. LLM-enhanced scoring is a future item.
|
|
9
|
+
*/
|
|
10
|
+
// ─── Internal Constants ──────────────────────────────────────────
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
minSimilarity: 0.6,
|
|
13
|
+
autoResolveThreshold: 0.85,
|
|
14
|
+
maxCandidates: 10,
|
|
15
|
+
autoResolve: true,
|
|
16
|
+
};
|
|
17
|
+
/** Words whose presence/absence flips meaning. */
|
|
18
|
+
const NEGATION_WORDS = new Set([
|
|
19
|
+
'not', 'no', 'never', 'none', 'neither', 'nor', 'cannot', "can't",
|
|
20
|
+
"don't", "doesn't", "didn't", "won't", "wouldn't", "shouldn't",
|
|
21
|
+
"isn't", "aren't", "wasn't", "weren't", "hasn't", "haven't", "hadn't",
|
|
22
|
+
]);
|
|
23
|
+
/** Antonym pairs where one in text A and the other in text B signals conflict. */
|
|
24
|
+
const STATE_ANTONYMS = [
|
|
25
|
+
['enabled', 'disabled'],
|
|
26
|
+
['active', 'inactive'],
|
|
27
|
+
['active', 'deprecated'],
|
|
28
|
+
['running', 'stopped'],
|
|
29
|
+
['running', 'crashed'],
|
|
30
|
+
['up', 'down'],
|
|
31
|
+
['true', 'false'],
|
|
32
|
+
['yes', 'no'],
|
|
33
|
+
['on', 'off'],
|
|
34
|
+
['open', 'closed'],
|
|
35
|
+
['allowed', 'denied'],
|
|
36
|
+
['allowed', 'blocked'],
|
|
37
|
+
['available', 'unavailable'],
|
|
38
|
+
['connected', 'disconnected'],
|
|
39
|
+
['online', 'offline'],
|
|
40
|
+
['present', 'absent'],
|
|
41
|
+
['healthy', 'unhealthy'],
|
|
42
|
+
['valid', 'invalid'],
|
|
43
|
+
['complete', 'incomplete'],
|
|
44
|
+
['success', 'failure'],
|
|
45
|
+
];
|
|
46
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
47
|
+
/** Normalize text for comparison: lowercase, collapse whitespace. */
|
|
48
|
+
function normalize(text) {
|
|
49
|
+
return text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
50
|
+
}
|
|
51
|
+
/** Tokenize into word-boundary tokens. */
|
|
52
|
+
function tokenize(text) {
|
|
53
|
+
return normalize(text).split(/[\s,;:()[\]{}]+/).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
/** Convert VectorStore distance (lower = closer) to a 0-1 similarity score. */
|
|
56
|
+
function distanceToSimilarity(distance) {
|
|
57
|
+
// sqlite-vec uses L2 distance by default. Convert to 0-1 similarity.
|
|
58
|
+
// distance=0 => similarity=1, distance grows => similarity decays toward 0.
|
|
59
|
+
return 1 / (1 + distance);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build a safe FTS query from content. Takes the first several meaningful words
|
|
63
|
+
* to avoid FTS syntax errors from special characters.
|
|
64
|
+
*/
|
|
65
|
+
function buildFtsQuery(content) {
|
|
66
|
+
const words = tokenize(content)
|
|
67
|
+
.filter(w => w.length > 2 && !/^\d+$/.test(w))
|
|
68
|
+
.slice(0, 6);
|
|
69
|
+
if (words.length === 0)
|
|
70
|
+
return '';
|
|
71
|
+
// OR-join for broad recall
|
|
72
|
+
return words.join(' OR ');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract numbers and their rough context (preceding word) from text.
|
|
76
|
+
* Returns pairs of [contextWord, number].
|
|
77
|
+
*/
|
|
78
|
+
function extractNumbers(text) {
|
|
79
|
+
const results = [];
|
|
80
|
+
const tokens = tokenize(text);
|
|
81
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
82
|
+
const num = parseFloat(tokens[i]);
|
|
83
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
84
|
+
const context = i > 0 ? tokens[i - 1] : '';
|
|
85
|
+
results.push({ context, value: num });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if one text contains a negation of the other.
|
|
92
|
+
* Returns true if one has a negation word in a position where the other doesn't
|
|
93
|
+
* (or vice versa), given high token overlap.
|
|
94
|
+
*/
|
|
95
|
+
function detectNegation(tokensA, tokensB) {
|
|
96
|
+
const negA = tokensA.filter(t => NEGATION_WORDS.has(t));
|
|
97
|
+
const negB = tokensB.filter(t => NEGATION_WORDS.has(t));
|
|
98
|
+
// One has negation words, the other doesn't (or different count)
|
|
99
|
+
if (negA.length !== negB.length) {
|
|
100
|
+
// Check that the non-negation content overlaps substantially
|
|
101
|
+
const contentA = new Set(tokensA.filter(t => !NEGATION_WORDS.has(t)));
|
|
102
|
+
const contentB = new Set(tokensB.filter(t => !NEGATION_WORDS.has(t)));
|
|
103
|
+
const intersection = [...contentA].filter(t => contentB.has(t));
|
|
104
|
+
const smaller = Math.min(contentA.size, contentB.size);
|
|
105
|
+
// Need at least 40% content overlap to consider it a negation of the same claim
|
|
106
|
+
return smaller > 0 && intersection.length / smaller >= 0.4;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check for antonym state pairs between two token sets.
|
|
112
|
+
* Returns the matching antonym pair if found, or null.
|
|
113
|
+
*/
|
|
114
|
+
function detectStateConflict(tokensA, tokensB) {
|
|
115
|
+
const setA = new Set(tokensA);
|
|
116
|
+
const setB = new Set(tokensB);
|
|
117
|
+
for (const [word1, word2] of STATE_ANTONYMS) {
|
|
118
|
+
if ((setA.has(word1) && setB.has(word2)) || (setA.has(word2) && setB.has(word1))) {
|
|
119
|
+
// Verify there's enough shared context (not just random words)
|
|
120
|
+
const contentA = new Set(tokensA.filter(t => t !== word1 && t !== word2));
|
|
121
|
+
const contentB = new Set(tokensB.filter(t => t !== word1 && t !== word2));
|
|
122
|
+
const intersection = [...contentA].filter(t => contentB.has(t));
|
|
123
|
+
const smaller = Math.min(contentA.size, contentB.size);
|
|
124
|
+
if (smaller > 0 && intersection.length / smaller >= 0.3) {
|
|
125
|
+
return setA.has(word1) ? [word1, word2] : [word2, word1];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check for numeric conflicts: same contextual subject, different numbers.
|
|
133
|
+
*/
|
|
134
|
+
function detectNumericConflict(textA, textB) {
|
|
135
|
+
const numsA = extractNumbers(textA);
|
|
136
|
+
const numsB = extractNumbers(textB);
|
|
137
|
+
for (const a of numsA) {
|
|
138
|
+
for (const b of numsB) {
|
|
139
|
+
// Same context word, different value
|
|
140
|
+
if (a.context && a.context === b.context && a.value !== b.value) {
|
|
141
|
+
return { contextWord: a.context, valueA: a.value, valueB: b.value };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
// ─── ContradictionDetector ───────────────────────────────────────
|
|
148
|
+
export class ContradictionDetector {
|
|
149
|
+
factStore;
|
|
150
|
+
vectorStore;
|
|
151
|
+
config;
|
|
152
|
+
constructor(factStore, vectorStore, config) {
|
|
153
|
+
this.factStore = factStore;
|
|
154
|
+
this.vectorStore = vectorStore;
|
|
155
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* On fact ingest, check if the new fact contradicts existing active facts.
|
|
159
|
+
* Uses vector similarity (when available) + FTS to find candidates, then
|
|
160
|
+
* scores each candidate with heuristic contradiction checks.
|
|
161
|
+
*/
|
|
162
|
+
async detectOnIngest(agentId, newFact) {
|
|
163
|
+
const candidates = await this.findCandidates(agentId, newFact);
|
|
164
|
+
const contradictions = [];
|
|
165
|
+
for (const candidate of candidates) {
|
|
166
|
+
const scored = this.scoreContradiction(newFact.content, candidate);
|
|
167
|
+
if (scored) {
|
|
168
|
+
contradictions.push(scored);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Sort by contradiction score descending
|
|
172
|
+
contradictions.sort((a, b) => b.contradictionScore - a.contradictionScore);
|
|
173
|
+
const result = {
|
|
174
|
+
contradictions,
|
|
175
|
+
autoResolved: false,
|
|
176
|
+
resolvedCount: 0,
|
|
177
|
+
};
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Resolve a detected contradiction between an existing fact and a new fact.
|
|
182
|
+
*/
|
|
183
|
+
resolveContradiction(oldFactId, newFactId, resolution) {
|
|
184
|
+
switch (resolution) {
|
|
185
|
+
case 'supersede':
|
|
186
|
+
this.factStore.markSuperseded(oldFactId, newFactId);
|
|
187
|
+
break;
|
|
188
|
+
case 'keep-both':
|
|
189
|
+
// No-op: both facts remain active
|
|
190
|
+
break;
|
|
191
|
+
case 'reject-new':
|
|
192
|
+
this.factStore.invalidateFact(newFactId);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Auto-resolve high-confidence contradictions: newer supersedes older.
|
|
198
|
+
* Only resolves candidates above the autoResolveThreshold.
|
|
199
|
+
*
|
|
200
|
+
* @param agentId - The agent whose facts are being resolved (for audit trail)
|
|
201
|
+
* @param candidates - Scored contradiction candidates from detectOnIngest
|
|
202
|
+
* @returns Count of auto-resolved contradictions
|
|
203
|
+
*/
|
|
204
|
+
async autoResolve(_agentId, candidates) {
|
|
205
|
+
if (!this.config.autoResolve)
|
|
206
|
+
return 0;
|
|
207
|
+
let resolved = 0;
|
|
208
|
+
for (const candidate of candidates) {
|
|
209
|
+
if (candidate.contradictionScore >= this.config.autoResolveThreshold) {
|
|
210
|
+
// The existing fact is older; the new fact (which triggered detection)
|
|
211
|
+
// is assumed to be the more recent truth. We mark the existing as superseded.
|
|
212
|
+
// Note: the caller must supply the newFactId when wiring this into ingest.
|
|
213
|
+
// For now, we invalidate the old fact since we don't have the new fact's id here.
|
|
214
|
+
this.factStore.invalidateFact(candidate.existingFactId);
|
|
215
|
+
resolved++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return resolved;
|
|
219
|
+
}
|
|
220
|
+
// ─── Private Methods ────────────────────────────────────────────
|
|
221
|
+
/**
|
|
222
|
+
* Find candidate facts that might contradict the new fact.
|
|
223
|
+
* Uses vector search (if available) and FTS, deduplicates, and returns
|
|
224
|
+
* up to maxCandidates results above minSimilarity.
|
|
225
|
+
*/
|
|
226
|
+
async findCandidates(agentId, newFact) {
|
|
227
|
+
const seen = new Set();
|
|
228
|
+
const candidates = [];
|
|
229
|
+
const { maxCandidates, minSimilarity } = this.config;
|
|
230
|
+
// Path 1: Vector similarity search (if VectorStore is available)
|
|
231
|
+
if (this.vectorStore) {
|
|
232
|
+
try {
|
|
233
|
+
const vectorResults = await this.vectorStore.search(newFact.content, {
|
|
234
|
+
tables: ['facts'],
|
|
235
|
+
limit: maxCandidates * 2, // over-fetch to allow for filtering
|
|
236
|
+
});
|
|
237
|
+
for (const vr of vectorResults) {
|
|
238
|
+
const similarity = distanceToSimilarity(vr.distance);
|
|
239
|
+
if (similarity < minSimilarity)
|
|
240
|
+
continue;
|
|
241
|
+
if (seen.has(vr.sourceId))
|
|
242
|
+
continue;
|
|
243
|
+
seen.add(vr.sourceId);
|
|
244
|
+
// Retrieve the full fact row for metadata checks
|
|
245
|
+
const facts = this.factStore.searchFacts(vr.content.slice(0, 30), {
|
|
246
|
+
agentId,
|
|
247
|
+
limit: 1,
|
|
248
|
+
});
|
|
249
|
+
const fact = facts.find(f => f.id === vr.sourceId);
|
|
250
|
+
if (fact && !fact.supersededBy && !fact.invalidAt) {
|
|
251
|
+
candidates.push({ fact, similarity });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Vector search failed (embedding model unavailable, etc.)
|
|
257
|
+
// Fall through to FTS-only path
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Path 2: FTS search (always available, fills gaps)
|
|
261
|
+
if (candidates.length < maxCandidates) {
|
|
262
|
+
const ftsQuery = buildFtsQuery(newFact.content);
|
|
263
|
+
if (ftsQuery) {
|
|
264
|
+
try {
|
|
265
|
+
const ftsResults = this.factStore.searchFacts(ftsQuery, {
|
|
266
|
+
agentId,
|
|
267
|
+
domain: newFact.domain,
|
|
268
|
+
limit: maxCandidates * 2,
|
|
269
|
+
});
|
|
270
|
+
for (const fact of ftsResults) {
|
|
271
|
+
if (seen.has(fact.id))
|
|
272
|
+
continue;
|
|
273
|
+
if (fact.supersededBy || fact.invalidAt)
|
|
274
|
+
continue;
|
|
275
|
+
seen.add(fact.id);
|
|
276
|
+
// Compute a rough token-overlap similarity for FTS results
|
|
277
|
+
const similarity = this.tokenOverlapSimilarity(newFact.content, fact.content);
|
|
278
|
+
if (similarity >= minSimilarity) {
|
|
279
|
+
candidates.push({ fact, similarity });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// FTS query failed (malformed query, etc.)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Sort by similarity descending, trim to maxCandidates
|
|
289
|
+
candidates.sort((a, b) => b.similarity - a.similarity);
|
|
290
|
+
return candidates.slice(0, maxCandidates);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Score a candidate fact against the new fact content for contradiction.
|
|
294
|
+
* Returns a ContradictionCandidate if any heuristic fires, null otherwise.
|
|
295
|
+
*/
|
|
296
|
+
scoreContradiction(newContent, candidate) {
|
|
297
|
+
const existingContent = candidate.fact.content;
|
|
298
|
+
const tokensNew = tokenize(newContent);
|
|
299
|
+
const tokensExisting = tokenize(existingContent);
|
|
300
|
+
let bestScore = 0;
|
|
301
|
+
let bestReason = '';
|
|
302
|
+
// Heuristic 1: Negation detection (score: 0.9)
|
|
303
|
+
if (detectNegation(tokensNew, tokensExisting)) {
|
|
304
|
+
bestScore = 0.9;
|
|
305
|
+
bestReason = 'Negation detected: one fact negates the other';
|
|
306
|
+
}
|
|
307
|
+
// Heuristic 2: State conflict via antonym pairs (score: 0.85)
|
|
308
|
+
const stateConflict = detectStateConflict(tokensNew, tokensExisting);
|
|
309
|
+
if (stateConflict && stateConflict.length === 2) {
|
|
310
|
+
const score = 0.85;
|
|
311
|
+
if (score > bestScore) {
|
|
312
|
+
bestScore = score;
|
|
313
|
+
bestReason = `State conflict: "${stateConflict[0]}" vs "${stateConflict[1]}"`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Heuristic 3: Numeric conflict (score: 0.8)
|
|
317
|
+
const numConflict = detectNumericConflict(newContent, existingContent);
|
|
318
|
+
if (numConflict) {
|
|
319
|
+
const score = 0.8;
|
|
320
|
+
if (score > bestScore) {
|
|
321
|
+
bestScore = score;
|
|
322
|
+
bestReason = `Numeric conflict on "${numConflict.contextWord}": ${numConflict.valueA} vs ${numConflict.valueB}`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Heuristic 4: Temporal supersede — high similarity, different conclusion (score: 0.7)
|
|
326
|
+
// If similarity is very high (>0.8) but content isn't identical, it's likely a revised version
|
|
327
|
+
if (bestScore === 0 && candidate.similarity > 0.8) {
|
|
328
|
+
const contentDiffers = normalize(newContent) !== normalize(existingContent);
|
|
329
|
+
if (contentDiffers) {
|
|
330
|
+
bestScore = 0.7;
|
|
331
|
+
bestReason = 'Temporal supersede: high similarity with different content (likely revised)';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (bestScore === 0)
|
|
335
|
+
return null;
|
|
336
|
+
return {
|
|
337
|
+
existingFactId: candidate.fact.id,
|
|
338
|
+
existingContent,
|
|
339
|
+
similarityScore: candidate.similarity,
|
|
340
|
+
contradictionScore: bestScore,
|
|
341
|
+
reason: bestReason,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Compute Jaccard-like token overlap between two texts.
|
|
346
|
+
* Returns 0-1 where 1 means identical token sets.
|
|
347
|
+
*/
|
|
348
|
+
tokenOverlapSimilarity(textA, textB) {
|
|
349
|
+
const setA = new Set(tokenize(textA));
|
|
350
|
+
const setB = new Set(tokenize(textB));
|
|
351
|
+
if (setA.size === 0 || setB.size === 0)
|
|
352
|
+
return 0;
|
|
353
|
+
let intersection = 0;
|
|
354
|
+
for (const token of setA) {
|
|
355
|
+
if (setB.has(token))
|
|
356
|
+
intersection++;
|
|
357
|
+
}
|
|
358
|
+
const union = new Set([...setA, ...setB]).size;
|
|
359
|
+
return union > 0 ? intersection / union : 0;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=contradiction-detector.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Contradiction Resolution Policy
|
|
3
|
+
*
|
|
4
|
+
* Defines thresholds and flags that control how the background indexer acts
|
|
5
|
+
* on detected contradictions during fact ingest.
|
|
6
|
+
*
|
|
7
|
+
* Tiers (by contradictionScore):
|
|
8
|
+
* >= autoSupersedeThreshold → mark old fact superseded, remove stale vector
|
|
9
|
+
* >= autoInvalidateThreshold → mark old fact invalid (no supersede linkage)
|
|
10
|
+
* below autoInvalidateThreshold → log-only (pending review)
|
|
11
|
+
*/
|
|
12
|
+
export interface ContradictionResolutionPolicy {
|
|
13
|
+
/** Score threshold at or above which the old fact is auto-superseded by the new one. Default: 0.80 */
|
|
14
|
+
autoSupersedeThreshold: number;
|
|
15
|
+
/** Score threshold at or above which the old fact is auto-invalidated. Default: 0.60 */
|
|
16
|
+
autoInvalidateThreshold: number;
|
|
17
|
+
/** When true, always write an audit row regardless of tier. Default: true */
|
|
18
|
+
alwaysAudit: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const DEFAULT_CONTRADICTION_POLICY: ContradictionResolutionPolicy;
|
|
21
|
+
//# sourceMappingURL=contradiction-resolution-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contradiction-resolution-policy.d.ts","sourceRoot":"","sources":["../src/contradiction-resolution-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,sGAAsG;IACtG,sBAAsB,EAAE,MAAM,CAAC;IAC/B,wFAAwF;IACxF,uBAAuB,EAAE,MAAM,CAAC;IAChC,6EAA6E;IAC7E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,4BAA4B,EAAE,6BAI1C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Contradiction Resolution Policy
|
|
3
|
+
*
|
|
4
|
+
* Defines thresholds and flags that control how the background indexer acts
|
|
5
|
+
* on detected contradictions during fact ingest.
|
|
6
|
+
*
|
|
7
|
+
* Tiers (by contradictionScore):
|
|
8
|
+
* >= autoSupersedeThreshold → mark old fact superseded, remove stale vector
|
|
9
|
+
* >= autoInvalidateThreshold → mark old fact invalid (no supersede linkage)
|
|
10
|
+
* below autoInvalidateThreshold → log-only (pending review)
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_CONTRADICTION_POLICY = {
|
|
13
|
+
autoSupersedeThreshold: 0.80,
|
|
14
|
+
autoInvalidateThreshold: 0.60,
|
|
15
|
+
alwaysAudit: true,
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=contradiction-resolution-policy.js.map
|
package/dist/cross-agent.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface OrgRegistry {
|
|
|
27
27
|
* Default fleet org structure.
|
|
28
28
|
*
|
|
29
29
|
* ── EXAMPLE DATA ──────────────────────────────────────────────────────────
|
|
30
|
-
* The agent names below (
|
|
30
|
+
* The agent names below (alice, bob, director1, etc.) are PLACEHOLDERS.
|
|
31
31
|
* Replace them with your own agent IDs to match your fleet configuration.
|
|
32
32
|
*
|
|
33
33
|
* Single-agent installs: you don't need to edit this. Your agent ID is
|
package/dist/cross-agent.js
CHANGED
|
@@ -21,7 +21,7 @@ import { FleetStore } from './fleet-store.js';
|
|
|
21
21
|
* Default fleet org structure.
|
|
22
22
|
*
|
|
23
23
|
* ── EXAMPLE DATA ──────────────────────────────────────────────────────────
|
|
24
|
-
* The agent names below (
|
|
24
|
+
* The agent names below (alice, bob, director1, etc.) are PLACEHOLDERS.
|
|
25
25
|
* Replace them with your own agent IDs to match your fleet configuration.
|
|
26
26
|
*
|
|
27
27
|
* Single-agent installs: you don't need to edit this. Your agent ID is
|
|
@@ -34,27 +34,27 @@ import { FleetStore } from './fleet-store.js';
|
|
|
34
34
|
*/
|
|
35
35
|
export function defaultOrgRegistry() {
|
|
36
36
|
const agents = {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
alice: { agentId: 'alice', tier: 'council' },
|
|
38
|
+
bob: { agentId: 'bob', tier: 'council' },
|
|
39
39
|
agent4: { agentId: 'agent4', tier: 'council' },
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
director1: { agentId: 'director1', tier: 'director', org: '
|
|
44
|
-
director2: { agentId: 'director2', tier: 'director', org: '
|
|
45
|
-
director3: { agentId: 'director3', tier: 'director', org: '
|
|
46
|
-
director4: { agentId: 'director4', tier: 'director', org: '
|
|
47
|
-
director5: { agentId: 'director5', tier: 'director', org: '
|
|
48
|
-
director6: { agentId: 'director6', tier: 'director', org: '
|
|
49
|
-
director7: { agentId: 'director7', tier: 'director', org: '
|
|
50
|
-
director8: { agentId: 'director8', tier: 'director', org: '
|
|
40
|
+
dave: { agentId: 'dave', tier: 'council' },
|
|
41
|
+
carol: { agentId: 'carol', tier: 'council' },
|
|
42
|
+
oscar: { agentId: 'oscar', tier: 'council' },
|
|
43
|
+
director1: { agentId: 'director1', tier: 'director', org: 'alice-org', councilLead: 'alice' },
|
|
44
|
+
director2: { agentId: 'director2', tier: 'director', org: 'alice-org', councilLead: 'alice' },
|
|
45
|
+
director3: { agentId: 'director3', tier: 'director', org: 'alice-org', councilLead: 'alice' },
|
|
46
|
+
director4: { agentId: 'director4', tier: 'director', org: 'bob-org', councilLead: 'bob' },
|
|
47
|
+
director5: { agentId: 'director5', tier: 'director', org: 'bob-org', councilLead: 'bob' },
|
|
48
|
+
director6: { agentId: 'director6', tier: 'director', org: 'bob-org', councilLead: 'bob' },
|
|
49
|
+
director7: { agentId: 'director7', tier: 'director', org: 'dave-org', councilLead: 'dave' },
|
|
50
|
+
director8: { agentId: 'director8', tier: 'director', org: 'dave-org', councilLead: 'dave' },
|
|
51
51
|
specialist1: { agentId: 'specialist1', tier: 'specialist' },
|
|
52
52
|
specialist2: { agentId: 'specialist2', tier: 'specialist' },
|
|
53
53
|
};
|
|
54
54
|
const orgs = {
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
55
|
+
'alice-org': ['alice', 'director1', 'director2', 'director3'],
|
|
56
|
+
'bob-org': ['bob', 'director4', 'director5', 'director6'],
|
|
57
|
+
'dave-org': ['dave', 'director7', 'director8'],
|
|
58
58
|
};
|
|
59
59
|
return { orgs, agents };
|
|
60
60
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HyperMem Canonical Degradation Contracts, Phase C0.2
|
|
3
|
+
*
|
|
4
|
+
* Defines the typed surfaces, reason enum, and format builders for degraded
|
|
5
|
+
* prompt-visible outputs. These shapes stay in volatile context and should not
|
|
6
|
+
* cross the stable-prefix boundary.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Closed set of reasons for any degradation decision.
|
|
10
|
+
* Use these values in telemetry and tests instead of ad-hoc strings.
|
|
11
|
+
*/
|
|
12
|
+
export type DegradationReason = 'gradient_t2_prose' | 'gradient_t3_stub' | 'eviction_oversize' | 'eviction_turn0_trim' | 'wave_guard_pressure_high' | 'wave_guard_pressure_elevated' | 'budget_cluster_drop' | 'artifact_oversize' | 'artifact_fetch_hint' | 'replay_cold_redis' | 'replay_stabilizing' | 'replay_exited' | 'pressure_mismatch' | 'unknown';
|
|
13
|
+
/** All valid DegradationReason values as a readonly array. */
|
|
14
|
+
export declare const DEGRADATION_REASONS: readonly ["gradient_t2_prose", "gradient_t3_stub", "eviction_oversize", "eviction_turn0_trim", "wave_guard_pressure_high", "wave_guard_pressure_elevated", "budget_cluster_drop", "artifact_oversize", "artifact_fetch_hint", "replay_cold_redis", "replay_stabilizing", "replay_exited", "pressure_mismatch", "unknown"];
|
|
15
|
+
/** Field-length caps for canonical degraded strings. */
|
|
16
|
+
export declare const DEGRADATION_LIMITS: {
|
|
17
|
+
readonly toolName: 64;
|
|
18
|
+
readonly toolId: 64;
|
|
19
|
+
readonly reason: 48;
|
|
20
|
+
readonly toolSummary: 120;
|
|
21
|
+
readonly toolArtifactId: 64;
|
|
22
|
+
readonly artifactId: 64;
|
|
23
|
+
readonly artifactPath: 160;
|
|
24
|
+
readonly artifactFetchHint: 80;
|
|
25
|
+
readonly replayState: 32;
|
|
26
|
+
readonly replaySummary: 120;
|
|
27
|
+
};
|
|
28
|
+
export declare function isDegradationReason(value: string): value is DegradationReason;
|
|
29
|
+
/**
|
|
30
|
+
* Canonical shape for a degraded tool call/result pair.
|
|
31
|
+
*
|
|
32
|
+
* Prompt-visible format:
|
|
33
|
+
* [tool:<name> id=<id> status=ejected reason=<reason> summary=<stub>]
|
|
34
|
+
*
|
|
35
|
+
* Optional artifact pointer (Phase 1 of tool_artifacts):
|
|
36
|
+
* [tool:<name> id=<id> status=ejected reason=<reason> artifact=<artifactId> summary=<stub>]
|
|
37
|
+
*
|
|
38
|
+
* The `artifact=` field is backwards-compatible and optional. When present, it
|
|
39
|
+
* lets the compositor rehydrate the full tool result payload from the
|
|
40
|
+
* tool_artifacts table without needing to rewrite the transcript.
|
|
41
|
+
*/
|
|
42
|
+
export interface ToolChainStub {
|
|
43
|
+
name: string;
|
|
44
|
+
id: string;
|
|
45
|
+
status: 'ejected';
|
|
46
|
+
reason: DegradationReason;
|
|
47
|
+
summary: string;
|
|
48
|
+
/** Optional durable pointer into the tool_artifacts table. */
|
|
49
|
+
artifactId?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function formatToolChainStub(stub: ToolChainStub): string;
|
|
52
|
+
export declare function parseToolChainStub(text: string): ToolChainStub | null;
|
|
53
|
+
export declare function isToolChainStub(text: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Canonical shape for a degraded oversized artifact replaced by a pointer.
|
|
56
|
+
*
|
|
57
|
+
* Prompt-visible format:
|
|
58
|
+
* [artifact:<id> path=<path> size=<tokens> status=degraded fetch=<hint>]
|
|
59
|
+
*/
|
|
60
|
+
export interface ArtifactRef {
|
|
61
|
+
id: string;
|
|
62
|
+
path: string;
|
|
63
|
+
sizeTokens: number;
|
|
64
|
+
status: 'degraded';
|
|
65
|
+
reason: DegradationReason;
|
|
66
|
+
fetchHint: string;
|
|
67
|
+
}
|
|
68
|
+
export declare function formatArtifactRef(ref: ArtifactRef): string;
|
|
69
|
+
export declare function parseArtifactRef(text: string): ArtifactRef | null;
|
|
70
|
+
export declare function isArtifactRef(text: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* State of the replay recovery window.
|
|
73
|
+
*/
|
|
74
|
+
export type ReplayState = 'entering' | 'stabilizing' | 'exited';
|
|
75
|
+
export declare function isReplayState(value: string): value is ReplayState;
|
|
76
|
+
/**
|
|
77
|
+
* Canonical shape for a replay recovery mode marker.
|
|
78
|
+
*
|
|
79
|
+
* Prompt-visible format:
|
|
80
|
+
* [replay state=<state> status=bounded reason=<reason> summary=<stub>]
|
|
81
|
+
*/
|
|
82
|
+
export interface ReplayMarker {
|
|
83
|
+
state: ReplayState;
|
|
84
|
+
status: 'bounded';
|
|
85
|
+
reason: DegradationReason;
|
|
86
|
+
summary: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function formatReplayMarker(marker: ReplayMarker): string;
|
|
89
|
+
export declare function parseReplayMarker(text: string): ReplayMarker | null;
|
|
90
|
+
export declare function isReplayMarker(text: string): boolean;
|
|
91
|
+
export declare function isDegradedContent(text: string): boolean;
|
|
92
|
+
export interface DegradationEvent {
|
|
93
|
+
event: 'degradation';
|
|
94
|
+
ts: string;
|
|
95
|
+
agentId: string;
|
|
96
|
+
sessionKey: string;
|
|
97
|
+
degradationClass: 'tool_chain' | 'artifact' | 'replay';
|
|
98
|
+
reason: DegradationReason;
|
|
99
|
+
tokensSaved: number;
|
|
100
|
+
emittedText: string;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=degradation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degradation.d.ts","sourceRoot":"","sources":["../src/degradation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB,mBAAmB,GACnB,kBAAkB,GAClB,mBAAmB,GACnB,qBAAqB,GACrB,0BAA0B,GAC1B,8BAA8B,GAC9B,qBAAqB,GACrB,mBAAmB,GACnB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,SAAS,CAAC;AAEd,8DAA8D;AAC9D,eAAO,MAAM,mBAAmB,2TAeiB,CAAC;AAElD,wDAAwD;AACxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;CAWrB,CAAC;AAiBX,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAID;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAS/D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAQrE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAID;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAM1D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAajE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAID;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ,CAAC;AAEhE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,WAAW,CAEjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAK/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAWnE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAID,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;IACvD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB"}
|