pi-hermes-memory 0.7.8 → 0.7.9
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 +21 -17
- package/package.json +1 -1
- package/src/config.ts +14 -1
- package/src/extension-root-migration.ts +101 -0
- package/src/handlers/index-sessions.ts +1 -1
- package/src/handlers/skills-command.ts +544 -75
- package/src/index.ts +37 -11
- package/src/project-memory-migration.ts +1 -1
- package/src/store/memory-store.ts +1 -1
- package/src/store/session-anchor-search.ts +472 -0
- package/src/store/skill-store.ts +142 -43
- package/src/tools/session-search-tool.ts +106 -1
- package/src/types.ts +10 -5
package/README.md
CHANGED
|
@@ -103,7 +103,7 @@ The extension stores memory at two levels:
|
|
|
103
103
|
|
|
104
104
|
| Tier | Location | What goes here | Available when |
|
|
105
105
|
|---|---|---|---|
|
|
106
|
-
| **Global** | `~/.pi/agent/memory/` | Facts that apply everywhere — your name, preferences, OS, tools | Searchable via `memory_search` |
|
|
106
|
+
| **Global** | `~/.pi/agent/pi-hermes-memory/` | Facts that apply everywhere — your name, preferences, OS, tools | Searchable via `memory_search` |
|
|
107
107
|
| **Project** | `~/.pi/agent/projects-memory/<project>/` | Facts scoped to one codebase — architecture decisions, API quirks, team norms | Searchable when cwd matches the project |
|
|
108
108
|
|
|
109
109
|
By default, full Markdown memories are **not** injected into the system prompt. The system prompt gets a full-detail `<memory-policy>` that tells the agent when to call `memory_search` and how to treat memory results. This keeps first-turn token usage low while preserving access to user, project, failure, correction, insight, preference, convention, and tool-quirk memories.
|
|
@@ -185,7 +185,7 @@ The agent also gets a `skill` tool for saving reusable procedures:
|
|
|
185
185
|
|
|
186
186
|
Skills are stored in Pi-native locations:
|
|
187
187
|
|
|
188
|
-
- Global skills: `~/.pi/agent/skills/<slug>/SKILL.md`
|
|
188
|
+
- Global skills: `~/.pi/agent/pi-hermes-memory/skills/<slug>/SKILL.md`
|
|
189
189
|
- Project skills: `~/.pi/agent/projects-memory/<project>/skills/<slug>/SKILL.md`
|
|
190
190
|
|
|
191
191
|
The extension classifies new skills automatically:
|
|
@@ -241,13 +241,13 @@ This lets Pi discover project skills as native skills without copying them into
|
|
|
241
241
|
|---|---|---|---|
|
|
242
242
|
| **memory** | `MEMORY.md` | Agent's notes — env facts, project conventions, tool quirks, lessons learned | 5,000 chars |
|
|
243
243
|
| **user** | `USER.md` | User profile — name, preferences, communication style, habits | 5,000 chars |
|
|
244
|
-
| **skills** | `~/.pi/agent/skills/<slug>/SKILL.md` or `projects-memory/<project>/skills/<slug>/SKILL.md` | Procedures — *how* to debug, deploy, test, or fix something | Unlimited |
|
|
244
|
+
| **skills** | `~/.pi/agent/pi-hermes-memory/skills/<slug>/SKILL.md` or `projects-memory/<project>/skills/<slug>/SKILL.md` | Procedures — *how* to debug, deploy, test, or fix something | Unlimited |
|
|
245
245
|
| **extended** | `sessions.db` | Searchable memories beyond the core limit | Unlimited |
|
|
246
246
|
| **sessions** | `sessions.db` | Past conversation history (searchable via FTS5) | Unlimited |
|
|
247
247
|
|
|
248
248
|
### Session History Search
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
By default, the extension indexes your Pi session history into a SQLite database with FTS5 full-text search. The agent can search across all past conversations using the `session_search` tool:
|
|
251
251
|
|
|
252
252
|
| Tool | What it does |
|
|
253
253
|
|---|---|---|
|
|
@@ -260,6 +260,8 @@ Session history is indexed automatically on session shutdown. To bulk-import exi
|
|
|
260
260
|
/memory-index-sessions
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
+
For users who prefer source anchors over snippets, `sessionSearch.variant` can be set to `anchors`. In that opt-in mode, the same `session_search` tool reads session JSONL files directly and accepts a Markdown request with fields such as `from`, `to`, `cwd`, and `limit`, plus `all`, `any`, and `exclude` lists. It returns plain text with `count`, an optional `message`, and compact `path:startLine-endLine` style anchors with short reasons instead of summaries or previews.
|
|
264
|
+
|
|
263
265
|
### Extended Memory Store
|
|
264
266
|
|
|
265
267
|
The extension keeps Markdown memory as the human-readable source of truth, and mirrors successful writes into the SQLite-backed search store used by `memory_search`.
|
|
@@ -390,8 +392,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
390
392
|
"memoryCharLimit": 5000,
|
|
391
393
|
"userCharLimit": 5000,
|
|
392
394
|
"projectCharLimit": 5000,
|
|
393
|
-
"memoryDir": "~/.pi/agent/memory",
|
|
395
|
+
"memoryDir": "~/.pi/agent/pi-hermes-memory",
|
|
394
396
|
"projectsMemoryDir": "projects-memory",
|
|
397
|
+
"sessionSearch": { "variant": "legacy" },
|
|
395
398
|
"nudgeInterval": 10,
|
|
396
399
|
"nudgeToolCalls": 15,
|
|
397
400
|
"reviewRecentMessages": 0,
|
|
@@ -418,8 +421,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
418
421
|
| `memoryCharLimit` | `5000` | Max characters in MEMORY.md |
|
|
419
422
|
| `userCharLimit` | `5000` | Max characters in USER.md |
|
|
420
423
|
| `projectCharLimit` | `5000` | Max characters in project-scoped MEMORY.md |
|
|
421
|
-
| `memoryDir` | `~/.pi/agent/memory` | Custom directory for
|
|
424
|
+
| `memoryDir` | `~/.pi/agent/pi-hermes-memory` | Custom directory for extension storage files |
|
|
422
425
|
| `projectsMemoryDir` | `projects-memory` | Subdirectory under `~/.pi/agent/` for project-scoped memory |
|
|
426
|
+
| `sessionSearch` | `{ "variant": "legacy" }` | Session search implementation: `legacy` keeps the existing SQLite/FTS snippet search; `anchors` uses the opt-in Markdown request surface and returns compact JSONL line-range anchors from `~/.pi/agent/sessions/` |
|
|
423
427
|
| `nudgeInterval` | `10` | Turns between auto-reviews |
|
|
424
428
|
| `nudgeToolCalls` | `15` | Tool calls between auto-reviews (OR with turns) |
|
|
425
429
|
| `reviewRecentMessages` | `0` | Recent messages included in background review (`0` = all) |
|
|
@@ -444,11 +448,16 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
444
448
|
|
|
445
449
|
```
|
|
446
450
|
~/.pi/agent/
|
|
447
|
-
├──
|
|
448
|
-
│ ├──
|
|
449
|
-
│
|
|
450
|
-
│
|
|
451
|
-
│
|
|
451
|
+
├── pi-hermes-memory/ ← Global extension storage root
|
|
452
|
+
│ ├── MEMORY.md ← Agent's personal notes (env facts, patterns, lessons)
|
|
453
|
+
│ ├── USER.md ← User profile (name, preferences, habits)
|
|
454
|
+
│ ├── sessions.db ← SQLite database (session history + extended memory)
|
|
455
|
+
│ ├── skills/ ← Global extension-managed skills
|
|
456
|
+
│ │ ├── debug-typescript-errors/
|
|
457
|
+
│ │ │ └── SKILL.md
|
|
458
|
+
│ │ └── testing-checklist/
|
|
459
|
+
│ │ └── SKILL.md
|
|
460
|
+
│ └── .skills-migrated-to-extension-storage
|
|
452
461
|
├── projects-memory/ ← ALL project-scoped memories (one subfolder per project)
|
|
453
462
|
│ ├── my-project/
|
|
454
463
|
│ │ ├── MEMORY.md
|
|
@@ -457,11 +466,6 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
457
466
|
│ │ └── SKILL.md
|
|
458
467
|
│ └── another-project/
|
|
459
468
|
│ └── MEMORY.md
|
|
460
|
-
├── memory/ ← Global memory
|
|
461
|
-
│ ├── MEMORY.md ← Agent's personal notes (env facts, patterns, lessons)
|
|
462
|
-
│ ├── USER.md ← User profile (name, preferences, habits)
|
|
463
|
-
│ ├── sessions.db ← SQLite database (session history + extended memory)
|
|
464
|
-
│ └── .skills-migrated-to-pi-native
|
|
465
469
|
├── hermes-memory-config.json
|
|
466
470
|
└── ...
|
|
467
471
|
```
|
|
@@ -482,7 +486,7 @@ The `sessions.db` SQLite database stores session history and extended memory ent
|
|
|
482
486
|
- **System prompts are invisible**: Pi's TUI does not display the system prompt. Use `/memory-preview-context` to inspect whether policy-only or legacy memory injection is active.
|
|
483
487
|
- **Project skill visibility depends on Pi discovery cycles**: project skills are exposed through `resources_discover` using the active project's `skills/` path. If a moved or newly created project skill doesn't show up immediately in a running session, trigger a reload/new session so Pi refreshes discovered resources.
|
|
484
488
|
- **Project move requires active project context**: in `/memory-skills`, the `p` hotkey is disabled when Pi is not currently in a detected project directory.
|
|
485
|
-
- **Skills are agent-generated**: Skills are created by the agent based on its experience. They may not always be perfectly structured. You can move, delete, or still edit them directly in `~/.pi/agent/skills/` or the active project's `skills/` folder.
|
|
489
|
+
- **Skills are agent-generated**: Skills are created by the agent based on its experience. They may not always be perfectly structured. You can move, delete, or still edit them directly in `~/.pi/agent/pi-hermes-memory/skills/` or the active project's `skills/` folder.
|
|
486
490
|
|
|
487
491
|
## Architecture
|
|
488
492
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hermes-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
4
4
|
"description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. Token-aware policy-only memory by default, SQLite FTS5 search, auto-consolidation, procedural skills. 368 tests. Ported from Hermes agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as os from "node:os";
|
|
4
|
-
import type { MemoryConfig, MemoryOverflowStrategy } from "./types.js";
|
|
4
|
+
import type { MemoryConfig, MemoryOverflowStrategy, SessionSearchVariant } from "./types.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MEMORY_CHAR_LIMIT,
|
|
7
7
|
DEFAULT_USER_CHAR_LIMIT,
|
|
@@ -18,11 +18,16 @@ import {
|
|
|
18
18
|
} from "./constants.js";
|
|
19
19
|
|
|
20
20
|
const MEMORY_OVERFLOW_STRATEGIES: readonly MemoryOverflowStrategy[] = ["auto-consolidate", "reject", "fifo-evict"];
|
|
21
|
+
const SESSION_SEARCH_VARIANTS: readonly SessionSearchVariant[] = ["legacy", "anchors"];
|
|
21
22
|
|
|
22
23
|
function isMemoryOverflowStrategy(value: unknown): value is MemoryOverflowStrategy {
|
|
23
24
|
return typeof value === "string" && MEMORY_OVERFLOW_STRATEGIES.includes(value as MemoryOverflowStrategy);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
function isSessionSearchVariant(value: unknown): value is SessionSearchVariant {
|
|
28
|
+
return typeof value === "string" && SESSION_SEARCH_VARIANTS.includes(value as SessionSearchVariant);
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
const DEFAULT_CONFIG: MemoryConfig = {
|
|
27
32
|
memoryMode: "policy-only",
|
|
28
33
|
memoryPolicyStyle: "full",
|
|
@@ -45,6 +50,7 @@ const DEFAULT_CONFIG: MemoryConfig = {
|
|
|
45
50
|
consolidationTimeoutMs: DEFAULT_CONSOLIDATION_TIMEOUT_MS,
|
|
46
51
|
nudgeToolCalls: DEFAULT_NUDGE_TOOL_CALLS,
|
|
47
52
|
projectsMemoryDir: DEFAULT_PROJECTS_MEMORY_DIR,
|
|
53
|
+
sessionSearch: { variant: "legacy" },
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
export const DEFAULT_CONFIG_PATH = path.join(
|
|
@@ -107,6 +113,13 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
|
|
|
107
113
|
if (typeof parsed.projectCharLimit === "number") config.projectCharLimit = parsed.projectCharLimit;
|
|
108
114
|
if (typeof parsed.memoryDir === "string") config.memoryDir = parsed.memoryDir;
|
|
109
115
|
if (typeof parsed.projectsMemoryDir === "string") config.projectsMemoryDir = parsed.projectsMemoryDir;
|
|
116
|
+
if (
|
|
117
|
+
typeof parsed.sessionSearch === "object" &&
|
|
118
|
+
parsed.sessionSearch !== null &&
|
|
119
|
+
isSessionSearchVariant(parsed.sessionSearch.variant)
|
|
120
|
+
) {
|
|
121
|
+
config.sessionSearch = { variant: parsed.sessionSearch.variant };
|
|
122
|
+
}
|
|
110
123
|
if (hasMemoryOverflowStrategy) {
|
|
111
124
|
config.autoConsolidate = config.memoryOverflowStrategy === "auto-consolidate";
|
|
112
125
|
} else if (hasLegacyAutoConsolidate) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface ExtensionRootMigrationResult {
|
|
6
|
+
moved: number;
|
|
7
|
+
merged: number;
|
|
8
|
+
skipped: number;
|
|
9
|
+
warnings: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function pathExists(filePath: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(filePath);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function moveFileSafe(source: string, target: string): Promise<void> {
|
|
22
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await fs.rename(source, target);
|
|
26
|
+
return;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const code = (error as NodeJS.ErrnoException)?.code;
|
|
29
|
+
if (code !== "EXDEV") throw error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await fs.copyFile(source, target);
|
|
33
|
+
await fs.unlink(source);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function moveDirContents(sourceDir: string, targetDir: string, result: ExtensionRootMigrationResult): Promise<void> {
|
|
37
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
38
|
+
|
|
39
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
42
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
43
|
+
|
|
44
|
+
if (!await pathExists(targetPath)) {
|
|
45
|
+
try {
|
|
46
|
+
await moveFileSafe(sourcePath, targetPath);
|
|
47
|
+
result.moved++;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
result.warnings.push(`${sourcePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
await moveDirContents(sourcePath, targetPath, result);
|
|
56
|
+
result.merged++;
|
|
57
|
+
try {
|
|
58
|
+
const remaining = await fs.readdir(sourcePath);
|
|
59
|
+
if (remaining.length === 0) await fs.rmdir(sourcePath);
|
|
60
|
+
} catch {
|
|
61
|
+
// best effort
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
result.skipped++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Move legacy extension assets from ~/.pi/agent/memory into
|
|
72
|
+
* ~/.pi/agent/pi-hermes-memory. Existing destination files win.
|
|
73
|
+
*/
|
|
74
|
+
export async function migrateExtensionRoot(
|
|
75
|
+
legacyRoot: string,
|
|
76
|
+
targetRoot: string,
|
|
77
|
+
): Promise<ExtensionRootMigrationResult> {
|
|
78
|
+
const result: ExtensionRootMigrationResult = {
|
|
79
|
+
moved: 0,
|
|
80
|
+
merged: 0,
|
|
81
|
+
skipped: 0,
|
|
82
|
+
warnings: [],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (path.resolve(legacyRoot) === path.resolve(targetRoot)) return result;
|
|
86
|
+
if (!existsSync(legacyRoot)) return result;
|
|
87
|
+
|
|
88
|
+
await fs.mkdir(targetRoot, { recursive: true });
|
|
89
|
+
await moveDirContents(legacyRoot, targetRoot, result);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const remaining = await fs.readdir(legacyRoot);
|
|
93
|
+
if (remaining.length === 0) {
|
|
94
|
+
await fs.rmdir(legacyRoot);
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// best effort
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
@@ -34,7 +34,7 @@ export function registerIndexSessionsCommand(pi: ExtensionAPI): void {
|
|
|
34
34
|
|
|
35
35
|
ctx.ui.notify(`📁 Found ${totalFiles} session files across ${projectDirs.length} projects\n⏳ Indexing...`, 'info');
|
|
36
36
|
|
|
37
|
-
const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'memory');
|
|
37
|
+
const memoryDir = path.join(os.homedir(), '.pi', 'agent', 'pi-hermes-memory');
|
|
38
38
|
const dbManager = new DatabaseManager(memoryDir);
|
|
39
39
|
|
|
40
40
|
try {
|