audrey 0.9.0 → 0.11.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/mcp-server/config.js +30 -16
- package/mcp-server/index.js +33 -5
- package/package.json +2 -1
- package/src/affect.js +1 -1
- package/src/audrey.js +49 -1
- package/src/db.js +321 -282
- package/src/embedding.js +90 -53
- package/src/encode.js +63 -61
- package/src/export.js +5 -3
- package/src/import.js +15 -8
- package/src/migrate.js +27 -9
- package/src/prompts.js +43 -0
- package/src/recall.js +13 -14
package/src/embedding.js
CHANGED
|
@@ -11,33 +11,14 @@ import { createHash } from 'node:crypto';
|
|
|
11
11
|
* @property {(buffer: Buffer) => number[]} bufferToVector
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {Object} MockEmbeddingConfig
|
|
16
|
-
* @property {'mock'} provider
|
|
17
|
-
* @property {number} [dimensions=64]
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {Object} OpenAIEmbeddingConfig
|
|
22
|
-
* @property {'openai'} provider
|
|
23
|
-
* @property {string} [apiKey]
|
|
24
|
-
* @property {string} [model='text-embedding-3-small']
|
|
25
|
-
* @property {number} [dimensions=1536]
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
14
|
/** @implements {EmbeddingProvider} */
|
|
29
15
|
export class MockEmbeddingProvider {
|
|
30
|
-
/** @param {Partial<MockEmbeddingConfig>} [config={}] */
|
|
31
16
|
constructor({ dimensions = 64 } = {}) {
|
|
32
17
|
this.dimensions = dimensions;
|
|
33
18
|
this.modelName = 'mock-embedding';
|
|
34
19
|
this.modelVersion = '1.0.0';
|
|
35
20
|
}
|
|
36
21
|
|
|
37
|
-
/**
|
|
38
|
-
* @param {string} text
|
|
39
|
-
* @returns {Promise<number[]>}
|
|
40
|
-
*/
|
|
41
22
|
async embed(text) {
|
|
42
23
|
const hash = createHash('sha256').update(text).digest();
|
|
43
24
|
const vector = new Array(this.dimensions);
|
|
@@ -48,26 +29,14 @@ export class MockEmbeddingProvider {
|
|
|
48
29
|
return vector.map(v => v / magnitude);
|
|
49
30
|
}
|
|
50
31
|
|
|
51
|
-
/**
|
|
52
|
-
* @param {string[]} texts
|
|
53
|
-
* @returns {Promise<number[][]>}
|
|
54
|
-
*/
|
|
55
32
|
async embedBatch(texts) {
|
|
56
33
|
return Promise.all(texts.map(t => this.embed(t)));
|
|
57
34
|
}
|
|
58
35
|
|
|
59
|
-
/**
|
|
60
|
-
* @param {number[]} vector
|
|
61
|
-
* @returns {Buffer}
|
|
62
|
-
*/
|
|
63
36
|
vectorToBuffer(vector) {
|
|
64
37
|
return Buffer.from(new Float32Array(vector).buffer);
|
|
65
38
|
}
|
|
66
39
|
|
|
67
|
-
/**
|
|
68
|
-
* @param {Buffer} buffer
|
|
69
|
-
* @returns {number[]}
|
|
70
|
-
*/
|
|
71
40
|
bufferToVector(buffer) {
|
|
72
41
|
return Array.from(new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4));
|
|
73
42
|
}
|
|
@@ -75,7 +44,6 @@ export class MockEmbeddingProvider {
|
|
|
75
44
|
|
|
76
45
|
/** @implements {EmbeddingProvider} */
|
|
77
46
|
export class OpenAIEmbeddingProvider {
|
|
78
|
-
/** @param {Partial<OpenAIEmbeddingConfig>} [config={}] */
|
|
79
47
|
constructor({ apiKey, model = 'text-embedding-3-small', dimensions = 1536, timeout = 30000 } = {}) {
|
|
80
48
|
this.apiKey = apiKey || process.env.OPENAI_API_KEY;
|
|
81
49
|
this.model = model;
|
|
@@ -85,10 +53,6 @@ export class OpenAIEmbeddingProvider {
|
|
|
85
53
|
this.modelVersion = 'latest';
|
|
86
54
|
}
|
|
87
55
|
|
|
88
|
-
/**
|
|
89
|
-
* @param {string} text
|
|
90
|
-
* @returns {Promise<number[]>}
|
|
91
|
-
*/
|
|
92
56
|
async embed(text) {
|
|
93
57
|
const controller = new AbortController();
|
|
94
58
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
@@ -110,10 +74,6 @@ export class OpenAIEmbeddingProvider {
|
|
|
110
74
|
}
|
|
111
75
|
}
|
|
112
76
|
|
|
113
|
-
/**
|
|
114
|
-
* @param {string[]} texts
|
|
115
|
-
* @returns {Promise<number[][]>}
|
|
116
|
-
*/
|
|
117
77
|
async embedBatch(texts) {
|
|
118
78
|
const controller = new AbortController();
|
|
119
79
|
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
@@ -135,34 +95,111 @@ export class OpenAIEmbeddingProvider {
|
|
|
135
95
|
}
|
|
136
96
|
}
|
|
137
97
|
|
|
138
|
-
/**
|
|
139
|
-
* @param {number[]} vector
|
|
140
|
-
* @returns {Buffer}
|
|
141
|
-
*/
|
|
142
98
|
vectorToBuffer(vector) {
|
|
143
99
|
return Buffer.from(new Float32Array(vector).buffer);
|
|
144
100
|
}
|
|
145
101
|
|
|
146
|
-
/**
|
|
147
|
-
* @param {Buffer} buffer
|
|
148
|
-
* @returns {number[]}
|
|
149
|
-
*/
|
|
150
102
|
bufferToVector(buffer) {
|
|
151
103
|
return Array.from(new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4));
|
|
152
104
|
}
|
|
153
105
|
}
|
|
154
106
|
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
107
|
+
/** @implements {EmbeddingProvider} */
|
|
108
|
+
export class LocalEmbeddingProvider {
|
|
109
|
+
constructor({ model = 'Xenova/all-MiniLM-L6-v2' } = {}) {
|
|
110
|
+
this.model = model;
|
|
111
|
+
this.dimensions = 384;
|
|
112
|
+
this.modelName = model;
|
|
113
|
+
this.modelVersion = '1.0.0';
|
|
114
|
+
this._pipeline = null;
|
|
115
|
+
this._readyPromise = null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
ready() {
|
|
119
|
+
if (!this._readyPromise) {
|
|
120
|
+
this._readyPromise = import('@huggingface/transformers').then(({ pipeline }) =>
|
|
121
|
+
pipeline('feature-extraction', this.model, { dtype: 'fp32' })
|
|
122
|
+
).then(pipe => { this._pipeline = pipe; });
|
|
123
|
+
}
|
|
124
|
+
return this._readyPromise;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async embed(text) {
|
|
128
|
+
await this.ready();
|
|
129
|
+
const output = await this._pipeline(text, { pooling: 'mean', normalize: true });
|
|
130
|
+
return Array.from(output.data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async embedBatch(texts) {
|
|
134
|
+
return Promise.all(texts.map(t => this.embed(t)));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
vectorToBuffer(vector) {
|
|
138
|
+
return Buffer.from(new Float32Array(vector).buffer);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
bufferToVector(buffer) {
|
|
142
|
+
return Array.from(new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** @implements {EmbeddingProvider} */
|
|
147
|
+
export class GeminiEmbeddingProvider {
|
|
148
|
+
constructor({ apiKey, model = 'gemini-embedding-001', timeout = 30000 } = {}) {
|
|
149
|
+
this.apiKey = apiKey || process.env.GOOGLE_API_KEY;
|
|
150
|
+
this.model = model;
|
|
151
|
+
this.dimensions = 3072;
|
|
152
|
+
this.timeout = timeout;
|
|
153
|
+
this.modelName = model;
|
|
154
|
+
this.modelVersion = 'latest';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async embed(text) {
|
|
158
|
+
if (!this.apiKey) throw new Error('Gemini embedding requires GOOGLE_API_KEY');
|
|
159
|
+
const controller = new AbortController();
|
|
160
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
161
|
+
try {
|
|
162
|
+
const response = await fetch(
|
|
163
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${this.model}:embedContent?key=${this.apiKey}`,
|
|
164
|
+
{
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: { 'Content-Type': 'application/json' },
|
|
167
|
+
body: JSON.stringify({ model: `models/${this.model}`, content: { parts: [{ text }] } }),
|
|
168
|
+
signal: controller.signal,
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
if (!response.ok) throw new Error(`Gemini embedding failed: ${response.status}`);
|
|
172
|
+
const data = await response.json();
|
|
173
|
+
return data.embedding.values;
|
|
174
|
+
} finally {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async embedBatch(texts) {
|
|
180
|
+
return Promise.all(texts.map(t => this.embed(t)));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
vectorToBuffer(vector) {
|
|
184
|
+
return Buffer.from(new Float32Array(vector).buffer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
bufferToVector(buffer) {
|
|
188
|
+
return Array.from(new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
159
192
|
export function createEmbeddingProvider(config) {
|
|
160
193
|
switch (config.provider) {
|
|
161
194
|
case 'mock':
|
|
162
195
|
return new MockEmbeddingProvider(config);
|
|
163
196
|
case 'openai':
|
|
164
197
|
return new OpenAIEmbeddingProvider(config);
|
|
198
|
+
case 'local':
|
|
199
|
+
return new LocalEmbeddingProvider(config);
|
|
200
|
+
case 'gemini':
|
|
201
|
+
return new GeminiEmbeddingProvider(config);
|
|
165
202
|
default:
|
|
166
|
-
throw new Error(`Unknown embedding provider: ${config.provider}. Valid: mock, openai`);
|
|
203
|
+
throw new Error(`Unknown embedding provider: ${config.provider}. Valid: mock, openai, local, gemini`);
|
|
167
204
|
}
|
|
168
205
|
}
|
package/src/encode.js
CHANGED
|
@@ -1,61 +1,63 @@
|
|
|
1
|
-
import { generateId } from './ulid.js';
|
|
2
|
-
import { sourceReliability } from './confidence.js';
|
|
3
|
-
import { arousalSalienceBoost } from './affect.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {import('better-sqlite3').Database} db
|
|
7
|
-
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
8
|
-
* @param {{ content: string, source: string, salience?: number, causal?: { trigger?: string, consequence?: string }, tags?: string[], supersedes?: string }} params
|
|
9
|
-
* @returns {Promise<string>}
|
|
10
|
-
*/
|
|
11
|
-
export async function encodeEpisode(db, embeddingProvider, {
|
|
12
|
-
content,
|
|
13
|
-
source,
|
|
14
|
-
salience = 0.5,
|
|
15
|
-
causal,
|
|
16
|
-
tags,
|
|
17
|
-
supersedes,
|
|
18
|
-
context = {},
|
|
19
|
-
affect = {},
|
|
20
|
-
arousalWeight = 0.3,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
JSON.stringify(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
import { generateId } from './ulid.js';
|
|
2
|
+
import { sourceReliability } from './confidence.js';
|
|
3
|
+
import { arousalSalienceBoost } from './affect.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('better-sqlite3').Database} db
|
|
7
|
+
* @param {import('./embedding.js').EmbeddingProvider} embeddingProvider
|
|
8
|
+
* @param {{ content: string, source: string, salience?: number, causal?: { trigger?: string, consequence?: string }, tags?: string[], supersedes?: string, context?: object, affect?: object, arousalWeight?: number, private?: boolean }} params
|
|
9
|
+
* @returns {Promise<string>}
|
|
10
|
+
*/
|
|
11
|
+
export async function encodeEpisode(db, embeddingProvider, {
|
|
12
|
+
content,
|
|
13
|
+
source,
|
|
14
|
+
salience = 0.5,
|
|
15
|
+
causal,
|
|
16
|
+
tags,
|
|
17
|
+
supersedes,
|
|
18
|
+
context = {},
|
|
19
|
+
affect = {},
|
|
20
|
+
arousalWeight = 0.3,
|
|
21
|
+
private: isPrivate = false,
|
|
22
|
+
}) {
|
|
23
|
+
if (!content || typeof content !== 'string') throw new Error('content must be a non-empty string');
|
|
24
|
+
if (salience < 0 || salience > 1) throw new Error('salience must be between 0 and 1');
|
|
25
|
+
if (tags && !Array.isArray(tags)) throw new Error('tags must be an array');
|
|
26
|
+
|
|
27
|
+
const reliability = sourceReliability(source);
|
|
28
|
+
const vector = await embeddingProvider.embed(content);
|
|
29
|
+
const embeddingBuffer = embeddingProvider.vectorToBuffer(vector);
|
|
30
|
+
const id = generateId();
|
|
31
|
+
const now = new Date().toISOString();
|
|
32
|
+
|
|
33
|
+
const boost = arousalSalienceBoost(affect.arousal);
|
|
34
|
+
const effectiveSalience = Math.min(1.0, salience + (boost * arousalWeight));
|
|
35
|
+
|
|
36
|
+
const insertAndLink = db.transaction(() => {
|
|
37
|
+
db.prepare(`
|
|
38
|
+
INSERT INTO episodes (
|
|
39
|
+
id, content, embedding, source, source_reliability, salience, context, affect,
|
|
40
|
+
tags, causal_trigger, causal_consequence, created_at,
|
|
41
|
+
embedding_model, embedding_version, supersedes, "private"
|
|
42
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
43
|
+
`).run(
|
|
44
|
+
id, content, embeddingBuffer, source, reliability, effectiveSalience,
|
|
45
|
+
JSON.stringify(context),
|
|
46
|
+
JSON.stringify(affect),
|
|
47
|
+
tags ? JSON.stringify(tags) : null,
|
|
48
|
+
causal?.trigger || null, causal?.consequence || null,
|
|
49
|
+
now, embeddingProvider.modelName, embeddingProvider.modelVersion,
|
|
50
|
+
supersedes || null,
|
|
51
|
+
isPrivate ? 1 : 0,
|
|
52
|
+
);
|
|
53
|
+
db.prepare(
|
|
54
|
+
'INSERT INTO vec_episodes(id, embedding, source, consolidated) VALUES (?, ?, ?, ?)'
|
|
55
|
+
).run(id, embeddingBuffer, source, BigInt(0));
|
|
56
|
+
if (supersedes) {
|
|
57
|
+
db.prepare('UPDATE episodes SET superseded_by = ? WHERE id = ?').run(id, supersedes);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
insertAndLink();
|
|
62
|
+
return id;
|
|
63
|
+
}
|
package/src/export.js
CHANGED
|
@@ -8,21 +8,23 @@ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')
|
|
|
8
8
|
|
|
9
9
|
export function exportMemories(db) {
|
|
10
10
|
const episodes = db.prepare(
|
|
11
|
-
'SELECT id, content, source, source_reliability, salience, tags, causal_trigger, causal_consequence, created_at, supersedes, superseded_by, consolidated FROM episodes'
|
|
11
|
+
'SELECT id, content, source, source_reliability, salience, context, affect, tags, causal_trigger, causal_consequence, created_at, supersedes, superseded_by, consolidated, "private" FROM episodes'
|
|
12
12
|
).all().map(ep => ({
|
|
13
13
|
...ep,
|
|
14
14
|
tags: safeJsonParse(ep.tags, null),
|
|
15
|
+
context: safeJsonParse(ep.context, null),
|
|
16
|
+
affect: safeJsonParse(ep.affect, null),
|
|
15
17
|
}));
|
|
16
18
|
|
|
17
19
|
const semantics = db.prepare(
|
|
18
|
-
'SELECT id, content, state, conditions, evidence_episode_ids, evidence_count, supporting_count, contradicting_count, source_type_diversity, consolidation_checkpoint, created_at, last_reinforced_at, retrieval_count, challenge_count FROM semantics'
|
|
20
|
+
'SELECT id, content, state, conditions, evidence_episode_ids, evidence_count, supporting_count, contradicting_count, source_type_diversity, consolidation_checkpoint, created_at, last_reinforced_at, retrieval_count, challenge_count, interference_count, salience FROM semantics'
|
|
19
21
|
).all().map(sem => ({
|
|
20
22
|
...sem,
|
|
21
23
|
evidence_episode_ids: safeJsonParse(sem.evidence_episode_ids, []),
|
|
22
24
|
}));
|
|
23
25
|
|
|
24
26
|
const procedures = db.prepare(
|
|
25
|
-
'SELECT id, content, state, trigger_conditions, evidence_episode_ids, success_count, failure_count, created_at, last_reinforced_at, retrieval_count FROM procedures'
|
|
27
|
+
'SELECT id, content, state, trigger_conditions, evidence_episode_ids, success_count, failure_count, created_at, last_reinforced_at, retrieval_count, interference_count, salience FROM procedures'
|
|
26
28
|
).all().map(proc => ({
|
|
27
29
|
...proc,
|
|
28
30
|
evidence_episode_ids: safeJsonParse(proc.evidence_episode_ids, []),
|
package/src/import.js
CHANGED
|
@@ -5,9 +5,9 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
const insertEpisode = db.prepare(`
|
|
8
|
-
INSERT INTO episodes (id, content, source, source_reliability, salience, tags,
|
|
9
|
-
causal_trigger, causal_consequence, created_at, supersedes, superseded_by, consolidated)
|
|
10
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8
|
+
INSERT INTO episodes (id, content, source, source_reliability, salience, context, affect, tags,
|
|
9
|
+
causal_trigger, causal_consequence, created_at, supersedes, superseded_by, consolidated, "private")
|
|
10
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11
11
|
`);
|
|
12
12
|
|
|
13
13
|
const insertVecEpisode = db.prepare(
|
|
@@ -17,8 +17,9 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
17
17
|
const insertSemantic = db.prepare(`
|
|
18
18
|
INSERT INTO semantics (id, content, state, conditions, evidence_episode_ids,
|
|
19
19
|
evidence_count, supporting_count, contradicting_count, source_type_diversity,
|
|
20
|
-
consolidation_checkpoint, created_at, last_reinforced_at, retrieval_count, challenge_count
|
|
21
|
-
|
|
20
|
+
consolidation_checkpoint, created_at, last_reinforced_at, retrieval_count, challenge_count,
|
|
21
|
+
interference_count, salience)
|
|
22
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
22
23
|
`);
|
|
23
24
|
|
|
24
25
|
const insertVecSemantic = db.prepare(
|
|
@@ -27,8 +28,9 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
27
28
|
|
|
28
29
|
const insertProcedure = db.prepare(`
|
|
29
30
|
INSERT INTO procedures (id, content, state, trigger_conditions, evidence_episode_ids,
|
|
30
|
-
success_count, failure_count, created_at, last_reinforced_at, retrieval_count
|
|
31
|
-
|
|
31
|
+
success_count, failure_count, created_at, last_reinforced_at, retrieval_count,
|
|
32
|
+
interference_count, salience)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
32
34
|
`);
|
|
33
35
|
|
|
34
36
|
const insertVecProcedure = db.prepare(
|
|
@@ -53,10 +55,13 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
53
55
|
|
|
54
56
|
for (const ep of snapshot.episodes) {
|
|
55
57
|
const tags = ep.tags ? JSON.stringify(ep.tags) : null;
|
|
58
|
+
const context = ep.context ? JSON.stringify(ep.context) : '{}';
|
|
59
|
+
const affect = ep.affect ? JSON.stringify(ep.affect) : '{}';
|
|
56
60
|
insertEpisode.run(
|
|
57
61
|
ep.id, ep.content, ep.source, ep.source_reliability, ep.salience ?? 0.5,
|
|
58
|
-
tags, ep.causal_trigger ?? null, ep.causal_consequence ?? null,
|
|
62
|
+
context, affect, tags, ep.causal_trigger ?? null, ep.causal_consequence ?? null,
|
|
59
63
|
ep.created_at, ep.supersedes ?? null, ep.superseded_by ?? null, ep.consolidated ?? 0,
|
|
64
|
+
ep.private ?? 0,
|
|
60
65
|
);
|
|
61
66
|
|
|
62
67
|
const vector = await embeddingProvider.embed(ep.content);
|
|
@@ -71,6 +76,7 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
71
76
|
sem.evidence_count ?? 0, sem.supporting_count ?? 0, sem.contradicting_count ?? 0,
|
|
72
77
|
sem.source_type_diversity ?? 0, sem.consolidation_checkpoint ?? null,
|
|
73
78
|
sem.created_at, sem.last_reinforced_at ?? null, sem.retrieval_count ?? 0, sem.challenge_count ?? 0,
|
|
79
|
+
sem.interference_count ?? 0, sem.salience ?? 0.5,
|
|
74
80
|
);
|
|
75
81
|
|
|
76
82
|
const vector = await embeddingProvider.embed(sem.content);
|
|
@@ -84,6 +90,7 @@ export async function importMemories(db, embeddingProvider, snapshot) {
|
|
|
84
90
|
JSON.stringify(proc.evidence_episode_ids || []),
|
|
85
91
|
proc.success_count ?? 0, proc.failure_count ?? 0,
|
|
86
92
|
proc.created_at, proc.last_reinforced_at ?? null, proc.retrieval_count ?? 0,
|
|
93
|
+
proc.interference_count ?? 0, proc.salience ?? 0.5,
|
|
87
94
|
);
|
|
88
95
|
|
|
89
96
|
const vector = await embeddingProvider.embed(proc.content);
|
package/src/migrate.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { dropVec0Tables, createVec0Tables } from './db.js';
|
|
2
|
+
|
|
3
|
+
export async function reembedAll(db, embeddingProvider, { dropAndRecreate = false } = {}) {
|
|
4
|
+
if (dropAndRecreate) {
|
|
5
|
+
dropVec0Tables(db);
|
|
6
|
+
createVec0Tables(db, embeddingProvider.dimensions);
|
|
7
|
+
}
|
|
8
|
+
|
|
2
9
|
const episodes = db.prepare('SELECT id, content, source FROM episodes').all();
|
|
3
10
|
const semantics = db.prepare('SELECT id, content, state FROM semantics').all();
|
|
4
11
|
const procedures = db.prepare('SELECT id, content, state FROM procedures').all();
|
|
@@ -7,26 +14,37 @@ export async function reembedAll(db, embeddingProvider) {
|
|
|
7
14
|
const vector = await embeddingProvider.embed(ep.content);
|
|
8
15
|
const buffer = embeddingProvider.vectorToBuffer(vector);
|
|
9
16
|
db.prepare('UPDATE episodes SET embedding = ? WHERE id = ?').run(buffer, ep.id);
|
|
10
|
-
db.prepare('
|
|
17
|
+
const exists = db.prepare('SELECT id FROM vec_episodes WHERE id = ?').get(ep.id);
|
|
18
|
+
if (!exists) {
|
|
19
|
+
db.prepare('INSERT INTO vec_episodes(id, embedding, source, consolidated) VALUES (?, ?, ?, ?)').run(ep.id, buffer, ep.source, BigInt(0));
|
|
20
|
+
} else {
|
|
21
|
+
db.prepare('UPDATE vec_episodes SET embedding = ? WHERE id = ?').run(buffer, ep.id);
|
|
22
|
+
}
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
for (const sem of semantics) {
|
|
14
26
|
const vector = await embeddingProvider.embed(sem.content);
|
|
15
27
|
const buffer = embeddingProvider.vectorToBuffer(vector);
|
|
16
28
|
db.prepare('UPDATE semantics SET embedding = ? WHERE id = ?').run(buffer, sem.id);
|
|
17
|
-
db.prepare('
|
|
29
|
+
const exists = db.prepare('SELECT id FROM vec_semantics WHERE id = ?').get(sem.id);
|
|
30
|
+
if (!exists) {
|
|
31
|
+
db.prepare('INSERT INTO vec_semantics(id, embedding, state) VALUES (?, ?, ?)').run(sem.id, buffer, sem.state);
|
|
32
|
+
} else {
|
|
33
|
+
db.prepare('UPDATE vec_semantics SET embedding = ? WHERE id = ?').run(buffer, sem.id);
|
|
34
|
+
}
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
for (const proc of procedures) {
|
|
21
38
|
const vector = await embeddingProvider.embed(proc.content);
|
|
22
39
|
const buffer = embeddingProvider.vectorToBuffer(vector);
|
|
23
40
|
db.prepare('UPDATE procedures SET embedding = ? WHERE id = ?').run(buffer, proc.id);
|
|
24
|
-
db.prepare('
|
|
41
|
+
const exists = db.prepare('SELECT id FROM vec_procedures WHERE id = ?').get(proc.id);
|
|
42
|
+
if (!exists) {
|
|
43
|
+
db.prepare('INSERT INTO vec_procedures(id, embedding, state) VALUES (?, ?, ?)').run(proc.id, buffer, proc.state);
|
|
44
|
+
} else {
|
|
45
|
+
db.prepare('UPDATE vec_procedures SET embedding = ? WHERE id = ?').run(buffer, proc.id);
|
|
46
|
+
}
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
return {
|
|
28
|
-
episodes: episodes.length,
|
|
29
|
-
semantics: semantics.length,
|
|
30
|
-
procedures: procedures.length,
|
|
31
|
-
};
|
|
49
|
+
return { episodes: episodes.length, semantics: semantics.length, procedures: procedures.length };
|
|
32
50
|
}
|
package/src/prompts.js
CHANGED
|
@@ -152,3 +152,46 @@ CLAIM B: ${claimB}${contextSection}`,
|
|
|
152
152
|
},
|
|
153
153
|
];
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {{ role: string, content: string }[]} turns
|
|
158
|
+
* @returns {import('./llm.js').ChatMessage[]}
|
|
159
|
+
*/
|
|
160
|
+
export function buildReflectionPrompt(turns) {
|
|
161
|
+
const transcript = turns.map(t => `${t.role.toUpperCase()}: ${t.content}`).join('\n\n');
|
|
162
|
+
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
role: 'system',
|
|
166
|
+
content: `You are performing memoryReflection. Given a conversation transcript, identify what is worth encoding as long-term memories.
|
|
167
|
+
|
|
168
|
+
Respond with ONLY valid JSON in this exact format:
|
|
169
|
+
{
|
|
170
|
+
"memories": [
|
|
171
|
+
{
|
|
172
|
+
"content": "The memory to encode — a clear, self-contained statement",
|
|
173
|
+
"source": "direct-observation" or "told-by-user" or "inference",
|
|
174
|
+
"salience": 0.0 to 1.0,
|
|
175
|
+
"tags": ["tag1", "tag2"],
|
|
176
|
+
"private": true or false,
|
|
177
|
+
"affect": { "valence": -1 to 1, "arousal": 0 to 1, "label": "emotion label" } or null
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Rules:
|
|
183
|
+
- Encode facts about the user, decisions made, things that shifted
|
|
184
|
+
- Mark private: true for AI self-observations, emotional reactions, things felt but not said
|
|
185
|
+
- Mark private: false for facts about the user and project context
|
|
186
|
+
- Omit trivial exchanges — only encode what would matter in a future session
|
|
187
|
+
- Salience: 1.0 = extremely important, 0.5 = useful, 0.3 = background context
|
|
188
|
+
- Return empty memories array if nothing is worth encoding`,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: turns.length > 0
|
|
193
|
+
? `Reflect on this conversation and identify what to encode:\n\n${transcript}`
|
|
194
|
+
: 'No conversation turns to reflect on.',
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
}
|
package/src/recall.js
CHANGED
|
@@ -150,7 +150,8 @@ function matchesDateFilters(createdAt, filters) {
|
|
|
150
150
|
return true;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters = {}) {
|
|
153
|
+
function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters = {}, includePrivate = false) {
|
|
154
|
+
const privateClause = includePrivate ? '' : 'AND e."private" = 0';
|
|
154
155
|
const rows = db.prepare(`
|
|
155
156
|
SELECT e.*, (1.0 - v.distance) AS similarity
|
|
156
157
|
FROM vec_episodes v
|
|
@@ -158,6 +159,7 @@ function knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includePro
|
|
|
158
159
|
WHERE v.embedding MATCH ?
|
|
159
160
|
AND k = ?
|
|
160
161
|
AND e.superseded_by IS NULL
|
|
162
|
+
${privateClause}
|
|
161
163
|
`).all(queryBuffer, candidateK);
|
|
162
164
|
|
|
163
165
|
const results = [];
|
|
@@ -258,6 +260,7 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
258
260
|
sources,
|
|
259
261
|
after,
|
|
260
262
|
before,
|
|
263
|
+
includePrivate = false,
|
|
261
264
|
} = options;
|
|
262
265
|
|
|
263
266
|
const queryVector = await embeddingProvider.embed(query);
|
|
@@ -271,7 +274,7 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
271
274
|
const allResults = [];
|
|
272
275
|
|
|
273
276
|
if (searchTypes.includes('episodic')) {
|
|
274
|
-
const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters);
|
|
277
|
+
const episodic = knnEpisodic(db, queryBuffer, candidateK, now, minConfidence, includeProvenance, confidenceConfig, filters, includePrivate);
|
|
275
278
|
allResults.push(...episodic);
|
|
276
279
|
}
|
|
277
280
|
|
|
@@ -281,13 +284,11 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
281
284
|
allResults.push(...semResults);
|
|
282
285
|
|
|
283
286
|
if (semIds.length > 0) {
|
|
284
|
-
const updateStmt = db.prepare(
|
|
285
|
-
'UPDATE semantics SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id = ?'
|
|
286
|
-
);
|
|
287
287
|
const nowISO = now.toISOString();
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
const placeholders = semIds.map(() => '?').join(',');
|
|
289
|
+
db.prepare(
|
|
290
|
+
`UPDATE semantics SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
|
|
291
|
+
).run(nowISO, ...semIds);
|
|
291
292
|
}
|
|
292
293
|
}
|
|
293
294
|
|
|
@@ -297,13 +298,11 @@ export async function* recallStream(db, embeddingProvider, query, options = {})
|
|
|
297
298
|
allResults.push(...procResults);
|
|
298
299
|
|
|
299
300
|
if (procIds.length > 0) {
|
|
300
|
-
const updateStmt = db.prepare(
|
|
301
|
-
'UPDATE procedures SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id = ?'
|
|
302
|
-
);
|
|
303
301
|
const nowISO = now.toISOString();
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
302
|
+
const placeholders = procIds.map(() => '?').join(',');
|
|
303
|
+
db.prepare(
|
|
304
|
+
`UPDATE procedures SET retrieval_count = retrieval_count + 1, last_reinforced_at = ? WHERE id IN (${placeholders})`
|
|
305
|
+
).run(nowISO, ...procIds);
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
308
|
|