claude-recall 0.20.6 → 0.20.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/auto-corrections/SKILL.md +3 -1
- package/.claude/skills/auto-corrections/manifest.json +5 -3
- package/.claude/skills/auto-preferences/SKILL.md +11 -1
- package/.claude/skills/auto-preferences/manifest.json +13 -3
- package/README.md +4 -2
- package/dist/cli/claude-recall-cli.js +13 -1
- package/dist/cli/commands/hook-commands.js +5 -0
- package/dist/hooks/correction-detector.js +1 -1
- package/dist/hooks/memory-stop-hook.js +37 -1
- package/dist/hooks/post-compact-reload.js +57 -0
- package/dist/hooks/precompact-preserve.js +3 -0
- package/dist/pi/extension.js +31 -5
- package/docs/cc-agent-harness.md +114 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Corrections
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 16 memories. Last updated: 2026-04-06.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
@@ -24,6 +24,8 @@ Auto-generated from 14 memories. Last updated: 2026-04-06.
|
|
|
24
24
|
- CORRECTION: Memory with complex metadata
|
|
25
25
|
- CORRECTION: Memory with complex metadata
|
|
26
26
|
- CORRECTION: Memory with complex metadata
|
|
27
|
+
- CORRECTION: Memory with complex metadata
|
|
28
|
+
- CORRECTION: Memory with complex metadata
|
|
27
29
|
- CORRECTION: License copyright should include user's name instead of 'Claude Recall Contributors'
|
|
28
30
|
- CORRECTION: License copyright should list your name instead of 'Claude Recall Contributors'
|
|
29
31
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "corrections",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"sourceHash": "1313608fb812bd1bed85d9854e8958179f7584619e0160604c56bd40ec8cc5b0",
|
|
4
|
+
"memoryCount": 16,
|
|
5
|
+
"generatedAt": "2026-04-06T17:24:01.505Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1775496241485_1yyc5ht88",
|
|
8
|
+
"memory_1775494279035_j6uj5lzxo",
|
|
7
9
|
"memory_1775492069326_vksvzmt3f",
|
|
8
10
|
"memory_1775491767369_sepsjmg8y",
|
|
9
11
|
"memory_1775169786543_43p8to1hu",
|
|
@@ -8,10 +8,20 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Preferences
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 79 memories. Last updated: 2026-04-06.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
15
|
+
- Session test preference 1775496241643
|
|
16
|
+
- Test preference 1775496241514-2
|
|
17
|
+
- Test preference 1775496241514-1
|
|
18
|
+
- Test preference 1775496241514-0
|
|
19
|
+
- Test memory content
|
|
20
|
+
- Session test preference 1775494279149
|
|
21
|
+
- Test preference 1775494279061-2
|
|
22
|
+
- Test preference 1775494279061-1
|
|
23
|
+
- Test preference 1775494279061-0
|
|
24
|
+
- Test memory content
|
|
15
25
|
- Session test preference 1775492069465
|
|
16
26
|
- Test preference 1775492069353-2
|
|
17
27
|
- Test preference 1775492069353-1
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "preferences",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-04-
|
|
3
|
+
"sourceHash": "1cad20b547cd1594370facfdae6b4ae8700516e9be712d8b28459462398c9276",
|
|
4
|
+
"memoryCount": 79,
|
|
5
|
+
"generatedAt": "2026-04-06T17:24:01.663Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1775496241645_xtz7cwan4",
|
|
8
|
+
"memory_1775496241576_j7sxxuge3",
|
|
9
|
+
"memory_1775496241542_rg3hj24ud",
|
|
10
|
+
"memory_1775496241516_y3inz8rlf",
|
|
11
|
+
"memory_1775496241411_vwdz6igrm",
|
|
12
|
+
"memory_1775494279150_n4pq7zy11",
|
|
13
|
+
"memory_1775494279108_6hbe8qoit",
|
|
14
|
+
"memory_1775494279088_nv8hjdm7s",
|
|
15
|
+
"memory_1775494279062_jx4wrwn6s",
|
|
16
|
+
"memory_1775494278982_fsc491z41",
|
|
7
17
|
"memory_1775492069467_5cturlg0a",
|
|
8
18
|
"memory_1775492069400_icg4tjivf",
|
|
9
19
|
"memory_1775492069377_goix7nu9v",
|
package/README.md
CHANGED
|
@@ -81,10 +81,12 @@ Once installed, Claude Recall works automatically in the background:
|
|
|
81
81
|
1. **Session start** — active rules are loaded before the first action. In Claude Code, this happens via the `search_enforcer` hook; in Pi, rules are injected into the system prompt automatically
|
|
82
82
|
2. **As you work** — every prompt is classified for corrections and preferences. Natural statements like *"we use tabs here"* or *"no, put tests in `__tests__/`"* are detected and stored
|
|
83
83
|
3. **Tool outcomes** — results from all tools (Bash, Edit, Write, and more) are captured. Failures are stored as memories; Bash failures are paired with successful fixes
|
|
84
|
-
4. **End of session** — session episodes are created, candidate lessons extracted from failures, and a promotion cycle graduates validated patterns into active rules
|
|
84
|
+
4. **End of session** — session episodes are created, candidate lessons extracted from failures, and a promotion cycle graduates validated patterns into active rules. A session extraction pass sends the last 50 transcript entries to Haiku to extract durable project knowledge from long coding sessions
|
|
85
85
|
5. **Reask detection** — frustration signals ("still broken", "that didn't work") are recorded as outcome events
|
|
86
86
|
6. **Before context compression** — aggressive memory sweep captures important context before the window shrinks
|
|
87
|
-
7. **
|
|
87
|
+
7. **After context compression** (Claude Code only) — rules are automatically re-injected into context so they're not lost when the window shrinks
|
|
88
|
+
8. **Multi-agent outcomes** — subagent completions (completed/failed/killed) are captured from task notifications and recorded as outcome events
|
|
89
|
+
9. **Rules sync** (Claude Code only) — top 30 rules are exported as typed `.md` files to Claude Code's native memory directory
|
|
88
90
|
|
|
89
91
|
Classification uses Claude Haiku (via `ANTHROPIC_API_KEY`) with silent regex fallback. No configuration needed.
|
|
90
92
|
|
|
@@ -693,8 +693,20 @@ async function main() {
|
|
|
693
693
|
// This avoids registry lookups on every hook invocation.
|
|
694
694
|
const cliScript = path.join(packageDir, 'dist', 'cli', 'claude-recall-cli.js');
|
|
695
695
|
const hookCmd = `node ${cliScript} hook run`;
|
|
696
|
-
settings.hooksVersion = '
|
|
696
|
+
settings.hooksVersion = '11.0.0'; // v11 = add SessionStart(compact) for post-compaction rule reload
|
|
697
697
|
settings.hooks = {
|
|
698
|
+
SessionStart: [
|
|
699
|
+
{
|
|
700
|
+
matcher: "compact",
|
|
701
|
+
hooks: [
|
|
702
|
+
{
|
|
703
|
+
type: "command",
|
|
704
|
+
command: `${hookCmd} post-compact-reload`,
|
|
705
|
+
timeout: 10
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
}
|
|
709
|
+
],
|
|
698
710
|
PostToolUse: [
|
|
699
711
|
{
|
|
700
712
|
hooks: [
|
|
@@ -85,6 +85,11 @@ class HookCommands {
|
|
|
85
85
|
await handleToolFailure(input);
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
|
+
case 'post-compact-reload': {
|
|
89
|
+
const { handlePostCompactReload } = await Promise.resolve().then(() => __importStar(require('../../hooks/post-compact-reload')));
|
|
90
|
+
await handlePostCompactReload(input);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
88
93
|
case 'bash-failure-watcher': {
|
|
89
94
|
// Backward compat alias — routes to tool-outcome-watcher
|
|
90
95
|
const { handleBashFailureWatcher } = await Promise.resolve().then(() => __importStar(require('../../hooks/tool-outcome-watcher')));
|
|
@@ -64,6 +64,6 @@ async function handleCorrectionDetector(input) {
|
|
|
64
64
|
? result.extract.substring(0, 60) + '...'
|
|
65
65
|
: result.extract;
|
|
66
66
|
// Output hook message that Claude sees
|
|
67
|
-
console.log(`<user-prompt-submit-hook
|
|
67
|
+
console.log(`<user-prompt-submit-hook>📌 Recall: auto-captured ${result.type} — ${summary}</user-prompt-submit-hook>`);
|
|
68
68
|
(0, shared_1.hookLog)('correction-detector', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
|
|
69
69
|
}
|
|
@@ -107,6 +107,9 @@ async function handleMemoryStop(input) {
|
|
|
107
107
|
stored++;
|
|
108
108
|
(0, shared_1.hookLog)('memory-stop', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
|
|
109
109
|
}
|
|
110
|
+
if (stored > 0) {
|
|
111
|
+
console.log(`📝 Recall: captured ${stored} memories from this session`);
|
|
112
|
+
}
|
|
110
113
|
(0, shared_1.hookLog)('memory-stop', `Session end: stored ${stored} memories from ${entries.length} entries`);
|
|
111
114
|
// Session extraction: learn from long coding sessions (reads wider window)
|
|
112
115
|
try {
|
|
@@ -114,8 +117,24 @@ async function handleMemoryStop(input) {
|
|
|
114
117
|
const sessionEntries = (0, shared_1.readTranscriptTail)(transcriptPath, 50);
|
|
115
118
|
if (sessionEntries.length >= 10) {
|
|
116
119
|
const conversationEntries = buildConversationEntries(sessionEntries);
|
|
120
|
+
// Record failed subagent outcomes
|
|
121
|
+
for (const entry of conversationEntries) {
|
|
122
|
+
if (entry.toolName === 'Agent' && entry.isError) {
|
|
123
|
+
try {
|
|
124
|
+
outcomeStorage.createOutcomeEvent({
|
|
125
|
+
event_type: 'agent_failure',
|
|
126
|
+
actor: 'tool',
|
|
127
|
+
action_summary: entry.text,
|
|
128
|
+
next_state_summary: entry.text,
|
|
129
|
+
tags: ['agent', 'subagent'],
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch { /* non-critical */ }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
117
135
|
const extracted = await (0, event_processors_1.extractSessionLearnings)(conversationEntries, input?.session_id ?? '', projectId, 5);
|
|
118
136
|
if (extracted > 0) {
|
|
137
|
+
console.log(`🔍 Recall: extracted ${extracted} learnings from session analysis`);
|
|
119
138
|
(0, shared_1.hookLog)('memory-stop', `Session extraction: stored ${extracted} learnings`);
|
|
120
139
|
stored += extracted;
|
|
121
140
|
}
|
|
@@ -143,6 +162,9 @@ async function handleMemoryStop(input) {
|
|
|
143
162
|
const { PromotionEngine } = await Promise.resolve().then(() => __importStar(require('../services/promotion-engine')));
|
|
144
163
|
const result = PromotionEngine.getInstance().runCycle(projectId);
|
|
145
164
|
if (result.promoted > 0 || result.archived > 0) {
|
|
165
|
+
if (result.promoted > 0) {
|
|
166
|
+
console.log(`⬆️ Recall: ${result.promoted} lesson(s) promoted to active rules`);
|
|
167
|
+
}
|
|
146
168
|
(0, shared_1.hookLog)('memory-stop', `Promotion: ${result.promoted} promoted, ${result.archived} archived`);
|
|
147
169
|
}
|
|
148
170
|
}
|
|
@@ -417,7 +439,21 @@ function buildConversationEntries(entries) {
|
|
|
417
439
|
else {
|
|
418
440
|
const text = (0, shared_1.extractTextFromEntry)(entry);
|
|
419
441
|
if (text && text.length > 5) {
|
|
420
|
-
|
|
442
|
+
// Detect subagent task notifications
|
|
443
|
+
const notifMatch = text.match(/<task-notification>[\s\S]*?<\/task-notification>/);
|
|
444
|
+
if (notifMatch) {
|
|
445
|
+
const status = notifMatch[0].match(/<status>(.*?)<\/status>/)?.[1] ?? 'unknown';
|
|
446
|
+
const summary = notifMatch[0].match(/<summary>(.*?)<\/summary>/)?.[1] ?? '';
|
|
447
|
+
result.push({
|
|
448
|
+
role: 'tool_result',
|
|
449
|
+
text: `Agent ${status}: ${summary}`.substring(0, 300),
|
|
450
|
+
toolName: 'Agent',
|
|
451
|
+
isError: status === 'failed' || status === 'killed',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
result.push({ role: 'assistant', text: text.substring(0, 300) });
|
|
456
|
+
}
|
|
421
457
|
}
|
|
422
458
|
}
|
|
423
459
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* post-compact-reload hook — fires on SessionStart with source "compact".
|
|
4
|
+
*
|
|
5
|
+
* After context compaction, recall rules loaded earlier in the session are
|
|
6
|
+
* gone from the model's context. This hook re-injects them by outputting
|
|
7
|
+
* the active rules to stdout, which CC injects as a system message.
|
|
8
|
+
*
|
|
9
|
+
* Input: { session_id, hook_event_name: "SessionStart", source: "compact" }
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.handlePostCompactReload = handlePostCompactReload;
|
|
13
|
+
const shared_1 = require("./shared");
|
|
14
|
+
const memory_1 = require("../services/memory");
|
|
15
|
+
const config_1 = require("../services/config");
|
|
16
|
+
const DIRECTIVE = 'These rules were re-loaded after context compaction.\n' +
|
|
17
|
+
'Continue applying them. Cite at the point of application: (applied from memory: <rule>)';
|
|
18
|
+
function extractVal(value) {
|
|
19
|
+
if (typeof value === 'string')
|
|
20
|
+
return value;
|
|
21
|
+
if (typeof value === 'object' && value !== null) {
|
|
22
|
+
return value.content || value.value || JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
return String(value ?? '');
|
|
25
|
+
}
|
|
26
|
+
function formatRules(rules) {
|
|
27
|
+
const sections = [];
|
|
28
|
+
if (rules.preferences.length > 0) {
|
|
29
|
+
sections.push('## Preferences\n' + rules.preferences.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
30
|
+
}
|
|
31
|
+
if (rules.corrections.length > 0) {
|
|
32
|
+
sections.push('## Corrections\n' + rules.corrections.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
33
|
+
}
|
|
34
|
+
if (rules.failures.length > 0) {
|
|
35
|
+
sections.push('## Failures\n' + rules.failures.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
36
|
+
}
|
|
37
|
+
if (rules.devops.length > 0) {
|
|
38
|
+
sections.push('## DevOps Rules\n' + rules.devops.map(m => `- ${extractVal(m.value)}`).join('\n'));
|
|
39
|
+
}
|
|
40
|
+
return sections.join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
async function handlePostCompactReload(input) {
|
|
43
|
+
try {
|
|
44
|
+
const projectId = config_1.ConfigService.getInstance().getProjectId();
|
|
45
|
+
const rules = memory_1.MemoryService.getInstance().loadActiveRules(projectId);
|
|
46
|
+
const totalRules = rules.preferences.length + rules.corrections.length +
|
|
47
|
+
rules.failures.length + rules.devops.length;
|
|
48
|
+
if (totalRules === 0)
|
|
49
|
+
return;
|
|
50
|
+
const body = formatRules(rules);
|
|
51
|
+
console.log(`🔄 Recall: ${totalRules} rules re-loaded after context compaction\n\n${DIRECTIVE}\n\n---\n\n${body}`);
|
|
52
|
+
(0, shared_1.hookLog)('post-compact-reload', `Re-injected ${totalRules} rules after compaction`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
(0, shared_1.hookLog)('post-compact-reload', `Error: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -93,6 +93,9 @@ async function handlePrecompactPreserve(input) {
|
|
|
93
93
|
stored++;
|
|
94
94
|
(0, shared_1.hookLog)('precompact', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
|
|
95
95
|
}
|
|
96
|
+
if (stored > 0) {
|
|
97
|
+
console.log(`💾 Recall: preserved ${stored} memories before context compression`);
|
|
98
|
+
}
|
|
96
99
|
(0, shared_1.hookLog)('precompact', `PreCompact sweep: stored ${stored} memories from ${entries.length} entries`);
|
|
97
100
|
// Reset search enforcer hook-state so Claude is forced to re-load rules
|
|
98
101
|
// after context compression. Without this, the enforcer thinks rules are
|
package/dist/pi/extension.js
CHANGED
|
@@ -137,21 +137,47 @@ function default_1(pi) {
|
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
139
|
// --- Event: detect corrections from user input ---
|
|
140
|
-
pi.on('input', (event,
|
|
140
|
+
pi.on('input', (event, ctx) => {
|
|
141
141
|
collectedUserTexts.push(event.text);
|
|
142
|
-
(0, event_processors_1.processUserInput)(event.text, sessionId).
|
|
142
|
+
(0, event_processors_1.processUserInput)(event.text, sessionId).then(msg => {
|
|
143
|
+
if (msg && ctx.hasUI) {
|
|
144
|
+
try {
|
|
145
|
+
ctx.ui.notify(`📌 ${msg}`, 'info');
|
|
146
|
+
}
|
|
147
|
+
catch { /* non-critical */ }
|
|
148
|
+
}
|
|
149
|
+
}).catch(() => { });
|
|
143
150
|
return { action: 'continue' };
|
|
144
151
|
});
|
|
145
152
|
// --- Event: session end — episode + promotion + session extraction ---
|
|
146
|
-
pi.on('session_shutdown', (_event,
|
|
147
|
-
(0, event_processors_1.processSessionEnd)(collectedUserTexts, sessionId, projectId).
|
|
153
|
+
pi.on('session_shutdown', (_event, ctx) => {
|
|
154
|
+
(0, event_processors_1.processSessionEnd)(collectedUserTexts, sessionId, projectId).then(result => {
|
|
155
|
+
if (ctx.hasUI) {
|
|
156
|
+
try {
|
|
157
|
+
if (result.stored > 0) {
|
|
158
|
+
ctx.ui.notify(`📝 Recall: captured ${result.stored} memories from this session`, 'info');
|
|
159
|
+
}
|
|
160
|
+
if (result.promoted > 0) {
|
|
161
|
+
ctx.ui.notify(`⬆️ Recall: ${result.promoted} lesson(s) promoted to active rules`, 'info');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch { /* non-critical */ }
|
|
165
|
+
}
|
|
166
|
+
}).catch(() => { });
|
|
148
167
|
// Session extraction: learn from long coding sessions
|
|
149
168
|
const allEntries = [
|
|
150
169
|
...collectedUserTexts.map(t => ({ role: 'user', text: t })),
|
|
151
170
|
...collectedToolResults,
|
|
152
171
|
];
|
|
153
172
|
if (allEntries.length >= 10) {
|
|
154
|
-
(0, event_processors_1.extractSessionLearnings)(allEntries, sessionId, projectId, 5).
|
|
173
|
+
(0, event_processors_1.extractSessionLearnings)(allEntries, sessionId, projectId, 5).then(extracted => {
|
|
174
|
+
if (extracted > 0 && ctx.hasUI) {
|
|
175
|
+
try {
|
|
176
|
+
ctx.ui.notify(`🔍 Recall: extracted ${extracted} learnings from session`, 'info');
|
|
177
|
+
}
|
|
178
|
+
catch { /* non-critical */ }
|
|
179
|
+
}
|
|
180
|
+
}).catch(() => { });
|
|
155
181
|
}
|
|
156
182
|
});
|
|
157
183
|
// --- Event: pre-compaction — aggressive capture ---
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Claude Code Agent Harness — Architecture Reference
|
|
2
|
+
|
|
3
|
+
Reference notes on Claude Code's internal agent harness architecture, based on source code analysis (v1.0.x, March 2026). Useful for understanding integration points and designing Claude Recall features.
|
|
4
|
+
|
|
5
|
+
## Core Orchestration
|
|
6
|
+
|
|
7
|
+
**Query loop** — main while-loop that calls the API, executes tools, handles continuations, retries, and abort. Key behaviors:
|
|
8
|
+
- Infinite loop with explicit terminal conditions (end_turn, max_tokens, budget exceeded, turn limit)
|
|
9
|
+
- Pre-API hooks, post-sampling hooks, stop hooks at each stage
|
|
10
|
+
- Memory prefetch started non-blocking before API call
|
|
11
|
+
- Skill discovery prefetch in parallel with streaming
|
|
12
|
+
|
|
13
|
+
**Tool execution pipeline** — partitions tool calls into batches:
|
|
14
|
+
- Read-only tools (Read, Grep, Glob) run concurrently (max 10)
|
|
15
|
+
- Write tools (Bash, Edit, Write) run serially
|
|
16
|
+
- Each tool goes through: permission check → execute → yield result → apply context modifiers
|
|
17
|
+
- Tool result size budget enforced per turn
|
|
18
|
+
|
|
19
|
+
## Permission System
|
|
20
|
+
|
|
21
|
+
Three modes: `default` (interactive), `auto` (ML classifier), `bypass`.
|
|
22
|
+
|
|
23
|
+
Pipeline per tool call:
|
|
24
|
+
1. Rules-based check (always-allow/deny/ask lists)
|
|
25
|
+
2. Bash classifier (ML, 2s timeout for speculative decisions)
|
|
26
|
+
3. Hook execution (PreToolUse hooks)
|
|
27
|
+
4. Interactive UI prompt (if needed)
|
|
28
|
+
5. Decision persisted to ToolPermissionContext
|
|
29
|
+
|
|
30
|
+
Risk classification: LOW/MEDIUM/HIGH per tool action. Denial tracking state for threshold-based fallback.
|
|
31
|
+
|
|
32
|
+
## Multi-Agent
|
|
33
|
+
|
|
34
|
+
**Coordinator mode** — multi-agent orchestration with parallel worker phases and shared scratchpad.
|
|
35
|
+
|
|
36
|
+
**Agent tool** — forks subagents with inherited context:
|
|
37
|
+
- Child shares parent's prompt cache (byte-identical prefix = free context)
|
|
38
|
+
- Hard turn limit per agent
|
|
39
|
+
- Task notifications via XML: `<task-notification><status>completed</status>...</task-notification>`
|
|
40
|
+
- Agent types: worker (async), teammate (in-process, visible UI), fork (implicit context inheritance)
|
|
41
|
+
|
|
42
|
+
**Worker constraints:**
|
|
43
|
+
- SIMPLE mode: Bash, Read, Edit only
|
|
44
|
+
- Normal mode: full tool list including MCP
|
|
45
|
+
|
|
46
|
+
## State Management
|
|
47
|
+
|
|
48
|
+
**AppState** (Zustand store, 200+ fields):
|
|
49
|
+
- messages, tasks, agents, permissions, MCP clients, models, plugins, settings
|
|
50
|
+
- Observable via selectors
|
|
51
|
+
- Task registry: `{ [taskId]: TaskState }` with status tracking
|
|
52
|
+
|
|
53
|
+
**Task types:** local_bash, local_agent, remote_agent, in_process_teammate, local_workflow, monitor_mcp, dream
|
|
54
|
+
|
|
55
|
+
**Session persistence:** transcript JSONL written per session, resumable.
|
|
56
|
+
|
|
57
|
+
## Context Management / Compaction
|
|
58
|
+
|
|
59
|
+
Four compaction strategies (in order of aggressiveness):
|
|
60
|
+
1. **Microcompact** — cache-editing on every turn (efficient, preserves cache)
|
|
61
|
+
2. **Snip compact** — truncate oldest history (feature-gated)
|
|
62
|
+
3. **Context collapse** — deduplicate/compress (feature-gated)
|
|
63
|
+
4. **Autocompact** — full summarization when token threshold crossed
|
|
64
|
+
|
|
65
|
+
Token tracking: per-message estimates, cache creation/read tokens, danger zone threshold, cumulative budget.
|
|
66
|
+
|
|
67
|
+
Pre/post compact hooks fire at each stage.
|
|
68
|
+
|
|
69
|
+
## Safety & Guardrails
|
|
70
|
+
|
|
71
|
+
- Cyber risk instruction (defensive security, CTF rules)
|
|
72
|
+
- Secret scanner (gitleaks patterns, credential redaction)
|
|
73
|
+
- Path traversal prevention
|
|
74
|
+
- Command injection protection (fixed in security audit)
|
|
75
|
+
- Crypto: `crypto.randomUUID()` / `crypto.randomBytes()` throughout (no weak RNG)
|
|
76
|
+
|
|
77
|
+
## Memory System
|
|
78
|
+
|
|
79
|
+
**extractMemories** — forked sub-agent after each query loop:
|
|
80
|
+
- Reads last ~N messages, decides what to extract
|
|
81
|
+
- 5-turn budget, read-then-write strategy
|
|
82
|
+
- Mutual exclusivity: skips if main agent already wrote to memory
|
|
83
|
+
- Shares parent's prompt cache
|
|
84
|
+
|
|
85
|
+
**autoDream** — background consolidation:
|
|
86
|
+
- Three-gate trigger: 24h since last + 5 sessions + no parallel consolidation
|
|
87
|
+
- Four phases: orient → gather → consolidate → prune
|
|
88
|
+
- PID-based locking, 1h stale window
|
|
89
|
+
|
|
90
|
+
**Memory retrieval** — Sonnet sidequery selects up to 5 relevant memories per query from the memory directory.
|
|
91
|
+
|
|
92
|
+
## Integration Points for Claude Recall
|
|
93
|
+
|
|
94
|
+
### Currently Used
|
|
95
|
+
- PostToolUse / PostToolUseFailure hooks (tool outcome capture)
|
|
96
|
+
- UserPromptSubmit hook (correction detection)
|
|
97
|
+
- Stop hook (session-end processing)
|
|
98
|
+
- PreCompact hook (pre-compaction capture)
|
|
99
|
+
- MCP server (tool registration)
|
|
100
|
+
- Skills directory (behavioral guidance)
|
|
101
|
+
|
|
102
|
+
### Available but Unused
|
|
103
|
+
- Permission decision hooks (observe denied tools as learning signals)
|
|
104
|
+
- Task notification parsing (multi-agent session outcomes)
|
|
105
|
+
- Agent fork interception (inject memory context into subagents)
|
|
106
|
+
- Compaction triggers (pre-compact hooks with message access)
|
|
107
|
+
- Memory prefetch integration (inject recall results alongside native memory)
|
|
108
|
+
|
|
109
|
+
### Architectural Constraints
|
|
110
|
+
- Hooks run as external processes (no shared memory, no prompt cache)
|
|
111
|
+
- MCP tools are request-response (no streaming, no multi-turn)
|
|
112
|
+
- Cannot fork sub-agents from hooks
|
|
113
|
+
- Cannot modify the query loop or tool pipeline directly
|
|
114
|
+
- Feature flags (GrowthBook `tengu_*`) control many code paths — not accessible externally
|
package/package.json
CHANGED