pi-hermes-memory 0.6.6 → 0.6.8
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 +24 -3
- package/docs/0.7/TAGGED-SESSION-SKILL-REVIEW.md +112 -0
- package/docs/images/memory-architecture.svg +1 -1
- package/docs/images/session-lifecycle.svg +1 -1
- package/docs/images/source-architecture.svg +1 -1
- package/docs/mermaid/memory-architecture.mmd +22 -4
- package/docs/mermaid/session-lifecycle.mmd +34 -31
- package/docs/mermaid/source-architecture.mmd +17 -1
- package/package.json +1 -1
- package/src/config.ts +8 -0
- package/src/constants.ts +2 -0
- package/src/handlers/correction-detector.ts +28 -3
- package/src/handlers/learn-memory.ts +16 -10
- package/src/handlers/preview-context.ts +67 -0
- package/src/handlers/sync-markdown-memories.ts +136 -0
- package/src/index.ts +7 -3
- package/src/store/memory-store.ts +13 -6
- package/src/store/sqlite-memory-store.ts +472 -46
- package/src/tools/memory-tool.ts +155 -4
- package/src/types.ts +9 -1
|
@@ -41,7 +41,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
41
41
|
lines.push(" User: Who you are — name, preferences, communication style");
|
|
42
42
|
lines.push(" Failures: What didn't work — corrections, failures, insights");
|
|
43
43
|
lines.push(" Skills: Procedures — how to debug, deploy, test");
|
|
44
|
-
lines.push(" Extended:
|
|
44
|
+
lines.push(" Extended: SQLite search mirror for Markdown memory + backfill");
|
|
45
45
|
lines.push("");
|
|
46
46
|
lines.push(" Memory Categories:");
|
|
47
47
|
lines.push(" ─────────────────");
|
|
@@ -70,7 +70,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
70
70
|
lines.push(" Search past conversations across all sessions");
|
|
71
71
|
lines.push("");
|
|
72
72
|
lines.push(" memory_search");
|
|
73
|
-
lines.push(" Search
|
|
73
|
+
lines.push(" Search the SQLite-backed memory mirror/store");
|
|
74
74
|
lines.push(" Filters: project, target, category");
|
|
75
75
|
lines.push(" Categories: failure, correction, insight, preference, convention, tool-quirk");
|
|
76
76
|
}
|
|
@@ -87,6 +87,8 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
87
87
|
lines.push(" /memory-interview Answer questions to pre-fill profile");
|
|
88
88
|
lines.push(" /memory-switch-project List all project memories");
|
|
89
89
|
lines.push(" /memory-index-sessions Import past sessions for search");
|
|
90
|
+
lines.push(" /memory-sync-markdown Backfill Markdown memories into SQLite");
|
|
91
|
+
lines.push(" /memory-preview-context Show injected memory/skill prompt blocks");
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
if (section.startsWith("✅")) {
|
|
@@ -115,12 +117,13 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
115
117
|
lines.push(" ╚══════════════════════════════════════════════╝");
|
|
116
118
|
lines.push("");
|
|
117
119
|
lines.push(" 1. Session starts → Core memory + recent failures injected");
|
|
118
|
-
lines.push(" 2. During conversation → Agent saves
|
|
119
|
-
lines.push(" 3.
|
|
120
|
-
lines.push(" 4.
|
|
121
|
-
lines.push(" 5. On
|
|
122
|
-
lines.push(" 6.
|
|
123
|
-
lines.push(" 7.
|
|
120
|
+
lines.push(" 2. During conversation → Agent saves to Markdown memory");
|
|
121
|
+
lines.push(" 3. Successful saves → Best-effort SQLite search sync");
|
|
122
|
+
lines.push(" 4. Every 10 turns → Background review saves items");
|
|
123
|
+
lines.push(" 5. On correction → Immediate save as [correction] category");
|
|
124
|
+
lines.push(" 6. On failure → Saves what failed + why");
|
|
125
|
+
lines.push(" 7. When full → Auto-consolidation merges");
|
|
126
|
+
lines.push(" 8. Session ends → Final flush");
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
if (section.startsWith("🏗️")) {
|
|
@@ -137,10 +140,11 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
137
140
|
lines.push(" │ Project memory — When cwd matches │");
|
|
138
141
|
lines.push(" └─────────────────────────────────────┘");
|
|
139
142
|
lines.push("");
|
|
140
|
-
lines.push(" Searchable on Demand (
|
|
143
|
+
lines.push(" Searchable on Demand (SQLite mirror/store)");
|
|
141
144
|
lines.push(" ┌─────────────────────────────────────┐");
|
|
142
145
|
lines.push(" │ session_search(\"auth flow\") │");
|
|
143
146
|
lines.push(" │ memory_search(\"testing patterns\") │");
|
|
147
|
+
lines.push(" │ /memory-sync-markdown (backfill old md)│");
|
|
144
148
|
lines.push(" │ memory_search(\"auth\", cat:\"failure\")│");
|
|
145
149
|
lines.push(" └─────────────────────────────────────┘");
|
|
146
150
|
}
|
|
@@ -153,9 +157,11 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
|
|
|
153
157
|
lines.push("");
|
|
154
158
|
lines.push(" \"Memory is full\"");
|
|
155
159
|
lines.push(" → /memory-consolidate to merge entries");
|
|
160
|
+
lines.push(" → If it still fails, the save does NOT silently become SQLite-only");
|
|
156
161
|
lines.push("");
|
|
157
162
|
lines.push(" \"Can't find something\"");
|
|
158
|
-
lines.push(" → memory_search to search
|
|
163
|
+
lines.push(" → memory_search to search the SQLite mirror/store");
|
|
164
|
+
lines.push(" → /memory-sync-markdown to import older Markdown entries");
|
|
159
165
|
lines.push("");
|
|
160
166
|
lines.push(" \"Agent forgot something\"");
|
|
161
167
|
lines.push(" → Check /memory-insights, tell agent \"remember X\"");
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview context command — /memory-preview-context shows the memory/skill blocks
|
|
3
|
+
* that are injected into the system prompt.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { MemoryStore } from "../store/memory-store.js";
|
|
8
|
+
import { SkillStore } from "../store/skill-store.js";
|
|
9
|
+
|
|
10
|
+
export function registerPreviewContextCommand(
|
|
11
|
+
pi: ExtensionAPI,
|
|
12
|
+
store: MemoryStore,
|
|
13
|
+
projectStore: MemoryStore | null,
|
|
14
|
+
skillStore: SkillStore,
|
|
15
|
+
projectName: string,
|
|
16
|
+
): void {
|
|
17
|
+
pi.registerCommand("memory-preview-context", {
|
|
18
|
+
description: "Preview the memory/skill context blocks injected into the system prompt",
|
|
19
|
+
handler: async (_args, ctx) => {
|
|
20
|
+
const memoryBlock = store.formatForSystemPrompt();
|
|
21
|
+
const projectBlock = projectStore ? projectStore.formatProjectBlock(projectName) : "";
|
|
22
|
+
const skillIndex = await skillStore.formatIndexForSystemPrompt();
|
|
23
|
+
|
|
24
|
+
const lines: string[] = [];
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push(" ╔══════════════════════════════════════════════╗");
|
|
27
|
+
lines.push(" ║ 👀 Injected Context Preview ║");
|
|
28
|
+
lines.push(" ╚══════════════════════════════════════════════╝");
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push(" This is the memory/skill context appended to the system prompt.");
|
|
31
|
+
lines.push(" (Core hidden system instructions are NOT shown.)");
|
|
32
|
+
lines.push("");
|
|
33
|
+
|
|
34
|
+
let blockCount = 0;
|
|
35
|
+
|
|
36
|
+
if (memoryBlock) {
|
|
37
|
+
blockCount++;
|
|
38
|
+
lines.push(" ── MEMORY + USER + RECENT FAILURES ─────────────────────────");
|
|
39
|
+
lines.push(memoryBlock);
|
|
40
|
+
lines.push("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (projectBlock) {
|
|
44
|
+
blockCount++;
|
|
45
|
+
lines.push(` ── PROJECT MEMORY (${projectName}) ─────────────────────────`);
|
|
46
|
+
lines.push(projectBlock);
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (skillIndex) {
|
|
51
|
+
blockCount++;
|
|
52
|
+
lines.push(" ── SKILL INDEX ─────────────────────────────────────────────");
|
|
53
|
+
lines.push(skillIndex);
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (blockCount === 0) {
|
|
58
|
+
lines.push(" No memory context blocks are currently injected.");
|
|
59
|
+
lines.push(" Add memory entries or skills, then run this command again.");
|
|
60
|
+
lines.push("");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
lines.push(` Blocks shown: ${blockCount}`);
|
|
64
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown memory sync command — /memory-sync-markdown imports existing
|
|
3
|
+
* Markdown-backed memories into the SQLite search store.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
import { DatabaseManager } from '../store/db.js';
|
|
10
|
+
import {
|
|
11
|
+
parseMarkdownMemoryEntry,
|
|
12
|
+
syncMemoryEntry,
|
|
13
|
+
} from '../store/sqlite-memory-store.js';
|
|
14
|
+
import { ENTRY_DELIMITER, MEMORY_FILE, USER_FILE } from '../constants.js';
|
|
15
|
+
|
|
16
|
+
interface BackfillCounters {
|
|
17
|
+
filesScanned: number;
|
|
18
|
+
entriesScanned: number;
|
|
19
|
+
imported: number;
|
|
20
|
+
skipped: number;
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readEntries(filePath: string): string[] {
|
|
25
|
+
if (!fs.existsSync(filePath)) return [];
|
|
26
|
+
const raw = fs.readFileSync(filePath, 'utf-8').trim();
|
|
27
|
+
if (!raw) return [];
|
|
28
|
+
return raw.split(ENTRY_DELIMITER).map((entry) => entry.trim()).filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function importEntries(
|
|
32
|
+
dbManager: DatabaseManager,
|
|
33
|
+
counters: BackfillCounters,
|
|
34
|
+
entries: string[],
|
|
35
|
+
target: 'memory' | 'user' | 'failure',
|
|
36
|
+
project: string | null = null,
|
|
37
|
+
): void {
|
|
38
|
+
for (const rawEntry of entries) {
|
|
39
|
+
counters.entriesScanned++;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = parseMarkdownMemoryEntry(rawEntry, target, project);
|
|
42
|
+
const result = syncMemoryEntry(dbManager, parsed);
|
|
43
|
+
if (result.action === 'inserted') counters.imported++;
|
|
44
|
+
else counters.skipped++;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
counters.warnings.push(
|
|
47
|
+
`${path.basename(project ?? 'global')}/${target}: ${err instanceof Error ? err.message : String(err)}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function scanProjectDirs(agentRoot: string, globalDir: string): Array<{ name: string; memoryFile: string }> {
|
|
54
|
+
if (!fs.existsSync(agentRoot)) return [];
|
|
55
|
+
const globalDirName = path.basename(globalDir);
|
|
56
|
+
|
|
57
|
+
return fs.readdirSync(agentRoot)
|
|
58
|
+
.map((name) => ({ name, dir: path.join(agentRoot, name) }))
|
|
59
|
+
.filter(({ name, dir }) => name !== globalDirName && fs.existsSync(dir) && fs.statSync(dir).isDirectory())
|
|
60
|
+
.map(({ name, dir }) => ({ name, memoryFile: path.join(dir, MEMORY_FILE) }))
|
|
61
|
+
.filter(({ memoryFile }) => fs.existsSync(memoryFile));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function registerSyncMarkdownMemoriesCommand(
|
|
65
|
+
pi: ExtensionAPI,
|
|
66
|
+
dbManager: DatabaseManager,
|
|
67
|
+
globalDir: string,
|
|
68
|
+
): void {
|
|
69
|
+
pi.registerCommand('memory-sync-markdown', {
|
|
70
|
+
description: 'Backfill Markdown memories into the SQLite search store',
|
|
71
|
+
handler: async (_args, ctx: ExtensionCommandContext) => {
|
|
72
|
+
const counters: BackfillCounters = {
|
|
73
|
+
filesScanned: 0,
|
|
74
|
+
entriesScanned: 0,
|
|
75
|
+
imported: 0,
|
|
76
|
+
skipped: 0,
|
|
77
|
+
warnings: [],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
ctx.ui.notify('🔄 Scanning Markdown memory files for SQLite backfill...', 'info');
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const globalMemoryFile = path.join(globalDir, MEMORY_FILE);
|
|
84
|
+
const globalUserFile = path.join(globalDir, USER_FILE);
|
|
85
|
+
const globalFailureFile = path.join(globalDir, 'failures.md');
|
|
86
|
+
|
|
87
|
+
const importFile = (
|
|
88
|
+
filePath: string,
|
|
89
|
+
target: 'memory' | 'user' | 'failure',
|
|
90
|
+
project: string | null = null,
|
|
91
|
+
) => {
|
|
92
|
+
if (!fs.existsSync(filePath)) return;
|
|
93
|
+
counters.filesScanned++;
|
|
94
|
+
const entries = readEntries(filePath);
|
|
95
|
+
importEntries(dbManager, counters, entries, target, project);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
importFile(globalMemoryFile, 'memory');
|
|
99
|
+
importFile(globalUserFile, 'user');
|
|
100
|
+
importFile(globalFailureFile, 'failure');
|
|
101
|
+
|
|
102
|
+
const agentRoot = path.dirname(globalDir);
|
|
103
|
+
const projects = scanProjectDirs(agentRoot, globalDir);
|
|
104
|
+
for (const project of projects) {
|
|
105
|
+
importFile(project.memoryFile, 'memory', project.name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let output = `\n✅ Markdown → SQLite sync complete!\n\n`;
|
|
109
|
+
output += `📊 Results:\n`;
|
|
110
|
+
output += `├─ Files scanned: ${counters.filesScanned}\n`;
|
|
111
|
+
output += `├─ Entries scanned: ${counters.entriesScanned}\n`;
|
|
112
|
+
output += `├─ Imported into SQLite: ${counters.imported}\n`;
|
|
113
|
+
output += `└─ Skipped as duplicates: ${counters.skipped}\n`;
|
|
114
|
+
|
|
115
|
+
if (projects.length > 0) {
|
|
116
|
+
output += `\n📁 Project memories scanned: ${projects.length}\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (counters.warnings.length > 0) {
|
|
120
|
+
output += `\n⚠️ Warnings (${counters.warnings.length}):\n`;
|
|
121
|
+
for (const warning of counters.warnings.slice(0, 5)) {
|
|
122
|
+
output += `├─ ${warning}\n`;
|
|
123
|
+
}
|
|
124
|
+
if (counters.warnings.length > 5) {
|
|
125
|
+
output += `└─ ... and ${counters.warnings.length - 5} more\n`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
output += `\n💡 Re-running this command is safe — existing SQLite rows are de-duplicated.`;
|
|
130
|
+
ctx.ui.notify(output, 'info');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
ctx.ui.notify(`❌ Markdown sync failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -45,6 +45,8 @@ import { registerInterviewCommand } from "./handlers/interview.js";
|
|
|
45
45
|
import { registerSwitchProjectCommand } from "./handlers/switch-project.js";
|
|
46
46
|
import { registerIndexSessionsCommand } from "./handlers/index-sessions.js";
|
|
47
47
|
import { registerLearnMemoryCommand } from "./handlers/learn-memory.js";
|
|
48
|
+
import { registerSyncMarkdownMemoriesCommand } from "./handlers/sync-markdown-memories.js";
|
|
49
|
+
import { registerPreviewContextCommand } from "./handlers/preview-context.js";
|
|
48
50
|
import { loadConfig } from "./config.js";
|
|
49
51
|
import { detectProject } from "./project.js";
|
|
50
52
|
|
|
@@ -90,8 +92,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
|
|
93
|
-
// ── 3. Register the memory tool (with project store) ──
|
|
94
|
-
registerMemoryTool(pi, store, projectStore);
|
|
95
|
+
// ── 3. Register the memory tool (with project store + SQLite sync) ──
|
|
96
|
+
registerMemoryTool(pi, store, projectStore, dbManager, projectName);
|
|
95
97
|
|
|
96
98
|
// ── 4. Register the skill tool ──
|
|
97
99
|
registerSkillTool(pi, skillStore);
|
|
@@ -109,7 +111,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
111
|
registerConsolidateCommand(pi, store);
|
|
110
112
|
|
|
111
113
|
// ── 8. Setup correction detection ──
|
|
112
|
-
setupCorrectionDetector(pi, store, projectStore, config);
|
|
114
|
+
setupCorrectionDetector(pi, store, projectStore, config, dbManager);
|
|
113
115
|
|
|
114
116
|
// ── 9. Setup skill auto-trigger ──
|
|
115
117
|
setupSkillAutoTrigger(pi, store, skillStore, config);
|
|
@@ -120,6 +122,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
120
122
|
registerInterviewCommand(pi, store);
|
|
121
123
|
registerSwitchProjectCommand(pi);
|
|
122
124
|
registerLearnMemoryCommand(pi);
|
|
125
|
+
registerSyncMarkdownMemoriesCommand(pi, dbManager, globalDir);
|
|
126
|
+
registerPreviewContextCommand(pi, store, projectStore, skillStore, projectName);
|
|
123
127
|
|
|
124
128
|
// ── 11. SQLite session search + extended memory ──
|
|
125
129
|
registerSessionSearchTool(pi, dbManager);
|
|
@@ -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
|
-
|
|
284
|
-
|
|
285
|
-
const maxFailures =
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
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");
|