pi-hermes-memory 0.6.8 → 0.7.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/src/store/db.ts CHANGED
@@ -51,9 +51,11 @@ export class DatabaseManager {
51
51
  db.exec(SCHEMA_SQL);
52
52
  }
53
53
 
54
- // Extra safety: always ensure the legacy memories columns exist, even when
55
- // schema execution succeeds (idempotent on upgraded DBs).
54
+ // Extra safety: always ensure legacy memories columns exist, then migrate
55
+ // legacy CHECK(target IN ('memory','user')) constraints to include 'failure'.
56
56
  this.ensureMemoriesColumns(db);
57
+ this.migrateLegacyMemoriesTargetConstraint(db);
58
+ this.rebuildMemoryFts(db);
57
59
 
58
60
  return db;
59
61
  }
@@ -85,6 +87,56 @@ export class DatabaseManager {
85
87
  }
86
88
  }
87
89
 
90
+ private migrateLegacyMemoriesTargetConstraint(db: Database.Database): void {
91
+ const tableSqlRow = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='memories'").get() as { sql?: string } | undefined;
92
+ const tableSql = tableSqlRow?.sql ?? '';
93
+ if (!tableSql) return;
94
+
95
+ // Legacy schema allowed only memory/user. New schema must allow failure too.
96
+ const hasLegacyTargetCheck = /target\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*target\s+IN\s*\(\s*'memory'\s*,\s*'user'\s*\)\s*\)/i.test(tableSql);
97
+ if (!hasLegacyTargetCheck) return;
98
+
99
+ const tx = db.transaction(() => {
100
+ db.exec('PRAGMA foreign_keys = OFF');
101
+
102
+ db.exec(`
103
+ CREATE TABLE memories_new (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ project TEXT,
106
+ target TEXT NOT NULL CHECK (target IN ('memory', 'user', 'failure')),
107
+ category TEXT CHECK (category IN ('failure', 'correction', 'insight', 'preference', 'convention', 'tool-quirk')),
108
+ content TEXT NOT NULL,
109
+ failure_reason TEXT,
110
+ tool_state TEXT,
111
+ corrected_to TEXT,
112
+ created DATE NOT NULL,
113
+ last_referenced DATE NOT NULL
114
+ );
115
+ `);
116
+
117
+ db.exec(`
118
+ INSERT INTO memories_new (id, project, target, category, content, failure_reason, tool_state, corrected_to, created, last_referenced)
119
+ SELECT id, project, target, category, content, failure_reason, tool_state, corrected_to, created, last_referenced
120
+ FROM memories;
121
+ `);
122
+
123
+ db.exec('DROP TABLE memories');
124
+ db.exec('ALTER TABLE memories_new RENAME TO memories');
125
+
126
+ db.exec('PRAGMA foreign_keys = ON');
127
+ });
128
+
129
+ tx();
130
+ }
131
+
132
+ private rebuildMemoryFts(db: Database.Database): void {
133
+ const ftsTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_fts'").get() as { name?: string } | undefined;
134
+ if (!ftsTable) return;
135
+
136
+ // Keep FTS index consistent after table rebuild/migrations.
137
+ db.exec("INSERT INTO memory_fts(memory_fts) VALUES('rebuild')");
138
+ }
139
+
88
140
  /**
89
141
  * Close the database connection.
90
142
  */
@@ -424,13 +424,19 @@ export class MemoryStore {
424
424
  }
425
425
  }
426
426
 
