Skip to content

Profile learning recursively re-ingests its own analysis prompt #176

Description

@cristeaadrian

Summary

buildUserAnalysisContext embeds the raw content of the recent prompts into the profile-learning prompt with no per-item size cap, and the profile-analysis prompt is not excluded from later passes. A prior analysis prompt gets saved as a user prompt, then re-embedded into the next analysis, nesting recursively until the request exceeds the model window. Profile learning (and often auto-capture) then fails:

prompt is too long: N tokens > 1000000 maximum
ContextOverflowError: Session too large to compact

The user_prompts table fills with these ever-larger self-referential rows and the DB bloats without bound.

Likely also the root cause behind #170's overflow comment ("keeps sending everything over and over... context so large it overflows... input is too large").

Root cause

  1. Uncapped prompt embedding in buildUserAnalysisContext (user-memory-learning.js):

    prompts.map((p, i) => `${i + 1}. ${p.content}`).join("\n\n")

    Each content is inserted verbatim; a large recent prompt (pasted log, image data URL, or a prior analysis prompt) can exceed the window on its own.

  2. The persist-time guard misses the analysis prompt. isStructuredSummaryPromptMessage:

    return userMessage.includes("Analyze this conversation.") && userMessage.includes('type="skip"');

    The profile-learning prompt starts with # User Profile Analysis and matches neither string, so it is persisted via savePrompt and re-ingested next cycle. Growth compounds each pass.

Related: changelog snapshots store embeddings

Each user_profile_changelogs.profile_data_snapshot stores a full profile copy, dominated by the centroid/anchor embedding vectors on every item. Those vectors are needed on the live profile for similarity matching but are dead weight in changelog history, so user-profiles.db bloats far beyond the actual profile content.

Impact

  • Profile learning / auto-capture fail on every idle once the loop takes hold.
  • user-prompts.db grows without bound.
  • Surfaces as a recurring toast unrelated to current session size, so it looks like an OpenCode core problem.

Suggested fix

  1. In buildUserAnalysisContext, skip analysis prompts and cap each embedded prompt:

    prompts.map((p, i) => {
      const c = p.content || "";
      if (c.includes("# User Profile Analysis")) return `${i + 1}. [skipped: internal analysis prompt]`;
      const capped = c.length > 4000 ? c.slice(0, 4000) + "..." : c;
      return `${i + 1}. ${capped}`;
    }).join("\n\n")
  2. Broaden the persist-time guard so the analysis prompt is never saved (match # User Profile Analysis, or check before savePrompt).

  3. Cap total assembled prompt size and strip image data URLs from stored content.

  4. Strip centroid/anchor (embedding fields) from profile_data_snapshot before persisting a changelog entry.

Workaround

DELETE FROM user_prompts WHERE content LIKE '%# User Profile Analysis%';
VACUUM;

(Back up user-prompts.db first.)

Environment

  • opencode-mem v2.19.4, OpenCode v1.18.3 (Bun), macOS
  • Provider: amazon-bedrock, model global.anthropic.claude-sonnet-5 (1M window)

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