pi-hermes-memory 0.6.0 → 0.6.1

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
@@ -38,6 +38,7 @@ pi install npm:pi-hermes-memory
38
38
  |---|---|
39
39
  | 🔍 **Session Search** | Search across all past conversations via SQLite FTS5 |
40
40
  | 🧠 **Persistent Memory** | Facts, preferences, lessons saved to markdown files |
41
+ | ⚠️ **Failure Memory** | Learn from failures — stores what didn't work and why |
41
42
  | 📚 **Procedural Skills** | The agent saves *how* it solved problems as reusable docs |
42
43
  | ⚡ **Background Learning** | Every 10 turns (or 15 tool calls) the agent reviews and saves |
43
44
  | 🔧 **Correction Detection** | When you correct the agent, it saves immediately |
@@ -124,9 +125,51 @@ System Prompt
124
125
  │ • tests use node:test with tsx │
125
126
  │ ═══ END MEMORY ═══ │
126
127
  │ </memory-context> │
128
+ │ │
129
+ │ <memory-context> │
130
+ │ RECENT FAILURES & LESSONS (learn from): │
131
+ │ • [correction] Use pnpm, not npm │
132
+ │ • [failure] Tried localStorage — XSS │
133
+ │ • [insight] Auth0 handles refresh tokens│
134
+ │ ═══ END MEMORY ═══ │
135
+ │ </memory-context> │
127
136
  └─────────────────────────────────────────┘
128
137
  ```
129
138
 
139
+ ## Failure Memory
140
+
141
+ The agent learns from failures, corrections, and insights — just like humans do.
142
+
143
+ ### Memory Categories
144
+
145
+ | Category | What it stores | Example |
146
+ |---|---|---|
147
+ | `failure` | What didn't work and why | "Tried localStorage for tokens — XSS vulnerability" |
148
+ | `correction` | User corrections | "Use pnpm, not npm" |
149
+ | `insight` | Learnings from experience | "Auth0 SDK handles refresh tokens automatically" |
150
+ | `preference` | User preferences | "Prefers dark theme" |
151
+ | `convention` | Project conventions | "Monorepo uses turborepo" |
152
+ | `tool-quirk` | Tool-specific knowledge | "CI needs --frozen-lockfile" |
153
+
154
+ ### How It Works
155
+
156
+ 1. **Auto-detection**: Background review extracts failures from conversations
157
+ 2. **Correction capture**: When you correct the agent, it saves what went wrong
158
+ 3. **System prompt injection**: Recent failures (last 7 days) are injected at session start
159
+ 4. **Searchable**: Use `memory_search("auth", category: "failure")` to find past failures
160
+
161
+ ### Example
162
+
163
+ ```
164
+ User: No, use pnpm not npm
165
+ Agent: [saves correction memory]
166
+
167
+ Next session:
168
+ Agent: "I remember you prefer pnpm over npm. Let me use that."
169
+ ```
170
+
171
+ The agent learns from its mistakes so you don't have to repeat yourself.
172
+
130
173
  Memory blocks are wrapped in `<memory-context>` XML tags with a guard note ("NOT new user input") to prevent the LLM from treating stored facts as instructions.
131
174
 
132
175
  ## Usage
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hermes-memory",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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",
@@ -15,7 +15,7 @@ import type { ConsolidationResult } from "../types.js";
15
15
  export async function triggerConsolidation(
16
16
  pi: ExtensionAPI,
17
17
  store: MemoryStore,
18
- target: "memory" | "user",
18
+ target: "memory" | "user" | "failure",
19
19
  signal?: AbortSignal,
20
20
  ): Promise<ConsolidationResult> {
21
21
  const entries =
@@ -29,7 +29,7 @@ export class MemoryStore {
29
29
  private userEntries: string[] = [];
30
30
  private failureEntries: string[] = [];
31
31
  private snapshot: MemorySnapshot = { memory: "", user: "" };
32
- private consolidator: ((target: "memory" | "user", signal?: AbortSignal) => Promise<ConsolidationResult>) | null = null;
32
+ private consolidator: ((target: "memory" | "user" | "failure", signal?: AbortSignal) => Promise<ConsolidationResult>) | null = null;
33
33
 
34
34
  constructor(private config: MemoryConfig) {}
35
35
 
@@ -37,7 +37,7 @@ export class MemoryStore {
37
37
  * Inject a consolidation function (avoids circular imports).
38
38
  * Called from index.ts after both store and pi are available.
39
39
  */
40
- setConsolidator(fn: (target: "memory" | "user", signal?: AbortSignal) => Promise<ConsolidationResult>): void {
40
+ setConsolidator(fn: (target: "memory" | "user" | "failure", signal?: AbortSignal) => Promise<ConsolidationResult>): void {
41
41
  this.consolidator = fn;
42
42
  }
43
43
 
@@ -100,7 +100,7 @@ export class MemoryStore {
100
100
 
101
101
  // ─── CRUD ───
102
102
 
103
- async add(target: "memory" | "user", content: string, signal?: AbortSignal): Promise<MemoryResult> {
103
+ async add(target: "memory" | "user" | "failure", content: string, signal?: AbortSignal): Promise<MemoryResult> {
104
104
  return this._add(target, content, signal);
105
105
  }
106
106
 
@@ -202,7 +202,7 @@ export class MemoryStore {
202
202
  return this.successResponse(target, "Entry added.");
203
203
  }
204
204
 
205
- async replace(target: "memory" | "user", oldText: string, newContent: string): Promise<MemoryResult> {
205
+ async replace(target: "memory" | "user" | "failure", oldText: string, newContent: string): Promise<MemoryResult> {
206
206
  oldText = oldText.trim();
207
207
  newContent = newContent.trim();
208
208
  if (!oldText) return { success: false, error: "old_text cannot be empty." };
@@ -248,7 +248,7 @@ export class MemoryStore {
248
248
  return this.successResponse(target, "Entry replaced.");
249
249
  }
250
250
 
251
- async remove(target: "memory" | "user", oldText: string): Promise<MemoryResult> {
251
+ async remove(target: "memory" | "user" | "failure", oldText: string): Promise<MemoryResult> {
252
252
  oldText = oldText.trim();
253
253
  if (!oldText) return { success: false, error: "old_text cannot be empty." };
254
254
 
File without changes
File without changes