audrey 0.8.0 → 0.9.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/README.md +76 -8
- package/mcp-server/config.js +1 -1
- package/mcp-server/index.js +14 -4
- package/package.json +1 -1
- package/src/affect.js +64 -0
- package/src/audrey.js +33 -5
- package/src/db.js +1 -0
- package/src/encode.js +10 -3
- package/src/index.js +1 -0
- package/src/recall.js +14 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Audrey
|
|
2
2
|
|
|
3
|
-
Biological memory architecture for AI agents.
|
|
3
|
+
Biological memory architecture for AI agents. Memory that decays, consolidates, feels, and learns — not just a database.
|
|
4
4
|
|
|
5
5
|
## Why Audrey Exists
|
|
6
6
|
|
|
@@ -19,7 +19,7 @@ Audrey fixes all of this by modeling memory the way the brain does:
|
|
|
19
19
|
| Neocortex | Semantic Memory | Consolidated principles and patterns |
|
|
20
20
|
| Sleep Replay | Consolidation Engine | Extracts patterns from episodes, promotes to principles |
|
|
21
21
|
| Prefrontal Cortex | Validation Engine | Truth-checking, contradiction detection |
|
|
22
|
-
| Amygdala |
|
|
22
|
+
| Amygdala | Affect System | Emotional encoding, arousal-salience coupling, mood-congruent recall |
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
@@ -67,22 +67,26 @@ const brain = new Audrey({
|
|
|
67
67
|
embedding: { provider: 'mock', dimensions: 8 }, // or 'openai' for production
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
// 2. Encode observations
|
|
70
|
+
// 2. Encode observations — with optional emotional context
|
|
71
71
|
await brain.encode({
|
|
72
72
|
content: 'Stripe API returns 429 above 100 req/s',
|
|
73
73
|
source: 'direct-observation',
|
|
74
74
|
tags: ['stripe', 'rate-limit'],
|
|
75
|
+
affect: { valence: -0.4, arousal: 0.7, label: 'frustration' },
|
|
75
76
|
});
|
|
76
77
|
|
|
77
|
-
// 3. Recall what you know
|
|
78
|
-
const memories = await brain.recall('stripe rate limits', {
|
|
79
|
-
|
|
78
|
+
// 3. Recall what you know — mood-congruent retrieval
|
|
79
|
+
const memories = await brain.recall('stripe rate limits', {
|
|
80
|
+
limit: 5,
|
|
81
|
+
mood: { valence: -0.3 }, // frustrated right now? memories encoded in frustration surface first
|
|
82
|
+
});
|
|
80
83
|
|
|
81
84
|
// 4. Filtered recall — by tag, source, or date range
|
|
82
85
|
const recent = await brain.recall('stripe', {
|
|
83
86
|
tags: ['rate-limit'],
|
|
84
87
|
sources: ['direct-observation'],
|
|
85
88
|
after: '2026-02-01T00:00:00Z',
|
|
89
|
+
context: { task: 'debugging', domain: 'payments' }, // context-dependent retrieval
|
|
86
90
|
});
|
|
87
91
|
|
|
88
92
|
// 5. Consolidate episodes into principles (the "sleep" cycle)
|
|
@@ -127,6 +131,31 @@ const brain = new Audrey({
|
|
|
127
131
|
minEpisodes: 3, // Minimum cluster size for principle extraction
|
|
128
132
|
},
|
|
129
133
|
|
|
134
|
+
// Context-dependent retrieval (v0.8.0)
|
|
135
|
+
context: {
|
|
136
|
+
enabled: true, // Enable encoding-specificity principle
|
|
137
|
+
weight: 0.3, // Max 30% confidence boost on full context match
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Emotional memory (v0.9.0)
|
|
141
|
+
affect: {
|
|
142
|
+
enabled: true, // Enable affect system
|
|
143
|
+
weight: 0.2, // Max 20% mood-congruence boost
|
|
144
|
+
arousalWeight: 0.3, // Yerkes-Dodson arousal-salience coupling
|
|
145
|
+
resonance: { // Detect emotional echoes across experiences
|
|
146
|
+
enabled: true,
|
|
147
|
+
k: 5, // How many past episodes to check
|
|
148
|
+
threshold: 0.5, // Semantic similarity threshold
|
|
149
|
+
affectThreshold: 0.6, // Emotional similarity threshold
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Interference-based forgetting (v0.7.0)
|
|
154
|
+
interference: {
|
|
155
|
+
enabled: true, // New episodes suppress similar existing memories
|
|
156
|
+
weight: 0.15, // Suppression strength
|
|
157
|
+
},
|
|
158
|
+
|
|
130
159
|
// Decay settings
|
|
131
160
|
decay: {
|
|
132
161
|
dormantThreshold: 0.1, // Below this confidence = dormant
|
|
@@ -284,6 +313,12 @@ const id = await brain.encode({
|
|
|
284
313
|
},
|
|
285
314
|
tags: ['stripe', 'production'], // Optional. Array of strings.
|
|
286
315
|
supersedes: 'previous-id', // Optional. ID of episode this corrects.
|
|
316
|
+
context: { task: 'debugging' }, // Optional. Situational context for retrieval.
|
|
317
|
+
affect: { // Optional. Emotional context.
|
|
318
|
+
valence: -0.5, // -1 (negative) to 1 (positive)
|
|
319
|
+
arousal: 0.7, // 0 (calm) to 1 (activated)
|
|
320
|
+
label: 'frustration', // Human-readable emotion label
|
|
321
|
+
},
|
|
287
322
|
});
|
|
288
323
|
```
|
|
289
324
|
|
|
@@ -316,6 +351,8 @@ const memories = await brain.recall('stripe rate limits', {
|
|
|
316
351
|
sources: ['direct-observation'], // Only episodic memories from these sources
|
|
317
352
|
after: '2026-02-01T00:00:00Z', // Only memories created after this date
|
|
318
353
|
before: '2026-03-01T00:00:00Z', // Only memories created before this date
|
|
354
|
+
context: { task: 'debugging' }, // Boost memories encoded in matching context
|
|
355
|
+
mood: { valence: -0.3, arousal: 0.5 }, // Mood-congruent retrieval
|
|
319
356
|
});
|
|
320
357
|
```
|
|
321
358
|
|
|
@@ -332,6 +369,8 @@ Each result:
|
|
|
332
369
|
score: 0.74, // similarity * confidence
|
|
333
370
|
source: 'consolidation',
|
|
334
371
|
state: 'active',
|
|
372
|
+
contextMatch: 0.8, // When retrieval context provided
|
|
373
|
+
moodCongruence: 0.7, // When mood provided
|
|
335
374
|
provenance: { // When includeProvenance: true
|
|
336
375
|
evidenceEpisodeIds: ['01XYZ...', '01DEF...'],
|
|
337
376
|
evidenceCount: 3,
|
|
@@ -467,6 +506,8 @@ brain.on('decay', ({ totalEvaluated, transitionedToDormant }) => { ... });
|
|
|
467
506
|
brain.on('rollback', ({ runId, rolledBackMemories }) => { ... });
|
|
468
507
|
brain.on('forget', ({ id, type, purged }) => { ... });
|
|
469
508
|
brain.on('purge', ({ episodes, semantics, procedures }) => { ... });
|
|
509
|
+
brain.on('interference', ({ newEpisodeId, suppressedId, similarity }) => { ... });
|
|
510
|
+
brain.on('resonance', ({ episodeId, resonances }) => { ... });
|
|
470
511
|
brain.on('migration', ({ episodes, semantics, procedures }) => { ... });
|
|
471
512
|
brain.on('error', (err) => { ... });
|
|
472
513
|
```
|
|
@@ -492,6 +533,9 @@ src/
|
|
|
492
533
|
decay.js Ebbinghaus forgetting curves.
|
|
493
534
|
embedding.js Pluggable providers (Mock, OpenAI). Batch embedding.
|
|
494
535
|
encode.js Immutable episodic memory creation + vec0 writes.
|
|
536
|
+
affect.js Emotional memory: arousal-salience coupling, mood-congruent recall, resonance.
|
|
537
|
+
context.js Context-dependent retrieval modifier (encoding specificity).
|
|
538
|
+
interference.js Competitive memory suppression (engram competition).
|
|
495
539
|
forget.js Soft-delete, hard-delete, query-based forget, bulk purge.
|
|
496
540
|
introspect.js Health dashboard queries.
|
|
497
541
|
llm.js Pluggable LLM providers (Mock, Anthropic, OpenAI).
|
|
@@ -531,7 +575,7 @@ All mutations use SQLite transactions. CHECK constraints enforce valid states an
|
|
|
531
575
|
## Running Tests
|
|
532
576
|
|
|
533
577
|
```bash
|
|
534
|
-
npm test #
|
|
578
|
+
npm test # 379 tests across 28 files
|
|
535
579
|
npm run test:watch
|
|
536
580
|
```
|
|
537
581
|
|
|
@@ -547,7 +591,29 @@ Demonstrates the full pipeline: encode 3 rate-limit observations, consolidate in
|
|
|
547
591
|
|
|
548
592
|
## Changelog
|
|
549
593
|
|
|
550
|
-
### v0.
|
|
594
|
+
### v0.9.0 — Emotional Memory (current)
|
|
595
|
+
|
|
596
|
+
- Valence-arousal affect model (Russell's circumplex) on every episode
|
|
597
|
+
- Arousal-salience coupling via Yerkes-Dodson inverted-U curve
|
|
598
|
+
- Mood-congruent recall — matching emotional state boosts retrieval confidence
|
|
599
|
+
- Emotional resonance detection — new experiences that echo past emotional patterns emit events
|
|
600
|
+
- MCP server: `memory_encode` accepts `affect`, `memory_recall` accepts `mood`
|
|
601
|
+
- 379 tests across 28 test files
|
|
602
|
+
|
|
603
|
+
### v0.8.0 — Context-Dependent Retrieval
|
|
604
|
+
|
|
605
|
+
- Encoding specificity principle: context stored with memory, matching context boosts recall
|
|
606
|
+
- MCP server: `memory_encode` and `memory_recall` accept `context`
|
|
607
|
+
- 340 tests across 27 test files
|
|
608
|
+
|
|
609
|
+
### v0.7.0 — Interference + Salience
|
|
610
|
+
|
|
611
|
+
- Interference-based forgetting: new memories competitively suppress similar existing ones
|
|
612
|
+
- Salience-weighted confidence: high-salience memories resist decay
|
|
613
|
+
- Spaced-repetition reconsolidation: retrieval intervals affect reinforcement strength
|
|
614
|
+
- 310 tests across 25 test files
|
|
615
|
+
|
|
616
|
+
### v0.6.0 — Filtered Recall + Forget
|
|
551
617
|
|
|
552
618
|
- Filtered recall: tag, source, and date-range filters on `recall()` and `recallStream()`
|
|
553
619
|
- `forget()` — soft-delete any memory by ID
|
|
@@ -608,6 +674,8 @@ Demonstrates the full pipeline: encode 3 rate-limit observations, consolidate in
|
|
|
608
674
|
|
|
609
675
|
**Why soft-delete by default?** Hard-deletes are irreversible. Soft-delete preserves data integrity and audit trails while excluding the memory from recall. Use `purge: true` or `brain.purge()` when you need permanent removal (GDPR, storage cleanup).
|
|
610
676
|
|
|
677
|
+
**Why emotional memory?** Every memory system stores facts. Biological memory stores facts with emotional context — and that context changes how memories are retrieved. Emotional arousal modulates encoding strength (amygdala-hippocampal interaction). Current mood biases which memories surface (Bower, 1981). This isn't a novelty feature — it's the foundation for AI that remembers like it cares.
|
|
678
|
+
|
|
611
679
|
## License
|
|
612
680
|
|
|
613
681
|
MIT
|
package/mcp-server/config.js
CHANGED
package/mcp-server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -164,10 +164,15 @@ async function main() {
|
|
|
164
164
|
tags: z.array(z.string()).optional().describe('Optional tags for categorization'),
|
|
165
165
|
salience: z.number().min(0).max(1).optional().describe('Importance weight 0-1'),
|
|
166
166
|
context: z.record(z.string()).optional().describe('Situational context as key-value pairs (e.g., {task: "debugging", domain: "payments"})'),
|
|
167
|
+
affect: z.object({
|
|
168
|
+
valence: z.number().min(-1).max(1).describe('Emotional valence: -1 (very negative) to 1 (very positive)'),
|
|
169
|
+
arousal: z.number().min(0).max(1).optional().describe('Emotional arousal: 0 (calm) to 1 (highly activated)'),
|
|
170
|
+
label: z.string().optional().describe('Human-readable emotion label (e.g., "curiosity", "frustration", "relief")'),
|
|
171
|
+
}).optional().describe('Emotional affect — how this memory feels'),
|
|
167
172
|
},
|
|
168
|
-
async ({ content, source, tags, salience, context }) => {
|
|
173
|
+
async ({ content, source, tags, salience, context, affect }) => {
|
|
169
174
|
try {
|
|
170
|
-
const id = await audrey.encode({ content, source, tags, salience, context });
|
|
175
|
+
const id = await audrey.encode({ content, source, tags, salience, context, affect });
|
|
171
176
|
return toolResult({ id, content, source });
|
|
172
177
|
} catch (err) {
|
|
173
178
|
return toolError(err);
|
|
@@ -187,8 +192,12 @@ async function main() {
|
|
|
187
192
|
after: z.string().optional().describe('Only return memories created after this ISO date'),
|
|
188
193
|
before: z.string().optional().describe('Only return memories created before this ISO date'),
|
|
189
194
|
context: z.record(z.string()).optional().describe('Retrieval context — memories encoded in matching context get boosted'),
|
|
195
|
+
mood: z.object({
|
|
196
|
+
valence: z.number().min(-1).max(1).describe('Current emotional valence: -1 (negative) to 1 (positive)'),
|
|
197
|
+
arousal: z.number().min(0).max(1).optional().describe('Current arousal: 0 (calm) to 1 (activated)'),
|
|
198
|
+
}).optional().describe('Current mood — boosts recall of memories encoded in similar emotional state'),
|
|
190
199
|
},
|
|
191
|
-
async ({ query, limit, types, min_confidence, tags, sources, after, before, context }) => {
|
|
200
|
+
async ({ query, limit, types, min_confidence, tags, sources, after, before, context, mood }) => {
|
|
192
201
|
try {
|
|
193
202
|
const results = await audrey.recall(query, {
|
|
194
203
|
limit: limit ?? 10,
|
|
@@ -199,6 +208,7 @@ async function main() {
|
|
|
199
208
|
after,
|
|
200
209
|
before,
|
|
201
210
|
context,
|
|
211
|
+
mood,
|
|
202
212
|
});
|
|
203
213
|
return toolResult(results);
|
|
204
214
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audrey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Biological memory architecture for AI agents — encode, consolidate, and recall memories with confidence decay, contradiction detection, and causal graphs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/affect.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function arousalSalienceBoost(arousal) {
|
|
2
|
+
if (arousal === undefined || arousal === null) return 0;
|
|
3
|
+
// Inverted-U (Yerkes-Dodson): peaks at 0.7, Gaussian sigma=0.3
|
|
4
|
+
return Math.exp(-Math.pow(arousal - 0.7, 2) / (2 * 0.3 * 0.3));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function affectSimilarity(a, b) {
|
|
8
|
+
if (!a || !b) return 0;
|
|
9
|
+
if (a.valence === undefined || b.valence === undefined) return 0;
|
|
10
|
+
const valenceDist = Math.abs(a.valence - b.valence);
|
|
11
|
+
const valenceSim = 1.0 - (valenceDist / 2.0);
|
|
12
|
+
if (a.arousal === undefined || b.arousal === undefined) return valenceSim;
|
|
13
|
+
const arousalSim = 1.0 - Math.abs(a.arousal - b.arousal);
|
|
14
|
+
// Valence is primary (70%), arousal secondary (30%) per Bower 1981
|
|
15
|
+
return 0.7 * valenceSim + 0.3 * arousalSim;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function moodCongruenceModifier(encodingAffect, retrievalMood, weight = 0.2) {
|
|
19
|
+
if (!encodingAffect || !retrievalMood) return 1.0;
|
|
20
|
+
const similarity = affectSimilarity(encodingAffect, retrievalMood);
|
|
21
|
+
if (similarity === 0) return 1.0;
|
|
22
|
+
return 1.0 + (weight * similarity);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function detectResonance(db, embeddingProvider, episodeId, { content, affect }, config = {}) {
|
|
26
|
+
const { enabled = true, k = 5, threshold = 0.5, affectThreshold = 0.6 } = config;
|
|
27
|
+
if (!enabled || !affect || affect.valence === undefined) return [];
|
|
28
|
+
|
|
29
|
+
const vector = await embeddingProvider.embed(content);
|
|
30
|
+
const buffer = embeddingProvider.vectorToBuffer(vector);
|
|
31
|
+
|
|
32
|
+
const matches = db.prepare(`
|
|
33
|
+
SELECT e.*, (1.0 - v.distance) AS similarity
|
|
34
|
+
FROM vec_episodes v
|
|
35
|
+
JOIN episodes e ON e.id = v.id
|
|
36
|
+
WHERE v.embedding MATCH ?
|
|
37
|
+
AND k = ?
|
|
38
|
+
AND e.id != ?
|
|
39
|
+
AND e.superseded_by IS NULL
|
|
40
|
+
`).all(buffer, k, episodeId);
|
|
41
|
+
|
|
42
|
+
const resonances = [];
|
|
43
|
+
for (const match of matches) {
|
|
44
|
+
if (match.similarity < threshold) continue;
|
|
45
|
+
let priorAffect;
|
|
46
|
+
try { priorAffect = JSON.parse(match.affect || '{}'); } catch { continue; }
|
|
47
|
+
if (priorAffect.valence === undefined) continue;
|
|
48
|
+
|
|
49
|
+
const emotionalSimilarity = affectSimilarity(affect, priorAffect);
|
|
50
|
+
if (emotionalSimilarity < affectThreshold) continue;
|
|
51
|
+
|
|
52
|
+
resonances.push({
|
|
53
|
+
priorEpisodeId: match.id,
|
|
54
|
+
priorContent: match.content,
|
|
55
|
+
priorAffect,
|
|
56
|
+
semanticSimilarity: match.similarity,
|
|
57
|
+
emotionalSimilarity,
|
|
58
|
+
timeDelta: Date.now() - new Date(match.created_at).getTime(),
|
|
59
|
+
priorCreatedAt: match.created_at,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return resonances;
|
|
64
|
+
}
|
package/src/audrey.js
CHANGED
|
@@ -16,6 +16,7 @@ import { importMemories } from './import.js';
|
|
|
16
16
|
import { suggestConsolidationParams as suggestParamsFn } from './adaptive.js';
|
|
17
17
|
import { reembedAll } from './migrate.js';
|
|
18
18
|
import { applyInterference } from './interference.js';
|
|
19
|
+
import { detectResonance } from './affect.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @typedef {'direct-observation' | 'told-by-user' | 'tool-result' | 'inference' | 'model-generated'} SourceType
|
|
@@ -92,6 +93,7 @@ export class Audrey extends EventEmitter {
|
|
|
92
93
|
decay = {},
|
|
93
94
|
interference = {},
|
|
94
95
|
context = {},
|
|
96
|
+
affect = {},
|
|
95
97
|
} = {}) {
|
|
96
98
|
super();
|
|
97
99
|
|
|
@@ -118,6 +120,7 @@ export class Audrey extends EventEmitter {
|
|
|
118
120
|
sourceReliability: confidence.sourceReliability,
|
|
119
121
|
interferenceWeight: interference.weight ?? 0.1,
|
|
120
122
|
contextWeight: context.weight ?? 0.3,
|
|
123
|
+
affectWeight: affect.weight ?? 0.2,
|
|
121
124
|
};
|
|
122
125
|
this.consolidationConfig = {
|
|
123
126
|
minEpisodes: consolidation.minEpisodes || 3,
|
|
@@ -134,6 +137,17 @@ export class Audrey extends EventEmitter {
|
|
|
134
137
|
enabled: context.enabled ?? true,
|
|
135
138
|
weight: context.weight ?? 0.3,
|
|
136
139
|
};
|
|
140
|
+
this.affectConfig = {
|
|
141
|
+
enabled: affect.enabled ?? true,
|
|
142
|
+
weight: affect.weight ?? 0.2,
|
|
143
|
+
arousalWeight: affect.arousalWeight ?? 0.3,
|
|
144
|
+
resonance: {
|
|
145
|
+
enabled: affect.resonance?.enabled ?? true,
|
|
146
|
+
k: affect.resonance?.k ?? 5,
|
|
147
|
+
threshold: affect.resonance?.threshold ?? 0.5,
|
|
148
|
+
affectThreshold: affect.resonance?.affectThreshold ?? 0.6,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
async _ensureMigrated() {
|
|
@@ -173,7 +187,8 @@ export class Audrey extends EventEmitter {
|
|
|
173
187
|
*/
|
|
174
188
|
async encode(params) {
|
|
175
189
|
await this._ensureMigrated();
|
|
176
|
-
const
|
|
190
|
+
const encodeParams = { ...params, arousalWeight: this.affectConfig.arousalWeight };
|
|
191
|
+
const id = await encodeEpisode(this.db, this.embeddingProvider, encodeParams);
|
|
177
192
|
this.emit('encode', { id, ...params });
|
|
178
193
|
if (this.interferenceConfig.enabled) {
|
|
179
194
|
applyInterference(this.db, this.embeddingProvider, id, params, this.interferenceConfig)
|
|
@@ -184,6 +199,15 @@ export class Audrey extends EventEmitter {
|
|
|
184
199
|
})
|
|
185
200
|
.catch(err => this.emit('error', err));
|
|
186
201
|
}
|
|
202
|
+
if (this.affectConfig.enabled && this.affectConfig.resonance.enabled && params.affect?.valence !== undefined) {
|
|
203
|
+
detectResonance(this.db, this.embeddingProvider, id, params, this.affectConfig.resonance)
|
|
204
|
+
.then(echoes => {
|
|
205
|
+
if (echoes.length > 0) {
|
|
206
|
+
this.emit('resonance', { episodeId: id, affect: params.affect, echoes });
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
.catch(err => this.emit('error', err));
|
|
210
|
+
}
|
|
187
211
|
this._emitValidation(id, params);
|
|
188
212
|
return id;
|
|
189
213
|
}
|
|
@@ -235,10 +259,14 @@ export class Audrey extends EventEmitter {
|
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
_recallConfig(options) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
262
|
+
let config = options.confidenceConfig ?? this.confidenceConfig;
|
|
263
|
+
if (this.contextConfig.enabled && options.context) {
|
|
264
|
+
config = { ...config, retrievalContext: options.context };
|
|
265
|
+
}
|
|
266
|
+
if (this.affectConfig.enabled && options.mood) {
|
|
267
|
+
config = { ...config, retrievalMood: options.mood };
|
|
268
|
+
}
|
|
269
|
+
return config;
|
|
242
270
|
}
|
|
243
271
|
|
|
244
272
|
/**
|
package/src/db.js
CHANGED
package/src/encode.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generateId } from './ulid.js';
|
|
2
2
|
import { sourceReliability } from './confidence.js';
|
|
3
|
+
import { arousalSalienceBoost } from './affect.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @param {import('better-sqlite3').Database} db
|
|
@@ -15,6 +16,8 @@ export async function encodeEpisode(db, embeddingProvider, {
|
|
|
15
16
|
tags,
|
|
16
17
|
supersedes,
|
|
17
18
|
context = {},
|
|
19
|
+
affect = {},
|
|
20
|
+
arousalWeight = 0.3,
|
|
18
21
|
}) {
|
|
19
22
|
if (!content || typeof content !== 'string') throw new Error('content must be a non-empty string');
|
|
20
23
|
if (salience < 0 || salience > 1) throw new Error('salience must be between 0 and 1');
|
|
@@ -26,16 +29,20 @@ export async function encodeEpisode(db, embeddingProvider, {
|
|
|
26
29
|
const id = generateId();
|
|
27
30
|
const now = new Date().toISOString();
|
|
28
31
|
|
|
32
|
+
const boost = arousalSalienceBoost(affect.arousal);
|
|
33
|
+
const effectiveSalience = Math.min(1.0, salience + (boost * arousalWeight));
|
|
34
|
+
|
|
29
35
|
const insertAndLink = db.transaction(() => {
|
|
30
36
|
db.prepare(`
|
|
31
37
|
INSERT INTO episodes (
|
|
32
|
-
id, content, embedding, source, source_reliability, salience, context,
|
|
38
|
+
id, content, embedding, source, source_reliability, salience, context, affect,
|
|
33
39
|
tags, causal_trigger, causal_consequence, created_at,
|
|
34
40
|
embedding_model, embedding_version, supersedes
|
|
35
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
36
42
|
`).run(
|
|
37
|
-
id, content, embeddingBuffer, source, reliability,
|
|
43
|
+
id, content, embeddingBuffer, source, reliability, effectiveSalience,
|
|
38
44
|
JSON.stringify(context),
|
|
45
|
+
JSON.stringify(affect),
|
|
39
46
|
tags ? JSON.stringify(tags) : null,
|
|
40
47
|
causal?.trigger || null, causal?.consequence || null,
|
|
41
48
|
now, embeddingProvider.modelName, embeddingProvider.modelVersion,
|
package/src/index.js
CHANGED
|
@@ -17,3 +17,4 @@ export { reembedAll } from './migrate.js';
|
|
|
17
17
|
export { forgetMemory, forgetByQuery, purgeMemories } from './forget.js';
|
|
18
18
|
export { applyInterference, interferenceModifier } from './interference.js';
|
|
19
19
|
export { contextMatchRatio, contextModifier } from './context.js';
|
|
20
|
+
export { arousalSalienceBoost, affectSimilarity, moodCongruenceModifier, detectResonance } from './affect.js';
|
package/src/recall.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computeConfidence, DEFAULT_HALF_LIVES, salienceModifier } from './confidence.js';
|
|
2
2
|
import { interferenceModifier } from './interference.js';
|
|
3
3
|
import { contextMatchRatio, contextModifier } from './context.js';
|
|
4
|
+
import { moodCongruenceModifier, affectSimilarity } from './affect.js';
|
|
4
5
|
import { daysBetween, safeJsonParse } from './utils.js';
|
|
5
6
|
|
|
6
7
|
function computeEpisodicConfidence(ep, now, confidenceConfig = {}) {
|
|
@@ -65,7 +66,7 @@ function computeProceduralConfidence(proc, now, confidenceConfig = {}) {
|
|
|
65
66
|
return Math.max(0, Math.min(1, confidence));
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMatch) {
|
|
69
|
+
function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMatch, moodCongruence) {
|
|
69
70
|
const entry = {
|
|
70
71
|
id: ep.id,
|
|
71
72
|
content: ep.content,
|
|
@@ -78,6 +79,9 @@ function buildEpisodicEntry(ep, confidence, score, includeProvenance, contextMat
|
|
|
78
79
|
if (contextMatch !== undefined) {
|
|
79
80
|
entry.contextMatch = contextMatch;
|
|
80
81
|
}
|
|
82
|
+
if (moodCongruence !== undefined) {
|
|
83
|
+
entry.moodCongruence = moodCongruence;
|
|
84
|
+
}
|
|
81
85
|
if (includeProvenance) {
|
|
82
86
|
entry.provenance = {
|
|
83
87
|
source: ep.source,
|
|
@@ -174,9 +178,17 @@ function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includePro
|
|
|
174
178
|
confidence = Math.max(0, Math.min(1, confidence));
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
let moodMatch;
|
|
182
|
+
if (confidenceConfig?.retrievalMood) {
|
|
183
|
+
const encodingAffect = safeJsonParse(row.affect, {});
|
|
184
|
+
moodMatch = affectSimilarity(encodingAffect, confidenceConfig.retrievalMood);
|
|
185
|
+
confidence *= moodCongruenceModifier(encodingAffect, confidenceConfig.retrievalMood, confidenceConfig.affectWeight);
|
|
186
|
+
confidence = Math.max(0, Math.min(1, confidence));
|
|
187
|
+
}
|
|
188
|
+
|
|
177
189
|
if (confidence < minConfidence) continue;
|
|
178
190
|
const score = row.similarity * confidence;
|
|
179
|
-
results.push(buildEpisodicEntry(row, confidence, score, includeProvenance, ctxMatch));
|
|
191
|
+
results.push(buildEpisodicEntry(row, confidence, score, includeProvenance, ctxMatch, moodMatch));
|
|
180
192
|
}
|
|
181
193
|
return results;
|
|
182
194
|
}
|