claude-recall 0.32.0 → 0.34.0

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
@@ -92,6 +92,8 @@ tail -5 ~/.claude-recall/hook-logs/cc-classifier.log # which model ran, what w
92
92
  claude-recall search "something you said"
93
93
  ```
94
94
 
95
+ Design details (the `claude -p` key-precedence gotcha, recursion guards, which features run on the subscription): [docs/cc-llm-capture.md](docs/cc-llm-capture.md).
96
+
95
97
  ### Pi
96
98
 
97
99
  ```bash
@@ -160,7 +162,8 @@ Once installed, Claude Recall works in the background (CC = Claude Code):
160
162
  |---|---|:-:|:-:|:-:|
161
163
  | **Session start** | Active rules are injected into the agent's context | ✓ | ✓ | ✓ |
162
164
  | **As you type** | Prompts are classified; durable preferences/corrections are stored | ✓ | ✓ | ✓ |
163
- | **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) | | | ✓ |
164
167
  | **Tool outcomes** | Failures are recorded; Bash failures are paired with their eventual fix | ✓ | ✓ | ✓ |
165
168
  | **Re-ask detection** | Frustration signals (*"still broken"*) are recorded as outcome events | ✓ | ✓ | ✓ |
166
169
  | **Before context compression** | Important context is captured before the window shrinks | ✓ | ✓ | |
@@ -169,7 +172,7 @@ Once installed, Claude Recall works in the background (CC = Claude Code):
169
172
  | **Session exit** | An auto-checkpoint (`{completed, remaining, blockers}`) is saved for next time | ✓ | ✓ | |
170
173
  | **End of session** | Failure patterns become candidate lessons; validated ones are promoted to rules | ✓ | ✓ | |
171
174
 
172
- Classification runs on each runtime's **own** LLM — Claude Code via headless `claude -p` on your subscription; Kiro via `kiro-cli chat --no-interactive` on Kiro credits — with `ANTHROPIC_API_KEY` (if you exported one *and* opted in) and regex as fallbacks. No API key is ever required; no configuration needed.
175
+ Classification runs on each runtime's **own** LLM — Claude Code via headless `claude -p` on your subscription; Kiro via `kiro-cli chat --no-interactive` on Kiro credits — with regex as the fallback. **An exported `ANTHROPIC_API_KEY` is never touched** unless you explicitly opt in with `CLAUDE_RECALL_PREFER_API_KEY=1`. No API key is ever required; no configuration needed.
173
176
 
174
177
  ```bash
175
178
  # Verify it's working
@@ -206,7 +209,7 @@ claude-recall checkpoint save --completed "API layer" --remaining "wire the UI"
206
209
  claude-recall checkpoint load
207
210
  ```
208
211
 
209
- Auto-checkpoints are also saved on session exit in Claude Code and Pi (Pi has no `--resume`, so this is its main recovery path). Extraction runs on your **Claude subscription** (headless `claude -p`) — like capture, no API key needed; an exported `ANTHROPIC_API_KEY` is only a fallback. The same applies to the other background LLM features (failure hindsight hints, end-of-session lesson extraction). A quality gate refuses to overwrite a manual checkpoint with a fabricated one when the task was already complete.
212
+ Auto-checkpoints are also saved on session exit in Claude Code and Pi (Pi has no `--resume`, so this is its main recovery path). Extraction runs on your **Claude subscription** (headless `claude -p`) — like capture, no API key needed and no key touched. The same applies to the other background LLM features (failure hindsight hints, end-of-session lesson extraction). Pi-only machines without the `claude` binary can opt in to an `ANTHROPIC_API_KEY` with `CLAUDE_RECALL_PREFER_API_KEY=1`. A quality gate refuses to overwrite a manual checkpoint with a fabricated one when the task was already complete.
210
213
 
211
214
  ### Troubleshooting
212
215
 
@@ -323,7 +326,7 @@ action → outcome event → episode → candidate lesson → promotion → acti
323
326
  outcome resolved per injected rule
