claude-recall 0.33.0 → 0.34.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 CHANGED
@@ -28,7 +28,7 @@ That's the whole workflow. A background hook classified your prompt with an LLM
28
28
  >
29
29
  > **Agent:** Installing vitest with **pnpm** *(applied from memory: "Use pnpm, not npm")* …
30
30
 
31
- Your rules are injected at session start and again right before each relevant tool call — at the moment of decision, not 50,000 tokens upstream. This works across agents too: correct Claude Code on Tuesday, and Kiro applies it on Friday.
31
+ Your rules are injected at session start and kept alive mid-session — just-in-time before each relevant tool call (Claude Code, Pi), or as a periodic refresh (Kiro) — at the moment of decision, not 50,000 tokens upstream. This works across agents too: correct Claude Code on Tuesday, and Kiro applies it on Friday.
32
32
 
33
33
  **And you can audit what it knows at any time:**
34
34
 
@@ -45,7 +45,7 @@ $ claude-recall search "pnpm"
45
45
  ## Features
46
46
 
47
47
  - **Automatic capture** — an LLM classifier detects preferences, corrections, and project facts in your normal prompts, running on **the agent's own LLM** (Claude subscription / Kiro credits — never a separate API key), with regex fallback when no LLM is available
48
- - **Applied where it counts** — rules load at session start *and* are re-surfaced just-in-time before each tool call
48
+ - **Applied where it counts** — rules load at session start and are re-surfaced mid-session: just-in-time before each tool call (Claude Code, Pi) or as a periodic refresh every N prompts (Kiro)
49
49
  - **Project-scoped** — each project gets its own memory namespace; switch directories and the agent switches context
50
50
  - **Learns from failures** — records what broke, why, and what fixed it, so mistakes aren't repeated
51
51
  - **Outcome-aware** — tracks whether rules actually help (tool results, test cycles, re-asks) and promotes validated lessons into active rules
@@ -162,7 +162,8 @@ Once installed, Claude Recall works in the background (CC = Claude Code):
162
162
  |---|---|:-:|:-:|:-:|
163
163
  | **Session start** | Active rules are injected into the agent's context | ✓ | ✓ | ✓ |
164
164
  | **As you type** | Prompts are classified; durable preferences/corrections are stored | ✓ | ✓ | ✓ |
165
- | **Before each tool call** | Relevant rules are re-surfaced next to the action (just-in-time injection) | ✓ | ✓ ||
165
+ | **Before each tool call** | Relevant rules are re-surfaced next to the action (just-in-time injection) | ✓ | ✓ | |
166
+ | **Every 15 prompts** | Active rules are re-injected so long sessions can't silently lose them (interval configurable) | | | ✓ |
166
167
  | **Tool outcomes** | Failures are recorded; Bash failures are paired with their eventual fix | ✓ | ✓ | ✓ |
167
168
  | **Re-ask detection** | Frustration signals (*"still broken"*) are recorded as outcome events | ✓ | ✓ | ✓ |
168
169
  | **Before context compression** | Important context is captured before the window shrinks | ✓ | ✓ | |
@@ -325,7 +326,7 @@ action → outcome event → episode → candidate lesson → promotion → acti
325
326
  outcome resolved per injected rule
326
327
  ```
327
328
 
328
- Failures become candidate lessons (deduplicated by similarity); lessons seen 2+ times (or once, if severe) are promoted to active rules; every just-in-time injection is recorded and resolved against the tool's outcome, building per-rule effectiveness data over time.
329
+ Failures become candidate lessons (deduplicated by similarity); lessons seen 2+ times (or once, if severe) are promoted to active rules; every just-in-time injection (Claude Code, Pi) is recorded and resolved against the tool's outcome, building per-rule effectiveness data over time.
329
330
 
330
331
  ---
331
332
 
@@ -395,6 +396,7 @@ Defaults work out of the box; tune via environment variables as needed.
395
396
  | `CLAUDE_RECALL_KIRO_MODEL` | `claude-haiku-4.5` | Dedicated model for Kiro-LLM capture classification — **independent of your interactive Kiro chat model**. Raise to `claude-sonnet-4.6` for steadier judgement at more credits. See [docs/kiro-llm-capture.md](docs/kiro-llm-capture.md). |
396
397
  | `CLAUDE_RECALL_PREFER_API_KEY` | _(unset)_ | **The only switch that enables the `ANTHROPIC_API_KEY` backend** (and prefers it first). For Pi-only machines without the `claude` binary, or a stronger model you deliberately pay for. Applies to all LLM features, under Claude Code, Kiro, and Pi. |
397
398
  | `CLAUDE_RECALL_KIRO_LLM_TIMEOUT_MS` | `30000` | Hard cap on the headless `kiro-cli` classify call before the capture worker gives up and falls back to regex. |
399
+ | `CLAUDE_RECALL_REFRESH_INTERVAL` | `15` | **Kiro only.** Re-inject the active rules into context every N prompts, so marathon sessions can't silently lose them to context rollover (Kiro has no post-compaction event). `0` disables. |
398
400
  | `CLAUDE_RECALL_LOAD_BUDGET_TOKENS` | `2000` | Token budget for the `load_rules` payload. Rules are emitted in priority order (corrections → preferences by citation → devops by citation → failures) and dropped rules surface via `search_memory`. |
399
401
  | `CLAUDE_RECALL_AUTO_DEMOTE` | `false` | When `true`, auto-demote rules on MCP boot where `load_count >= CLAUDE_RECALL_DEMOTE_MIN_LOADS`, `cite_count = 0`, and age `> CLAUDE_RECALL_DEMOTE_MIN_AGE_DAYS`. Still reversible via `rules promote <id>`. |
400
402
  | `CLAUDE_RECALL_DEMOTE_MIN_LOADS` | `20` | Minimum load count before a rule qualifies for auto-demotion. |
@@ -406,6 +406,12 @@ class ClaudeRecallCLI {
406
406
  console.error('\nCheck your connection, then run `claude-recall upgrade` again.');
407
407
  process.exit(1);
408
408
  }
409
+ // `latest` is interpolated into an npm argument (via a shell on Windows) —
410
+ // accept only a plausible semver string from the registry response.
411
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(latest)) {
412
+ console.error(`❌ Unexpected version string from the registry: "${latest}"`);
413
+ process.exit(1);
414
+ }
409
415
  console.log(`Installed: ${current}`);
410
416
  console.log(`Latest: ${latest}`);
411
417
  const needsInstall = current !== latest;
@@ -413,7 +419,11 @@ class ClaudeRecallCLI {
413
419
  console.log(`\n📦 Upgrading ${current} → ${latest}...\n`);
414
420
  // Run npm install -g, streaming output so the user sees progress / errors live.
415
421
  // shell: true on Windows — npm is npm.cmd there and a bare spawnSync ENOENTs.
416
- const install = spawnSync('npm', ['install', '-g', 'claude-recall@latest'], {
422
+ // Pin the exact version `npm view` just resolved instead of `@latest`:
423
+ // right after a publish, the dist-tag replica npm installs from can lag
424
+ // the metadata endpoint `npm view` read — `@latest` then silently
425
+ // reinstalls the OLD version with exit 0 (observed live on 0.34.0).
426
+ const install = spawnSync('npm', ['install', '-g', `claude-recall@${latest}`], {
417
427
  stdio: 'inherit',
418
428
  shell: process.platform === 'win32',
419
429
  });
@@ -429,7 +439,9 @@ class ClaudeRecallCLI {
429
439
  if (install.status !== 0) {
430
440
  // npm prints its own error — add the practical remediation on top
431
441
  console.error('\n❌ Install failed.');
432
- console.error('\nMost common cause: your global npm prefix is owned by root (EACCES).');
442
+ console.error(`\nIf npm said it can't find claude-recall@${latest} (ETARGET): the release`);
443
+ console.error('is minutes old and the registry is still propagating — wait a minute and re-run.');
444
+ console.error('\nMost common cause otherwise: your global npm prefix is owned by root (EACCES).');
433
445
  console.error('\nQuick fix:');
