pi-hermes-memory 0.7.3 → 0.7.5
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 +8 -2
- package/package.json +1 -1
- package/src/config.ts +33 -5
- package/src/constants.ts +27 -0
- package/src/handlers/correction-detector.ts +55 -6
- package/src/store/memory-store.ts +55 -7
- package/src/tools/memory-tool.ts +24 -1
- package/src/types.ts +15 -1
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ System Prompt
|
|
|
119
119
|
└─────────────────────────────────────────┘
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Set `"memoryPolicyStyle"` to `"compact"`, `"custom"`, or `"none"` to
|
|
122
|
+
Set `"memoryPolicyStyle"` to `"full"`, `"compact"`, `"custom"`, or `"none"` to choose policy verbosity while keeping policy-only mode. Set `"memoryMode": "legacy-inject"` to restore the old behavior that injects MEMORY.md, USER.md, project memory, recent failures, and the skill index into the prompt.
|
|
123
123
|
|
|
124
124
|
## Failure Memory
|
|
125
125
|
|
|
@@ -357,6 +357,7 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
357
357
|
"nudgeToolCalls": 15,
|
|
358
358
|
"reviewRecentMessages": 0,
|
|
359
359
|
"reviewEnabled": true,
|
|
360
|
+
"memoryOverflowStrategy": "auto-consolidate",
|
|
360
361
|
"autoConsolidate": true,
|
|
361
362
|
"correctionDetection": true,
|
|
362
363
|
"failureInjectionEnabled": true,
|
|
@@ -383,8 +384,13 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
383
384
|
| `nudgeToolCalls` | `15` | Tool calls between auto-reviews (OR with turns) |
|
|
384
385
|
| `reviewRecentMessages` | `0` | Recent messages included in background review (`0` = all) |
|
|
385
386
|
| `reviewEnabled` | `true` | Enable/disable background learning loop |
|
|
386
|
-
| `
|
|
387
|
+
| `memoryOverflowStrategy` | `auto-consolidate` | Behavior when MEMORY.md, USER.md, or project-scoped memory reaches its character limit: `auto-consolidate` runs the existing consolidation flow; `reject` returns an error; `fifo-evict` rotates older entries in file order until the new entry fits |
|
|
388
|
+
| `autoConsolidate` | `true` | Legacy alias for `memoryOverflowStrategy` when `memoryOverflowStrategy` is not set (`true` = `auto-consolidate`, `false` = `reject`) |
|
|
387
389
|
| `correctionDetection` | `true` | Detect user corrections and save immediately |
|
|
390
|
+
| `correctionStrongPatterns` | unset | Optional case-insensitive regex sources replacing strong correction patterns; omitted preserves defaults, invalid entries are ignored |
|
|
391
|
+
| `correctionWeakPatterns` | unset | Optional case-insensitive regex sources replacing weak correction patterns; omitted preserves defaults, invalid entries are ignored |
|
|
392
|
+
| `correctionNegativePatterns` | unset | Optional case-insensitive regex sources replacing negative correction patterns; omitted preserves defaults, invalid entries are ignored |
|
|
393
|
+
| `correctionDirectiveWords` | unset | Optional directive words replacing the weak-pattern directive words; omitted preserves defaults |
|
|
388
394
|
| `failureInjectionEnabled` | `true` | Legacy mode only: enable/disable injecting recent failure memories into the system prompt |
|
|
389
395
|
| `failureInjectionMaxAgeDays` | `7` | Legacy mode only: maximum age in days for injected failure memories |
|
|
390
396
|
| `failureInjectionMaxEntries` | `5` | Legacy mode only: maximum number of failure memories to inject |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hermes-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
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 } from "./types.js";
|
|
4
|
+
import type { MemoryConfig, MemoryOverflowStrategy } from "./types.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MEMORY_CHAR_LIMIT,
|
|
7
7
|
DEFAULT_USER_CHAR_LIMIT,
|
|
@@ -16,6 +16,12 @@ import {
|
|
|
16
16
|
DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
|
|
17
17
|
} from "./constants.js";
|
|
18
18
|
|
|
19
|
+
const MEMORY_OVERFLOW_STRATEGIES: readonly MemoryOverflowStrategy[] = ["auto-consolidate", "reject", "fifo-evict"];
|
|
20
|
+
|
|
21
|
+
function isMemoryOverflowStrategy(value: unknown): value is MemoryOverflowStrategy {
|
|
22
|
+
return typeof value === "string" && MEMORY_OVERFLOW_STRATEGIES.includes(value as MemoryOverflowStrategy);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
const DEFAULT_CONFIG: MemoryConfig = {
|
|
20
26
|
memoryMode: "policy-only",
|
|
21
27
|
memoryPolicyStyle: "full",
|
|
@@ -29,6 +35,7 @@ const DEFAULT_CONFIG: MemoryConfig = {
|
|
|
29
35
|
flushOnShutdown: true,
|
|
30
36
|
flushMinTurns: DEFAULT_FLUSH_MIN_TURNS,
|
|
31
37
|
flushRecentMessages: DEFAULT_FLUSH_RECENT_MESSAGES,
|
|
38
|
+
memoryOverflowStrategy: "auto-consolidate",
|
|
32
39
|
autoConsolidate: true,
|
|
33
40
|
correctionDetection: true,
|
|
34
41
|
failureInjectionEnabled: true,
|
|
@@ -45,16 +52,21 @@ export const DEFAULT_CONFIG_PATH = path.join(
|
|
|
45
52
|
"hermes-memory-config.json",
|
|
46
53
|
);
|
|
47
54
|
|
|
48
|
-
export function loadConfig(): MemoryConfig {
|
|
55
|
+
export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
|
|
49
56
|
try {
|
|
50
|
-
if (fs.existsSync(
|
|
51
|
-
const raw = fs.readFileSync(
|
|
57
|
+
if (fs.existsSync(configPath)) {
|
|
58
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
52
59
|
const parsed = JSON.parse(raw);
|
|
53
60
|
// Merge: override defaults with user config
|
|
54
61
|
const config: MemoryConfig = { ...DEFAULT_CONFIG };
|
|
55
62
|
const isNonNegativeNumber = (value: unknown): value is number => (
|
|
56
63
|
typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
57
64
|
);
|
|
65
|
+
const isStringArray = (value: unknown): value is string[] => (
|
|
66
|
+
Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
67
|
+
);
|
|
68
|
+
let hasLegacyAutoConsolidate = false;
|
|
69
|
+
let hasMemoryOverflowStrategy = false;
|
|
58
70
|
if (parsed.memoryMode === "policy-only" || parsed.memoryMode === "legacy-inject") config.memoryMode = parsed.memoryMode;
|
|
59
71
|
if (
|
|
60
72
|
parsed.memoryPolicyStyle === "full" ||
|
|
@@ -72,8 +84,19 @@ export function loadConfig(): MemoryConfig {
|
|
|
72
84
|
if (typeof parsed.flushOnShutdown === "boolean") config.flushOnShutdown = parsed.flushOnShutdown;
|
|
73
85
|
if (typeof parsed.flushMinTurns === "number") config.flushMinTurns = parsed.flushMinTurns;
|
|
74
86
|
if (isNonNegativeNumber(parsed.flushRecentMessages)) config.flushRecentMessages = parsed.flushRecentMessages;
|
|
75
|
-
if (typeof parsed.autoConsolidate === "boolean")
|
|
87
|
+
if (typeof parsed.autoConsolidate === "boolean") {
|
|
88
|
+
config.autoConsolidate = parsed.autoConsolidate;
|
|
89
|
+
hasLegacyAutoConsolidate = true;
|
|
90
|
+
}
|
|
91
|
+
if (isMemoryOverflowStrategy(parsed.memoryOverflowStrategy)) {
|
|
92
|
+
config.memoryOverflowStrategy = parsed.memoryOverflowStrategy;
|
|
93
|
+
hasMemoryOverflowStrategy = true;
|
|
94
|
+
}
|
|
76
95
|
if (typeof parsed.correctionDetection === "boolean") config.correctionDetection = parsed.correctionDetection;
|
|
96
|
+
if (isStringArray(parsed.correctionStrongPatterns)) config.correctionStrongPatterns = parsed.correctionStrongPatterns;
|
|
97
|
+
if (isStringArray(parsed.correctionWeakPatterns)) config.correctionWeakPatterns = parsed.correctionWeakPatterns;
|
|
98
|
+
if (isStringArray(parsed.correctionNegativePatterns)) config.correctionNegativePatterns = parsed.correctionNegativePatterns;
|
|
99
|
+
if (isStringArray(parsed.correctionDirectiveWords)) config.correctionDirectiveWords = parsed.correctionDirectiveWords;
|
|
77
100
|
if (typeof parsed.failureInjectionEnabled === "boolean") config.failureInjectionEnabled = parsed.failureInjectionEnabled;
|
|
78
101
|
if (typeof parsed.failureInjectionMaxAgeDays === "number") config.failureInjectionMaxAgeDays = parsed.failureInjectionMaxAgeDays;
|
|
79
102
|
if (typeof parsed.failureInjectionMaxEntries === "number") config.failureInjectionMaxEntries = parsed.failureInjectionMaxEntries;
|
|
@@ -81,6 +104,11 @@ export function loadConfig(): MemoryConfig {
|
|
|
81
104
|
if (typeof parsed.projectCharLimit === "number") config.projectCharLimit = parsed.projectCharLimit;
|
|
82
105
|
if (typeof parsed.memoryDir === "string") config.memoryDir = parsed.memoryDir;
|
|
83
106
|
if (typeof parsed.projectsMemoryDir === "string") config.projectsMemoryDir = parsed.projectsMemoryDir;
|
|
107
|
+
if (hasMemoryOverflowStrategy) {
|
|
108
|
+
config.autoConsolidate = config.memoryOverflowStrategy === "auto-consolidate";
|
|
109
|
+
} else if (hasLegacyAutoConsolidate) {
|
|
110
|
+
config.memoryOverflowStrategy = config.autoConsolidate ? "auto-consolidate" : "reject";
|
|
111
|
+
}
|
|
84
112
|
return config;
|
|
85
113
|
}
|
|
86
114
|
} catch {
|
package/src/constants.ts
CHANGED
|
@@ -182,6 +182,33 @@ export const CORRECTION_NEGATIVE_PATTERNS: RegExp[] = [
|
|
|
182
182
|
/^stop.{0,5}(there|here|for now)/i,
|
|
183
183
|
];
|
|
184
184
|
|
|
185
|
+
/** Directive words required after weak correction patterns */
|
|
186
|
+
export const CORRECTION_DIRECTIVE_WORDS: string[] = [
|
|
187
|
+
"use",
|
|
188
|
+
"don't",
|
|
189
|
+
"dont",
|
|
190
|
+
"do",
|
|
191
|
+
"try",
|
|
192
|
+
"make",
|
|
193
|
+
"run",
|
|
194
|
+
"install",
|
|
195
|
+
"add",
|
|
196
|
+
"remove",
|
|
197
|
+
"delete",
|
|
198
|
+
"change",
|
|
199
|
+
"fix",
|
|
200
|
+
"put",
|
|
201
|
+
"set",
|
|
202
|
+
"write",
|
|
203
|
+
"go",
|
|
204
|
+
"stop",
|
|
205
|
+
"start",
|
|
206
|
+
"the",
|
|
207
|
+
"that",
|
|
208
|
+
"this",
|
|
209
|
+
"it",
|
|
210
|
+
];
|
|
211
|
+
|
|
185
212
|
// ─── Correction save prompt ───
|
|
186
213
|
export const CORRECTION_SAVE_PROMPT = `The user just corrected you. Review what went wrong and save the correction to persistent memory.
|
|
187
214
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
CORRECTION_STRONG_PATTERNS,
|
|
21
21
|
CORRECTION_WEAK_PATTERNS,
|
|
22
22
|
CORRECTION_NEGATIVE_PATTERNS,
|
|
23
|
+
CORRECTION_DIRECTIVE_WORDS,
|
|
23
24
|
ENTRY_DELIMITER,
|
|
24
25
|
} from "../constants.js";
|
|
25
26
|
import type { MemoryConfig } from "../types.js";
|
|
@@ -38,23 +39,71 @@ function extractCorrectionDirective(text: string): string {
|
|
|
38
39
|
return cleaned || text;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
function compileCorrectionPatterns(
|
|
43
|
+
configured: string[] | undefined,
|
|
44
|
+
defaults: RegExp[],
|
|
45
|
+
): RegExp[] {
|
|
46
|
+
if (configured === undefined) return defaults;
|
|
47
|
+
|
|
48
|
+
const patterns: RegExp[] = [];
|
|
49
|
+
for (const source of configured) {
|
|
50
|
+
try {
|
|
51
|
+
patterns.push(new RegExp(source, "i"));
|
|
52
|
+
} catch {
|
|
53
|
+
// Ignore invalid configured regex entries; valid entries still apply.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return patterns;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function escapeRegexLiteral(value: string): string {
|
|
60
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasDirectiveWord(remainder: string, words: string[]): boolean {
|
|
64
|
+
if (words.length === 0) return false;
|
|
65
|
+
const source = words.map(escapeRegexLiteral).join("|");
|
|
66
|
+
return new RegExp(`\\b(${source})\\b`, "i").test(remainder);
|
|
67
|
+
}
|
|
68
|
+
|
|
41
69
|
/**
|
|
42
70
|
* Check if a user message is a correction using the two-pass filter.
|
|
43
71
|
* Returns true if the message should trigger an immediate save.
|
|
44
72
|
*/
|
|
45
|
-
|
|
73
|
+
type CorrectionPatternConfig = Pick<MemoryConfig,
|
|
74
|
+
"correctionStrongPatterns" |
|
|
75
|
+
"correctionWeakPatterns" |
|
|
76
|
+
"correctionNegativePatterns" |
|
|
77
|
+
"correctionDirectiveWords"
|
|
78
|
+
>;
|
|
79
|
+
|
|
80
|
+
export function isCorrection(text: string, config?: CorrectionPatternConfig): boolean {
|
|
81
|
+
const negativePatterns = compileCorrectionPatterns(
|
|
82
|
+
config?.correctionNegativePatterns,
|
|
83
|
+
CORRECTION_NEGATIVE_PATTERNS,
|
|
84
|
+
);
|
|
85
|
+
const strongPatterns = compileCorrectionPatterns(
|
|
86
|
+
config?.correctionStrongPatterns,
|
|
87
|
+
CORRECTION_STRONG_PATTERNS,
|
|
88
|
+
);
|
|
89
|
+
const weakPatterns = compileCorrectionPatterns(
|
|
90
|
+
config?.correctionWeakPatterns,
|
|
91
|
+
CORRECTION_WEAK_PATTERNS,
|
|
92
|
+
);
|
|
93
|
+
const directiveWords = config?.correctionDirectiveWords ?? CORRECTION_DIRECTIVE_WORDS;
|
|
94
|
+
|
|
46
95
|
// Check negative patterns first — suppress even if positive matches
|
|
47
|
-
for (const pattern of
|
|
96
|
+
for (const pattern of negativePatterns) {
|
|
48
97
|
if (pattern.test(text)) return false;
|
|
49
98
|
}
|
|
50
99
|
|
|
51
100
|
// Check strong patterns — always trigger
|
|
52
|
-
for (const pattern of
|
|
101
|
+
for (const pattern of strongPatterns) {
|
|
53
102
|
if (pattern.test(text)) return true;
|
|
54
103
|
}
|
|
55
104
|
|
|
56
105
|
// Check weak patterns — only trigger if followed by a directive clause
|
|
57
|
-
for (const pattern of
|
|
106
|
+
for (const pattern of weakPatterns) {
|
|
58
107
|
if (pattern.test(text)) {
|
|
59
108
|
// Look for a directive after the weak pattern match
|
|
60
109
|
// Directive = a verb or "the/that/this" in the remainder of the text
|
|
@@ -62,7 +111,7 @@ export function isCorrection(text: string): boolean {
|
|
|
62
111
|
if (match && match.index === 0) {
|
|
63
112
|
const remainder = text.slice(match[0].length).trim();
|
|
64
113
|
// Simple heuristic: remainder contains something directive-ish
|
|
65
|
-
if (
|
|
114
|
+
if (hasDirectiveWord(remainder, directiveWords)) {
|
|
66
115
|
return true;
|
|
67
116
|
}
|
|
68
117
|
}
|
|
@@ -91,7 +140,7 @@ export function setupCorrectionDetector(
|
|
|
91
140
|
if (event.message.role !== "user") return;
|
|
92
141
|
const text = getMessageText(event.message);
|
|
93
142
|
if (!text) return;
|
|
94
|
-
if (isCorrection(text)) {
|
|
143
|
+
if (isCorrection(text, config)) {
|
|
95
144
|
pendingCorrection = true;
|
|
96
145
|
}
|
|
97
146
|
});
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MEMORY_FILE,
|
|
25
25
|
USER_FILE,
|
|
26
26
|
} from "../constants.js";
|
|
27
|
-
import type { MemoryConfig, MemoryResult, MemorySnapshot, ConsolidationResult, MemoryCategory } from "../types.js";
|
|
27
|
+
import type { MemoryConfig, MemoryResult, MemorySnapshot, ConsolidationResult, MemoryCategory, MemoryOverflowStrategy } from "../types.js";
|
|
28
28
|
|
|
29
29
|
export class MemoryStore {
|
|
30
30
|
private memoryEntries: string[] = [];
|
|
@@ -77,6 +77,10 @@ export class MemoryStore {
|
|
|
77
77
|
return entries.length ? entries.join(ENTRY_DELIMITER).length : 0;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
private memoryOverflowStrategy(): MemoryOverflowStrategy {
|
|
81
|
+
return this.config.memoryOverflowStrategy ?? (this.config.autoConsolidate ? "auto-consolidate" : "reject");
|
|
82
|
+
}
|
|
83
|
+
|
|
80
84
|
// ─── Load from disk ───
|
|
81
85
|
|
|
82
86
|
async loadFromDisk(): Promise<void> {
|
|
@@ -176,8 +180,14 @@ export class MemoryStore {
|
|
|
176
180
|
|
|
177
181
|
const newTotal = [...entries, encoded].join(ENTRY_DELIMITER).length;
|
|
178
182
|
if (newTotal > limit) {
|
|
183
|
+
const strategy = this.memoryOverflowStrategy();
|
|
184
|
+
|
|
185
|
+
if (strategy === "fifo-evict") {
|
|
186
|
+
return this.fifoEvictAndAdd(target, entries, encoded, content.length, limit);
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
// Auto-consolidate once if configured — limit retries to prevent infinite loops
|
|
180
|
-
if (
|
|
190
|
+
if (strategy === "auto-consolidate" && this.consolidator && _retriesLeft > 0) {
|
|
181
191
|
try {
|
|
182
192
|
const result = await this.consolidator(target, signal);
|
|
183
193
|
if (result.consolidated) {
|
|
@@ -190,11 +200,7 @@ export class MemoryStore {
|
|
|
190
200
|
// Consolidation failed — fall through to error
|
|
191
201
|
}
|
|
192
202
|
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
success: false,
|
|
196
|
-
error: `Memory at ${current}/${limit} chars. Adding this entry (${content.length} chars) would exceed the limit. Replace or remove existing entries first.`,
|
|
197
|
-
};
|
|
203
|
+
return this.memoryFullError(target, content.length);
|
|
198
204
|
}
|
|
199
205
|
|
|
200
206
|
entries.push(encoded);
|
|
@@ -204,6 +210,48 @@ export class MemoryStore {
|
|
|
204
210
|
return this.successResponse(target, "Entry added.");
|
|
205
211
|
}
|
|
206
212
|
|
|
213
|
+
private async fifoEvictAndAdd(
|
|
214
|
+
target: "memory" | "user" | "failure",
|
|
215
|
+
entries: string[],
|
|
216
|
+
encoded: string,
|
|
217
|
+
contentLength: number,
|
|
218
|
+
limit: number,
|
|
219
|
+
): Promise<MemoryResult> {
|
|
220
|
+
if (encoded.length > limit) {
|
|
221
|
+
return this.memoryFullError(target, contentLength);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const remaining = [...entries];
|
|
225
|
+
const evictedEntries: string[] = [];
|
|
226
|
+
|
|
227
|
+
while ([...remaining, encoded].join(ENTRY_DELIMITER).length > limit && remaining.length > 0) {
|
|
228
|
+
const evicted = remaining.shift()!;
|
|
229
|
+
evictedEntries.push(this.stripMetadata(evicted));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
remaining.push(encoded);
|
|
233
|
+
this.setEntries(target, remaining);
|
|
234
|
+
await this.saveToDisk(target);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
...this.successResponse(
|
|
238
|
+
target,
|
|
239
|
+
`Memory updated. Rotated ${evictedEntries.length} older ${evictedEntries.length === 1 ? "entry" : "entries"} to stay within the limit.`,
|
|
240
|
+
),
|
|
241
|
+
evicted_entries: evictedEntries,
|
|
242
|
+
evicted_count: evictedEntries.length,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private memoryFullError(target: "memory" | "user" | "failure", contentLength: number): MemoryResult {
|
|
247
|
+
const current = this.charCount(target);
|
|
248
|
+
const limit = this.charLimit(target);
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: `Memory at ${current}/${limit} chars. Adding this entry (${contentLength} chars) would exceed the limit. Replace or remove existing entries first.`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
207
255
|
async replace(target: "memory" | "user" | "failure", oldText: string, newContent: string): Promise<MemoryResult> {
|
|
208
256
|
oldText = oldText.trim();
|
|
209
257
|
newContent = newContent.trim();
|
package/src/tools/memory-tool.ts
CHANGED
|
@@ -29,6 +29,29 @@ function appendSyncWarning(result: MemoryResult, warning: string): MemoryResult
|
|
|
29
29
|
} as MemoryResult;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function formatMemoryToolText(result: MemoryResult): string {
|
|
33
|
+
const evictedEntries = result.evicted_entries ?? [];
|
|
34
|
+
if (result.success && evictedEntries.length > 0) {
|
|
35
|
+
const lines = [
|
|
36
|
+
result.message ?? `Memory updated. Rotated ${evictedEntries.length} older ${evictedEntries.length === 1 ? "entry" : "entries"} to stay within the limit.`,
|
|
37
|
+
"",
|
|
38
|
+
"Rotated active memory entries:",
|
|
39
|
+
"",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
evictedEntries.forEach((entry, index) => {
|
|
43
|
+
lines.push(`${index + 1}. ${entry}`);
|
|
44
|
+
lines.push("");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
lines.push("If one of these entries should stay active, add it again.");
|
|
48
|
+
if (result.usage) lines.push(`Usage: ${result.usage}`);
|
|
49
|
+
return lines.join("\n").trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return JSON.stringify(result);
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
function sqliteProjectFor(rawTarget: "memory" | "user" | "project" | "failure", projectName?: string | null): string | null | undefined {
|
|
33
56
|
if (rawTarget === "project") return projectName?.trim() || null;
|
|
34
57
|
if (rawTarget === "memory") return null;
|
|
@@ -305,7 +328,7 @@ export function registerMemoryTool(
|
|
|
305
328
|
}
|
|
306
329
|
|
|
307
330
|
return {
|
|
308
|
-
content: [{ type: "text", text:
|
|
331
|
+
content: [{ type: "text", text: formatMemoryToolText(result) }],
|
|
309
332
|
details: result,
|
|
310
333
|
};
|
|
311
334
|
},
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import type { TextContent } from "@mariozechner/pi-ai";
|
|
6
6
|
|
|
7
|
+
export type MemoryOverflowStrategy = "auto-consolidate" | "reject" | "fifo-evict";
|
|
8
|
+
|
|
7
9
|
export interface MemoryConfig {
|
|
8
10
|
/** Prompt memory mode. Default: policy-only */
|
|
9
11
|
memoryMode: "policy-only" | "legacy-inject";
|
|
@@ -35,10 +37,20 @@ export interface MemoryConfig {
|
|
|
35
37
|
memoryDir?: string;
|
|
36
38
|
/** Directory for project-scoped memory (relative to ~/.pi/agent). Default: "projects-memory" */
|
|
37
39
|
projectsMemoryDir?: string;
|
|
38
|
-
/**
|
|
40
|
+
/** Strategy when memory is full. Default: auto-consolidate */
|
|
41
|
+
memoryOverflowStrategy?: MemoryOverflowStrategy;
|
|
42
|
+
/** Legacy alias for memoryOverflowStrategy. Default: true */
|
|
39
43
|
autoConsolidate: boolean;
|
|
40
44
|
/** Detect user corrections and trigger immediate memory save. Default: true */
|
|
41
45
|
correctionDetection: boolean;
|
|
46
|
+
/** Override strong correction regex sources. Missing = defaults; [] = none. */
|
|
47
|
+
correctionStrongPatterns?: string[];
|
|
48
|
+
/** Override weak correction regex sources. Missing = defaults; [] = none. */
|
|
49
|
+
correctionWeakPatterns?: string[];
|
|
50
|
+
/** Override negative correction regex sources. Missing = defaults; [] = none. */
|
|
51
|
+
correctionNegativePatterns?: string[];
|
|
52
|
+
/** Override directive words used after weak correction patterns. Missing = defaults; [] = none. */
|
|
53
|
+
correctionDirectiveWords?: string[];
|
|
42
54
|
/** Inject recent failure memories into the system prompt. Default: true */
|
|
43
55
|
failureInjectionEnabled: boolean;
|
|
44
56
|
/** Maximum age in days for injected failure memories. Default: 7 */
|
|
@@ -71,6 +83,8 @@ export interface MemoryResult {
|
|
|
71
83
|
entries?: string[];
|
|
72
84
|
usage?: string;
|
|
73
85
|
entry_count?: number;
|
|
86
|
+
evicted_entries?: string[];
|
|
87
|
+
evicted_count?: number;
|
|
74
88
|
matches?: string[];
|
|
75
89
|
}
|
|
76
90
|
|