pi-hermes-memory 0.1.0 → 0.2.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 +209 -78
- package/docs/0.2/PLAN.md +290 -0
- package/docs/0.2/TASKS.md +134 -0
- package/docs/0.2/TEST-PLAN.md +216 -0
- package/docs/ROADMAP.md +245 -135
- package/package.json +9 -5
- package/src/config.ts +11 -0
- package/src/constants.ts +78 -3
- package/src/handlers/auto-consolidate.ts +94 -0
- package/src/handlers/background-review.ts +42 -3
- package/src/handlers/correction-detector.ts +156 -0
- package/src/handlers/insights.ts +20 -1
- package/src/handlers/session-flush.ts +1 -0
- package/src/handlers/skill-auto-trigger.ts +108 -0
- package/src/handlers/skills-command.ts +38 -0
- package/src/index.ts +66 -13
- package/src/store/memory-store.ts +75 -21
- package/src/store/skill-store.ts +292 -0
- package/src/tools/memory-tool.ts +25 -6
- package/src/tools/skill-tool.ts +142 -0
- package/src/types.ts +42 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill tool — registers the LLM-callable `skill` tool for procedural memory.
|
|
3
|
+
* Complements the `memory` tool (declarative knowledge) with procedural knowledge.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
9
|
+
import { SkillStore } from "../store/skill-store.js";
|
|
10
|
+
import { SKILL_TOOL_DESCRIPTION } from "../constants.js";
|
|
11
|
+
|
|
12
|
+
export function registerSkillTool(pi: ExtensionAPI, store: SkillStore): void {
|
|
13
|
+
pi.registerTool({
|
|
14
|
+
name: "skill",
|
|
15
|
+
label: "Skill",
|
|
16
|
+
description: SKILL_TOOL_DESCRIPTION,
|
|
17
|
+
promptSnippet: "Save or manage reusable procedures and patterns",
|
|
18
|
+
promptGuidelines: [
|
|
19
|
+
"Use the skill tool after completing complex tasks that required trial and error or multiple tool calls.",
|
|
20
|
+
"Use 'create' to save a new reusable procedure, 'patch' to update a section of an existing skill.",
|
|
21
|
+
"Do NOT use skills for temporary task state — only for durable, reusable procedures.",
|
|
22
|
+
],
|
|
23
|
+
parameters: Type.Object({
|
|
24
|
+
action: StringEnum(["create", "view", "patch", "edit", "delete"] as const),
|
|
25
|
+
name: Type.Optional(
|
|
26
|
+
Type.String({ description: "Skill name (for create). e.g., 'debug-typescript-errors'" })
|
|
27
|
+
),
|
|
28
|
+
file_name: Type.Optional(
|
|
29
|
+
Type.String({ description: "Skill file name (for view/patch/edit/delete). e.g., 'debug-typescript-errors.md'" })
|
|
30
|
+
),
|
|
31
|
+
description: Type.Optional(
|
|
32
|
+
Type.String({ description: "One-line description of when to use this skill (for create/edit)" })
|
|
33
|
+
),
|
|
34
|
+
section: Type.Optional(
|
|
35
|
+
Type.String({ description: "Section header to patch (for patch action). e.g., 'Procedure', 'Pitfalls'" })
|
|
36
|
+
),
|
|
37
|
+
content: Type.Optional(
|
|
38
|
+
Type.String({ description: "Body content for create, new section content for patch, or new body for edit" })
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
42
|
+
const { action, name, file_name, description, section, content } = params;
|
|
43
|
+
|
|
44
|
+
let result;
|
|
45
|
+
switch (action) {
|
|
46
|
+
case "create":
|
|
47
|
+
if (!name) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "name is required for 'create' action." }) }],
|
|
50
|
+
details: {},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (!description) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "description is required for 'create' action." }) }],
|
|
56
|
+
details: {},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (!content) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "content (skill body) is required for 'create' action." }) }],
|
|
62
|
+
details: {},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
result = await store.create(name, description, content);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case "view":
|
|
69
|
+
if (!file_name) {
|
|
70
|
+
// List all skills
|
|
71
|
+
const index = await store.loadIndex();
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, skills: index }) }],
|
|
74
|
+
details: { skills: index },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const doc = await store.loadSkill(file_name);
|
|
78
|
+
if (!doc) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: `Skill '${file_name}' not found.` }) }],
|
|
81
|
+
details: {},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
result = { success: true, ...doc };
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case "patch":
|
|
88
|
+
if (!file_name) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "file_name is required for 'patch' action." }) }],
|
|
91
|
+
details: {},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (!section) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "section is required for 'patch' action." }) }],
|
|
97
|
+
details: {},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!content) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "content is required for 'patch' action." }) }],
|
|
103
|
+
details: {},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
result = await store.patch(file_name, section, content);
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case "edit":
|
|
110
|
+
if (!file_name) {
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "file_name is required for 'edit' action." }) }],
|
|
113
|
+
details: {},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
result = await store.edit(file_name, description || "", content || "");
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case "delete":
|
|
120
|
+
if (!file_name) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify({ success: false, error: "file_name is required for 'delete' action." }) }],
|
|
123
|
+
details: {},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
result = await store.delete(file_name);
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
default:
|
|
130
|
+
result = {
|
|
131
|
+
success: false,
|
|
132
|
+
error: `Unknown action '${action}'. Use: create, view, patch, edit, delete`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
138
|
+
details: result,
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface MemoryConfig {
|
|
|
9
9
|
memoryCharLimit: number;
|
|
10
10
|
/** Max chars for USER.md (user profile). Default: 1375 */
|
|
11
11
|
userCharLimit: number;
|
|
12
|
+
/** Max chars for project-level MEMORY.md. Default: 2200 */
|
|
13
|
+
projectCharLimit: number;
|
|
12
14
|
/** Turns between background auto-reviews. Default: 10 */
|
|
13
15
|
nudgeInterval: number;
|
|
14
16
|
/** Enable background learning loop. Default: true */
|
|
@@ -21,6 +23,12 @@ export interface MemoryConfig {
|
|
|
21
23
|
flushMinTurns: number;
|
|
22
24
|
/** Override memory directory. Default: ~/.pi/agent/memory */
|
|
23
25
|
memoryDir?: string;
|
|
26
|
+
/** Auto-consolidate when memory is full instead of returning error. Default: true */
|
|
27
|
+
autoConsolidate: boolean;
|
|
28
|
+
/** Detect user corrections and trigger immediate memory save. Default: true */
|
|
29
|
+
correctionDetection: boolean;
|
|
30
|
+
/** Tool calls before triggering background review (in addition to turn count). Default: 15 */
|
|
31
|
+
nudgeToolCalls: number;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export interface MemoryResult {
|
|
@@ -39,6 +47,40 @@ export interface MemorySnapshot {
|
|
|
39
47
|
user: string;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
export interface ConsolidationResult {
|
|
51
|
+
/** Whether consolidation succeeded */
|
|
52
|
+
consolidated: boolean;
|
|
53
|
+
/** Error message if consolidation failed */
|
|
54
|
+
error?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SkillIndex {
|
|
58
|
+
/** File name (slug.md) */
|
|
59
|
+
fileName: string;
|
|
60
|
+
/** Human-readable name */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Short description for system prompt index */
|
|
63
|
+
description: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface SkillDocument extends SkillIndex {
|
|
67
|
+
/** Full markdown body (after frontmatter) */
|
|
68
|
+
body: string;
|
|
69
|
+
/** Version number */
|
|
70
|
+
version: number;
|
|
71
|
+
/** ISO date created */
|
|
72
|
+
created: string;
|
|
73
|
+
/** ISO date last updated */
|
|
74
|
+
updated: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface SkillResult {
|
|
78
|
+
success: boolean;
|
|
79
|
+
error?: string;
|
|
80
|
+
message?: string;
|
|
81
|
+
fileName?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
42
84
|
/**
|
|
43
85
|
* Extract displayable text from a Pi session entry message.
|
|
44
86
|
*
|