Summary
When using the Claude provider (CLAUDE_MEM_PROVIDER=claude, SDKAgent), the observer agent accumulates every observation into a single, ever-growing SDK conversation with no sliding window, no summarization, and no message/token cap. On long primary sessions the conversation eventually exceeds the model's context window, the SDK returns "Prompt is too long", and the session is aborted — silently producing zero memory for that whole session.
The OpenRouter provider already solves exactly this problem; the Claude provider just never got the same treatment.
Version / environment
- claude-mem
v10.6.3
CLAUDE_MEM_PROVIDER=claude, CLAUDE_MEM_MODEL=claude-sonnet-4-5 (~200K context)
- Triggered reliably by long sessions with large tool outputs (big
Reads, long Bash/test logs, web fetches).
Root cause
Each observation is rendered with the full pretty-printed tool_input and tool_output embedded in the prompt:
src/sdk/prompts.ts — buildObservationPrompt (~L114-118)
return `<observed_from_primary_session>
<what_happened>${obs.tool_name}</what_happened>
<occurred_at>${new Date(obs.created_at_epoch).toISOString()}</occurred_at>${obs.cwd ? `\n <working_directory>${obs.cwd}</working_directory>` : ''}
<parameters>${JSON.stringify(toolInput, null, 2)}</parameters>
<outcome>${JSON.stringify(toolOutput, null, 2)}</outcome>
</observed_from_primary_session>`;
These are fed into the SDK conversation one-by-one by the message generator, with no bound on count or cumulative size:
src/services/worker/SDKAgent.ts — message generator (~L371-408)
for await (const message of this.sessionManager.getMessageIterator(session.sessionDbId)) {
if (message.type === 'observation') {
const obsPrompt = buildObservationPrompt({ /* ... */ });
yield { type: 'user', message: { role: 'user', content: obsPrompt }, /* ... */ };
}
// no truncation, no rolling summary, no max-observations/max-tokens guard
}
A grep for truncate / MAX_CONTEXT / MAX_ESTIMATED / sliding in SDKAgent.ts finds nothing relevant — the only truncate references there are for shortening a single response in a log line, not the conversation history.
When the overflow hits, it is detected but the run just aborts with no memory saved:
src/services/worker/SDKAgent.ts (~L200-206, L253-255)
if (textContent.includes('prompt is too long') || textContent.includes('context window')) {
logger.error('SDK', 'Context overflow detected - terminating session');
session.abortController.abort();
return;
}
// ...
if (typeof textContent === 'string' && textContent.includes('Prompt is too long')) {
throw new Error('Claude session context overflow: prompt is too long');
}
The fix already exists for the other provider
OpenRouterAgent bounds its history with a sliding window keyed on configurable limits:
src/services/worker/OpenRouterAgent.ts — truncateHistory (~L296-329)
const MAX_CONTEXT_MESSAGES = parseInt(settings.CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES) || DEFAULT_MAX_CONTEXT_MESSAGES; // 20
const MAX_ESTIMATED_TOKENS = parseInt(settings.CLAUDE_MEM_OPENROUTER_MAX_TOKENS) || DEFAULT_MAX_ESTIMATED_TOKENS;
if (history.length <= MAX_CONTEXT_MESSAGES) {
const totalTokens = history.reduce((sum, m) => sum + this.estimateTokens(m.content), 0);
if (totalTokens <= MAX_ESTIMATED_TOKENS) return history;
}
// keep most-recent messages within both limits, drop the rest
SDKAgent has no equivalent. That asymmetry is the bug.
Suggested fix
Port the same bounding into the Claude provider. A few complementary options:
- Sliding window in
SDKAgent — mirror truncateHistory: cap the conversation by a configurable CLAUDE_MEM_CLAUDE_MAX_CONTEXT_MESSAGES / CLAUDE_MEM_CLAUDE_MAX_TOKENS (or a provider-agnostic CLAUDE_MEM_OBSERVER_MAX_*), keeping most-recent observations.
- Rolling summary — periodically (every N observations) replace older raw observations with a compact summary, so memory of early events survives compaction instead of being dropped.
- Trim per-observation payloads —
buildObservationPrompt embeds entire tool_output JSON; a per-field size cap (e.g. truncate any single parameters/outcome block over K bytes) would cut the dominant bloat source for Read/Bash/web tools without losing the event itself.
- Graceful degradation — on detected overflow, emit a minimal "observed but truncated" record (or trigger compaction and continue) rather than aborting with zero output, so a long session still yields some memory.
Workaround for users on the Claude provider today
Add the high-volume, low-signal read tools to CLAUDE_MEM_SKIP_TOOLS in ~/.claude-mem/settings.json, e.g. append Read,Grep,Glob,WebFetch,WebSearch. These are dropped at queue time in SessionRoutes.ts (~L507-510) before entering the observer conversation, which significantly slows the accumulation. It reduces the failure rate but does not eliminate it — a long session with heavy Bash/test output can still overflow, which is why the bounding logic above is the real fix.
Summary
When using the Claude provider (
CLAUDE_MEM_PROVIDER=claude,SDKAgent), the observer agent accumulates every observation into a single, ever-growing SDK conversation with no sliding window, no summarization, and no message/token cap. On long primary sessions the conversation eventually exceeds the model's context window, the SDK returns "Prompt is too long", and the session is aborted — silently producing zero memory for that whole session.The OpenRouter provider already solves exactly this problem; the Claude provider just never got the same treatment.
Version / environment
v10.6.3CLAUDE_MEM_PROVIDER=claude,CLAUDE_MEM_MODEL=claude-sonnet-4-5(~200K context)Reads, longBash/test logs, web fetches).Root cause
Each observation is rendered with the full pretty-printed
tool_inputandtool_outputembedded in the prompt:src/sdk/prompts.ts—buildObservationPrompt(~L114-118)These are fed into the SDK conversation one-by-one by the message generator, with no bound on count or cumulative size:
src/services/worker/SDKAgent.ts— message generator (~L371-408)A
grepfortruncate/MAX_CONTEXT/MAX_ESTIMATED/slidinginSDKAgent.tsfinds nothing relevant — the onlytruncatereferences there are for shortening a single response in a log line, not the conversation history.When the overflow hits, it is detected but the run just aborts with no memory saved:
src/services/worker/SDKAgent.ts(~L200-206, L253-255)The fix already exists for the other provider
OpenRouterAgentbounds its history with a sliding window keyed on configurable limits:src/services/worker/OpenRouterAgent.ts—truncateHistory(~L296-329)SDKAgenthas no equivalent. That asymmetry is the bug.Suggested fix
Port the same bounding into the Claude provider. A few complementary options:
SDKAgent— mirrortruncateHistory: cap the conversation by a configurableCLAUDE_MEM_CLAUDE_MAX_CONTEXT_MESSAGES/CLAUDE_MEM_CLAUDE_MAX_TOKENS(or a provider-agnosticCLAUDE_MEM_OBSERVER_MAX_*), keeping most-recent observations.buildObservationPromptembeds entiretool_outputJSON; a per-field size cap (e.g. truncate any singleparameters/outcomeblock over K bytes) would cut the dominant bloat source forRead/Bash/web tools without losing the event itself.Workaround for users on the Claude provider today
Add the high-volume, low-signal read tools to
CLAUDE_MEM_SKIP_TOOLSin~/.claude-mem/settings.json, e.g. appendRead,Grep,Glob,WebFetch,WebSearch. These are dropped at queue time inSessionRoutes.ts(~L507-510) before entering the observer conversation, which significantly slows the accumulation. It reduces the failure rate but does not eliminate it — a long session with heavyBash/test output can still overflow, which is why the bounding logic above is the real fix.