@yandy0725/pi-memory 1.3.1 → 1.3.3
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/index.ts +12 -0
- package/package.json +1 -1
- package/src/dream.ts +10 -2
- package/src/extract.ts +32 -21
- package/src/inject.ts +18 -28
package/index.ts
CHANGED
|
@@ -17,6 +17,15 @@ import { readDreamMeta, shouldNudge, writeDreamMeta } from "./src/nudge";
|
|
|
17
17
|
import { resolveMemoryDir } from "./src/paths";
|
|
18
18
|
import { searchSessions } from "./src/session-search";
|
|
19
19
|
|
|
20
|
+
function extractAgentsMdBlocks(systemPrompt: string): string[] {
|
|
21
|
+
const blocks: string[] = [];
|
|
22
|
+
const re = /<project_instructions\s+path="([^"]+)">\n([\s\S]*?)<\/project_instructions>/g;
|
|
23
|
+
for (const match of systemPrompt.matchAll(re)) {
|
|
24
|
+
blocks.push(match[0]);
|
|
25
|
+
}
|
|
26
|
+
return blocks;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
function resolveDefault(cfg: MemoryConfig, task: "dream" | "autoSurfacing" | "extractMemories", key: "model"): string | undefined;
|
|
21
30
|
function resolveDefault(cfg: MemoryConfig, task: "dream" | "autoSurfacing" | "extractMemories", key: "sessionPersistence"): SessionPersistenceConfig | undefined;
|
|
22
31
|
function resolveDefault(cfg: MemoryConfig, task: "dream" | "autoSurfacing" | "extractMemories", key: "model" | "sessionPersistence"): string | SessionPersistenceConfig | undefined {
|
|
@@ -31,6 +40,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
31
40
|
let indexSnapshot = "";
|
|
32
41
|
let toolRegistered = false;
|
|
33
42
|
const injectedTopics = new Set<string>();
|
|
43
|
+
let lastSystemPrompt = "";
|
|
34
44
|
|
|
35
45
|
pi.on("session_start", async (_event, ctx) => {
|
|
36
46
|
config = await loadConfig(ctx);
|
|
@@ -91,6 +101,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
91
101
|
});
|
|
92
102
|
|
|
93
103
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
104
|
+
lastSystemPrompt = event.systemPrompt;
|
|
94
105
|
if (!config?.enabled || !indexSnapshot || !memoryDir) return;
|
|
95
106
|
|
|
96
107
|
const autoSurfacing = config.autoSurfacing;
|
|
@@ -151,6 +162,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
151
162
|
if (!extractConfig?.enabled) return;
|
|
152
163
|
if (!event.messages || event.messages.length === 0) return;
|
|
153
164
|
runExtract({
|
|
165
|
+
agentsMdBlocks: extractAgentsMdBlocks(lastSystemPrompt),
|
|
154
166
|
model: resolveDefault(config, "extractMemories", "model"),
|
|
155
167
|
thinkLevel: extractConfig.thinkLevel,
|
|
156
168
|
memoryDir,
|
package/package.json
CHANGED
package/src/dream.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { runHeadlessAgent } from "./agent-runner";
|
|
4
4
|
import type { SessionPersistenceConfig, ThinkLevel } from "./config";
|
|
5
5
|
|
|
6
|
-
/** Build dream consolidation task.
|
|
6
|
+
/** Build dream consolidation task. */
|
|
7
7
|
export function buildDreamTask(memoryDir: string, maxLines: number): string {
|
|
8
8
|
return `You are a memory consolidation agent. Your job is to read all memory files
|
|
9
9
|
and consolidate them into a clean, deduplicated memory store.
|
|
@@ -33,6 +33,8 @@ Phase 4 — Prune & Index:
|
|
|
33
33
|
description: specific summary that helps LLM match queries (be specific!)
|
|
34
34
|
type: one of user, feedback, project, reference
|
|
35
35
|
updated: today's date
|
|
36
|
+
- Ensure entry titles are self-contained and descriptive
|
|
37
|
+
(only titles appear in future sessions' index, not the entry body)
|
|
36
38
|
- Generate a compact hook (~150 chars) for each topic summarizing its entries
|
|
37
39
|
- Rebuild MEMORY.md with one line per topic file (max ${maxLines} lines):
|
|
38
40
|
- [Name](file.md) — hook
|
|
@@ -47,7 +49,13 @@ CRITICAL for hooks and descriptions:
|
|
|
47
49
|
- Good: "SSH port 2222 on staging; MySQL 30s timeout; Redis auth fix"
|
|
48
50
|
- Each topic file's \`## Entry Title\` blocks contain the actual memory entries.
|
|
49
51
|
The MEMORY.md line is just a pointer — only ONE line per topic file.
|
|
50
|
-
|
|
52
|
+
|
|
53
|
+
IMPORTANT — Do not prune process rules:
|
|
54
|
+
- "Always do X" / "Never do Y" rules and workflow discipline entries are
|
|
55
|
+
as valuable as technical facts. Do not delete them as "obsolete"
|
|
56
|
+
just because they look like meta-instructions.
|
|
57
|
+
|
|
58
|
+
When done, output a concise summary of changes (merged N, removed N, moved N, updated N).`;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
export interface RunDreamOpts {
|
package/src/extract.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface RunExtractOpts {
|
|
|
13
13
|
parentModel?: Model<any>;
|
|
14
14
|
sessionPersistence?: SessionPersistenceConfig;
|
|
15
15
|
customTools?: ToolDefinition[];
|
|
16
|
+
agentsMdBlocks?: string[]; // 新增
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/** Build extraction task prompt using memory tools instead of raw file I/O. */
|
|
@@ -20,6 +21,7 @@ export function buildExtractTask(
|
|
|
20
21
|
memoryDir: string,
|
|
21
22
|
messages: Array<{ role: string; content: string }>,
|
|
22
23
|
maxTokens: number,
|
|
24
|
+
agentsMdBlocks: string[] = [], // 新增
|
|
23
25
|
): string {
|
|
24
26
|
const fromUser = messages.find((m) => m.role === "user");
|
|
25
27
|
const fromAssistant = messages.findLast((m) => m.role === "assistant");
|
|
@@ -35,34 +37,43 @@ export function buildExtractTask(
|
|
|
35
37
|
"",
|
|
36
38
|
"Analyze the conversation snippet below. If you find valuable learnings, persist them using the memory tools.",
|
|
37
39
|
"",
|
|
38
|
-
"
|
|
39
|
-
"-
|
|
40
|
-
"-
|
|
41
|
-
"-
|
|
40
|
+
"## Tools",
|
|
41
|
+
"- ls — list files in the memory directory",
|
|
42
|
+
"- read — read MEMORY.md and topic files to check for existing topics",
|
|
43
|
+
"- memory_search — full-text search across all memory files for related entries",
|
|
44
|
+
"- memory_add — persist a new memory entry to a topic file (creates the topic if new)",
|
|
42
45
|
"",
|
|
43
|
-
"
|
|
46
|
+
"Do NOT use 'bash', 'write', 'edit', or any other tools.",
|
|
44
47
|
"",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
'- "Always do X" / "Never do Y" rules',
|
|
50
|
-
"- References to external systems or documentation",
|
|
48
|
+
"## Workflow",
|
|
49
|
+
"1. Use ls + read to survey existing topic files and MEMORY.md index.",
|
|
50
|
+
"2. Use memory_search to check for overlapping or related entries.",
|
|
51
|
+
"3. Use memory_add to write new memories — only if the information is novel and valuable.",
|
|
51
52
|
"",
|
|
52
|
-
"
|
|
53
|
+
"## What to Remember",
|
|
54
|
+
"- Process rules: \"Always do X\" / \"Never do Y\" directives, workflow discipline, reporting standards, self-check habits — treat these as seriously as technical facts",
|
|
55
|
+
"- User preferences: coding style, tool choices, naming conventions, workflow habits",
|
|
56
|
+
"- Project conventions: architecture decisions, file organization, tech stack choices",
|
|
57
|
+
"- Discoveries: debugging workarounds, gotchas, configuration quirks, undocumented behavior",
|
|
58
|
+
"- References: external docs, APIs, or systems the user treats as important",
|
|
59
|
+
"- AGENTS.md rules that were violated in this conversation — extract for memory-level reinforcement (refer to the AGENTS.md content below)",
|
|
60
|
+
"",
|
|
61
|
+
"## What to Skip",
|
|
53
62
|
"- One-time task instructions or ephemeral details",
|
|
54
63
|
"- Code snippets or file paths derivable from the project",
|
|
55
|
-
"-
|
|
64
|
+
"- AGENTS.md rules that were followed without issue (refer to the AGENTS.md content below)",
|
|
56
65
|
"- Git history or recent changes",
|
|
66
|
+
"- Obvious or trivial observations",
|
|
57
67
|
"",
|
|
58
|
-
"
|
|
59
|
-
"-
|
|
60
|
-
|
|
61
|
-
"-
|
|
62
|
-
|
|
63
|
-
"- Be concise but complete",
|
|
64
|
-
"- If unsure, do NOT write anything",
|
|
68
|
+
"## Memory Entry Guidelines",
|
|
69
|
+
"- Entry titles must be self-contained and descriptive (only titles appear in future sessions' index)",
|
|
70
|
+
'- Choose the appropriate type: user, feedback, project, or reference (default "feedback")',
|
|
71
|
+
"- Be concise but complete — one clear point per entry",
|
|
72
|
+
"- When in doubt, skip it",
|
|
65
73
|
"",
|
|
74
|
+
...(agentsMdBlocks.length > 0
|
|
75
|
+
? ["## AGENTS.md Rules", ...agentsMdBlocks, ""]
|
|
76
|
+
: []),
|
|
66
77
|
"=== Conversation ===",
|
|
67
78
|
`User: ${truncatedUser}`,
|
|
68
79
|
`Assistant: ${truncatedAssistant}`,
|
|
@@ -72,7 +83,7 @@ export function buildExtractTask(
|
|
|
72
83
|
/** Fire-and-forget memory extraction. Does not await the headless agent. */
|
|
73
84
|
export async function runExtract(opts: RunExtractOpts): Promise<void> {
|
|
74
85
|
if (opts.messages.length === 0) return;
|
|
75
|
-
const task = buildExtractTask(opts.memoryDir, opts.messages, opts.maxContextTokens);
|
|
86
|
+
const task = buildExtractTask(opts.memoryDir, opts.messages, opts.maxContextTokens, opts.agentsMdBlocks ?? []);
|
|
76
87
|
// fire-and-forget: runner disposes internally via finally
|
|
77
88
|
runHeadlessAgent({
|
|
78
89
|
task,
|
package/src/inject.ts
CHANGED
|
@@ -60,25 +60,7 @@ export async function scanTopics(memoryDir: string): Promise<TopicManifest[]> {
|
|
|
60
60
|
return manifests.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function buildSurfacingPrompt(manifest: TopicManifest[], userPrompt: string): string {
|
|
64
|
-
const lines = manifest.map((t) => {
|
|
65
|
-
return `[${t.type}] ${t.filename} — ${t.description.slice(0, 80)}`;
|
|
66
|
-
});
|
|
67
63
|
|
|
68
|
-
return [
|
|
69
|
-
"You are a memory relevance selector. Below is a list of memory topic files and a user message.",
|
|
70
|
-
"Select up to N topic files that are relevant to the user's current query.",
|
|
71
|
-
"Response format: JSON with a 'selected_files' array of filenames.",
|
|
72
|
-
"",
|
|
73
|
-
"=== Topic Files ===",
|
|
74
|
-
...lines,
|
|
75
|
-
"",
|
|
76
|
-
"=== User Message ===",
|
|
77
|
-
userPrompt,
|
|
78
|
-
"",
|
|
79
|
-
'Return: {"selected_files": ["a.md", "b.md"]}',
|
|
80
|
-
].join("\n");
|
|
81
|
-
}
|
|
82
64
|
|
|
83
65
|
export async function injectSurfacedContent(
|
|
84
66
|
memoryDir: string,
|
|
@@ -107,18 +89,27 @@ export async function injectSurfacedContent(
|
|
|
107
89
|
return `<relevant_memories>\n${blocks.join("\n\n")}\n</relevant_memories>`;
|
|
108
90
|
}
|
|
109
91
|
|
|
110
|
-
/** Build the side-query task prompt.
|
|
111
|
-
export function buildSideQueryTask(
|
|
92
|
+
/** Build the side-query task prompt. */
|
|
93
|
+
export function buildSideQueryTask(
|
|
94
|
+
manifest: TopicManifest[],
|
|
95
|
+
userPrompt: string,
|
|
96
|
+
maxFiles: number,
|
|
97
|
+
): string {
|
|
98
|
+
const lines = manifest.map((t) =>
|
|
99
|
+
`[${t.type}] ${t.filename} — ${t.description.slice(0, 80)}`,
|
|
100
|
+
);
|
|
101
|
+
|
|
112
102
|
return [
|
|
113
|
-
|
|
103
|
+
`You are a memory relevance selector. Select up to ${maxFiles} topic files most relevant to the user query.`,
|
|
104
|
+
"If nothing matches, select none.",
|
|
114
105
|
"",
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
'If nothing is relevant, return {"selected_files": []}.',
|
|
106
|
+
"=== Topic Files ===",
|
|
107
|
+
...lines,
|
|
118
108
|
"",
|
|
119
|
-
|
|
109
|
+
"=== User Query ===",
|
|
110
|
+
userPrompt,
|
|
120
111
|
"",
|
|
121
|
-
'Respond with
|
|
112
|
+
'Respond with ONLY a JSON object: {"selected_files": ["filename.md", ...]}',
|
|
122
113
|
].join("\n");
|
|
123
114
|
}
|
|
124
115
|
|
|
@@ -151,8 +142,7 @@ export async function runSideQuery(
|
|
|
151
142
|
): Promise<string[]> {
|
|
152
143
|
const candidates = manifest.filter((t) => !injectedTopics.has(t.filename));
|
|
153
144
|
if (candidates.length === 0) return [];
|
|
154
|
-
const
|
|
155
|
-
const task = buildSideQueryTask(surfacingPrompt, maxFiles);
|
|
145
|
+
const task = buildSideQueryTask(candidates, userPrompt, maxFiles);
|
|
156
146
|
try {
|
|
157
147
|
const result = await runHeadlessAgent({
|
|
158
148
|
task,
|