agent-working-memory 0.7.16 → 0.8.5
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 +156 -15
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +2 -16
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +2 -11
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/common.d.ts +18 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +254 -141
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor.js +2 -15
- package/dist/adapters/cursor.js.map +1 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +28 -34
- package/dist/adapters/http.js.map +1 -1
- package/dist/api/routes.d.ts +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +280 -113
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts +42 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +268 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +45 -1
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.d.ts +24 -0
- package/dist/coordination/circuit-breaker.d.ts.map +1 -0
- package/dist/coordination/circuit-breaker.js +70 -0
- package/dist/coordination/circuit-breaker.js.map +1 -0
- package/dist/coordination/failure-modes.d.ts +21 -0
- package/dist/coordination/failure-modes.d.ts.map +1 -0
- package/dist/coordination/failure-modes.js +49 -0
- package/dist/coordination/failure-modes.js.map +1 -0
- package/dist/coordination/peer-decisions.js +7 -7
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +44 -1
- package/dist/coordination/routes.js.map +1 -1
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +18 -0
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/schemas.d.ts +9 -9
- package/dist/coordination/stale.d.ts +9 -1
- package/dist/coordination/stale.d.ts.map +1 -1
- package/dist/coordination/stale.js +53 -9
- package/dist/coordination/stale.js.map +1 -1
- package/dist/core/embeddings.d.ts +16 -10
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +54 -38
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/ml-worker-entry.d.ts +17 -0
- package/dist/core/ml-worker-entry.d.ts.map +1 -0
- package/dist/core/ml-worker-entry.js +193 -0
- package/dist/core/ml-worker-entry.js.map +1 -0
- package/dist/core/ml-worker.d.ts +59 -0
- package/dist/core/ml-worker.d.ts.map +1 -0
- package/dist/core/ml-worker.js +253 -0
- package/dist/core/ml-worker.js.map +1 -0
- package/dist/core/query-expander.d.ts +13 -15
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +48 -54
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.d.ts +9 -19
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +33 -35
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts +32 -5
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +201 -28
- package/dist/core/salience.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +128 -0
- package/dist/core/write-pipeline.d.ts.map +1 -0
- package/dist/core/write-pipeline.js +405 -0
- package/dist/core/write-pipeline.js.map +1 -0
- package/dist/engine/activation.d.ts +2 -2
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +238 -115
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/confidence.d.ts +62 -0
- package/dist/engine/confidence.d.ts.map +1 -0
- package/dist/engine/confidence.js +100 -0
- package/dist/engine/confidence.js.map +1 -0
- package/dist/engine/connections.d.ts +47 -8
- package/dist/engine/connections.d.ts.map +1 -1
- package/dist/engine/connections.js +83 -22
- package/dist/engine/connections.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +46 -10
- package/dist/engine/consolidation-scheduler.d.ts.map +1 -1
- package/dist/engine/consolidation-scheduler.js +170 -50
- package/dist/engine/consolidation-scheduler.js.map +1 -1
- package/dist/engine/consolidation.d.ts +16 -2
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +143 -44
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts +2 -2
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +9 -9
- package/dist/engine/eval.js.map +1 -1
- package/dist/engine/eviction.d.ts +4 -4
- package/dist/engine/eviction.d.ts.map +1 -1
- package/dist/engine/eviction.js +15 -15
- package/dist/engine/eviction.js.map +1 -1
- package/dist/engine/retraction.d.ts +93 -5
- package/dist/engine/retraction.d.ts.map +1 -1
- package/dist/engine/retraction.js +238 -19
- package/dist/engine/retraction.js.map +1 -1
- package/dist/engine/staging.d.ts +1 -1
- package/dist/engine/staging.d.ts.map +1 -1
- package/dist/engine/staging.js +3 -3
- package/dist/engine/staging.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +1 -1
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +4 -4
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +48 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +110 -155
- package/dist/mcp.js.map +1 -1
- package/dist/storage/factory.d.ts +55 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +135 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts +20 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -0
- package/dist/storage/pglite-schema.js +165 -0
- package/dist/storage/pglite-schema.js.map +1 -0
- package/dist/storage/pglite.d.ts +187 -0
- package/dist/storage/pglite.d.ts.map +1 -0
- package/dist/storage/pglite.js +1114 -0
- package/dist/storage/pglite.js.map +1 -0
- package/dist/storage/sqlite.d.ts +117 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +357 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.d.ts +63 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +27 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/types/engram.d.ts +92 -2
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +3 -1
- package/src/adapters/claude-code.ts +218 -234
- package/src/adapters/codex.ts +252 -262
- package/src/adapters/common.ts +411 -284
- package/src/adapters/cursor.ts +135 -150
- package/src/adapters/http.ts +93 -100
- package/src/adapters/index.ts +31 -31
- package/src/adapters/types.ts +75 -75
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +367 -120
- package/src/cli/migrate.ts +307 -0
- package/src/cli.ts +37 -1
- package/src/coordination/circuit-breaker.ts +83 -0
- package/src/coordination/events.ts +90 -90
- package/src/coordination/failure-modes.ts +50 -0
- package/src/coordination/peer-decisions.ts +105 -105
- package/src/coordination/plugin-loader.ts +60 -60
- package/src/coordination/plugin.ts +44 -44
- package/src/coordination/routes.ts +56 -1
- package/src/coordination/schema.ts +13 -0
- package/src/coordination/stale.ts +80 -11
- package/src/coordination/types.ts +311 -311
- package/src/coordination/write-mutex.ts +69 -69
- package/src/core/auto-tagger.ts +168 -168
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -93
- package/src/core/index.ts +5 -5
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -0
- package/src/core/ml-worker.ts +281 -0
- package/src/core/query-expander.ts +122 -128
- package/src/core/reranker.ts +119 -125
- package/src/core/salience.ts +200 -33
- package/src/core/write-pipeline.ts +521 -0
- package/src/engine/activation.ts +235 -109
- package/src/engine/confidence.ts +120 -0
- package/src/engine/connections.ts +162 -103
- package/src/engine/consolidation-scheduler.ts +242 -125
- package/src/engine/consolidation.ts +138 -45
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -116
- package/src/engine/staging.ts +74 -74
- package/src/hooks/sidecar.ts +5 -5
- package/src/index.ts +42 -28
- package/src/mcp.ts +105 -152
- package/src/storage/factory.ts +147 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -0
- package/src/storage/pglite.ts +1363 -0
- package/src/storage/sqlite.ts +402 -7
- package/src/storage/store.ts +80 -0
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/engram.ts +101 -3
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* Promise-based write mutex for SQLite coordination routes.
|
|
5
|
-
*
|
|
6
|
-
* SQLite only allows one writer at a time. Under burst from 5+ concurrent
|
|
7
|
-
* workers, multiple async handlers can race for the write lock, causing
|
|
8
|
-
* SQLITE_BUSY errors. This mutex serializes write operations (POST, PATCH,
|
|
9
|
-
* PUT, DELETE) through a single-concurrency queue while keeping reads (GET)
|
|
10
|
-
* unguarded for full parallelism.
|
|
11
|
-
*
|
|
12
|
-
* Usage: Register as a Fastify preHandler hook in the coordination module.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Creates a simple single-concurrency mutex.
|
|
17
|
-
* acquire() returns a release function — call it when done.
|
|
18
|
-
*/
|
|
19
|
-
export function createWriteMutex(): {
|
|
20
|
-
acquire(): Promise<() => void>;
|
|
21
|
-
pending: () => number;
|
|
22
|
-
} {
|
|
23
|
-
let queue: Array<(release: () => void) => void> = [];
|
|
24
|
-
let locked = false;
|
|
25
|
-
|
|
26
|
-
function release(): void {
|
|
27
|
-
const next = queue.shift();
|
|
28
|
-
if (next) {
|
|
29
|
-
// Hand lock directly to next waiter (no unlock/relock gap)
|
|
30
|
-
next(release);
|
|
31
|
-
} else {
|
|
32
|
-
locked = false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function acquire(): Promise<() => void> {
|
|
37
|
-
if (!locked) {
|
|
38
|
-
locked = true;
|
|
39
|
-
return Promise.resolve(release);
|
|
40
|
-
}
|
|
41
|
-
return new Promise<() => void>((resolve) => {
|
|
42
|
-
queue.push(resolve);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
acquire,
|
|
48
|
-
pending: () => queue.length,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** HTTP methods that perform writes and need serialization. */
|
|
53
|
-
const WRITE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
|
|
54
|
-
|
|
55
|
-
/** Routes that are safe to skip the mutex (read-only despite POST method). */
|
|
56
|
-
const READ_ONLY_ROUTES = new Set(['/next']);
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Check if a request needs write serialization.
|
|
60
|
-
* GET/HEAD/OPTIONS are always reads. POST /next is a read (checkin is idempotent upsert
|
|
61
|
-
* but low-contention). All other write methods go through the mutex.
|
|
62
|
-
*/
|
|
63
|
-
export function needsWriteLock(method: string, url: string): boolean {
|
|
64
|
-
if (!WRITE_METHODS.has(method)) return false;
|
|
65
|
-
// Strip query string for route matching
|
|
66
|
-
const path = url.split('?')[0];
|
|
67
|
-
if (READ_ONLY_ROUTES.has(path)) return false;
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Promise-based write mutex for SQLite coordination routes.
|
|
5
|
+
*
|
|
6
|
+
* SQLite only allows one writer at a time. Under burst from 5+ concurrent
|
|
7
|
+
* workers, multiple async handlers can race for the write lock, causing
|
|
8
|
+
* SQLITE_BUSY errors. This mutex serializes write operations (POST, PATCH,
|
|
9
|
+
* PUT, DELETE) through a single-concurrency queue while keeping reads (GET)
|
|
10
|
+
* unguarded for full parallelism.
|
|
11
|
+
*
|
|
12
|
+
* Usage: Register as a Fastify preHandler hook in the coordination module.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a simple single-concurrency mutex.
|
|
17
|
+
* acquire() returns a release function — call it when done.
|
|
18
|
+
*/
|
|
19
|
+
export function createWriteMutex(): {
|
|
20
|
+
acquire(): Promise<() => void>;
|
|
21
|
+
pending: () => number;
|
|
22
|
+
} {
|
|
23
|
+
let queue: Array<(release: () => void) => void> = [];
|
|
24
|
+
let locked = false;
|
|
25
|
+
|
|
26
|
+
function release(): void {
|
|
27
|
+
const next = queue.shift();
|
|
28
|
+
if (next) {
|
|
29
|
+
// Hand lock directly to next waiter (no unlock/relock gap)
|
|
30
|
+
next(release);
|
|
31
|
+
} else {
|
|
32
|
+
locked = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function acquire(): Promise<() => void> {
|
|
37
|
+
if (!locked) {
|
|
38
|
+
locked = true;
|
|
39
|
+
return Promise.resolve(release);
|
|
40
|
+
}
|
|
41
|
+
return new Promise<() => void>((resolve) => {
|
|
42
|
+
queue.push(resolve);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
acquire,
|
|
48
|
+
pending: () => queue.length,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** HTTP methods that perform writes and need serialization. */
|
|
53
|
+
const WRITE_METHODS = new Set(['POST', 'PATCH', 'PUT', 'DELETE']);
|
|
54
|
+
|
|
55
|
+
/** Routes that are safe to skip the mutex (read-only despite POST method). */
|
|
56
|
+
const READ_ONLY_ROUTES = new Set(['/next']);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a request needs write serialization.
|
|
60
|
+
* GET/HEAD/OPTIONS are always reads. POST /next is a read (checkin is idempotent upsert
|
|
61
|
+
* but low-contention). All other write methods go through the mutex.
|
|
62
|
+
*/
|
|
63
|
+
export function needsWriteLock(method: string, url: string): boolean {
|
|
64
|
+
if (!WRITE_METHODS.has(method)) return false;
|
|
65
|
+
// Strip query string for route matching
|
|
66
|
+
const path = url.split('?')[0];
|
|
67
|
+
if (READ_ONLY_ROUTES.has(path)) return false;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
package/src/core/auto-tagger.ts
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* Auto-Tagger — generates meta-tags for memories at write time.
|
|
5
|
-
*
|
|
6
|
-
* Meta-tags serve as categorical signals that boost BM25 recall.
|
|
7
|
-
* They're indexed in FTS5 alongside concept/content/tags, so queries
|
|
8
|
-
* that match a category tag get better BM25 scores.
|
|
9
|
-
*
|
|
10
|
-
* Three sources of tags:
|
|
11
|
-
* 1. Content analysis — extract topics, entities, categories from the text
|
|
12
|
-
* 2. Context propagation — inherit relevant tags from related memories
|
|
13
|
-
* 3. Type classification — fact/experience/belief/entity markers
|
|
14
|
-
*
|
|
15
|
-
* Design: lightweight heuristics, no LLM calls. Tags are additive —
|
|
16
|
-
* they enrich the existing tag set without replacing user-provided tags.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Extract meta-tags from memory content using keyword patterns.
|
|
21
|
-
* Returns tags prefixed with 'cat:' to distinguish from user tags.
|
|
22
|
-
*/
|
|
23
|
-
export function extractMetaTags(concept: string, content: string): string[] {
|
|
24
|
-
const tags: string[] = [];
|
|
25
|
-
const text = `${concept} ${content}`.toLowerCase();
|
|
26
|
-
|
|
27
|
-
// --- Category tags (broad topic classification) ---
|
|
28
|
-
|
|
29
|
-
// People / personal
|
|
30
|
-
if (/\b(i |my |i'm |i've |we |our |me )\b/.test(text)) {
|
|
31
|
-
tags.push('cat:personal');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Work / professional
|
|
35
|
-
if (/\b(work|job|office|meeting|project|team|manager|colleague|career|salary|hired)\b/.test(text)) {
|
|
36
|
-
tags.push('cat:work');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Technology / computing
|
|
40
|
-
if (/\b(code|programming|software|database|api|server|deploy|bug|git|typescript|python|react|node)\b/.test(text)) {
|
|
41
|
-
tags.push('cat:tech');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Health / wellness
|
|
45
|
-
if (/\b(health|doctor|exercise|yoga|gym|diet|sleep|meditation|therapy|medicine|symptom)\b/.test(text)) {
|
|
46
|
-
tags.push('cat:health');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Finance / money
|
|
50
|
-
if (/\b(money|budget|savings|invest|salary|cost|price|payment|bank|credit|expense|coupon|store|bought|purchased)\b/.test(text)) {
|
|
51
|
-
tags.push('cat:finance');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Home / living
|
|
55
|
-
if (/\b(home|house|apartment|room|kitchen|bedroom|furniture|garden|repair|renovation|neighbor|moved)\b/.test(text)) {
|
|
56
|
-
tags.push('cat:home');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Travel / location
|
|
60
|
-
if (/\b(travel|trip|vacation|flight|hotel|restaurant|city|country|visited|downtown|park)\b/.test(text)) {
|
|
61
|
-
tags.push('cat:location');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Education / learning
|
|
65
|
-
if (/\b(school|university|college|degree|course|class|study|learn|graduate|student|teacher|exam)\b/.test(text)) {
|
|
66
|
-
tags.push('cat:education');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Social / relationships
|
|
70
|
-
if (/\b(friend|family|partner|spouse|child|parent|sibling|birthday|party|dinner|wedding|date)\b/.test(text)) {
|
|
71
|
-
tags.push('cat:social');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Hobbies / entertainment
|
|
75
|
-
if (/\b(music|movie|book|game|sport|hobby|play|concert|theater|playlist|podcast|guitar|tennis|yoga|painting)\b/.test(text)) {
|
|
76
|
-
tags.push('cat:hobby');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Shopping / consumer
|
|
80
|
-
if (/\b(bought|purchased|ordered|shop|store|amazon|target|walmart|coupon|sale|discount|delivery)\b/.test(text)) {
|
|
81
|
-
tags.push('cat:shopping');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Food / cooking
|
|
85
|
-
if (/\b(cook|recipe|restaurant|meal|food|dinner|lunch|breakfast|coffee|tea|bake|kitchen)\b/.test(text)) {
|
|
86
|
-
tags.push('cat:food');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Pets / animals
|
|
90
|
-
if (/\b(pet|dog|cat|animal|vet|shelter|walk|breed)\b/.test(text)) {
|
|
91
|
-
tags.push('cat:pets');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Time markers
|
|
95
|
-
if (/\b(yesterday|today|last week|last month|tomorrow|next week|this morning|this evening|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/.test(text)) {
|
|
96
|
-
tags.push('cat:temporal');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// --- Entity extraction (simple noun phrase patterns) ---
|
|
100
|
-
|
|
101
|
-
// Proper nouns (capitalized words that aren't sentence starters)
|
|
102
|
-
const properNouns = content.match(/(?:^|\.\s+)?([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/g);
|
|
103
|
-
if (properNouns) {
|
|
104
|
-
const unique = [...new Set(properNouns.map(n => n.trim()).filter(n => n.length > 2 && n.length < 30))];
|
|
105
|
-
for (const noun of unique.slice(0, 5)) {
|
|
106
|
-
tags.push(`entity:${noun}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// --- Knowledge type tags ---
|
|
111
|
-
|
|
112
|
-
// Preference (I like/prefer/enjoy/love/hate)
|
|
113
|
-
if (/\b(i like|i prefer|i enjoy|i love|i hate|my favorite|i don't like)\b/.test(text)) {
|
|
114
|
-
tags.push('cat:preference');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Fact (declarative statements about identity/attributes)
|
|
118
|
-
if (/\b(my name is|i am a|i work at|i live in|i graduated|my birthday|i was born)\b/.test(text)) {
|
|
119
|
-
tags.push('cat:identity');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Plan / intention
|
|
123
|
-
if (/\b(i plan to|i'm going to|i want to|i'm thinking of|i'm considering|next week i|planning to)\b/.test(text)) {
|
|
124
|
-
tags.push('cat:plan');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Experience / event
|
|
128
|
-
if (/\b(i went|i visited|i attended|i tried|i saw|i heard|i found|i discovered)\b/.test(text)) {
|
|
129
|
-
tags.push('cat:experience');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return tags;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Propagate relevant tags from related memories to a new memory.
|
|
137
|
-
* Called after connection engine links the new memory to existing ones.
|
|
138
|
-
*
|
|
139
|
-
* Strategy: inherit meta-tags from strongly connected neighbors,
|
|
140
|
-
* but only tags that appear in 2+ neighbors (consensus filtering).
|
|
141
|
-
*/
|
|
142
|
-
export function propagateTagsFromNeighbors(
|
|
143
|
-
existingTags: string[],
|
|
144
|
-
neighborTagSets: string[][],
|
|
145
|
-
): string[] {
|
|
146
|
-
if (neighborTagSets.length === 0) return [];
|
|
147
|
-
|
|
148
|
-
// Count meta-tag occurrences across neighbors
|
|
149
|
-
const tagCounts = new Map<string, number>();
|
|
150
|
-
for (const tags of neighborTagSets) {
|
|
151
|
-
for (const tag of tags) {
|
|
152
|
-
if (tag.startsWith('cat:') || tag.startsWith('entity:')) {
|
|
153
|
-
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Only propagate tags that appear in 2+ neighbors (consensus)
|
|
159
|
-
const propagated: string[] = [];
|
|
160
|
-
const existingSet = new Set(existingTags);
|
|
161
|
-
for (const [tag, count] of tagCounts) {
|
|
162
|
-
if (count >= 2 && !existingSet.has(tag)) {
|
|
163
|
-
propagated.push(tag);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return propagated.slice(0, 5); // Cap to avoid tag bloat
|
|
168
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Auto-Tagger — generates meta-tags for memories at write time.
|
|
5
|
+
*
|
|
6
|
+
* Meta-tags serve as categorical signals that boost BM25 recall.
|
|
7
|
+
* They're indexed in FTS5 alongside concept/content/tags, so queries
|
|
8
|
+
* that match a category tag get better BM25 scores.
|
|
9
|
+
*
|
|
10
|
+
* Three sources of tags:
|
|
11
|
+
* 1. Content analysis — extract topics, entities, categories from the text
|
|
12
|
+
* 2. Context propagation — inherit relevant tags from related memories
|
|
13
|
+
* 3. Type classification — fact/experience/belief/entity markers
|
|
14
|
+
*
|
|
15
|
+
* Design: lightweight heuristics, no LLM calls. Tags are additive —
|
|
16
|
+
* they enrich the existing tag set without replacing user-provided tags.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract meta-tags from memory content using keyword patterns.
|
|
21
|
+
* Returns tags prefixed with 'cat:' to distinguish from user tags.
|
|
22
|
+
*/
|
|
23
|
+
export function extractMetaTags(concept: string, content: string): string[] {
|
|
24
|
+
const tags: string[] = [];
|
|
25
|
+
const text = `${concept} ${content}`.toLowerCase();
|
|
26
|
+
|
|
27
|
+
// --- Category tags (broad topic classification) ---
|
|
28
|
+
|
|
29
|
+
// People / personal
|
|
30
|
+
if (/\b(i |my |i'm |i've |we |our |me )\b/.test(text)) {
|
|
31
|
+
tags.push('cat:personal');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Work / professional
|
|
35
|
+
if (/\b(work|job|office|meeting|project|team|manager|colleague|career|salary|hired)\b/.test(text)) {
|
|
36
|
+
tags.push('cat:work');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Technology / computing
|
|
40
|
+
if (/\b(code|programming|software|database|api|server|deploy|bug|git|typescript|python|react|node)\b/.test(text)) {
|
|
41
|
+
tags.push('cat:tech');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Health / wellness
|
|
45
|
+
if (/\b(health|doctor|exercise|yoga|gym|diet|sleep|meditation|therapy|medicine|symptom)\b/.test(text)) {
|
|
46
|
+
tags.push('cat:health');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Finance / money
|
|
50
|
+
if (/\b(money|budget|savings|invest|salary|cost|price|payment|bank|credit|expense|coupon|store|bought|purchased)\b/.test(text)) {
|
|
51
|
+
tags.push('cat:finance');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Home / living
|
|
55
|
+
if (/\b(home|house|apartment|room|kitchen|bedroom|furniture|garden|repair|renovation|neighbor|moved)\b/.test(text)) {
|
|
56
|
+
tags.push('cat:home');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Travel / location
|
|
60
|
+
if (/\b(travel|trip|vacation|flight|hotel|restaurant|city|country|visited|downtown|park)\b/.test(text)) {
|
|
61
|
+
tags.push('cat:location');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Education / learning
|
|
65
|
+
if (/\b(school|university|college|degree|course|class|study|learn|graduate|student|teacher|exam)\b/.test(text)) {
|
|
66
|
+
tags.push('cat:education');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Social / relationships
|
|
70
|
+
if (/\b(friend|family|partner|spouse|child|parent|sibling|birthday|party|dinner|wedding|date)\b/.test(text)) {
|
|
71
|
+
tags.push('cat:social');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Hobbies / entertainment
|
|
75
|
+
if (/\b(music|movie|book|game|sport|hobby|play|concert|theater|playlist|podcast|guitar|tennis|yoga|painting)\b/.test(text)) {
|
|
76
|
+
tags.push('cat:hobby');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Shopping / consumer
|
|
80
|
+
if (/\b(bought|purchased|ordered|shop|store|amazon|target|walmart|coupon|sale|discount|delivery)\b/.test(text)) {
|
|
81
|
+
tags.push('cat:shopping');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Food / cooking
|
|
85
|
+
if (/\b(cook|recipe|restaurant|meal|food|dinner|lunch|breakfast|coffee|tea|bake|kitchen)\b/.test(text)) {
|
|
86
|
+
tags.push('cat:food');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Pets / animals
|
|
90
|
+
if (/\b(pet|dog|cat|animal|vet|shelter|walk|breed)\b/.test(text)) {
|
|
91
|
+
tags.push('cat:pets');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Time markers
|
|
95
|
+
if (/\b(yesterday|today|last week|last month|tomorrow|next week|this morning|this evening|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/.test(text)) {
|
|
96
|
+
tags.push('cat:temporal');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- Entity extraction (simple noun phrase patterns) ---
|
|
100
|
+
|
|
101
|
+
// Proper nouns (capitalized words that aren't sentence starters)
|
|
102
|
+
const properNouns = content.match(/(?:^|\.\s+)?([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/g);
|
|
103
|
+
if (properNouns) {
|
|
104
|
+
const unique = [...new Set(properNouns.map(n => n.trim()).filter(n => n.length > 2 && n.length < 30))];
|
|
105
|
+
for (const noun of unique.slice(0, 5)) {
|
|
106
|
+
tags.push(`entity:${noun}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Knowledge type tags ---
|
|
111
|
+
|
|
112
|
+
// Preference (I like/prefer/enjoy/love/hate)
|
|
113
|
+
if (/\b(i like|i prefer|i enjoy|i love|i hate|my favorite|i don't like)\b/.test(text)) {
|
|
114
|
+
tags.push('cat:preference');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fact (declarative statements about identity/attributes)
|
|
118
|
+
if (/\b(my name is|i am a|i work at|i live in|i graduated|my birthday|i was born)\b/.test(text)) {
|
|
119
|
+
tags.push('cat:identity');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Plan / intention
|
|
123
|
+
if (/\b(i plan to|i'm going to|i want to|i'm thinking of|i'm considering|next week i|planning to)\b/.test(text)) {
|
|
124
|
+
tags.push('cat:plan');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Experience / event
|
|
128
|
+
if (/\b(i went|i visited|i attended|i tried|i saw|i heard|i found|i discovered)\b/.test(text)) {
|
|
129
|
+
tags.push('cat:experience');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return tags;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Propagate relevant tags from related memories to a new memory.
|
|
137
|
+
* Called after connection engine links the new memory to existing ones.
|
|
138
|
+
*
|
|
139
|
+
* Strategy: inherit meta-tags from strongly connected neighbors,
|
|
140
|
+
* but only tags that appear in 2+ neighbors (consensus filtering).
|
|
141
|
+
*/
|
|
142
|
+
export function propagateTagsFromNeighbors(
|
|
143
|
+
existingTags: string[],
|
|
144
|
+
neighborTagSets: string[][],
|
|
145
|
+
): string[] {
|
|
146
|
+
if (neighborTagSets.length === 0) return [];
|
|
147
|
+
|
|
148
|
+
// Count meta-tag occurrences across neighbors
|
|
149
|
+
const tagCounts = new Map<string, number>();
|
|
150
|
+
for (const tags of neighborTagSets) {
|
|
151
|
+
for (const tag of tags) {
|
|
152
|
+
if (tag.startsWith('cat:') || tag.startsWith('entity:')) {
|
|
153
|
+
tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Only propagate tags that appear in 2+ neighbors (consensus)
|
|
159
|
+
const propagated: string[] = [];
|
|
160
|
+
const existingSet = new Set(existingTags);
|
|
161
|
+
for (const [tag, count] of tagCounts) {
|
|
162
|
+
if (count >= 2 && !existingSet.has(tag)) {
|
|
163
|
+
propagated.push(tag);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return propagated.slice(0, 5); // Cap to avoid tag bloat
|
|
168
|
+
}
|
package/src/core/decay.ts
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* ACT-R Base-Level Activation
|
|
5
|
-
*
|
|
6
|
-
* Based on Anderson's ACT-R cognitive architecture (1993).
|
|
7
|
-
* Memories that are accessed more recently and more frequently
|
|
8
|
-
* have higher activation — a well-established model of human memory.
|
|
9
|
-
*
|
|
10
|
-
* Formula: B(M) = ln(n + 1) - d * ln(ageDays / (n + 1))
|
|
11
|
-
*
|
|
12
|
-
* Where:
|
|
13
|
-
* n = access count
|
|
14
|
-
* d = decay exponent (default 0.5)
|
|
15
|
-
* ageDays = age of memory in days
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export function baseLevelActivation(
|
|
19
|
-
accessCount: number,
|
|
20
|
-
ageDays: number,
|
|
21
|
-
decayExponent: number = 0.5
|
|
22
|
-
): number {
|
|
23
|
-
const n = Math.max(accessCount, 0);
|
|
24
|
-
const age = Math.max(ageDays, 0.001); // Avoid log(0)
|
|
25
|
-
return Math.log(n + 1) - decayExponent * Math.log(age / (n + 1));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Softplus — smooth approximation of ReLU.
|
|
30
|
-
* Used to keep activation scores positive without hard clipping.
|
|
31
|
-
*/
|
|
32
|
-
export function softplus(x: number): number {
|
|
33
|
-
return Math.log(1 + Math.exp(x));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Composite activation score combining content match, temporal decay,
|
|
38
|
-
* Hebbian boost, and confidence.
|
|
39
|
-
*
|
|
40
|
-
* Score = contentMatch * softplus(B(M) + scale * hebbianBoost) * confidence
|
|
41
|
-
*/
|
|
42
|
-
export function compositeScore(params: {
|
|
43
|
-
contentMatch: number;
|
|
44
|
-
accessCount: number;
|
|
45
|
-
ageDays: number;
|
|
46
|
-
hebbianBoost: number;
|
|
47
|
-
confidence: number;
|
|
48
|
-
decayExponent?: number;
|
|
49
|
-
hebbianScale?: number;
|
|
50
|
-
}): number {
|
|
51
|
-
const {
|
|
52
|
-
contentMatch,
|
|
53
|
-
accessCount,
|
|
54
|
-
ageDays,
|
|
55
|
-
hebbianBoost,
|
|
56
|
-
confidence,
|
|
57
|
-
decayExponent = 0.5,
|
|
58
|
-
hebbianScale = 1.0,
|
|
59
|
-
} = params;
|
|
60
|
-
|
|
61
|
-
const bm = baseLevelActivation(accessCount, ageDays, decayExponent);
|
|
62
|
-
return contentMatch * softplus(bm + hebbianScale * hebbianBoost) * confidence;
|
|
63
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* ACT-R Base-Level Activation
|
|
5
|
+
*
|
|
6
|
+
* Based on Anderson's ACT-R cognitive architecture (1993).
|
|
7
|
+
* Memories that are accessed more recently and more frequently
|
|
8
|
+
* have higher activation — a well-established model of human memory.
|
|
9
|
+
*
|
|
10
|
+
* Formula: B(M) = ln(n + 1) - d * ln(ageDays / (n + 1))
|
|
11
|
+
*
|
|
12
|
+
* Where:
|
|
13
|
+
* n = access count
|
|
14
|
+
* d = decay exponent (default 0.5)
|
|
15
|
+
* ageDays = age of memory in days
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export function baseLevelActivation(
|
|
19
|
+
accessCount: number,
|
|
20
|
+
ageDays: number,
|
|
21
|
+
decayExponent: number = 0.5
|
|
22
|
+
): number {
|
|
23
|
+
const n = Math.max(accessCount, 0);
|
|
24
|
+
const age = Math.max(ageDays, 0.001); // Avoid log(0)
|
|
25
|
+
return Math.log(n + 1) - decayExponent * Math.log(age / (n + 1));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Softplus — smooth approximation of ReLU.
|
|
30
|
+
* Used to keep activation scores positive without hard clipping.
|
|
31
|
+
*/
|
|
32
|
+
export function softplus(x: number): number {
|
|
33
|
+
return Math.log(1 + Math.exp(x));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Composite activation score combining content match, temporal decay,
|
|
38
|
+
* Hebbian boost, and confidence.
|
|
39
|
+
*
|
|
40
|
+
* Score = contentMatch * softplus(B(M) + scale * hebbianBoost) * confidence
|
|
41
|
+
*/
|
|
42
|
+
export function compositeScore(params: {
|
|
43
|
+
contentMatch: number;
|
|
44
|
+
accessCount: number;
|
|
45
|
+
ageDays: number;
|
|
46
|
+
hebbianBoost: number;
|
|
47
|
+
confidence: number;
|
|
48
|
+
decayExponent?: number;
|
|
49
|
+
hebbianScale?: number;
|
|
50
|
+
}): number {
|
|
51
|
+
const {
|
|
52
|
+
contentMatch,
|
|
53
|
+
accessCount,
|
|
54
|
+
ageDays,
|
|
55
|
+
hebbianBoost,
|
|
56
|
+
confidence,
|
|
57
|
+
decayExponent = 0.5,
|
|
58
|
+
hebbianScale = 1.0,
|
|
59
|
+
} = params;
|
|
60
|
+
|
|
61
|
+
const bm = baseLevelActivation(accessCount, ageDays, decayExponent);
|
|
62
|
+
return contentMatch * softplus(bm + hebbianScale * hebbianBoost) * confidence;
|
|
63
|
+
}
|