427
- /** Atomic write: temp file + fs.rename() — same crash-safety as Hermes. */
427
+ /**
428
+ * Atomic write: temp file + fs.rename().
429
+ * Creates temp files in the same directory as the target to avoid
430
+ * cross-device rename errors (EXDEV) when os.tmpdir() is on a different
431
+ * drive than the memory directory (common on Windows).
432
+ */
428
433
  private async saveToDisk(target: "memory" | "user" | "failure"): Promise<void> {
429
434
  const filePath = this.pathFor(target);
430
435
  const entries = this.entriesFor(target);
431
436
  const content = entries.length ? entries.join(ENTRY_DELIMITER) : "";
432
437
 
433
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-memory-"));
438
+ // Use the memory directory for temp files so rename stays on the same device
439
+ const tmpDir = await fs.mkdtemp(path.join(this.memoryDir, ".tmp-"));
434
440
  const tmpPath = path.join(tmpDir, "write.tmp");
435
441
 
436
442
  try {
@@ -440,7 +446,7 @@ export class MemoryStore {
440
446
  try { await fs.unlink(tmpPath); } catch { /* ignore */ }
441
447
  throw err;
442
448
  } finally {
443
- try { await fs.rmdir(tmpDir); } catch { /* ignore */ }
449
+ try { await fs.rm(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
444
450
  }
445
451
  }
446
452
  }
@@ -283,10 +283,14 @@ export class SkillStore {
283
283
 
284
284
  // ─── Internal helpers ───
285
285
 
286
- /** Atomic write: temp file + rename (same crash-safety as MemoryStore) */
286
+ /**
287
+ * Atomic write: temp file + rename.
288
+ * Creates temp files in the skills directory to avoid cross-device
289
+ * rename errors (EXDEV) on Windows when os.tmpdir() is on a different drive.
290
+ */
287
291
  private async atomicWrite(fileName: string, content: string): Promise<void> {
288
292
  const filePath = path.join(this.skillsDir, fileName);
289
- const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-skill-"));
293
+ const tmpDir = await fs.mkdtemp(path.join(this.skillsDir, ".tmp-"));
290
294
  const tmpPath = path.join(tmpDir, "write.tmp");
291
295
 
292
296
  try {
@@ -296,7 +300,7 @@ export class SkillStore {
296
300
  try { await fs.unlink(tmpPath); } catch { /* ignore */ }
297
301
  throw err;
298
302
  } finally {
299
- try { await fs.rmdir(tmpDir); } catch { /* ignore */ }
303
+ try { await fs.rm(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
300
304
  }
301
305
  }
302
306
  }
package/src/types.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  import type { TextContent } from "@mariozechner/pi-ai";
6
6
 
7
7
  export interface MemoryConfig {
8
+ /** Prompt memory mode. Default: policy-only */
9
+ memoryMode: "policy-only" | "legacy-inject";
8
10
  /** Max chars for MEMORY.md (agent notes). Default: 5000 */
9
11
  memoryCharLimit: number;
10
12
  /** Max chars for USER.md (user profile). Default: 5000 */
@@ -13,6 +15,8 @@ export interface MemoryConfig {
13
15
  projectCharLimit: number;
14
16
  /** Turns between background auto-reviews. Default: 10 */
15
17
  nudgeInterval: number;
18
+ /** Recent conversation messages included in background review. 0 = all. Default: 0 */
19
+ reviewRecentMessages?: number;
16
20
  /** Enable background learning loop. Default: true */
17
21
  reviewEnabled: boolean;
18
22
  /** Flush memories before compaction. Default: true */
@@ -21,8 +25,12 @@ export interface MemoryConfig {
21
25
  flushOnShutdown: boolean;
22
26
  /** Minimum user turns before flush triggers. Default: 6 */
23
27
  flushMinTurns: number;
28
+ /** Recent conversation messages included in session flush. 0 = all. Default: 0 */
29
+ flushRecentMessages?: number;
24
30
  /** Override memory directory. Default: ~/.pi/agent/memory */
25
31
  memoryDir?: string;
32
+ /** Directory for project-scoped memory (relative to ~/.pi/agent). Default: "projects-memory" */
33
+ projectsMemoryDir?: string;
26
34
  /** Auto-consolidate when memory is full instead of returning error. Default: true */
27
35
  autoConsolidate: boolean;
28
36
  /** Detect user corrections and trigger immediate memory save. Default: true */
@@ -1,112 +0,0 @@
1
- # v0.7 Proposal: Tagged Session Review → Skill Creation
2
-
3
- ## Why
4
-
5
- Community feedback highlighted a real gap:
6
- - quick correction capture is useful,
7
- - but **durable behavior should be promoted to skills intentionally**,
8
- - and that flow should be reviewable in the TUI.
9
-
10
- ## Goal
11
-
12
- Add a deterministic review workflow:
13
- 1. collect candidate learnings from session messages,
14
- 2. review them in a TUI modal,
15
- 3. promote selected items into a draft skill,
16
- 4. save via existing `skill` tool.
17
-
18
- ## Non-goals
19
-
20
- - Auto-creating skills without user review.
21
- - Replacing core memory entirely.
22
- - Requiring git history for extraction.
23
-
24
- ## UX (Target)
25
-
26
- ### Command
27
- - `/memory-review-candidates`
28
-
29
- ### Modal flow
30
- 1. **Candidate list** (tag, source session, snippet, confidence)
31
- 2. Actions per candidate: `approve`, `reject`, `edit`, `merge with...`
32
- 3. Multi-select candidates and choose `Create skill draft`
33
- 4. Draft editor with sections:
34
- - `## When to Use`
35
- - `## Procedure`
36
- - `## Pitfalls`
37
- - `## Verification`
38
- 5. Save with `skill.create`
39
-
40
- ## Candidate Sources
41
-
42
- Priority order:
43
- 1. Explicit message tags from Pi sessions (when available)
44
- 2. Heuristic extraction from conversation patterns:
45
- - repeated corrections,
46
- - multi-step successful runs,
47
- - repeated tool sequences,
48
- - resolved failures with clear fix.
49
-
50
- ## Data Model (SQLite)
51
-
52
- New table (proposal): `memory_candidates`
53
-
54
- Columns:
55
- - `id` INTEGER PK
56
- - `session_id` TEXT
57
- - `message_id` TEXT
58
- - `project` TEXT
59
- - `tag` TEXT
60
- - `snippet` TEXT
61
- - `rationale` TEXT
62
- - `status` TEXT CHECK (`pending`,`approved`,`rejected`,`promoted`)
63
- - `created_at` TEXT
64
- - `updated_at` TEXT
65
- - `promoted_skill` TEXT NULL
66
-
67
- ## Integration with Existing System
68
-
69
- - Keep current memory + failure capture.
70
- - Add a **promotion path** from memory/candidates to skills.
71
- - Use existing `skill` tool as persistence layer.
72
- - Use existing `session_search`/indexing infra for candidate discovery context.
73
-
74
- ## Rollout Plan
75
-
76
- ### Phase 1: Candidate staging (no modal)
77
- - Create `memory_candidates` table
78
- - Add extraction + `/memory-candidates` list command
79
- - Add approve/reject commands
80
-
81
- ### Phase 2: TUI review modal
82
- - Interactive candidate triage in one place
83
- - Batch select + merge + edit
84
-
85
- ### Phase 3: Skill draft + save
86
- - Generate skill draft from approved candidates
87
- - Edit + save with `skill.create`
88
-
89
- ### Phase 4: Quality controls
90
- - duplicate candidate suppression
91
- - confidence thresholds
92
- - weekly reminder: pending candidates review
93
-
94
- ## Success Criteria
95
-
96
- - Lower noisy memory growth in `MEMORY.md`
97
- - Higher percentage of reusable knowledge landing in `skills/`
98
- - Fewer repeated correction loops across sessions
99
- - Users report that learning feels intentional, not "whack-a-mole"
100
-
101
- ## Open Questions
102
-
103
- 1. Should candidates be extracted turn-by-turn or session-end only?
104
- 2. Should approved candidates auto-expire if not promoted in N days?
105
- 3. Should skill drafts include linked source message IDs for traceability?
106
- 4. Should project scope be default-on with optional cross-project merge mode?
107
-
108
- ## Notes
109
-
110
- This proposal complements (not replaces) core memory.
111
- Memory remains fast capture; skills remain durable procedure.
112
- The new modal creates the missing bridge between them.