434
446
  console.error(' sudo npm install -g claude-recall');
435
447
  console.error('\nPermanent fix (no more sudo for any global install on this machine):');
@@ -440,6 +452,26 @@ class ClaudeRecallCLI {
440
452
  console.error('\nThen re-run: claude-recall upgrade');
441
453
  process.exit(install.status ?? 1);
442
454
  }
455
+ // Trust but verify: confirm the globally installed version actually IS
456
+ // `latest` before claiming success. The success message used to report
457
+ // intent, not fact — a propagation race left 0.33.0 installed while the
458
+ // command printed "✓ Upgraded to 0.34.0".
459
+ let installedNow = '';
460
+ try {
461
+ const ls = JSON.parse(execSync('npm ls -g claude-recall --json', {
462
+ encoding: 'utf-8',
463
+ stdio: ['pipe', 'pipe', 'pipe'],
464
+ }));
465
+ installedNow = ls?.dependencies?.['claude-recall']?.version ?? '';
466
+ }
467
+ catch { /* verification is best-effort; fall through to the mismatch path */ }
468
+ if (installedNow !== latest) {
469
+ console.error(`\n❌ Install reported success but the global version is ${installedNow || 'unreadable'}, not ${latest}.`);
470
+ console.error(' This usually means the npm registry is still propagating a very recent');
471
+ console.error(' release. Wait a minute, then re-run: claude-recall upgrade');
472
+ console.error(` Or install the exact version directly: npm install -g claude-recall@${latest}`);
473
+ process.exit(1);
474
+ }
443
475
  // Kill any running MCP servers so Claude Code respawns them with the new binary
444
476
  console.log('\n🧹 Cleaning up running MCP servers (Claude Code respawns them on next tool call)...');
445
477
  try {
@@ -44,7 +44,7 @@ const repair_1 = require("./repair");
44
44
  * `claude-recall kiro setup` writes a Kiro custom agent config
45
45
  * (.kiro/agents/recall.json) wiring the same shared memory database into
46
46
  * Kiro CLI: MCP tools, rules auto-loaded into context at agentSpawn,
47
- * just-in-time rule injection on preToolUse, user-prompt capture, and tool
47
+ * user-prompt capture with a periodic mid-session rule refresh, and tool
48
48
  * outcome tracking. See src/hooks/kiro-hooks.ts for the adapter details and
49
49
  * kiro.dev/docs/cli/custom-agents for the config format.
50
50
  */
@@ -81,23 +81,31 @@ class KiroCommands {
81
81
  fs.writeFileSync(p, JSON.stringify(KiroCommands.buildClassifierAgentConfig(), null, 2) + '\n');
82
82
  return p;
83
83
  }
84
- /** The four lifecycle hook entries (shared by fresh config and merge). */
84
+ /**
85
+ * The lifecycle hook entries (shared by fresh config and merge).
86
+ *
87
+ * No preToolUse entry: Kiro ignores preToolUse stdout (exit codes gate the
88
+ * tool; only exit-2 stderr reaches the LLM — kiro.dev/docs/cli/hooks), so
89
+ * the kiro-rule-injector wired there by versions ≤0.33 never reached the
90
+ * model. Mid-session injection rides userPromptSubmit instead (the periodic
91
+ * rule refresh inside kiro-capture); stale kiro-rule-injector entries are
92
+ * stripped on merge.
93
+ */
85
94
  static buildHookEntries(hookCmd) {
86
95
  return {
87
96
  // stdout of agentSpawn is added to context → rules present from turn one
88
97
  agentSpawn: [
89
98
  { command: `${hookCmd} kiro-agent-spawn`, timeout_ms: 10000 },
90
99
  ],
91
- // Capture. kiro-capture spawns a detached worker that classifies the
92
- // prompt via Kiro's own headless LLM (no ANTHROPIC_API_KEY needed) and
93
- // stores in the background — see src/hooks/kiro-classifier.ts. The hook
94
- // itself returns in milliseconds, so the short timeout is ample.
100
+ // Capture + periodic rule refresh. kiro-capture spawns a detached worker
101
+ // that classifies the prompt via Kiro's own headless LLM (no
102
+ // ANTHROPIC_API_KEY needed) and stores in the background — see
103
+ // src/hooks/kiro-classifier.ts. The hook itself returns in milliseconds,
104
+ // so the short timeout is ample. Its stdout is added to context, which
105
+ // also carries the every-N-prompts rule re-injection for long sessions.
95
106
  userPromptSubmit: [
96
107
  { command: `${hookCmd} kiro-capture`, timeout_ms: 8000 },
97
108
  ],
98
- preToolUse: [
99
- { matcher: '*', command: `${hookCmd} kiro-rule-injector`, timeout_ms: 5000 },
100
- ],
101
109
  postToolUse: [
102
110
  { matcher: '*', command: `${hookCmd} kiro-tool-outcome`, timeout_ms: 5000 },
103
111
  ],
@@ -106,7 +114,7 @@ class KiroCommands {
106
114
  static buildAgentConfig(hookCmd, mcpCommand, mcpArgs) {
107
115
  return {
108
116
  name: 'recall',
109
- description: 'Kiro agent with Claude Recall persistent memory: rules auto-loaded at start, just-in-time injection per tool call, automatic capture of corrections and outcomes.',
117
+ description: 'Kiro agent with Claude Recall persistent memory: rules auto-loaded at start, periodic mid-session rule refresh, automatic capture of corrections and outcomes.',
110
118
  // Also load any servers the user configured in .kiro/settings/mcp.json
111
119
  includeMcpJson: true,
112
120
  mcpServers: {
@@ -162,7 +170,6 @@ class KiroCommands {
162
170
  const handlerMarkers = {
163
171
  agentSpawn: 'kiro-agent-spawn',
164
172
  userPromptSubmit: 'kiro-capture',
165
- preToolUse: 'kiro-rule-injector',
166
173
  postToolUse: 'kiro-tool-outcome',
167
174
  };
168
175
  // Superseded claude-recall hooks to strip from an event before wiring the
@@ -170,28 +177,41 @@ class KiroCommands {
170
177
  // across versions. Pre-0.29 wired userPromptSubmit to `correction-detector`
171
178
  // directly; 0.29 replaced it with `kiro-capture`. Leaving the old entry
172
179
  // makes BOTH fire, double-capturing (often as near-duplicate memories).
180
+ // Versions ≤0.33 wired preToolUse to `kiro-rule-injector`, whose stdout
181
+ // Kiro never adds to context — a per-tool-call no-op, removed entirely.
173
182
  // Only our own commands (containing 'claude-recall' / 'hook run') are
174
183
  // touched — a user's unrelated hook on the same event is preserved.
175
184
  const supersededMarkers = {
176
185
  userPromptSubmit: ['correction-detector'],
186
+ preToolUse: ['kiro-rule-injector'],
177
187
  };
178
188
  const isOurCommand = (cmd) => cmd.includes('claude-recall') || cmd.includes('hook run');
179
- for (const [event, entries] of Object.entries(KiroCommands.buildHookEntries(hookCmd))) {
180
- if (!Array.isArray(config.hooks[event])) {
181
- config.hooks[event] = [];
182
- }
189
+ const entriesByEvent = KiroCommands.buildHookEntries(hookCmd);
190
+ // Visit superseded-only events too (e.g. preToolUse, which we no longer
191
+ // wire but must still clean up), without inventing empty hook arrays in
192
+ // configs that never had them.
193
+ const allEvents = new Set([...Object.keys(entriesByEvent), ...Object.keys(supersededMarkers)]);
194
+ for (const event of allEvents) {
195
+ const entries = entriesByEvent[event] ?? [];
183
196
  const marker = handlerMarkers[event];
184
197
  // Strip superseded entries first (but never the current marker).
185
- for (const stale of supersededMarkers[event] ?? []) {
186
- if (stale === marker)
187
- continue;
188
- const before = config.hooks[event].length;
189
- config.hooks[event] = config.hooks[event].filter((h) => !(typeof h?.command === 'string' && h.command.includes(stale) && isOurCommand(h.command)));
190
- const removed = before - config.hooks[event].length;
191
- if (removed > 0) {
192
- changes.push(`hooks.${event}: removed ${removed} superseded ${stale} entr${removed === 1 ? 'y' : 'ies'}`);
198
+ if (Array.isArray(config.hooks[event])) {
199
+ for (const stale of supersededMarkers[event] ?? []) {
200
+ if (stale === marker)
201
+ continue;
202
+ const before = config.hooks[event].length;
203
+ config.hooks[event] = config.hooks[event].filter((h) => !(typeof h?.command === 'string' && h.command.includes(stale) && isOurCommand(h.command)));
204
+ const removed = before - config.hooks[event].length;
205
+ if (removed > 0) {
206
+ changes.push(`hooks.${event}: removed ${removed} superseded ${stale} entr${removed === 1 ? 'y' : 'ies'}`);
207
+ }
193
208
  }
194
209
  }
210
+ if (entries.length === 0)
211
+ continue;
212
+ if (!Array.isArray(config.hooks[event])) {
213
+ config.hooks[event] = [];
214
+ }
195
215
  const alreadyWired = config.hooks[event].some((h) => typeof h?.command === 'string' && h.command.includes(marker));
196
216
  if (alreadyWired) {
197
217
  changes.push(`hooks.${event}: ${marker} already wired — skipped`);
@@ -12,11 +12,15 @@
12
12
  * understand. fs_write inputs use `path`, mapped to `file_path`.
13
13
  * 2. PostToolUse carries `tool_response` (an object), not a `tool_output`
14
14
  * string.
15
- * 3. Hook stdout is added directly to the agent's context no
16
- * hookSpecificOutput JSON envelope — and the agentSpawn event gives a
17
- * context slot at session start, which we use to load active rules
18
- * up front (Claude Code needs the search-enforcer dance for this;
19
- * Kiro gets it for free).
15
+ * 3. Hook stdout reaches the model's context ONLY on agentSpawn and
16
+ * userPromptSubmit (no hookSpecificOutput JSON envelope). preToolUse
17
+ * stdout is IGNORED exit codes gate the tool call, and only exit-2
18
+ * stderr is shown to the LLM; postToolUse stdout is ignored too
19
+ * (kiro.dev/docs/cli/hooks, verified empirically 2026-07-13). So all
20
+ * context injection rides agentSpawn (rules up front at session start)
21
+ * and userPromptSubmit (periodic mid-session refresh, see
22
+ * emitRuleRefresh). Claude Code needs the search-enforcer dance for
23
+ * this; Kiro gets it for free.
20
24
  *
21
25
  * userPromptSubmit needs no adapter: Kiro sends { prompt, session_id, cwd },
22
26
  * exactly what correction-detector expects — wire it directly.
@@ -26,6 +30,39 @@
26
30
  * only assistant_response (no transcript file), so transcript-based failure
27
31
  * detectors and session extraction don't run under Kiro.
28
32
  */
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
29
66
  Object.defineProperty(exports, "__esModule", { value: true });
30
67
  exports.normalizeKiroInput = normalizeKiroInput;
31
68
  exports.handleKiroAgentSpawn = handleKiroAgentSpawn;
@@ -34,11 +71,12 @@ exports.handleKiroToolOutcome = handleKiroToolOutcome;
34
71
  exports.handleKiroCapture = handleKiroCapture;
35
72
  exports.handleKiroCaptureWorker = handleKiroCaptureWorker;
36
73
  const child_process_1 = require("child_process");
74
+ const fs = __importStar(require("fs"));
75
+ const path = __importStar(require("path"));
37
76
  const shared_1 = require("./shared");
38
77
  const directives_1 = require("../shared/directives");
39
78
  const memory_1 = require("../services/memory");
40
79
  const config_1 = require("../services/config");
41
- const rule_injector_1 = require("./rule-injector");
42
80
  const tool_outcome_watcher_1 = require("./tool-outcome-watcher");
43
81
  const correction_detector_1 = require("./correction-detector");
44
82
  const memory_tools_1 = require("../mcp/tools/memory-tools");
@@ -98,7 +136,7 @@ function formatRulesForContext() {
98
136
  * Standing instruction so the agent KNOWS it has persistent memory — and,
99
137
  * critically, that capture happens via BACKGROUND HOOKS independent of the MCP
100
138
  * tools. Under enterprise Kiro governance the claude-recall MCP server is
101
- * dropped from the toolset, but the userPromptSubmit/preToolUse/postToolUse
139
+ * dropped from the toolset, but the agentSpawn/userPromptSubmit/postToolUse
102
140
  * hooks still write to and read from the local DB. Without the "even without
103
141
  * the tools" clause the agent answers "I can't store that, I have no memory
104
142
  * tools" — technically true of the TOOL, but false of the system, since the
@@ -155,22 +193,18 @@ async function handleKiroAgentSpawn(_input) {
155
193
  }
156
194
  }
157
195
  /**
158
- * preToolUse — just-in-time rule injection. Same ranking/recording core as
159
- * the Claude Code rule-injector, but emits plain text: Kiro adds hook stdout
160
- * to context directly (no hookSpecificOutput envelope).
196
+ * preToolUse — DEPRECATED no-op. This used to emit just-in-time rule
197
+ * injections, on the assumption that Kiro adds preToolUse stdout to context.
198
+ * It does not: preToolUse stdout is ignored — exit codes gate the tool call
199
+ * and only exit-2 stderr reaches the LLM (kiro.dev/docs/cli/hooks, verified
200
+ * empirically 2026-07-13) — so nothing this handler printed ever reached the
201
+ * model, while every emission was falsely recorded as an injection. Mid-session
202
+ * injection now rides userPromptSubmit (emitRuleRefresh). The handler stays
203
+ * registered so agent configs wired by older versions keep exiting 0;
204
+ * `kiro setup` no longer wires it and strips stale entries on re-run.
161
205
  */
162
- async function handleKiroRuleInjector(input) {
163
- try {
164
- const normalized = normalizeKiroInput(input);
165
- const context = await (0, rule_injector_1.computeInjection)(normalized?.tool_name ?? '', normalized?.tool_input ?? {}, '');
166
- if (context) {
167
- process.stdout.write(context + '\n');
168
- }
169
- }
170
- catch (err) {
171
- // Best-effort — never block the tool call
172
- (0, shared_1.hookLog)(HOOK_NAME, `rule-injector error: ${(0, shared_1.safeErrorMessage)(err)}`);
173
- }
206
+ async function handleKiroRuleInjector(_input) {
207
+ (0, shared_1.hookLog)(HOOK_NAME, 'kiro-rule-injector is deprecated (Kiro ignores preToolUse stdout) — no-op; re-run `claude-recall kiro setup` to unwire it');
174
208
  }
175
209
  /**
176
210
  * postToolUse — outcome capture and Bash fix-pairing, delegated to the shared
@@ -184,6 +218,84 @@ async function handleKiroToolOutcome(input) {
184
218
  (0, shared_1.hookLog)(HOOK_NAME, `tool-outcome error: ${(0, shared_1.safeErrorMessage)(err)}`);
185
219
  }
186
220
  }
221
+ /**
222
+ * Mid-session rule refresh — the Kiro answer to long-session context loss.
223
+ * Rules enter context once at agentSpawn; when Kiro later compacts or rolls
224
+ * the conversation, they silently vanish and nothing re-injects them (Kiro has
225
+ * no post-compaction event, and preToolUse stdout is ignored — see
226
+ * handleKiroRuleInjector). userPromptSubmit stdout IS added to context
227
+ * same-turn, so every CLAUDE_RECALL_REFRESH_INTERVAL prompts (default 15,
228
+ * 0 disables) we re-emit the active rules from here.
229
+ *
230
+ * The prompt counter is per-session (keyed by session_id) in hook-state/;
231
+ * stale counter files are pruned on each session's first prompt.
232
+ */
233
+ const DEFAULT_REFRESH_INTERVAL = 15;
234
+ const REFRESH_STATE_PREFIX = 'kiro-refresh-';
235
+ const REFRESH_STATE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
236
+ function refreshInterval() {
237
+ const raw = process.env.CLAUDE_RECALL_REFRESH_INTERVAL;
238
+ if (raw === undefined || raw.trim() === '')
239
+ return DEFAULT_REFRESH_INTERVAL;
240
+ const n = parseInt(raw, 10);
241
+ if (!Number.isFinite(n))
242
+ return DEFAULT_REFRESH_INTERVAL;
243
+ return n > 0 ? n : 0;
244
+ }
245
+ /** Increment the per-session prompt counter and return the new count. */
246
+ function bumpPromptCount(sessionId) {
247
+ const safeId = sessionId.replace(/[^a-zA-Z0-9_-]/g, '_') || 'default';
248
+ const file = path.join((0, shared_1.hookStateDir)(), `${REFRESH_STATE_PREFIX}${safeId}.json`);
249
+ let count = 0;
250
+ try {
251
+ count = JSON.parse(fs.readFileSync(file, 'utf8')).count ?? 0;
252
+ }
253
+ catch { /* first prompt of the session, or unreadable — start fresh */ }
254
+ count++;
255
+ fs.writeFileSync(file, JSON.stringify({ count, updated: Date.now() }));
256
+ if (count === 1)
257
+ pruneStaleRefreshState();
258
+ return count;
259
+ }
260
+ /** Best-effort removal of counter files from long-dead sessions. */
261
+ function pruneStaleRefreshState() {
262
+ try {
263
+ const dir = (0, shared_1.hookStateDir)();
264
+ const cutoff = Date.now() - REFRESH_STATE_MAX_AGE_MS;
265
+ for (const name of fs.readdirSync(dir)) {
266
+ if (!name.startsWith(REFRESH_STATE_PREFIX))
267
+ continue;
268
+ const p = path.join(dir, name);
269
+ try {
270
+ if (fs.statSync(p).mtimeMs < cutoff)
271
+ fs.unlinkSync(p);
272
+ }
273
+ catch { /* another process won the race — fine */ }
274
+ }
275
+ }
276
+ catch { /* pruning is housekeeping, never let it interfere */ }
277
+ }
278
+ function emitRuleRefresh(input) {
279
+ try {
280
+ const interval = refreshInterval();
281
+ if (interval === 0)
282
+ return;
283
+ const count = bumpPromptCount(String(input?.session_id ?? 'default'));
284
+ if (count % interval !== 0)
285
+ return;
286
+ const rules = formatRulesForContext();
287
+ if (!rules.body)
288
+ return;
289
+ process.stdout.write(`🔄 Recall: periodic rule refresh (prompt ${count} this session). ` +
290
+ 'Earlier rule injections may have been compacted out of your context — continue applying these:\n\n' +
291
+ rules.body + '\n');
292
+ (0, shared_1.hookLog)(HOOK_NAME, `refresh: re-injected ${rules.total} rule(s) at prompt ${count} (interval ${interval})`);
293
+ }
294
+ catch (err) {
295
+ // The refresh is an enhancement — never let it break capture
296
+ (0, shared_1.hookLog)(HOOK_NAME, `refresh error: ${(0, shared_1.safeErrorMessage)(err)}`);
297
+ }
298
+ }
187
299
  /**
188
300
  * userPromptSubmit — the synchronous gate Kiro waits on. Under Kiro there is no
189
301
  * ANTHROPIC_API_KEY, so classification uses Kiro's own headless LLM
@@ -195,8 +307,12 @@ async function handleKiroToolOutcome(input) {
195
307
  * it over stdin, and returns in milliseconds. The worker performs the slow Kiro
196
308
  * classify call and stores the memory in the background. Same pattern as
197
309
  * session-end-checkpoint. See docs/kiro-llm-capture.md.
310
+ *
311
+ * It also owns the mid-session rule refresh (emitRuleRefresh above): the
312
+ * refresh must count EVERY prompt, so it runs before the capture pre-checks.
198
313
  */
199
314
  async function handleKiroCapture(input) {
315
+ emitRuleRefresh(input);
200
316
  const prompt = input?.prompt ?? '';
201
317
  // Cheap pre-checks mirror correction-detector so we don't spawn a worker (and
202
318
  // spend Kiro credits) on input that could never be stored.
@@ -0,0 +1,83 @@
1
+ <!--
2
+ Archived: the original Claude Recall launch article, published on LinkedIn by
3
+ Raoul Biagioni on August 7, 2025. Preserved verbatim as a historical record of
4
+ the project's origin. NOTE: it predates most of what Claude Recall is today —
5
+ Pi and Kiro support, the outcome-aware learning loop, LLM-based capture, the
6
+ no-API-key/subscription architecture — so treat it as origin story, not docs.
7
+ -->
8
+
9
+ # Why I Spent My Weekend Teaching a Goldfish-Brained AI to Remember
10
+
11
+ **Raoul Biagioni** — ♾️ AI Development Specialist | Agentic Engineer | Enabling AI-driven Business Innovation
12
+
13
+ *Published on LinkedIn, August 7, 2025*
14
+
15
+ ---
16
+
17
+ Claude Code forgets what you just told it. Not just between sessions. Even 10 messages into a conversation.
18
+
19
+ But what if it didn't?
20
+
21
+ ## The Problem That Sparked a Solution
22
+
23
+ Over the past six months, I watched myself explain the same codebase preferences to Claude over and over again.
24
+
25
+ PostgreSQL for data. TypeScript strict mode. Tests directory for saving tests.
26
+
27
+ Not just in new sessions. Even 20 conversation turns later, as the context window fills up, Claude starts forgetting what we discussed at the beginning.
28
+
29
+ That's when I decided: Claude Code needs a memory.
30
+
31
+ ## Building Claude Recall: A Memory That Actually Works
32
+
33
+ Sure, there are memory solutions out there. xmem requires an external vector database for storing memory. Mem0 (OpenMemory) needs Chrome extensions and API keys to connect across tools.
34
+
35
+ But I wanted something different: truly local, minimal dependencies, and built directly into Claude Code using MCP. No browser extensions. No vector DB setup. No API keys. Just `npm install claude-recall` and you're done.
36
+
37
+ Here's what I built in 72 hours of coding:
38
+
39
+ ### 🧠 Smart Capture
40
+
41
+ - Hooks that watch every tool Claude uses (118-line robust implementation)
42
+ - Patterns detected automatically from your workflow
43
+ - Zero configuration needed
44
+
45
+ ### ⚡ Smart Retrieval
46
+
47
+ - MCP server provides memory tools directly to Claude
48
+ - Pattern-based intent detection from natural language
49
+ - Context-aware memory filtering and relevance scoring
50
+
51
+ ### 🔧 The Tech Stack
52
+
53
+ Claude Recall consists of three main components: A JavaScript hook that intercepts Claude's tool usage (file reads, bash commands, etc.), a Node.js MCP server to expose memory search and retrieval tools to Claude, and a SQLite database that stores captured patterns and preferences locally.
54
+
55
+ When Claude performs an action, the hook captures it and sends it to the pattern detector. The detector uses regex-based pattern matching to identify things like file locations, tool preferences, and coding styles with confidence scores. Patterns scoring above 0.8 are stored in SQLite. During Claude sessions, the MCP server provides tools that Claude can call to search and retrieve these stored memories based on context and relevance scoring.
56
+
57
+ ## Real Results From Real Conversations
58
+
59
+ **Before Claude Recall (message #25 in same chat):**
60
+
61
+ - "What database do we use?" → Claude searches everywhere
62
+ - "Where's the auth logic?" → Claude searches everywhere
63
+ - "Write a test" → Claude saves the test in the project root
64
+
65
+ **After Claude Recall (message #100, still remembers):**
66
+
67
+ - "What database do we use?" → "PostgreSQL, as always"
68
+ - "Where's the auth logic?" → "src/services/auth.ts:42"
69
+ - "Write a test" → Claude saves the test in tests/
70
+
71
+ ## The Reality Check
72
+
73
+ I'm conscious that in the fast-evolving GenAI ecosystem, many projects are quickly destined to oblivion. I have no doubt that the problem I described and solved for myself will be solved better by someone else soon. But here's the thing: I had an absolute blast building Claude Recall with the help of Claude Code by Anthropic, and the amazing AI Orchestration Platform Claude-Flow by Reuven Cohen from Agentics Foundation.
74
+
75
+ Sometimes the best tools are the ones you build for yourself, not because they'll change the world, but because they solve your specific problem right now.
76
+
77
+ > 💡 **Key Takeaway:** We're living in the stone age of AI assistants. They're powerful but goldfish-brained — forgetting context even within the same conversation. That changes now.
78
+
79
+ 🔗 Link: in the comments.
80
+
81
+ 💬 Question for you: What's the ONE thing you wish your AI assistant would remember?
82
+
83
+ #AITools #DeveloperProductivity #OpenSource #ClaudeAI
@@ -0,0 +1,68 @@
1
+ <!--
2
+ DRAFT v2 — reader-first rewrite of the one-year follow-up (July 2026).
3
+ v1 (deleted) was structured around the author's broken-promise confession;
4
+ this version is structured around what the READER gets: three things they can
5
+ check/fix about their own agent's memory today, whether or not they install
6
+ anything. The tool enters late, the "when NOT to bother" section is explicit,
7
+ and the CTA offers a fair trade (2 commands, 1 week, reversible) for honest
8
+ answers to: does it work / am I over-promising / is it worth it at all.
9
+ Facts verified against v0.33.0 on 2026-07-10: write-time dedup (live-tested),
10
+ prune-on-boot, demotion (manual/opt-in — hence "can demote"), Kiro→CC shared
11
+ DB (live-tested), claude -p key-over-subscription precedence (live-tested).
12
+ -->
13
+
14
+ # Your Coding Agent Finally Has Memory. Here's Where It Still Fails You.
15
+
16
+ **Raoul Biagioni** — *Draft v2, July 2026*
17
+
18
+ ---
19
+
20
+ You've told your coding agent "use pnpm, not npm" at least five times. This year that was finally supposed to stop: Claude Code now ships auto-memory, on by default. It watches your corrections and writes itself notes.
21
+
22
+ Genuinely good. I build in this space and I'll say it plainly: for a lot of people, the built-in memory is enough.
23
+
24
+ But if you live in a coding agent daily, there are three failure modes it leaves wide open — and you'll hit all three without ever getting an error message. Whether or not you try my tool, you'll want to know they exist.
25
+
26
+ ## 1. Agent memory rots — silently
27
+
28
+ Built-in memory is great for the first twenty sessions. Then the notes pile up: contradictory entries, "yesterday" written three weeks ago, fixes referencing files you've deleted. Past a point, your agent isn't remembering — it's confidently misremembering, and stale notes are worse than no notes, because you don't think to double-check something the agent states as known fact.
29
+
30
+ **Check it yourself today:** open your project's auto-memory file and read what your agent believes about your codebase. If you've been at it more than a month, I'd bet money something in there is wrong.
31
+
32
+ ## 2. Your corrections don't travel
33
+
34
+ Correct Claude Code on Tuesday; Kiro makes the same mistake on Friday. Every agent keeps its own notebook, so a team (or a person) using more than one agent re-teaches every preference once per tool. The more agents you run, the more your corrections fragment.
35
+
36
+ ## 3. Your exported API key may be quietly paying for things
37
+
38
+ This one surprised me, and it generalizes beyond memory tools: **if you have `ANTHROPIC_API_KEY` exported for some other tool, `claude -p` — and anything built on it — will silently bill that key instead of your Claude subscription.** Two wallets, one of them draining without you deciding anything. Audit your environment; you may be paying twice for AI you already subscribe to.
39
+
40
+ ## What I built against these
41
+
42
+ A year ago I wrote here about giving my "goldfish-brained" agent a memory. That tool, Claude Recall, has since become specifically an answer to the three problems above:
43
+
44
+ - **Anti-rot:** it de-duplicates on write, prunes on every boot, and can demote rules that stop earning their keep — designed for session 200, not session 5.
45
+ - **One brain, every agent:** a single local SQLite file shared by Claude Code, Pi, and Kiro. Correct one agent; the others apply it.
46
+ - **Never your API key:** it runs on the login your agent already has (Claude subscription, Kiro credits). An exported key is untouched unless you explicitly opt in.
47
+
48
+ Local, no cloud, no telemetry, and you can read every stored memory with one command — or wipe them.
49
+
50
+ Setup is two commands: `npm install -g claude-recall`, then one setup command per project. Uninstalling is one.
51
+
52
+ ## When you should NOT bother
53
+
54
+ Fair is fair: if you use a single agent, a couple of sessions a week, on one or two projects — the built-in memory is probably enough for you. Don't install another tool for a problem you don't feel.
55
+
56
+ ## What I'm asking — and what you get
57
+
58
+ Here's the trade. I built this for myself, which means I genuinely don't know if it holds up in your workflow. So if the three failure modes above sound familiar, run it for a week next to the built-in memory and tell me the truth:
59
+
60
+ 1. **Does it work as expected** — right things captured, surfaced at the right moment?
61
+ 2. **Am I over-promising** — where does this article outrun your actual experience?
62
+ 3. **Is it worth it at all** — or is the built-in memory quietly good enough?
63
+
64
+ What you get in return: a memory that survives, travels, and never touches your key — if I'm right. And if I'm wrong, you've spent two commands finding out, and your feedback directly decides what gets built next. Either answer is useful to me; only one of us is guessing right now.
65
+
66
+ Link and quick-start in the comments. Tell me which of the three failure modes you've hit.
67
+
68
+ #AITools #DeveloperProductivity #OpenSource #ClaudeAI #AIMemory
@@ -0,0 +1,46 @@
1
+ <!--
2
+ Tight LinkedIn version (~430 words) of docs/2026-07-follow-up-article-draft.md
3
+ (reader-first v2). Same three failure modes, same honest opt-out, same CTA —
4
+ compressed for feed reading. Not yet published.
5
+ -->
6
+
7
+ # Your Coding Agent Finally Has Memory. Here's Where It Still Fails You.
8
+
9
+ Claude Code ships auto-memory, on by default. Your agent watches your corrections, writes itself notes, and remembers them next session.
10
+
11
+ Genuinely good. For many people it's enough.
12
+
13
+ But if you live in a coding agent daily, it leaves three failure modes wide open — and you'll hit all three without ever seeing an error.
14
+
15
+ **1. Agent memory rots — silently.**
16
+ Great for twenty sessions. Then the notes pile up: contradictions, "yesterday" written three weeks ago, fixes referencing deleted files — and only the first 200 lines even load at startup. Your agent stops remembering and starts confidently *mis*remembering. Anthropic knows: they shipped a periodic cleanup pass ("Auto Dream") for exactly this — a tidy-up of the same self-written notes, so a wrong note that reads plausibly stays believed. Check it yourself: open your auto-memory file and read what your agent believes about your codebase. To be clear, *no* memory system captures perfectly — mine included. The real question is what happens to the mistakes afterwards: dedup, pruning, retiring rules that stop earning their keep — or nothing.
17
+
18
+ **2. Your corrections don't travel.**
19
+ Correct Claude Code on Tuesday; Kiro repeats the mistake on Friday. Every agent keeps its own notebook, so every preference gets re-taught once per tool.
20
+
21
+ **3. Your exported API key may be quietly paying for things.**
22
+ If `ANTHROPIC_API_KEY` is exported for some other tool, headless Claude (`claude -p` — and every script or hook built on it) bills that key instead of your Claude subscription. Not a bug: Anthropic's docs say the env key *"takes precedence over subscription authentication"* — the interactive app asks you which to use; automation doesn't ask. Two wallets, one draining without you deciding anything. Audit your env.
23
+
24
+ **Claude Recall** — my open-source memory engine for coding agents — is built specifically against these three.
25
+
26
+ Some of you saw me launch it here last summer as a weekend hack: Claude-Code-only, regex-based capture, held together with enthusiasm. Today's version (v0.33) is a different animal — same local-first idea, now grown into what the weekend hack couldn't be:
27
+
28
+ ▸ **Anti-rot:** de-duplicates on write, prunes on every boot, can demote rules that stop earning their keep. Built for session 200, not session 5.
29
+ ▸ **One brain, every agent:** a single local SQLite file shared by Claude Code, Pi, and Kiro. Correct one; the others apply it.
30
+ ▸ **Never your API key:** runs on the login your agent already has. Your key is untouched unless you explicitly opt in.
31
+
32
+ Local. No cloud. No telemetry. Two commands to install; one to remove.
33
+
34
+ **When NOT to bother:** one agent, a few sessions a week, one or two projects → the built-in memory is probably enough. Don't install a tool for a problem you don't feel.
35
+
36
+ **The ask.** I built this for myself — I genuinely don't know if it holds up in *your* workflow. If the three failure modes sound familiar: run it for a week next to the built-in memory and tell me the truth.
37
+
38
+ 1. Does it work as expected?
39
+ 2. Am I over-promising?
40
+ 3. Is it worth it at all — or is built-in quietly enough?
41
+
42
+ Either answer is useful. Only one of us is guessing right now.
43
+
44
+ Link in the comments. Which of the three have you hit?
45
+
46
+ #AITools #DeveloperProductivity #OpenSource #ClaudeAI #AIMemory
package/docs/kiro.md CHANGED
@@ -30,7 +30,7 @@ and inside the Kiro chat, switch to the agent:
30
30
  /agent swap recall
31
31
  ```
32
32
 
33
- You get: active rules injected into context automatically at agent start (no tool call needed), just-in-time rule injection before each tool call, automatic capture of corrections/preferences from your prompts, tool-outcome tracking with Bash fix-pairing, and the full MCP tool surface (read-only tools pre-approved).
33
+ You get: active rules injected into context automatically at agent start (no tool call needed), a periodic mid-session rule refresh (see below), automatic capture of corrections/preferences from your prompts, tool-outcome tracking with Bash fix-pairing, and the full MCP tool surface (read-only tools pre-approved).
34
34
 
35
35
  ### Merging into an agent you already use
36
36
 
@@ -40,7 +40,13 @@ Don't want to swap agents? Merge Claude Recall into your own:
40
40
  claude-recall kiro setup --merge-into <agent-name>
41
41
  ```
42
42
 
43
- This finds the agent config (workspace `.kiro/agents/` first, then `~/.kiro/agents/`; `--global` to target the global one directly), writes a timestamped backup, and appends the claude-recall pieces — MCP server, pre-approved read-only tools, and the four hooks — while leaving your own config untouched. It only rewrites claude-recall's own entries: superseded ones from an older version are swapped for the current wiring (any unrelated hook you have on the same event is preserved). Idempotent: on an already-current agent, re-running changes nothing. If the agent restricts tools with an explicit list, `@claude-recall` is added to it.
43
+ This finds the agent config (workspace `.kiro/agents/` first, then `~/.kiro/agents/`; `--global` to target the global one directly), writes a timestamped backup, and appends the claude-recall pieces — MCP server, pre-approved read-only tools, and the three hooks (`agentSpawn`, `userPromptSubmit`, `postToolUse`) — while leaving your own config untouched. It only rewrites claude-recall's own entries: superseded ones from an older version are swapped for the current wiring (any unrelated hook you have on the same event is preserved). Idempotent: on an already-current agent, re-running changes nothing. If the agent restricts tools with an explicit list, `@claude-recall` is added to it.
44
+
45
+ ### Mid-session rule refresh (long sessions)
46
+
47
+ Rules enter context once, at agent start. In a long session Kiro eventually compacts or rolls the conversation and the rules silently vanish — Kiro exposes no post-compaction event to re-inject them (Claude Code has one, and claude-recall uses it there). So under Kiro, claude-recall re-injects the active rules **every 15 prompts** via the `userPromptSubmit` hook, whose stdout Kiro adds directly to context. Tune or disable with `CLAUDE_RECALL_REFRESH_INTERVAL` (`0` = off).
48
+
49
+ > **Note for configs wired by ≤0.33:** older versions also added a `preToolUse` rule injector. Kiro ignores `preToolUse` stdout — exit codes gate the tool call, and only exit-2 stderr reaches the model ([kiro.dev/docs/cli/hooks](https://kiro.dev/docs/cli/hooks)) — so that hook never actually injected anything. It is now a harmless no-op; re-run `claude-recall kiro setup --merge-into <agent>` once and the stale entry is stripped automatically.
44
50
 
45
51
  ---
46
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.33.0",
3
+ "version": "0.34.1",
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": {