Skip to content

fix(agent): strip MEDIA directives from compressor summarizer input (#14665)#15369

Closed
Tranquil-Flow wants to merge 2 commits into
NousResearch:mainfrom
Tranquil-Flow:fix/compressor-strip-media-directives
Closed

fix(agent): strip MEDIA directives from compressor summarizer input (#14665)#15369
Tranquil-Flow wants to merge 2 commits into
NousResearch:mainfrom
Tranquil-Flow:fix/compressor-strip-media-directives

Conversation

@Tranquil-Flow

@Tranquil-Flow Tranquil-Flow commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

_serialize_for_summary() passes full message content — including MEDIA:/path/to/file.ogg directives — to the summarizer LLM. If the summarizer echoes a MEDIA directive into the "## Critical Context" section, that summary gets re-injected into the next turn's message history via SUMMARY_PREFIX. The downstream model sees what it believes is a prior output directive and may re-emit it, triggering unwanted media delivery.

This PR strips MEDIA directives before they reach the summarizer, replacing them with a neutral [media attachment] placeholder.

Related Issue

Fixes #14665

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/context_compressor.py — added _MEDIA_RE = re.compile(r'MEDIA:\S+') and stripped all MEDIA directives from content with [media attachment] placeholder before sending to the summarizer. Applied to all message roles (assistant, tool, user) uniformly.
  • tests/agent/test_compressor_media_stripping.py — targeted tests (4)

Validation table

Content Before (sent to summarizer) After
"Here is the audio MEDIA:/tmp/voice.ogg done." MEDIA:/tmp/voice.ogg visible [media attachment]
"The file path is /tmp/test.txt" Preserved Preserved (no false positive)

How to Test

  1. pytest tests/agent/test_compressor_media_stripping.py -v — 4 tests covering assistant content, tool results, preservation of non-MEDIA paths, and multiple directives
  2. Tested on macOS (Python 3.14)

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS (Python 3.14)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

N/A — see commit description and PR diff.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels Apr 24, 2026
ilyasst added a commit to ilyasst/hermes-agent that referenced this pull request May 13, 2026
Symptom: local Qwen3.5 via llama-server with --jinja occasionally generates
"\nuser\n[<user_name>] ..." past the assistant stop boundary. Hermes formats
user messages as "[<user_name>] ..." (gateway/run.py:5692), so the model
learned that pattern as the user-turn delimiter; when sampling drifts past
the assistant stop, it continues into a plausible user turn. The gateway
delivers that verbatim to Telegram — the bot appears to literally echo
"[ilyass] nudge" back at the user.

Belt + suspenders:

- run_agent.py: inject stop sequences ["\nuser\n[", "\n\nuser\n"] into
  chat_completions api_kwargs after _build_api_kwargs(). Cuts generation
  at the leak boundary, saving tokens too. Disable via
  HERMES_DISABLE_USER_PREFIX_STOP=1.

- gateway/run.py: post-process final_response with a regex that detects
  and strips "\nuser\n[<name>] ..." patterns. Safety net for any leak
  that escapes the stop sequence (e.g., on streaming providers where stop
  detection differs). Logs a warning when it fires so we can monitor.

Related upstream (all open, none merged): NousResearch#6272 (strip [CONTEXT COMPACTION]
echoes), NousResearch#19887 (strip leaked template markers from gemma4 tool args),
NousResearch#15369 (strip MEDIA directives), NousResearch#18529 (title leaks <thinking>).
@Tranquil-Flow Tranquil-Flow force-pushed the fix/compressor-strip-media-directives branch from c327156 to ce1b4f8 Compare May 25, 2026 11:06
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #44708 — your commit was cherry-picked onto current main with your authorship preserved in git log (286ecd2). Only change on top: hoisted the regex to module level. Thanks!

#44708

@teknium1 teknium1 closed this Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context compaction can misread preserved todo/tool state as current user intent and leak MEDIA directives

3 participants