324
327
  ```
325
328
 
326
- 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.
327
330
 
328
331
  ---
329
332
 
@@ -387,12 +390,13 @@ Defaults work out of the box; tune via environment variables as needed.
387
390
  | Variable | Default | Effect |
388
391
  | ---------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------- |
389
392
  | `CLAUDE_RECALL_DB_PATH` | `~/.claude-recall/` | Database directory. |
390
- | `ANTHROPIC_API_KEY` | _(unset)_ | Optional personal API key for Haiku-based LLM features. **Never required and never provided by Claude Code** — capture, checkpoint extraction, hindsight hints, and session lessons all use each runtime's own LLM first (Claude subscription via `claude -p`; Kiro credits via `kiro-cli`). A key you exported is only consulted as a fallback, or first with `CLAUDE_RECALL_PREFER_API_KEY=1`. Regex is the final fallback. |
393
+ | `ANTHROPIC_API_KEY` | _(unset)_ | Optional personal API key for Haiku-based LLM features. **Never required, never provided by Claude Code, and never touched unless you opt in** with `CLAUDE_RECALL_PREFER_API_KEY=1` — capture, checkpoint extraction, hindsight hints, and session lessons all run on each runtime's own LLM (Claude subscription via `claude -p`; Kiro credits via `kiro-cli`), with regex as the fallback. |
391
394
  | `CLAUDE_RECALL_CC_MODEL` | `haiku` | Dedicated model for capture classification under Claude Code (passed to `claude -p --model`) — independent of your interactive session model. |
392
395
  | `CLAUDE_RECALL_CC_LLM_TIMEOUT_MS` | `30000` | Hard cap on the headless `claude -p` classify call before the capture worker gives up and falls through. |
393
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). |
394
- | `CLAUDE_RECALL_PREFER_API_KEY` | _(unset)_ | Force `ANTHROPIC_API_KEY`-based classification ahead of the runtime's included LLM (uses your Anthropic API credits e.g. for a model you pay for). Applies under both Claude Code and Kiro. |
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. |
395
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. |
396
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`. |
397
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>`. |
398
402
  | `CLAUDE_RECALL_DEMOTE_MIN_LOADS` | `20` | Minimum load count before a rule qualifies for auto-demotion. |
@@ -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.
@@ -5,10 +5,10 @@
5
5
  * Requires ANTHROPIC_API_KEY in the environment — a personal pay-as-you-go
6
6
  * API key the user exported themselves. Claude Code does NOT mint one from
7
7
  * the user's subscription (hooks just inherit the user's environment), which
8
- * is why this is a FALLBACK backend: the capture workers prefer each
9
- * runtime's included LLM (claude -p / kiro-cli — see cc-classifier.ts and
10
- * kiro-classifier.ts) and only land here when a key is present. Falls back
11
- * gracefully (returns null) when unavailable.
8
+ * is why this backend is STRICTLY OPT-IN (CLAUDE_RECALL_PREFER_API_KEY): the
9
+ * default chains use each runtime's included LLM only (claude -p / kiro-cli —
10
+ * see cc-classifier.ts and kiro-classifier.ts), and an exported key is never
11
+ * spent silently. Falls back gracefully (returns null) when unavailable.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.classifyWithLLM = classifyWithLLM;
@@ -146,11 +146,12 @@ function parseJSON(text) {
146
146
  * extraction, checkpoint extraction, batch classification) and return raw
147
147
  * text, or null when no backend is available.
148
148
  *
149
- * Backend order matches capture: the user's Claude SUBSCRIPTION (headless
150
- * `claude -p`) before a personally-exported ANTHROPIC_API_KEY, flipped by
151
- * CLAUDE_RECALL_PREFER_API_KEY. So running out of Anthropic API credits — or
152
- * never having a key at all does not disable these features on any machine
153
- * with the `claude` binary (Claude Code itself, or Pi running alongside it).
149
+ * Backend policy matches capture: the user's Claude SUBSCRIPTION (headless
150
+ * `claude -p`) is the only default backend — a personally-exported
151
+ * ANTHROPIC_API_KEY is STRICTLY OPT-IN via CLAUDE_RECALL_PREFER_API_KEY and
152
+ * never consulted otherwise, not even as a fallback. The opt-in exists for
153
+ * Pi-only users (no `claude` binary; their key is how they run Pi itself)
154
+ * and anyone deliberately paying for a stronger model.
154
155
  *
155
156
  * The CLI backend is skipped inside a nested headless session
156
157
  * (CLAUDE_RECALL_NESTED): if `claude -p` fires user-scope hooks of its own,
@@ -182,7 +183,7 @@ async function completeText(systemPrompt, userContent, maxTokens) {
182
183
  : (0, cc_classifier_1.completeWithClaudeCli)(`${systemPrompt}\n\n${userContent}`, { timeoutMs: SECONDARY_CLI_TIMEOUT_MS });
183
184
  const backends = process.env.CLAUDE_RECALL_PREFER_API_KEY
184
185
  ? [viaApi, viaCli]
185
- : [viaCli, viaApi];
186
+ : [viaCli];
186
187
  for (const backend of backends) {
187
188
  const text = await backend();
188
189
  if (text !== null && text.trim().length > 0)
@@ -166,18 +166,17 @@ function classifyContentRegex(text) {
166
166
  * workers, which set the *_CLASSIFIER env vars — a ~4s CLI call can't run
167
167
  * inline on a turn):
168
168
  * - Under Claude Code (CLAUDE_RECALL_CC_CLASSIFIER set by cc-capture-worker):
169
- * headless `claude -p` on the user's Claude SUBSCRIPTION → ANTHROPIC_API_KEY
170
- * if present → regex.
169
+ * headless `claude -p` on the user's Claude SUBSCRIPTION → regex.
171
170
  * - Under Kiro (CLAUDE_RECALL_KIRO_CLASSIFIER set by kiro-capture-worker):
172
171
  * Kiro's own headless LLM (`kiro-cli chat --no-interactive`, Kiro credits)
173
- * → ANTHROPIC_API_KEY if present → regex.
174
- * - Inline callers (memory-stop batch, etc.): ANTHROPIC_API_KEY regex.
172
+ * → regex.
173
+ * - Inline callers: straight to regex.
175
174
  *
176
- * The included LLM is preferred so a stray exported key doesn't silently
177
- * spend the user's Anthropic API credits; CLAUDE_RECALL_PREFER_API_KEY flips
178
- * the order. NOTE: ANTHROPIC_API_KEY is always a personal key the user
179
- * exported — Claude Code does NOT provide one from the subscription.
180
- * See docs/kiro-llm-capture.md.
175
+ * The ANTHROPIC_API_KEY path is STRICTLY OPT-IN via
176
+ * CLAUDE_RECALL_PREFER_API_KEY an exported key is never consulted
177
+ * otherwise, not even as a fallback. (It is always a personal key the user
178
+ * exported — Claude Code does NOT provide one from the subscription.)
179
+ * See docs/cc-llm-capture.md and docs/kiro-llm-capture.md.
181
180
  */
182
181
  async function classifyContent(text) {
183
182
  // Guard the LLM paths the same way the regex path is guarded: a question or
@@ -202,30 +201,25 @@ async function classifyContentInner(text) {
202
201
  // module graph for hook invocations that don't run in a capture worker.
203
202
  const tryKiro = async () => (await Promise.resolve().then(() => __importStar(require('./kiro-classifier')))).classifyWithKiro(text);
204
203
  const tryCc = async () => (await Promise.resolve().then(() => __importStar(require('./cc-classifier')))).classifyWithClaudeCli(text);
205
- // Order the LLM backends. Each runtime's INCLUDED LLM comes before a stray
206
- // ANTHROPIC_API_KEY: a key exported for other tools should not silently
207
- // spend the user's Anthropic API credits when the runtime already provides
208
- // an LLM Kiro via `kiro-cli chat --no-interactive` (Kiro credits), Claude
209
- // Code via `claude -p` (the user's Claude subscription). Set
210
- // CLAUDE_RECALL_PREFER_API_KEY to force the key first (e.g. to use a
211
- // stronger model you pay for). The CLI backends only run inside their
212
- // detached capture workers (which set the *_CLASSIFIER env vars) inline
213
- // hook paths stay key regex, since a ~4s CLI call can't block a turn.
204
+ // Pick the LLM backends. The ANTHROPIC_API_KEY path is STRICTLY OPT-IN
205
+ // (CLAUDE_RECALL_PREFER_API_KEY): claude-recall was built for runtimes that
206
+ // bring their own LLM Kiro via `kiro-cli chat --no-interactive` (Kiro
207
+ // credits), Claude Code via `claude -p` (the user's Claude subscription)
208
+ // and a key exported for other tools must NEVER be spent silently, not even
209
+ // as a fallback. Without the opt-in, the chain is included LLM → regex.
210
+ // The opt-in exists for Pi-only users (no `claude` binary; their key is how
211
+ // they run Pi itself) and anyone deliberately paying for a stronger model.
212
+ // The CLI backends only run inside their detached capture workers (which
213
+ // set the *_CLASSIFIER env vars) — inline hook paths go straight to regex,
214
+ // since a ~4s CLI call can't block a turn.
214
215
  const backends = [];
215
- if (underKiro && !preferApiKey) {
216
- backends.push(tryKiro, tryApiKey);
217
- }
218
- else if (underKiro) {
219
- backends.push(tryApiKey, tryKiro);
220
- }
221
- else if (underCc && !preferApiKey) {
222
- backends.push(tryCc, tryApiKey);
216
+ if (preferApiKey)
217
+ backends.push(tryApiKey);
218
+ if (underKiro) {
219
+ backends.push(tryKiro);
223
220
  }
224
221
  else if (underCc) {
225
- backends.push(tryApiKey, tryCc);
226
- }
227
- else {
228
- backends.push(tryApiKey);
222
+ backends.push(tryCc);
229
223
  }
230
224
  for (const backend of backends) {
231
225
  const result = await backend();
@@ -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
@@ -0,0 +1,147 @@
1
+ # Using your Claude subscription for memory capture (no API key)
2
+
3
+ The Claude Code counterpart to [kiro-llm-capture.md](kiro-llm-capture.md): how
4
+ claude-recall's LLM features run on the user's **Claude subscription** — the
5
+ same login that powers their interactive session — instead of a personal
6
+ `ANTHROPIC_API_KEY`.
7
+
8
+ ## The problem
9
+
10
+ Claude Recall's LLM classifier originally called Claude Haiku through the
11
+ Anthropic SDK, which needs `ANTHROPIC_API_KEY` — a **personal pay-as-you-go
12
+ key**. For a long time the docs claimed Claude Code "provides the key to its
13
+ hooks." That was false: Claude Code does not mint a key from the user's
14
+ subscription; hooks merely inherit the user's environment. So the key path
15
+ only ever worked for users who had exported their own key, and it silently
16
+ spent **their API credits** — a separate wallet from the Claude subscription
17
+ they were already paying for.
18
+
19
+ The failure mode that exposed all of this: a user's exported key ran out of
20
+ credits. Every SDK call failed silently, capture degraded to the regex
21
+ fallback, and the regex promptly stored four junk memories in one session —
22
+ while the user reasonably believed "the LLM" was doing the classifying.
23
+
24
+ ## The finding
25
+
26
+ **`claude -p "<prompt>"` is a headless one-shot completion that runs on the
27
+ user's Claude Code login** — the direct analogue of the earlier
28
+ `kiro-cli chat --no-interactive` discovery. A background worker can shell out
29
+ to it to classify text with no API key and no extra cost beyond the
30
+ subscription usage the user already has.
31
+
32
+ One gotcha made this non-trivial, and it's the most important line in the
33
+ implementation:
34
+
35
+ > **`claude -p` prefers `ANTHROPIC_API_KEY` over subscription auth when the
36
+ > key is present in the environment.**
37
+
38
+ So a stray exported key — possibly dead, possibly billing a wallet the user
39
+ forgot about — would silently hijack every headless call. The fix:
40
+ `completeWithClaudeCli()` **strips `ANTHROPIC_API_KEY` from the child env**,
41
+ forcing subscription auth unconditionally. (Verified live: with a
42
+ credits-exhausted key in the env, `claude -p` returned
43
+ `Credit balance is too low`; with the key stripped, the same call succeeded on
44
+ the login.)
45
+
46
+ ## The design
47
+
48
+ ```
49
+ Claude Code UserPromptSubmit
50
+
51
+
52
+ hook run correction-detector (name unchanged in settings.json;
53
+ │ returns in <100 ms)
54
+ │ spawn detached, pipe payload via stdin
55
+
56
+ hook run cc-capture-worker (background, survives parent exit)
57
+ │ sets CLAUDE_RECALL_CC_CLASSIFIER=1
58
+
59
+ handleCorrectionDetector → classifyContent()
60
+
61
+ ├─ classifyWithClaudeCli() → claude -p --model haiku "<prompt>"
62
+ │ (subscription auth, key stripped)
63
+ └─ regex fallback
64
+
65
+ (classifyWithLLM() / ANTHROPIC_API_KEY exists but is OPT-IN only —
66
+ see below; it is never in the default chain)
67
+ ```
68
+
69
+ Precedence mirrors Kiro: **the runtime's included LLM → regex.** An exported
70
+ `ANTHROPIC_API_KEY` is **never consulted by default — not even as a
71
+ fallback**; `CLAUDE_RECALL_PREFER_API_KEY=1` is the one switch that enables
72
+ (and prefers) it. The registered hook name (`hook run correction-detector`)
73
+ was deliberately kept, so existing `settings.json` files work with no
74
+ re-setup — only the dispatch behind it changed.
75
+
76
+ **Trade-off:** the synchronous `📌 Recall: auto-captured …` echo is gone.
77
+ A cold `claude -p` takes ~4 s, far too slow to block the user's prompt, so
78
+ capture is silent and lands a few seconds later — the same contract Kiro has
79
+ always had. Verify via `~/.claude-recall/hook-logs/cc-classifier.log`
80
+ (`classified via claude -p (model=…, Claude subscription, no API key)`) or
81
+ `claude-recall search`.
82
+
83
+ ### Recursion guard
84
+
85
+ The nested headless session is itself a Claude Code session. If the user has
86
+ claude-recall hooks registered at **user scope** (`~/.claude/settings.json`),
87
+ the classify call would fire those hooks, which would spawn another worker,
88
+ which would run another `claude -p` — forever. Two layers prevent this:
89
+
90
+ 1. `claude -p` children run with `cwd = os.tmpdir()`, so **project**
91
+ settings/hooks never load.
92
+ 2. Children are marked with `CLAUDE_RECALL_NESTED=1` (plus the legacy
93
+ `CLAUDE_RECALL_CC_CLASSIFIER=1`). The capture hook refuses to spawn a
94
+ worker, and the secondary-feature backend refuses to invoke the CLI, when
95
+ either marker is present.
96
+
97
+ ### Beyond capture: the secondary features (0.32.0)
98
+
99
+ `completeWithClaudeCli()` is a generic completion primitive, and the four
100
+ features that previously knew only the key path now route through it with the
101
+ same precedence:
102
+
103
+ | Feature | Runs in | Without any key, now |
104
+ | --- | --- | --- |
105
+ | Auto-checkpoint extraction | Session-exit worker (CC + **Pi** — Pi's only recovery path, it has no `--resume`) | Works via `claude -p` |
106
+ | Failure hindsight hints | Stop hook, inline | Works via `claude -p` |
107
+ | End-of-session lesson extraction | Stop hook, inline | Works via `claude -p` |
108
+ | Batch classification | Stop hook, inline | Works via `claude -p` |
109
+
110
+ Two budget rules keep the inline ones inside the Stop hook's ~40 s window
111
+ (capture doesn't need them — it runs in a detached worker):
112
+
113
+ - each secondary CLI call is capped at **10 s** (vs the worker's 30 s), and
114
+ - hindsight-hint generation is capped at **5 LLM calls per run** (the loop
115
+ over detected failures is otherwise unbounded; failures past the cap keep
116
+ the grounded generic lesson text).
117
+
118
+ Under **Pi** these functions run in-process, so Pi benefits automatically on
119
+ any machine with the `claude` binary installed alongside. Under **Kiro** the
120
+ Stop-hook features mostly don't run (no transcript in its hooks), and capture
121
+ uses the Kiro backend.
122
+
123
+ ### Configuration
124
+
125
+ | Variable | Default | Effect |
126
+ | --- | --- | --- |
127
+ | `CLAUDE_RECALL_CC_MODEL` | `haiku` | Model passed to `claude -p --model` — a dedicated classifier model, independent of the user's interactive session model. |
128
+ | `CLAUDE_RECALL_CC_LLM_TIMEOUT_MS` | `30000` | Hard cap on the capture worker's `claude -p` call. (Secondary features use a fixed 10 s per call.) |
129
+ | `CLAUDE_RECALL_PREFER_API_KEY` | *(unset)* | **The only switch that enables the `ANTHROPIC_API_KEY` backend** (and prefers it first) — without it an exported key is never touched, not even as a fallback. For Pi-only machines without the `claude` binary, or a stronger model you deliberately pay for. Applies to capture and the secondary features, on every runtime. |
130
+ | `CLAUDE_RECALL_NESTED` | *(set on `claude -p` children)* | Recursion marker — never set it by hand. |
131
+
132
+ ## Caveats
133
+
134
+ - **Requires the `claude` binary on `PATH`.** Absent (e.g. a bare CI box, or a
135
+ Pi-only machine), the CLI backend errors out and the chain falls through to
136
+ regex — or to an `ANTHROPIC_API_KEY` if you opted in with
137
+ `CLAUDE_RECALL_PREFER_API_KEY=1`. No crash, no exception.
138
+ - **Subscription usage.** Each classified prompt is one small Haiku call
139
+ against the user's Claude plan limits. That is the deliberate trade — the
140
+ user's existing plan instead of a second wallet.
141
+ - **Latency.** ~4 s cold per call. Fine in a detached worker; the reason the
142
+ inline features carry the 10 s cap and hint budget.
143
+ - **Consistency.** Same probabilistic caveat as the Kiro backend: the
144
+ classifier is an LLM judgement call. The classify prompt carries a
145
+ "standalone rule" test with negative examples precisely because early
146
+ versions stored one-off task instructions ("first fix the sentence") as
147
+ corrections.
@@ -9,7 +9,7 @@ Claude Haiku through `ANTHROPIC_API_KEY` — a personal API key the user had
9
9
  exported themselves. (Claude Code does **not** mint a key from the user's
10
10
  subscription; hooks merely inherit the environment. The headless-CLI approach
11
11
  documented here was later applied back to Claude Code via `claude -p` on
12
- subscription auth — see `src/hooks/cc-classifier.ts`.)
12
+ subscription auth — see [cc-llm-capture.md](cc-llm-capture.md).)
13
13
 
14
14
  Kiro CLI does **not** set `ANTHROPIC_API_KEY`. So under Kiro the classifier fell
15
15
  back to a conservative regex that only fires on explicit phrasings
@@ -89,20 +89,24 @@ Kiro userPromptSubmit
89
89
 
90
90
  handleCorrectionDetector → classifyContent()
91
91
 
92
- ├─ classifyWithLLM() → ANTHROPIC_API_KEY (unset under Kiro) → null
93
92
  ├─ classifyWithKiro() → kiro-cli chat --no-interactive --agent
94
93
  │ claude-recall-classifier --model claude-haiku-4.5
95
94
  │ → {"type","confidence","extract"}
96
- └─ regex fallback (only if both above yield nothing)
95
+ └─ regex fallback (only if the above yields nothing)
96
+
97
+ (classifyWithLLM() / ANTHROPIC_API_KEY exists but is OPT-IN only —
98
+ never in the default chain)
97
99
  ```
