Skip to content

Claude provider (SDKAgent) has no context bound — observer overflows with "Prompt is too long" and saves zero memory on long sessions #2956

Description

@dndungu

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.tsbuildObservationPrompt (~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.tstruncateHistory (~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:

  1. 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.
  2. 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.
  3. Trim per-observation payloadsbuildObservationPrompt 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions