Profile corruption: duplicate sections after repeated update_user_profile calls

#2 · closed · 1 comments

View on GitHub ↗

JBoggsy

## Summary The `update_user_profile` tool can produce a corrupted `user_profile.md` with duplicate section headers — some filled with data, others still containing the `_Not yet provided_` placeholder. This happens when the agent calls `update_user_profile` multiple times in succession, particularly after parallel tool call failures trigger retries. ## Steps to Reproduce 1. Start with a fresh `user_profile.md` (default template with all placeholder sections) 2. Go through onboarding and provide comprehensive info covering multiple sections 3. The agent calls `update_user_profile` multiple times (some may fail due to #1, triggering retries) 4. Open the Profile page or inspect `user_data/user_profile.md` ## Expected Behavior Each section header should appear exactly once in the profile. When a section is updated, the placeholder content (`_Not yet provided_`) should be replaced with the new content. ## Actual Behavior The profile ends up with duplicate sections. Example observed output: ```markdown ## Fields of Interest Quantitative finance roles at hedge funds... ## Fields of Interest <-- DUPLICATE Quantitative finance roles at hedge funds... ## Salary Preferences Expected base salary range: $90,000 - $140,000 USD... ## Location Preferences Primary preference: New York City... ## Salary Preferences <-- DUPLICATE, still placeholder _Not yet provided_ ## Location Preferences <-- DUPLICATE, still placeholder _Not yet provided_ ## Remote Work Preferences <-- never updated _Not yet provided_ ``` ## Root Cause The `update_user_profile` tool writes new content into the markdown file but does not properly replace existing placeholder sections. When the agent retries after a failed parallel call: 1. The first successful call updates some sections (e.g., inserting "Fields of Interest" content) 2. But the original placeholder sections lower in the file remain intact 3. Subsequent calls add more content, creating further duplicates The tool appears to insert/prepend section content rather than finding and replacing the matching `## Section Header` block. ## Impact - User profile contains contradictory data (filled sections + empty placeholders for the same field) - Downstream features that read the profile (system prompt injection, job matching) may see inconsistent or duplicated information - Users see a messy profile page with repeated headings ## Related Issues - Exacerbated by #1 (parallel tool call concatenation), which causes retries that multiply the problem ## Affected Files - `backend/agent/tools/profile.py` — the `update_user_profile` tool implementation - `backend/agent/user_profile.py` — profile read/write utilities ## Test Environment - OS: Linux - LLM Provider: Ollama (qwen3.5:35b) - Agent Design: default

Comments

JBoggsy

## Fixed in ca9010e **Root cause:** `write_profile_section()` used a regex `re.sub()` to replace a single section's content in the markdown body. When the LLM included embedded `## Header` lines in the content for one section (e.g. updating "Fields of Interest" with content that also contained `## Salary Preferences` and `## Location Preferences`), these were inserted within that section's slot while the original placeholder sections further down in the file remained intact — creating duplicate headings. **Fix:** Rewrote `write_profile_section()` in `backend/agent/user_profile.py` to use a structured **parse → merge → reassemble** approach: - Added `_parse_body_sections()` — splits a profile body into (preamble, sections_dict, section_order) - Added `_assemble_body()` — reassembles from sections, naturally preventing duplicates since each heading key appears exactly once in the dict - `write_profile_section()` now parses both the existing profile and the incoming content into section maps, merges them, and reassembles This also **self-heals** already-corrupted profiles: when duplicate headings exist, `_parse_body_sections()` keeps the version with real content and discards placeholder duplicates. **Tested with:** - 6 programmatic edge cases (single section, sequential updates, re-updates, embedded headers, new sections, section order preservation) - Self-healing of pre-corrupted profiles with duplicate sections - Full end-to-end onboarding using the Peter Grosman test character with Ollama (11 `update_user_profile` calls across 2 conversation turns) — all 10 sections unique with correct content