98
100
 
99
- Capture precedence under Kiro: **Kiro's included LLM → `ANTHROPIC_API_KEY` if
100
- present → regex as a last resort.** The Kiro LLM comes first *even when a key is
101
- set*, so a key exported for other tools never silently spends the user's
102
- Anthropic credits Kiro already ships an LLM. `CLAUDE_RECALL_PREFER_API_KEY=1`
103
- flips the order back to key-first for anyone who deliberately wants to pay for a
104
- stronger model. (Under Claude Code the same pattern applies with `claude -p` on
105
- the user's subscription as the included LLM see `src/hooks/cc-classifier.ts`.)
101
+ Capture precedence under Kiro: **Kiro's included LLM → regex as a last
102
+ resort.** An exported `ANTHROPIC_API_KEY` is **never consulted by default
103
+ not even as a fallback** — so a key exported for other tools never silently
104
+ spends the user's Anthropic credits; Kiro already ships an LLM.
105
+ `CLAUDE_RECALL_PREFER_API_KEY=1` is the one explicit switch that enables (and
106
+ prefers) the key, for anyone who deliberately wants to pay for a stronger
107
+ model. (Under Claude Code the same pattern applies with `claude -p` on the
108
+ user's subscription as the included LLM — see
109
+ [cc-llm-capture.md](cc-llm-capture.md).)
106
110
 
107
111
  ### Output parsing
108
112
 
@@ -119,7 +123,7 @@ returns `null`, degrading to regex — a hook must never throw.
119
123
  | `CLAUDE_RECALL_KIRO_CLASSIFIER` | *(set by the worker)* | Enables the Kiro-LLM path in `classifyContent`. Set automatically by `kiro-capture-worker`; never needed by hand. |
120
124
  | `CLAUDE_RECALL_KIRO_MODEL` | `claude-haiku-4.5` | Model for the classify call, passed explicitly as `--model`. This is a **dedicated classifier model, independent of the user's interactive Kiro chat model** (e.g. `auto`) — the headless call always uses this value. Raise to `claude-sonnet-4.6` for steadier judgement at ~3× the credits. |
121
125
  | `CLAUDE_RECALL_KIRO_LLM_TIMEOUT_MS` | `30000` | Hard cap on the headless call before the worker gives up and falls back to regex. |
122
- | `CLAUDE_RECALL_PREFER_API_KEY` | *(unset)* | Force `ANTHROPIC_API_KEY`-based classification ahead of Kiro's included LLM (opt-in; uses your Anthropic credits). |
126
+ | `CLAUDE_RECALL_PREFER_API_KEY` | *(unset)* | **The only switch that enables the `ANTHROPIC_API_KEY` backend** without it an exported key is never touched, not even as a fallback. |
123
127
 
124
128
  ## Caveats
125
129
 
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
 
@@ -74,7 +80,7 @@ To decide what's worth remembering, the capture hook classifies each prompt via
74
80
 
75
81
  **The classifier uses a dedicated, fixed model — not your chat model.** It always runs the model in `CLAUDE_RECALL_KIRO_MODEL` (default `claude-haiku-4.5`, chosen because classification is cheap and high-volume), **independent of your interactive Kiro chat model** (e.g. `auto`). This keeps classification cost predictable no matter what your chat is set to. Set `CLAUDE_RECALL_KIRO_MODEL` to any model from `kiro-cli chat --list-models` to change it. Every successful classification is logged to `~/.claude-recall/hook-logs/kiro-classifier.log` as `classified via kiro-cli (model=…, Kiro credits, no API key)`, so you can always see which model ran.
76
82
 
77
- **Under Kiro the included LLM is used first even if you happen to have `ANTHROPIC_API_KEY` set** — so a key exported for other tools won't quietly spend your Anthropic credits. Order: Kiro's LLM → `ANTHROPIC_API_KEY` (if present) → regex; set `CLAUDE_RECALL_PREFER_API_KEY=1` to force your key first (e.g. for a stronger model you pay for).
83
+ **An exported `ANTHROPIC_API_KEY` is never touched** — a key exported for other tools won't quietly spend your Anthropic credits, not even as a fallback. Order: Kiro's LLM → regex. Setting `CLAUDE_RECALL_PREFER_API_KEY=1` is the one explicit switch that enables (and prefers) your key, e.g. for a stronger model you pay for.
78
84
 
79
85
  Design details, latency measurements, and output-parsing internals: [kiro-llm-capture.md](kiro-llm-capture.md).
80
86
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
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": {