linksee-memory 0.1.0 → 0.1.2
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 +16 -0
- package/dist/db/migrate.js +14 -0
- package/dist/mcp/server.js +19 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -296,6 +296,22 @@ Caveat and active-goal layers are always preserved. Consider scheduling a weekly
|
|
|
296
296
|
|
|
297
297
|
## Changelog
|
|
298
298
|
|
|
299
|
+
### v0.1.1 — Pin threshold tweak (2026-04-19)
|
|
300
|
+
|
|
301
|
+
Based on real-world feedback that `importance=0.95` memories were not
|
|
302
|
+
being treated as pinned despite intent.
|
|
303
|
+
|
|
304
|
+
- **Pin threshold lowered from `>= 1.0` to `>= 0.9`.** Memories with
|
|
305
|
+
`importance >= 0.9` are now exempt from the auto-forget sweep and
|
|
306
|
+
surface `pinned: true` in `recall` and `remember` responses. This
|
|
307
|
+
matches the natural mental model ("0.9 = high importance = should
|
|
308
|
+
survive cleanup") without requiring exact `1.0`.
|
|
309
|
+
- All existing memories with `importance >= 0.9` (including older ones
|
|
310
|
+
set to `0.9` or `0.95`) become pinned automatically — no migration
|
|
311
|
+
needed.
|
|
312
|
+
- Updated tool descriptions and error messages to reflect the new
|
|
313
|
+
threshold.
|
|
314
|
+
|
|
299
315
|
### v0.1.0 — Major UX update (2026-04-18)
|
|
300
316
|
|
|
301
317
|
Based on one week of dogfooding, here's what changed:
|
package/dist/db/migrate.js
CHANGED
|
@@ -43,6 +43,20 @@ export function runMigrations(db) {
|
|
|
43
43
|
if (currentVersion > 0 && currentVersion < 4) {
|
|
44
44
|
db.exec(`INSERT INTO memories_fts(rowid, content) SELECT id, content FROM memories;`);
|
|
45
45
|
}
|
|
46
|
+
// v0.1.1 data migration: pin threshold lowered from 1.0 to 0.9.
|
|
47
|
+
//
|
|
48
|
+
// Before v0.1.1, `remember()` only set `protected = 1` for memories
|
|
49
|
+
// inserted with importance >= 1.0. After v0.1.1, the threshold is 0.9.
|
|
50
|
+
// Existing rows written under the old rule need to be reconciled so that
|
|
51
|
+
// `recall().pinned`, `list_entities.pinned_count`, and the auto-forget
|
|
52
|
+
// guard (`WHERE protected = 0 AND importance < 0.9`) all agree.
|
|
53
|
+
//
|
|
54
|
+
// This runs on every startup but is a no-op after the first successful
|
|
55
|
+
// run (UPDATE filter excludes already-protected rows).
|
|
56
|
+
const hasMemories = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'memories'").get();
|
|
57
|
+
if (hasMemories?.name) {
|
|
58
|
+
db.prepare(`UPDATE memories SET protected = 1 WHERE importance >= 0.9 AND protected = 0`).run();
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
// CLI entrypoint
|
|
48
62
|
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('migrate.ts') || process.argv[1]?.endsWith('migrate.js')) {
|
package/dist/mcp/server.js
CHANGED
|
@@ -12,7 +12,7 @@ import { refreshMomentumForEntity } from '../lib/momentum.js';
|
|
|
12
12
|
import { consolidate as runConsolidate } from '../lib/consolidate.js';
|
|
13
13
|
import { isPastedExternalContent } from '../lib/session-parser.js';
|
|
14
14
|
import { handleReadSmart as handleReadSmartImpl } from './read-smart.js';
|
|
15
|
-
const SERVER_VERSION = '0.1.
|
|
15
|
+
const SERVER_VERSION = '0.1.1';
|
|
16
16
|
const db = openDb();
|
|
17
17
|
runMigrations(db);
|
|
18
18
|
const server = new Server({ name: 'linksee-memory', version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
@@ -57,7 +57,7 @@ const TOOLS = [
|
|
|
57
57
|
entity_key: { type: 'string', description: 'Optional canonical key (email, domain, file path)' },
|
|
58
58
|
layer: { type: 'string', description: 'One of: goal / context / emotion / implementation / caveat / learning. Common aliases (why, decisions, warnings, how, ...) are accepted.' },
|
|
59
59
|
content: { type: 'string', description: 'The memory content (plain text or JSON)' },
|
|
60
|
-
importance: { type: 'number', minimum: 0, maximum: 1, description: '0.0-1.0.
|
|
60
|
+
importance: { type: 'number', minimum: 0, maximum: 1, description: '0.0-1.0. Set to 0.9 or higher to "pin" a memory (protects from forgetting even outside caveat layer).' },
|
|
61
61
|
force: { type: 'boolean', default: false, description: 'Bypass the paste-back/CI-log quality check. Only set when you are sure the content is original user or agent thought.' },
|
|
62
62
|
},
|
|
63
63
|
required: ['entity_name', 'entity_kind', 'layer', 'content'],
|
|
@@ -93,7 +93,7 @@ const TOOLS = [
|
|
|
93
93
|
memory_id: { type: 'number', description: 'The memory.id to update' },
|
|
94
94
|
content: { type: 'string', description: 'New content (plain text or JSON). If omitted, content is kept.' },
|
|
95
95
|
layer: { type: 'string', description: 'Move to a different layer (aliases accepted). If omitted, layer is kept.' },
|
|
96
|
-
importance: { type: 'number', minimum: 0, maximum: 1, description: 'New importance 0-1. Set to
|
|
96
|
+
importance: { type: 'number', minimum: 0, maximum: 1, description: 'New importance 0-1. Set to 0.9 or higher to pin.' },
|
|
97
97
|
},
|
|
98
98
|
required: ['memory_id'],
|
|
99
99
|
},
|
|
@@ -113,7 +113,7 @@ const TOOLS = [
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
name: 'forget',
|
|
116
|
-
description: 'Explicitly delete a memory by id, OR run auto-forgetting across all memories based on forgettingRisk (importance + heat + age). Caveat-layer, goal-layer, and importance
|
|
116
|
+
description: 'Explicitly delete a memory by id, OR run auto-forgetting across all memories based on forgettingRisk (importance + heat + age). Caveat-layer, goal-layer, and pinned (importance>=0.9) memories are always preserved. Prefer update_memory for corrections — forget is destructive.',
|
|
117
117
|
inputSchema: {
|
|
118
118
|
type: 'object',
|
|
119
119
|
properties: {
|
|
@@ -205,7 +205,7 @@ function handleRemember(args) {
|
|
|
205
205
|
const importance = Math.min(1, Math.max(0, Number(args.importance ?? 0.5)));
|
|
206
206
|
const result = db
|
|
207
207
|
.prepare('INSERT INTO memories (entity_id, layer, content, importance, protected) VALUES (?, ?, ?, ?, ?)')
|
|
208
|
-
.run(entityId, layer, rawContent, importance, importance >=
|
|
208
|
+
.run(entityId, layer, rawContent, importance, importance >= 0.9 ? 1 : 0);
|
|
209
209
|
db.prepare('INSERT INTO events (entity_id, kind, payload) VALUES (?, ?, ?)').run(entityId, 'memory_stored', JSON.stringify({ layer, memory_id: result.lastInsertRowid }));
|
|
210
210
|
const mom = refreshMomentumForEntity(db, entityId);
|
|
211
211
|
return JSON.stringify({
|
|
@@ -213,7 +213,7 @@ function handleRemember(args) {
|
|
|
213
213
|
memory_id: Number(result.lastInsertRowid),
|
|
214
214
|
entity_id: entityId,
|
|
215
215
|
layer,
|
|
216
|
-
pinned: importance >=
|
|
216
|
+
pinned: importance >= 0.9,
|
|
217
217
|
momentum: { score: mom.score, band: mom.band },
|
|
218
218
|
});
|
|
219
219
|
}
|
|
@@ -372,7 +372,7 @@ function handleRecall(args) {
|
|
|
372
372
|
const heatNorm = heat.score / 100;
|
|
373
373
|
const momNorm = Math.min(1, (r.momentum_score ?? 0) / 10);
|
|
374
374
|
const importanceBoost = r.importance; // 0-1
|
|
375
|
-
// Composite: give a bit to importance so pinned (
|
|
375
|
+
// Composite: give a bit to importance so pinned (>=0.9) memories always rank high
|
|
376
376
|
const w_rel = 0.45, w_heat = 0.25, w_mom = 0.15, w_imp = 0.15;
|
|
377
377
|
const composite = w_rel * relevance + w_heat * heatNorm + w_mom * momNorm + w_imp * importanceBoost;
|
|
378
378
|
// match_reasons: human-readable WHY this row is here
|
|
@@ -393,7 +393,7 @@ function handleRecall(args) {
|
|
|
393
393
|
reasons.push('heat:warm');
|
|
394
394
|
if (r.momentum_score >= 5)
|
|
395
395
|
reasons.push('entity_active');
|
|
396
|
-
if (r.importance >=
|
|
396
|
+
if (r.importance >= 0.9)
|
|
397
397
|
reasons.push('pinned');
|
|
398
398
|
else if (r.importance >= 0.8)
|
|
399
399
|
reasons.push('high_importance');
|
|
@@ -461,7 +461,7 @@ function handleRecall(args) {
|
|
|
461
461
|
content: parsedContent,
|
|
462
462
|
content_raw: r.content,
|
|
463
463
|
importance: r.importance,
|
|
464
|
-
pinned: r.importance >=
|
|
464
|
+
pinned: r.importance >= 0.9,
|
|
465
465
|
heat: Number(r.heat_score.toFixed(1)),
|
|
466
466
|
band: r.heat_band,
|
|
467
467
|
composite: Number(r.composite_score.toFixed(3)),
|
|
@@ -473,27 +473,27 @@ function handleRecall(args) {
|
|
|
473
473
|
}
|
|
474
474
|
function handleForget(args) {
|
|
475
475
|
if (args.memory_id) {
|
|
476
|
-
// Explicit delete by id — respect protected AND pinned (importance >=
|
|
476
|
+
// Explicit delete by id — respect protected AND pinned (importance >= 0.9)
|
|
477
477
|
const target = db.prepare('SELECT id, layer, importance, protected FROM memories WHERE id = ?').get(args.memory_id);
|
|
478
478
|
if (!target) {
|
|
479
479
|
return JSON.stringify({ ok: false, error: `memory_id ${args.memory_id} not found` });
|
|
480
480
|
}
|
|
481
|
-
if (target.protected === 1 || target.importance >=
|
|
481
|
+
if (target.protected === 1 || target.importance >= 0.9) {
|
|
482
482
|
return JSON.stringify({
|
|
483
483
|
ok: false,
|
|
484
484
|
preserved: true,
|
|
485
|
-
reason: target.protected === 1 ? `${target.layer}-layer is auto-protected` : 'pinned (importance
|
|
486
|
-
hint: 'Use update_memory to lower importance below
|
|
485
|
+
reason: target.protected === 1 ? `${target.layer}-layer is auto-protected` : 'pinned (importance>=0.9)',
|
|
486
|
+
hint: 'Use update_memory to lower importance below 0.9 first, then forget.',
|
|
487
487
|
});
|
|
488
488
|
}
|
|
489
489
|
const res = db.prepare('DELETE FROM memories WHERE id = ?').run(args.memory_id);
|
|
490
490
|
return JSON.stringify({ ok: true, deleted: res.changes, memory_id: args.memory_id });
|
|
491
491
|
}
|
|
492
|
-
// Auto-sweep — also respect importance
|
|
492
|
+
// Auto-sweep — also respect pin (importance >= 0.9) as protection
|
|
493
493
|
const rows = db
|
|
494
494
|
.prepare(`SELECT id, layer, importance, access_count, last_accessed_at, protected
|
|
495
495
|
FROM memories
|
|
496
|
-
WHERE protected = 0 AND importance <
|
|
496
|
+
WHERE protected = 0 AND importance < 0.9`)
|
|
497
497
|
.all();
|
|
498
498
|
const now = Math.floor(Date.now() / 1000);
|
|
499
499
|
const actions = [];
|
|
@@ -545,7 +545,7 @@ function handleConsolidate(args) {
|
|
|
545
545
|
FROM memories m
|
|
546
546
|
JOIN entities e ON e.id = m.entity_id
|
|
547
547
|
WHERE m.protected = 0
|
|
548
|
-
AND m.importance <
|
|
548
|
+
AND m.importance < 0.9
|
|
549
549
|
AND m.layer IN ('context', 'emotion', 'implementation')
|
|
550
550
|
AND m.created_at <= ?
|
|
551
551
|
GROUP BY m.entity_id, m.layer
|
|
@@ -601,7 +601,7 @@ function handleUpdateMemory(args) {
|
|
|
601
601
|
if (args.importance !== undefined) {
|
|
602
602
|
const imp = Math.min(1, Math.max(0, Number(args.importance)));
|
|
603
603
|
patch.importance = imp;
|
|
604
|
-
patch.protected = imp >=
|
|
604
|
+
patch.protected = imp >= 0.9 || existing.protected === 1 ? 1 : 0;
|
|
605
605
|
}
|
|
606
606
|
const keys = Object.keys(patch);
|
|
607
607
|
if (keys.length === 0) {
|
|
@@ -616,7 +616,7 @@ function handleUpdateMemory(args) {
|
|
|
616
616
|
ok: true,
|
|
617
617
|
memory_id: memoryId,
|
|
618
618
|
updated_fields: keys,
|
|
619
|
-
pinned: (patch.importance ?? existing.importance) >=
|
|
619
|
+
pinned: (patch.importance ?? existing.importance) >= 0.9,
|
|
620
620
|
});
|
|
621
621
|
}
|
|
622
622
|
function handleListEntities(args) {
|
|
@@ -633,7 +633,7 @@ function handleListEntities(args) {
|
|
|
633
633
|
SUM(CASE WHEN m.layer = 'caveat' THEN 1 ELSE 0 END) as caveat_count,
|
|
634
634
|
SUM(CASE WHEN m.layer = 'learning' THEN 1 ELSE 0 END) as learning_count,
|
|
635
635
|
SUM(CASE WHEN m.layer = 'implementation' THEN 1 ELSE 0 END) as impl_count,
|
|
636
|
-
SUM(CASE WHEN m.importance >=
|
|
636
|
+
SUM(CASE WHEN m.importance >= 0.9 THEN 1 ELSE 0 END) as pinned_count
|
|
637
637
|
FROM entities e
|
|
638
638
|
LEFT JOIN memories m ON m.entity_id = e.id
|
|
639
639
|
WHERE 1=1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linksee-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"mcpName": "io.github.michielinksee/linksee-memory",
|
|
5
5
|
"description": "Local-first agent memory MCP — cross-agent brain with 6-layer structured memory + token-saving file diff cache",
|
|
6
6
|
"type": "module",
|