claude-recall 0.20.5 → 0.20.7
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/.claude/skills/auto-corrections/SKILL.md +4 -1
- package/.claude/skills/auto-corrections/manifest.json +6 -3
- package/.claude/skills/auto-preferences/SKILL.md +21 -1
- package/.claude/skills/auto-preferences/manifest.json +23 -3
- package/.claude/skills/memory-management/SKILL.md +5 -4
- package/dist/cli/claude-recall-cli.js +13 -1
- package/dist/cli/commands/hook-commands.js +5 -0
- package/dist/hooks/llm-classifier.js +59 -0
- package/dist/hooks/memory-stop-hook.js +91 -0
- package/dist/hooks/post-compact-reload.js +57 -0
- package/dist/pi/extension.js +18 -1
- package/dist/shared/event-processors.js +89 -0
- package/docs/cc-agent-harness.md +114 -0
- package/package.json +1 -1
- package/skills/memory-management.md +4 -0
|
@@ -8,7 +8,7 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Corrections
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 15 memories. Last updated: 2026-04-06.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
@@ -22,6 +22,9 @@ Auto-generated from 12 memories. Last updated: 2026-04-02.
|
|
|
22
22
|
- CORRECTION: Memory with complex metadata
|
|
23
23
|
- CORRECTION: Memory with complex metadata
|
|
24
24
|
- CORRECTION: Memory with complex metadata
|
|
25
|
+
- CORRECTION: Memory with complex metadata
|
|
26
|
+
- CORRECTION: Memory with complex metadata
|
|
27
|
+
- CORRECTION: Memory with complex metadata
|
|
25
28
|
- CORRECTION: License copyright should include user's name instead of 'Claude Recall Contributors'
|
|
26
29
|
- CORRECTION: License copyright should list your name instead of 'Claude Recall Contributors'
|
|
27
30
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "corrections",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"sourceHash": "6bede026828253771f48ae5c05e80878ab69beb584dfc06f416814840016ad6c",
|
|
4
|
+
"memoryCount": 15,
|
|
5
|
+
"generatedAt": "2026-04-06T16:51:19.054Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1775494279035_j6uj5lzxo",
|
|
8
|
+
"memory_1775492069326_vksvzmt3f",
|
|
9
|
+
"memory_1775491767369_sepsjmg8y",
|
|
7
10
|
"memory_1775169786543_43p8to1hu",
|
|
8
11
|
"memory_1775169704632_wzwczltzu",
|
|
9
12
|
"memory_1775169639101_rmxkftqtk",
|
|
@@ -8,10 +8,30 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Preferences
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 74 memories. Last updated: 2026-04-06.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
15
|
+
- Session test preference 1775494279149
|
|
16
|
+
- Test preference 1775494279061-2
|
|
17
|
+
- Test preference 1775494279061-1
|
|
18
|
+
- Test preference 1775494279061-0
|
|
19
|
+
- Test memory content
|
|
20
|
+
- Session test preference 1775492069465
|
|
21
|
+
- Test preference 1775492069353-2
|
|
22
|
+
- Test preference 1775492069353-1
|
|
23
|
+
- Test preference 1775492069353-0
|
|
24
|
+
- Test memory content
|
|
25
|
+
- Session test preference 1775491767519
|
|
26
|
+
- Test preference 1775491767395-2
|
|
27
|
+
- Test preference 1775491767395-1
|
|
28
|
+
- Test preference 1775491767395-0
|
|
29
|
+
- Test memory content
|
|
30
|
+
- When planning implementation work, always divide into phases/stages. Each phase must have its own verification tests with concrete commands and expected outputs. Only proceed to the next phase when the current phase's tests pass. Never combine untested changes into a single deployment. This prevents hours of debugging cascading failures.
|
|
31
|
+
- Solving issues and testing that they are solved takes priority over committing and pushing. Don't suggest committing until the fix is verified end-to-end.
|
|
32
|
+
- When the user says "jam" it means "just answer me" — give a direct, concise answer without extra exploration, tool calls, or elaboration. Skip the research and just respond.
|
|
33
|
+
- After each refactoring, document the changes made. Don't batch documentation to the end — write it as you go.
|
|
34
|
+
- Any major refactoring requires exhaustive search to make sure nothing is missed. Always grep/search comprehensively before and after changes to verify no stale references, broken imports, or missed files remain.
|
|
15
35
|
- Session test preference 1775169786712
|
|
16
36
|
- Test preference 1775169786565-2
|
|
17
37
|
- Test preference 1775169786565-1
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "preferences",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"sourceHash": "77d6964d46ea058e3de341d9d43c79901bc596a18b5a03fa8e402368ba20b412",
|
|
4
|
+
"memoryCount": 74,
|
|
5
|
+
"generatedAt": "2026-04-06T16:51:19.174Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1775494279150_n4pq7zy11",
|
|
8
|
+
"memory_1775494279108_6hbe8qoit",
|
|
9
|
+
"memory_1775494279088_nv8hjdm7s",
|
|
10
|
+
"memory_1775494279062_jx4wrwn6s",
|
|
11
|
+
"memory_1775494278982_fsc491z41",
|
|
12
|
+
"memory_1775492069467_5cturlg0a",
|
|
13
|
+
"memory_1775492069400_icg4tjivf",
|
|
14
|
+
"memory_1775492069377_goix7nu9v",
|
|
15
|
+
"memory_1775492069354_wma3zh5i7",
|
|
16
|
+
"memory_1775492069258_q9d2k28wt",
|
|
17
|
+
"memory_1775491767523_p2xtn4uak",
|
|
18
|
+
"memory_1775491767447_q1dwsdfk3",
|
|
19
|
+
"memory_1775491767421_vgntf4jt8",
|
|
20
|
+
"memory_1775491767397_f181w5lqd",
|
|
21
|
+
"memory_1775491767290_s7ntmkwpg",
|
|
22
|
+
"memory_1775491073130_p8b493ay9",
|
|
23
|
+
"memory_1775236195716_i3pb5nls7",
|
|
24
|
+
"memory_1775210227089_j433ldlva",
|
|
25
|
+
"memory_1775208934902_2kovciriy",
|
|
26
|
+
"memory_1775208477621_fqa3w21j1",
|
|
7
27
|
"memory_1775169786717_1zmwoe6ai",
|
|
8
28
|
"memory_1775169786630_rdudb8hbc",
|
|
9
29
|
"memory_1775169786589_zurej1v51",
|
|
@@ -34,10 +34,11 @@ Persistent memory system that ensures Claude never repeats mistakes and always a
|
|
|
34
34
|
|
|
35
35
|
1. **ALWAYS load rules before acting** — Call `load_rules` as your very first action in a session, before even reading files. Rules inform how you explore, not just how you edit.
|
|
36
36
|
2. **ACT on loaded rules** — After loading, state which rules apply to your current task before proceeding. If a rule conflicts with your plan, follow the rule. If none apply, say so. Loading without applying is the same as not loading.
|
|
37
|
-
3. **Cite applied rules inline** — When a rule influences your work: (applied from memory: <rule>)
|
|
38
|
-
4. **
|
|
39
|
-
5. **
|
|
40
|
-
6. **
|
|
37
|
+
3. **Cite applied rules inline** — When a rule influences your work: (applied from memory: <rule>). Place the citation next to the action it influenced, not at the end of unrelated text.
|
|
38
|
+
4. **User says "recall" / "remember" / "store this" → use Claude Recall** — When the user says any of these keywords, ALWAYS use `mcp__claude-recall__store_memory`. Do NOT write to the native memory directory (`~/.claude/projects/*/memory/`) for these requests. Claude Recall is the user's preferred memory system.
|
|
39
|
+
5. **Ask before storing** — Before calling `store_memory`, tell the user what you plan to store and ask for confirmation
|
|
40
|
+
6. **Capture corrections immediately** — User fixes are highest priority (still ask first)
|
|
41
|
+
7. **Never store secrets** — No API keys, passwords, tokens, or PII
|
|
41
42
|
|
|
42
43
|
## Quick Reference
|
|
43
44
|
|
|
@@ -693,8 +693,20 @@ async function main() {
|
|
|
693
693
|
// This avoids registry lookups on every hook invocation.
|
|
694
694
|
const cliScript = path.join(packageDir, 'dist', 'cli', 'claude-recall-cli.js');
|
|
695
695
|
const hookCmd = `node ${cliScript} hook run`;
|
|
696
|
-
settings.hooksVersion = '
|
|
696
|
+
settings.hooksVersion = '11.0.0'; // v11 = add SessionStart(compact) for post-compaction rule reload
|
|
697
697
|
settings.hooks = {
|
|
698
|
+
SessionStart: [
|
|
699
|
+
{
|
|
700
|
+
matcher: "compact",
|
|
701
|
+
hooks: [
|
|
702
|
+
{
|
|
703
|
+
type: "command",
|
|
704
|
+
command: `${hookCmd} post-compact-reload`,
|
|
705
|
+
timeout: 10
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
}
|
|
709
|
+
],
|
|
698
710
|
PostToolUse: [
|
|
699
711
|
{
|
|
700
712
|
hooks: [
|
|
@@ -85,6 +85,11 @@ class HookCommands {
|
|
|
85
85
|
await handleToolFailure(input);
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
|
+
case 'post-compact-reload': {
|
|
89
|
+
const { handlePostCompactReload } = await Promise.resolve().then(() => __importStar(require('../../hooks/post-compact-reload')));
|
|
90
|
+
await handlePostCompactReload(input);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
88
93
|
case 'bash-failure-watcher': {
|
|
89
94
|
// Backward compat alias — routes to tool-outcome-watcher
|
|
90
95
|
const { handleBashFailureWatcher } = await Promise.resolve().then(() => __importStar(require('../../hooks/tool-outcome-watcher')));
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.classifyWithLLM = classifyWithLLM;
|
|
10
10
|
exports.extractHindsightHint = extractHindsightHint;
|
|
11
|
+
exports.extractSessionLearningsWithLLM = extractSessionLearningsWithLLM;
|
|
11
12
|
exports.classifyBatchWithLLM = classifyBatchWithLLM;
|
|
12
13
|
// Lazy singleton — avoid import cost when API key is absent
|
|
13
14
|
let clientInstance; // undefined = not yet checked
|
|
@@ -148,6 +149,64 @@ async function extractHindsightHint(failureDescription, context) {
|
|
|
148
149
|
return null;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
152
|
+
const SESSION_EXTRACTION_PROMPT = `You are analyzing a coding session transcript to extract durable project knowledge.
|
|
153
|
+
|
|
154
|
+
The transcript shows tool calls (Bash, Edit, Read, Grep, etc.) and their results, plus user and assistant messages.
|
|
155
|
+
|
|
156
|
+
Extract ONLY facts useful in FUTURE sessions:
|
|
157
|
+
- Project conventions discovered (file structure, naming patterns, build tools, test frameworks)
|
|
158
|
+
- Workflow patterns that worked or failed (e.g. "tests must be run from project root")
|
|
159
|
+
- Technical constraints or gotchas encountered (e.g. "this project uses ESM, not CJS")
|
|
160
|
+
- Environment requirements discovered (e.g. "needs Node 20+", "uses pnpm not npm")
|
|
161
|
+
|
|
162
|
+
Do NOT extract:
|
|
163
|
+
- Task-specific details (what was built, which files changed this session)
|
|
164
|
+
- Debugging steps unlikely to recur
|
|
165
|
+
- Code patterns visible by reading the codebase
|
|
166
|
+
- Anything in the EXISTING MEMORIES list below
|
|
167
|
+
|
|
168
|
+
Respond with ONLY valid JSON (no markdown fences):
|
|
169
|
+
[{"type":"project-knowledge|preference|devops|failure","content":"<imperative statement>","confidence":0.0-1.0}]
|
|
170
|
+
|
|
171
|
+
Return [] if nothing durable was learned. Max 10 items. Each content should be a concise imperative statement (e.g. "Run tests with pnpm test, not npm test").`;
|
|
172
|
+
/**
|
|
173
|
+
* Extract durable session learnings from a conversation summary using Haiku.
|
|
174
|
+
* Returns null if no API key or on any failure.
|
|
175
|
+
*/
|
|
176
|
+
async function extractSessionLearningsWithLLM(summary, existingMemories) {
|
|
177
|
+
const client = getClient();
|
|
178
|
+
if (!client)
|
|
179
|
+
return null;
|
|
180
|
+
try {
|
|
181
|
+
const memList = existingMemories.length > 0
|
|
182
|
+
? existingMemories.map(m => `- ${m}`).join('\n')
|
|
183
|
+
: '(none)';
|
|
184
|
+
const systemPrompt = SESSION_EXTRACTION_PROMPT + `\n\nEXISTING MEMORIES (do not duplicate):\n${memList}`;
|
|
185
|
+
const response = await client.messages.create({
|
|
186
|
+
model: MODEL,
|
|
187
|
+
max_tokens: 1000,
|
|
188
|
+
system: systemPrompt,
|
|
189
|
+
messages: [{ role: 'user', content: summary }],
|
|
190
|
+
});
|
|
191
|
+
const content = response.content?.[0];
|
|
192
|
+
if (content?.type !== 'text')
|
|
193
|
+
return null;
|
|
194
|
+
const results = parseJSON(content.text);
|
|
195
|
+
if (!Array.isArray(results))
|
|
196
|
+
return null;
|
|
197
|
+
const validTypes = ['project-knowledge', 'preference', 'devops', 'failure'];
|
|
198
|
+
return results
|
|
199
|
+
.filter((r) => r && validTypes.includes(r.type) && typeof r.content === 'string' && r.content.length > 5)
|
|
200
|
+
.map((r) => ({
|
|
201
|
+
type: r.type,
|
|
202
|
+
content: r.content,
|
|
203
|
+
confidence: typeof r.confidence === 'number' ? r.confidence : 0.7,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
151
210
|
async function classifyBatchWithLLM(texts) {
|
|
152
211
|
if (texts.length === 0)
|
|
153
212
|
return [];
|
|
@@ -46,6 +46,7 @@ const memory_1 = require("../services/memory");
|
|
|
46
46
|
const config_1 = require("../services/config");
|
|
47
47
|
const failure_detectors_1 = require("./failure-detectors");
|
|
48
48
|
const outcome_storage_1 = require("../services/outcome-storage");
|
|
49
|
+
const event_processors_1 = require("../shared/event-processors");
|
|
49
50
|
const MAX_STORE = 3;
|
|
50
51
|
async function handleMemoryStop(input) {
|
|
51
52
|
const transcriptPath = input?.transcript_path ?? '';
|
|
@@ -107,6 +108,37 @@ async function handleMemoryStop(input) {
|
|
|
107
108
|
(0, shared_1.hookLog)('memory-stop', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
|
|
108
109
|
}
|
|
109
110
|
(0, shared_1.hookLog)('memory-stop', `Session end: stored ${stored} memories from ${entries.length} entries`);
|
|
111
|
+
// Session extraction: learn from long coding sessions (reads wider window)
|
|
112
|
+
try {
|
|
113
|
+
(0, event_processors_1.setLogFunction)(shared_1.hookLog);
|
|
114
|
+
const sessionEntries = (0, shared_1.readTranscriptTail)(transcriptPath, 50);
|
|
115
|
+
if (sessionEntries.length >= 10) {
|
|
116
|
+
const conversationEntries = buildConversationEntries(sessionEntries);
|
|
117
|
+
// Record failed subagent outcomes
|
|
118
|
+
for (const entry of conversationEntries) {
|
|
119
|
+
if (entry.toolName === 'Agent' && entry.isError) {
|
|
120
|
+
try {
|
|
121
|
+
outcomeStorage.createOutcomeEvent({
|
|
122
|
+
event_type: 'agent_failure',
|
|
123
|
+
actor: 'tool',
|
|
124
|
+
action_summary: entry.text,
|
|
125
|
+
next_state_summary: entry.text,
|
|
126
|
+
tags: ['agent', 'subagent'],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch { /* non-critical */ }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const extracted = await (0, event_processors_1.extractSessionLearnings)(conversationEntries, input?.session_id ?? '', projectId, 5);
|
|
133
|
+
if (extracted > 0) {
|
|
134
|
+
(0, shared_1.hookLog)('memory-stop', `Session extraction: stored ${extracted} learnings`);
|
|
135
|
+
stored += extracted;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
(0, shared_1.hookLog)('memory-stop', `Session extraction error: ${(0, shared_1.safeErrorMessage)(err)}`);
|
|
141
|
+
}
|
|
110
142
|
// Scan for citations in assistant messages to track compliance
|
|
111
143
|
scanForCitations(transcriptPath);
|
|
112
144
|
// Scan transcript for failure signals (non-zero exits, test cycles, backtracking, etc.)
|
|
@@ -385,3 +417,62 @@ function extractTagsFromContext(context) {
|
|
|
385
417
|
}
|
|
386
418
|
return tags;
|
|
387
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Convert raw JSONL transcript entries to ConversationEntry[] for session extraction.
|
|
422
|
+
*/
|
|
423
|
+
function buildConversationEntries(entries) {
|
|
424
|
+
const result = [];
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if ((0, shared_1.isUserEntry)(entry)) {
|
|
427
|
+
const text = (0, shared_1.extractTextFromEntry)(entry);
|
|
428
|
+
if (text && text.length > 5) {
|
|
429
|
+
result.push({ role: 'user', text: text.substring(0, 300) });
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
const text = (0, shared_1.extractTextFromEntry)(entry);
|
|
434
|
+
if (text && text.length > 5) {
|
|
435
|
+
// Detect subagent task notifications
|
|
436
|
+
const notifMatch = text.match(/<task-notification>[\s\S]*?<\/task-notification>/);
|
|
437
|
+
if (notifMatch) {
|
|
438
|
+
const status = notifMatch[0].match(/<status>(.*?)<\/status>/)?.[1] ?? 'unknown';
|
|
439
|
+
const summary = notifMatch[0].match(/<summary>(.*?)<\/summary>/)?.[1] ?? '';
|
|
440
|
+
result.push({
|
|
441
|
+
role: 'tool_result',
|
|
442
|
+
text: `Agent ${status}: ${summary}`.substring(0, 300),
|
|
443
|
+
toolName: 'Agent',
|
|
444
|
+
isError: status === 'failed' || status === 'killed',
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
result.push({ role: 'assistant', text: text.substring(0, 300) });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Extract paired tool interactions across all entries
|
|
454
|
+
try {
|
|
455
|
+
const interactions = (0, shared_1.extractToolInteractions)(entries);
|
|
456
|
+
for (const ti of interactions) {
|
|
457
|
+
if (ti.call) {
|
|
458
|
+
result.push({
|
|
459
|
+
role: 'assistant',
|
|
460
|
+
text: JSON.stringify(ti.call.input || {}).substring(0, 150),
|
|
461
|
+
toolName: ti.call.name,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (ti.result) {
|
|
465
|
+
result.push({
|
|
466
|
+
role: 'tool_result',
|
|
467
|
+
text: ti.result.content.substring(0, 200),
|
|
468
|
+
toolName: ti.call.name,
|
|
469
|
+
isError: ti.result.isError,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Skip if parsing fails
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* post-compact-reload hook — fires on SessionStart with source "compact".
|
|
4
|
+
*
|
|
5
|
+
* After context compaction, recall rules loaded earlier in the session are
|
|
6
|
+
* gone from the model's context. This hook re-injects them by outputting
|
|
7
|
+
* the active rules to stdout, which CC injects as a system message.
|
|
8
|
+
*
|
|
9
|
+
* Input: { session_id, hook_event_name: "SessionStart", source: "compact" }
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.handlePostCompactReload = handlePostCompactReload;
|
|
13
|
+
const shared_1 = require("./shared");
|
|
14
|
+
const memory_1 = require("../services/memory");
|
|
15
|
+
const config_1 = require("../services/config");
|
|
16
|
+
const DIRECTIVE = 'These rules were re-loaded after context compaction.\n' +
|
|
17
|
+
'Continue applying them. Cite at the point of application: (applied from memory: <rule>)';
|
|
18
|
+
function extractVal(value) {
|
|
19
|
+
if (typeof value === 'string')
|
|
20
|
+
return value;
|
|
21
|
+
if (typeof value === 'object' && value !== null) {
|
|
22
|
+
return value.content || value.value || JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
return String(value ?? '');
|
|
25
|
+
}
|
|
26
|
+
function formatRules(rules) {
|
|
27
|
+
const sections = [];
|
|
28
|
+
if (rules.preferences.length > 0) {
|
|
29
|
+
sections.push('## Preferences\n' + rules.preferences.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
30
|
+
}
|
|
31
|
+
if (rules.corrections.length > 0) {
|
|
32
|
+
sections.push('## Corrections\n' + rules.corrections.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
33
|
+
}
|
|
34
|
+
if (rules.failures.length > 0) {
|
|
35
|
+
sections.push('## Failures\n' + rules.failures.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
36
|
+
}
|
|
37
|
+
if (rules.devops.length > 0) {
|
|
38
|
+
sections.push('## DevOps Rules\n' + rules.devops.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
39
|
+
}
|
|
40
|
+
return sections.join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
async function handlePostCompactReload(input) {
|
|
43
|
+
try {
|
|
44
|
+
const projectId = config_1.ConfigService.getInstance().getProjectId();
|
|
45
|
+
const rules = memory_1.MemoryService.getInstance().loadActiveRules(projectId);
|
|
46
|
+
const totalRules = rules.preferences.length + rules.corrections.length +
|
|
47
|
+
rules.failures.length + rules.devops.length;
|
|
48
|
+
if (totalRules === 0)
|
|
49
|
+
return;
|
|
50
|
+
const body = formatRules(rules);
|
|
51
|
+
console.log(`${DIRECTIVE}\n\n---\n\n${body}`);
|
|
52
|
+
(0, shared_1.hookLog)('post-compact-reload', `Re-injected ${totalRules} rules after compaction`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
(0, shared_1.hookLog)('post-compact-reload', `Error: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/pi/extension.js
CHANGED
|
@@ -64,6 +64,7 @@ function formatRules(rules) {
|
|
|
64
64
|
function default_1(pi) {
|
|
65
65
|
let projectId = '';
|
|
66
66
|
let sessionId = `pi_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
|
67
|
+
const collectedToolResults = [];
|
|
67
68
|
let rulesLoaded = false;
|
|
68
69
|
const collectedUserTexts = [];
|
|
69
70
|
// Route logs through Pi's UI when available
|
|
@@ -78,6 +79,7 @@ function default_1(pi) {
|
|
|
78
79
|
projectId = ctx.cwd.split('/').pop() || 'unknown';
|
|
79
80
|
rulesLoaded = false;
|
|
80
81
|
collectedUserTexts.length = 0;
|
|
82
|
+
collectedToolResults.length = 0;
|
|
81
83
|
(0, event_processors_1.resetPendingFailures)();
|
|
82
84
|
try {
|
|
83
85
|
config_1.ConfigService.getInstance().updateConfig({
|
|
@@ -112,6 +114,13 @@ function default_1(pi) {
|
|
|
112
114
|
.map(c => c.text)
|
|
113
115
|
.join('\n');
|
|
114
116
|
const result = (0, event_processors_1.processToolOutcome)(event.toolName, event.input, output, event.isError, sessionId);
|
|
117
|
+
// Collect for session extraction
|
|
118
|
+
collectedToolResults.push({
|
|
119
|
+
role: 'tool_result',
|
|
120
|
+
text: output.substring(0, 300),
|
|
121
|
+
toolName: event.toolName,
|
|
122
|
+
isError: event.isError,
|
|
123
|
+
});
|
|
115
124
|
if (ctx.hasUI) {
|
|
116
125
|
const label = event.input?.command
|
|
117
126
|
? truncateStr(event.input.command, 40)
|
|
@@ -133,9 +142,17 @@ function default_1(pi) {
|
|
|
133
142
|
(0, event_processors_1.processUserInput)(event.text, sessionId).catch(() => { });
|
|
134
143
|
return { action: 'continue' };
|
|
135
144
|
});
|
|
136
|
-
// --- Event: session end — episode + promotion ---
|
|
145
|
+
// --- Event: session end — episode + promotion + session extraction ---
|
|
137
146
|
pi.on('session_shutdown', (_event, _ctx) => {
|
|
138
147
|
(0, event_processors_1.processSessionEnd)(collectedUserTexts, sessionId, projectId).catch(() => { });
|
|
148
|
+
// Session extraction: learn from long coding sessions
|
|
149
|
+
const allEntries = [
|
|
150
|
+
...collectedUserTexts.map(t => ({ role: 'user', text: t })),
|
|
151
|
+
...collectedToolResults,
|
|
152
|
+
];
|
|
153
|
+
if (allEntries.length >= 10) {
|
|
154
|
+
(0, event_processors_1.extractSessionLearnings)(allEntries, sessionId, projectId, 5).catch(() => { });
|
|
155
|
+
}
|
|
139
156
|
});
|
|
140
157
|
// --- Event: pre-compaction — aggressive capture ---
|
|
141
158
|
pi.on('session_before_compact', (event, _ctx) => {
|
|
@@ -46,7 +46,10 @@ exports.processToolOutcome = processToolOutcome;
|
|
|
46
46
|
exports.processUserInput = processUserInput;
|
|
47
47
|
exports.processSessionEnd = processSessionEnd;
|
|
48
48
|
exports.processPreCompact = processPreCompact;
|
|
49
|
+
exports.buildSummary = buildSummary;
|
|
50
|
+
exports.extractSessionLearnings = extractSessionLearnings;
|
|
49
51
|
const shared_1 = require("../hooks/shared");
|
|
52
|
+
const llm_classifier_1 = require("../hooks/llm-classifier");
|
|
50
53
|
const memory_1 = require("../services/memory");
|
|
51
54
|
const outcome_storage_1 = require("../services/outcome-storage");
|
|
52
55
|
let logFn = () => { }; // silent by default
|
|
@@ -401,3 +404,89 @@ async function processPreCompact(userTexts, sessionId, maxStore = 5) {
|
|
|
401
404
|
}
|
|
402
405
|
return stored;
|
|
403
406
|
}
|
|
407
|
+
const SUMMARY_MAX_CHARS = 4000;
|
|
408
|
+
/**
|
|
409
|
+
* Build a condensed conversation summary from entries for LLM extraction.
|
|
410
|
+
*/
|
|
411
|
+
function buildSummary(entries) {
|
|
412
|
+
const lines = [];
|
|
413
|
+
let totalChars = 0;
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
let line;
|
|
416
|
+
if (entry.role === 'tool_result') {
|
|
417
|
+
const status = entry.isError ? ' [ERROR]' : '';
|
|
418
|
+
const tool = entry.toolName ? `${entry.toolName}` : 'tool';
|
|
419
|
+
line = `[${tool}${status}] ${truncate(entry.text, 200)}`;
|
|
420
|
+
}
|
|
421
|
+
else if (entry.role === 'assistant' && entry.toolName) {
|
|
422
|
+
line = `[assistant → ${entry.toolName}] ${truncate(entry.text, 150)}`;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
line = `[${entry.role}] ${truncate(entry.text, 200)}`;
|
|
426
|
+
}
|
|
427
|
+
if (totalChars + line.length > SUMMARY_MAX_CHARS)
|
|
428
|
+
break;
|
|
429
|
+
lines.push(line);
|
|
430
|
+
totalChars += line.length + 1;
|
|
431
|
+
}
|
|
432
|
+
return lines.join('\n');
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Extract durable learnings from a coding session using LLM analysis.
|
|
436
|
+
*
|
|
437
|
+
* Sends a condensed session summary to Haiku and stores extracted
|
|
438
|
+
* project knowledge, preferences, and workflow patterns.
|
|
439
|
+
*
|
|
440
|
+
* Requires ANTHROPIC_API_KEY — logs a message if unavailable.
|
|
441
|
+
*
|
|
442
|
+
* @param entries Conversation entries (user + assistant + tool results)
|
|
443
|
+
* @param sessionId Current session ID
|
|
444
|
+
* @param projectId Current project ID
|
|
445
|
+
* @param maxStore Max learnings to store (default 5)
|
|
446
|
+
* @returns Number of learnings stored
|
|
447
|
+
*/
|
|
448
|
+
async function extractSessionLearnings(entries, sessionId, projectId, maxStore = 5) {
|
|
449
|
+
if (entries.length < 10)
|
|
450
|
+
return 0;
|
|
451
|
+
try {
|
|
452
|
+
const summary = buildSummary(entries);
|
|
453
|
+
// Fetch existing memories for dedup context
|
|
454
|
+
const existingMemories = [];
|
|
455
|
+
try {
|
|
456
|
+
const ms = memory_1.MemoryService.getInstance();
|
|
457
|
+
const rules = ms.loadActiveRules(projectId);
|
|
458
|
+
const all = [...rules.preferences, ...rules.corrections, ...rules.failures, ...rules.devops];
|
|
459
|
+
for (const m of all.slice(0, 20)) {
|
|
460
|
+
const val = typeof m.value === 'object' ? (m.value?.content || JSON.stringify(m.value)) : String(m.value);
|
|
461
|
+
existingMemories.push(truncate(val, 80));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// Non-critical — extraction can proceed without dedup context
|
|
466
|
+
}
|
|
467
|
+
const learnings = await (0, llm_classifier_1.extractSessionLearningsWithLLM)(summary, existingMemories);
|
|
468
|
+
if (learnings === null) {
|
|
469
|
+
logFn('event-processor', 'Session extraction requires ANTHROPIC_API_KEY. Set it to enable learning from long sessions.');
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
472
|
+
if (learnings.length === 0)
|
|
473
|
+
return 0;
|
|
474
|
+
let stored = 0;
|
|
475
|
+
for (const learning of learnings) {
|
|
476
|
+
if (stored >= maxStore)
|
|
477
|
+
break;
|
|
478
|
+
// Dedup against existing memories
|
|
479
|
+
const existing = (0, shared_1.searchExisting)(learning.content.substring(0, 100));
|
|
480
|
+
if ((0, shared_1.isDuplicate)(learning.content, existing))
|
|
481
|
+
continue;
|
|
482
|
+
(0, shared_1.storeMemory)(learning.content, learning.type, projectId, learning.confidence);
|
|
483
|
+
stored++;
|
|
484
|
+
logFn('event-processor', `Session extraction: ${learning.type} — ${truncate(learning.content, 60)}`);
|
|
485
|
+
}
|
|
486
|
+
return stored;
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
logFn('event-processor', `extractSessionLearnings error: ${(0, shared_1.safeErrorMessage)(err)}`);
|
|
490
|
+
return 0;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Claude Code Agent Harness — Architecture Reference
|
|
2
|
+
|
|
3
|
+
Reference notes on Claude Code's internal agent harness architecture, based on source code analysis (v1.0.x, March 2026). Useful for understanding integration points and designing Claude Recall features.
|
|
4
|
+
|
|
5
|
+
## Core Orchestration
|
|
6
|
+
|
|
7
|
+
**Query loop** — main while-loop that calls the API, executes tools, handles continuations, retries, and abort. Key behaviors:
|
|
8
|
+
- Infinite loop with explicit terminal conditions (end_turn, max_tokens, budget exceeded, turn limit)
|
|
9
|
+
- Pre-API hooks, post-sampling hooks, stop hooks at each stage
|
|
10
|
+
- Memory prefetch started non-blocking before API call
|
|
11
|
+
- Skill discovery prefetch in parallel with streaming
|
|
12
|
+
|
|
13
|
+
**Tool execution pipeline** — partitions tool calls into batches:
|
|
14
|
+
- Read-only tools (Read, Grep, Glob) run concurrently (max 10)
|
|
15
|
+
- Write tools (Bash, Edit, Write) run serially
|
|
16
|
+
- Each tool goes through: permission check → execute → yield result → apply context modifiers
|
|
17
|
+
- Tool result size budget enforced per turn
|
|
18
|
+
|
|
19
|
+
## Permission System
|
|
20
|
+
|
|
21
|
+
Three modes: `default` (interactive), `auto` (ML classifier), `bypass`.
|
|
22
|
+
|
|
23
|
+
Pipeline per tool call:
|
|
24
|
+
1. Rules-based check (always-allow/deny/ask lists)
|
|
25
|
+
2. Bash classifier (ML, 2s timeout for speculative decisions)
|
|
26
|
+
3. Hook execution (PreToolUse hooks)
|
|
27
|
+
4. Interactive UI prompt (if needed)
|
|
28
|
+
5. Decision persisted to ToolPermissionContext
|
|
29
|
+
|
|
30
|
+
Risk classification: LOW/MEDIUM/HIGH per tool action. Denial tracking state for threshold-based fallback.
|
|
31
|
+
|
|
32
|
+
## Multi-Agent
|
|
33
|
+
|
|
34
|
+
**Coordinator mode** — multi-agent orchestration with parallel worker phases and shared scratchpad.
|
|
35
|
+
|
|
36
|
+
**Agent tool** — forks subagents with inherited context:
|
|
37
|
+
- Child shares parent's prompt cache (byte-identical prefix = free context)
|
|
38
|
+
- Hard turn limit per agent
|
|
39
|
+
- Task notifications via XML: `<task-notification><status>completed</status>...</task-notification>`
|
|
40
|
+
- Agent types: worker (async), teammate (in-process, visible UI), fork (implicit context inheritance)
|
|
41
|
+
|
|
42
|
+
**Worker constraints:**
|
|
43
|
+
- SIMPLE mode: Bash, Read, Edit only
|
|
44
|
+
- Normal mode: full tool list including MCP
|
|
45
|
+
|
|
46
|
+
## State Management
|
|
47
|
+
|
|
48
|
+
**AppState** (Zustand store, 200+ fields):
|
|
49
|
+
- messages, tasks, agents, permissions, MCP clients, models, plugins, settings
|
|
50
|
+
- Observable via selectors
|
|
51
|
+
- Task registry: `{ [taskId]: TaskState }` with status tracking
|
|
52
|
+
|
|
53
|
+
**Task types:** local_bash, local_agent, remote_agent, in_process_teammate, local_workflow, monitor_mcp, dream
|
|
54
|
+
|
|
55
|
+
**Session persistence:** transcript JSONL written per session, resumable.
|
|
56
|
+
|
|
57
|
+
## Context Management / Compaction
|
|
58
|
+
|
|
59
|
+
Four compaction strategies (in order of aggressiveness):
|
|
60
|
+
1. **Microcompact** — cache-editing on every turn (efficient, preserves cache)
|
|
61
|
+
2. **Snip compact** — truncate oldest history (feature-gated)
|
|
62
|
+
3. **Context collapse** — deduplicate/compress (feature-gated)
|
|
63
|
+
4. **Autocompact** — full summarization when token threshold crossed
|
|
64
|
+
|
|
65
|
+
Token tracking: per-message estimates, cache creation/read tokens, danger zone threshold, cumulative budget.
|
|
66
|
+
|
|
67
|
+
Pre/post compact hooks fire at each stage.
|
|
68
|
+
|
|
69
|
+
## Safety & Guardrails
|
|
70
|
+
|
|
71
|
+
- Cyber risk instruction (defensive security, CTF rules)
|
|
72
|
+
- Secret scanner (gitleaks patterns, credential redaction)
|
|
73
|
+
- Path traversal prevention
|
|
74
|
+
- Command injection protection (fixed in security audit)
|
|
75
|
+
- Crypto: `crypto.randomUUID()` / `crypto.randomBytes()` throughout (no weak RNG)
|
|
76
|
+
|
|
77
|
+
## Memory System
|
|
78
|
+
|
|
79
|
+
**extractMemories** — forked sub-agent after each query loop:
|
|
80
|
+
- Reads last ~N messages, decides what to extract
|
|
81
|
+
- 5-turn budget, read-then-write strategy
|
|
82
|
+
- Mutual exclusivity: skips if main agent already wrote to memory
|
|
83
|
+
- Shares parent's prompt cache
|
|
84
|
+
|
|
85
|
+
**autoDream** — background consolidation:
|
|
86
|
+
- Three-gate trigger: 24h since last + 5 sessions + no parallel consolidation
|
|
87
|
+
- Four phases: orient → gather → consolidate → prune
|
|
88
|
+
- PID-based locking, 1h stale window
|
|
89
|
+
|
|
90
|
+
**Memory retrieval** — Sonnet sidequery selects up to 5 relevant memories per query from the memory directory.
|
|
91
|
+
|
|
92
|
+
## Integration Points for Claude Recall
|
|
93
|
+
|
|
94
|
+
### Currently Used
|
|
95
|
+
- PostToolUse / PostToolUseFailure hooks (tool outcome capture)
|
|
96
|
+
- UserPromptSubmit hook (correction detection)
|
|
97
|
+
- Stop hook (session-end processing)
|
|
98
|
+
- PreCompact hook (pre-compaction capture)
|
|
99
|
+
- MCP server (tool registration)
|
|
100
|
+
- Skills directory (behavioral guidance)
|
|
101
|
+
|
|
102
|
+
### Available but Unused
|
|
103
|
+
- Permission decision hooks (observe denied tools as learning signals)
|
|
104
|
+
- Task notification parsing (multi-agent session outcomes)
|
|
105
|
+
- Agent fork interception (inject memory context into subagents)
|
|
106
|
+
- Compaction triggers (pre-compact hooks with message access)
|
|
107
|
+
- Memory prefetch integration (inject recall results alongside native memory)
|
|
108
|
+
|
|
109
|
+
### Architectural Constraints
|
|
110
|
+
- Hooks run as external processes (no shared memory, no prompt cache)
|
|
111
|
+
- MCP tools are request-response (no streaming, no multi-turn)
|
|
112
|
+
- Cannot fork sub-agents from hooks
|
|
113
|
+
- Cannot modify the query loop or tool pipeline directly
|
|
114
|
+
- Feature flags (GrowthBook `tengu_*`) control many code paths — not accessible externally
|
package/package.json
CHANGED
|
@@ -27,6 +27,10 @@ If the user states a preference ("I prefer tabs", "use functional style"), call
|
|
|
27
27
|
|
|
28
28
|
If a command fails or you need to backtrack, the failure is captured automatically. You don't need to store it manually.
|
|
29
29
|
|
|
30
|
+
## When the user says "recall", "remember", or "store this"
|
|
31
|
+
|
|
32
|
+
ALWAYS use `recall_store_memory`. These keywords mean the user wants Claude Recall specifically — not any other memory system.
|
|
33
|
+
|
|
30
34
|
## Before making decisions
|
|
31
35
|
|
|
32
36
|
Call `recall_search_memory` with relevant keywords to check for existing project knowledge before choosing approaches, tools, or conventions.
|