pi-hermes-memory 0.6.5 → 0.6.7

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 CHANGED
@@ -363,6 +363,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
363
363
  "reviewEnabled": true,
364
364
  "autoConsolidate": true,
365
365
  "correctionDetection": true,
366
+ "failureInjectionEnabled": true,
367
+ "failureInjectionMaxAgeDays": 7,
368
+ "failureInjectionMaxEntries": 5,
366
369
  "flushOnCompact": true,
367
370
  "flushOnShutdown": true,
368
371
  "flushMinTurns": 6
@@ -380,6 +383,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
380
383
  | `reviewEnabled` | `true` | Enable/disable background learning loop |
381
384
  | `autoConsolidate` | `true` | Auto-merge when memory hits capacity |
382
385
  | `correctionDetection` | `true` | Detect user corrections and save immediately |
386
+ | `failureInjectionEnabled` | `true` | Enable/disable injecting recent failure memories into the system prompt |
387
+ | `failureInjectionMaxAgeDays` | `7` | Maximum age in days for injected failure memories |
388
+ | `failureInjectionMaxEntries` | `5` | Maximum number of failure memories to inject |
383
389
  | `flushOnCompact` | `true` | Flush memories before Pi compacts context |
384
390
  | `flushOnShutdown` | `true` | Flush memories when session ends |
385
391
  | `flushMinTurns` | `6` | Minimum turns before flush triggers |
@@ -1,3 +1,28 @@
1
+ # v0.6.6 Changelog
2
+
3
+ ## Bug Fixes
4
+
5
+ ### Fix legacy SQLite upgrade error: `no such column: category`
6
+
7
+ Some users upgrading from older versions had an existing `sessions.db` with a legacy
8
+ `memories` table that did not include v0.6 failure-memory columns. During schema init,
9
+ creating `idx_memories_category` failed and `/memory-index-sessions` crashed with:
10
+
11
+ `❌ Session indexing failed: no such column: category`
12
+
13
+ Fix:
14
+
15
+ - Added automatic legacy schema migration in `DatabaseManager`
16
+ - Detects missing `memories` columns and adds them idempotently:
17
+ - `category`
18
+ - `failure_reason`
19
+ - `tool_state`
20
+ - `corrected_to`
21
+ - Retries schema initialization after migration
22
+ - Added regression test: `should migrate legacy memories table without category column`
23
+
24
+ ---
25
+
1
26
  # v0.6.5 Changelog
2
27
 
3
28
  ## Bug Fixes
@@ -0,0 +1,112 @@
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hermes-memory",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. SQLite FTS5 search across every conversation, auto-consolidation, memory aging, procedural skills. 272 tests. Ported from Hermes agent.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/config.ts CHANGED
@@ -9,6 +9,8 @@ import {
9
9
  DEFAULT_NUDGE_INTERVAL,
10
10
  DEFAULT_FLUSH_MIN_TURNS,
11
11
  DEFAULT_NUDGE_TOOL_CALLS,
12
+ DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
13
+ DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
12
14
  } from "./constants.js";
13
15
 
