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.
@@ -8,7 +8,7 @@ source: claude-recall
8
8
 
9
9
  # Corrections
10
10
 
11
- Auto-generated from 12 memories. Last updated: 2026-04-02.
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": "10b0ac995ebe0c54ad0a5972f28ba4a63e3429879bfb5aa92add0e616711aa33",
4
- "memoryCount": 12,
5
- "generatedAt": "2026-04-02T22:43:06.559Z",
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 54 memories. Last updated: 2026-04-02.
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": "d953115f40d7e590f2e4b1b034582b78ba1f1faa0d1521a6e107538ad328327d",
4
- "memoryCount": 54,
5
- "generatedAt": "2026-04-02T22:43:06.764Z",
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. **Ask before storing** Before calling `store_memory`, tell the user what you plan to store and ask for confirmation
39
- 5. **Capture corrections immediately** — User fixes are highest priority (still ask first)
40
- 6. **Never store secrets** — No API keys, passwords, tokens, or PII
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 = '10.0.0'; // v10 = add PostToolUseFailure for explicit error capture
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
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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.