14
16
  const DEFAULT_CONFIG: MemoryConfig = {
@@ -22,6 +24,9 @@ const DEFAULT_CONFIG: MemoryConfig = {
22
24
  flushMinTurns: DEFAULT_FLUSH_MIN_TURNS,
23
25
  autoConsolidate: true,
24
26
  correctionDetection: true,
27
+ failureInjectionEnabled: true,
28
+ failureInjectionMaxAgeDays: DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
29
+ failureInjectionMaxEntries: DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
25
30
  nudgeToolCalls: DEFAULT_NUDGE_TOOL_CALLS,
26
31
  };
27
32
 
@@ -48,6 +53,9 @@ export function loadConfig(): MemoryConfig {
48
53
  if (typeof parsed.flushMinTurns === "number") config.flushMinTurns = parsed.flushMinTurns;
49
54
  if (typeof parsed.autoConsolidate === "boolean") config.autoConsolidate = parsed.autoConsolidate;
50
55
  if (typeof parsed.correctionDetection === "boolean") config.correctionDetection = parsed.correctionDetection;
56
+ if (typeof parsed.failureInjectionEnabled === "boolean") config.failureInjectionEnabled = parsed.failureInjectionEnabled;
57
+ if (typeof parsed.failureInjectionMaxAgeDays === "number") config.failureInjectionMaxAgeDays = parsed.failureInjectionMaxAgeDays;
58
+ if (typeof parsed.failureInjectionMaxEntries === "number") config.failureInjectionMaxEntries = parsed.failureInjectionMaxEntries;
51
59
  if (typeof parsed.nudgeToolCalls === "number") config.nudgeToolCalls = parsed.nudgeToolCalls;
52
60
  if (typeof parsed.projectCharLimit === "number") config.projectCharLimit = parsed.projectCharLimit;
53
61
  if (typeof parsed.memoryDir === "string") config.memoryDir = parsed.memoryDir;
package/src/constants.ts CHANGED
@@ -18,6 +18,8 @@ export const DEFAULT_NUDGE_INTERVAL = 10;
18
18
  export const DEFAULT_FLUSH_MIN_TURNS = 6;
19
19
  export const DEFAULT_NUDGE_TOOL_CALLS = 15;
20
20
  export const DEFAULT_SKILL_TRIGGER_TOOL_CALLS = 8;
21
+ export const DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS = 7;
22
+ export const DEFAULT_FAILURE_INJECTION_MAX_ENTRIES = 5;
21
23
 
22
24
  // ─── File names ───
23
25
  export const MEMORY_FILE = "MEMORY.md";
package/src/store/db.ts CHANGED
@@ -38,11 +38,53 @@ export class DatabaseManager {
38
38
  db.pragma('foreign_keys = ON');
39
39
 
40
40
  // Create tables and triggers
41
- db.exec(SCHEMA_SQL);
41
+ try {
42
+ db.exec(SCHEMA_SQL);
43
+ } catch (err) {
44
+ if (!this.isLegacyMemoriesCategoryError(err)) {
45
+ throw err;
46
+ }
47
+
48
+ // Legacy DB from pre-v0.6 can have memories table without the category
49
+ // and failure metadata columns. Add missing columns, then retry schema.
50
+ this.ensureMemoriesColumns(db);
51
+ db.exec(SCHEMA_SQL);
52
+ }
53
+
54
+ // Extra safety: always ensure the legacy memories columns exist, even when
55
+ // schema execution succeeds (idempotent on upgraded DBs).
56
+ this.ensureMemoriesColumns(db);
42
57
 
43
58
  return db;
44
59
  }
45
60
 
61
+ private isLegacyMemoriesCategoryError(err: unknown): boolean {
62
+ if (!(err instanceof Error)) return false;
63
+ const msg = err.message.toLowerCase();
64
+ return msg.includes('no such column: category') || msg.includes('memories(category)');
65
+ }
66
+
67
+ private ensureMemoriesColumns(db: Database.Database): void {
68
+ const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memories'").get() as { name: string } | undefined;
69
+ if (!tableExists) return;
70
+
71
+ const columns = db.prepare('PRAGMA table_info(memories)').all() as { name: string }[];
72
+ const names = new Set(columns.map((c) => c.name));
73
+
74
+ if (!names.has('category')) {
75
+ db.exec('ALTER TABLE memories ADD COLUMN category TEXT');
76
+ }
77
+ if (!names.has('failure_reason')) {
78
+ db.exec('ALTER TABLE memories ADD COLUMN failure_reason TEXT');
79
+ }
80
+ if (!names.has('tool_state')) {
81
+ db.exec('ALTER TABLE memories ADD COLUMN tool_state TEXT');
82
+ }
83
+ if (!names.has('corrected_to')) {
84
+ db.exec('ALTER TABLE memories ADD COLUMN corrected_to TEXT');
85
+ }
86
+ }
87
+
46
88
  /**
47
89
  * Close the database connection.
48
90
  */
@@ -19,6 +19,8 @@ import {
19
19
  ENTRY_DELIMITER,
20
20
  DEFAULT_MEMORY_CHAR_LIMIT,
21
21
  DEFAULT_USER_CHAR_LIMIT,
22
+ DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
23
+ DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
22
24
  MEMORY_FILE,
23
25
  USER_FILE,
24
26
  } from "../constants.js";
@@ -280,12 +282,17 @@ export class MemoryStore {
280
282
  if (this.snapshot.user) parts.push(this.fenceBlock(this.snapshot.user));
281
283
 
282
284
  // Add recent failure memories
283
- const recentFailures = this.getFailureEntries(7);
284
- if (recentFailures.length > 0) {
285
- const maxFailures = 5;
286
- const failures = recentFailures.slice(0, maxFailures);
287
- const failureBlock = this.renderFailureBlock(failures);
288
- parts.push(this.fenceBlock(failureBlock));
285
+ if (this.config.failureInjectionEnabled !== false) {
286
+ const maxAgeDays = this.config.failureInjectionMaxAgeDays ?? DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS;
287
+ const maxFailures = this.config.failureInjectionMaxEntries ?? DEFAULT_FAILURE_INJECTION_MAX_ENTRIES;
288
+ const recentFailures = this.getFailureEntries(maxAgeDays);
289
+ if (recentFailures.length > 0) {
290
+ const failures = recentFailures.slice(0, maxFailures);
291
+ if (failures.length > 0) {
292
+ const failureBlock = this.renderFailureBlock(failures);
293
+ parts.push(this.fenceBlock(failureBlock));
294
+ }
295
+ }
289
296
  }
290
297
 
291
298
  return parts.join("\n\n");
package/src/types.ts CHANGED
@@ -27,6 +27,12 @@ export interface MemoryConfig {
27
27
  autoConsolidate: boolean;
28
28
  /** Detect user corrections and trigger immediate memory save. Default: true */
29
29
  correctionDetection: boolean;
30
+ /** Inject recent failure memories into the system prompt. Default: true */
31
+ failureInjectionEnabled: boolean;
32
+ /** Maximum age in days for injected failure memories. Default: 7 */
33
+ failureInjectionMaxAgeDays: number;
34
+ /** Maximum number of failure memories to inject. Default: 5 */
35
+ failureInjectionMaxEntries: number;
30
36
  /** Tool calls before triggering background review (in addition to turn count). Default: 15 */
31
37
  nudgeToolCalls: number;
32
38
  /** Enable session history search via SQLite FTS5. Default: true */