Skip to content

fix(honcho-memory): three-pass sanitizer for imperative, self-narration prefix, and self-narration phrase lines - #66770

Closed
bbasketballer75 wants to merge 5 commits into
NousResearch:mainfrom
bbasketballer75:fix/honcho-self-narration-demote
Closed

fix(honcho-memory): three-pass sanitizer for imperative, self-narration prefix, and self-narration phrase lines#66770
bbasketballer75 wants to merge 5 commits into
NousResearch:mainfrom
bbasketballer75:fix/honcho-self-narration-demote

Conversation

@bbasketballer75

Copy link
Copy Markdown

fix(honcho-memory): demote self-narration lines from AI Self-Representation

Summary

Extends the PR #66754 sanitization pass in plugins/memory/honcho/__init__.py:_sanitize_card_lines with two new filters that address a different class of self-trust-loop pollution:

  1. Self-narration filter: lines starting with hermes says, hermes said, HERMES SAYS:, HERMES SAID:, [AUTO-NARRATED], [DEBUG-LOG], or [SELF-TRACE] are demoted to a labeled [historical, demoted from <section>] block at the end of the section. The AI Self-Representation accumulates hundreds of these from prior debugging sessions, and once surfaced they re-assert themselves as present-tense facts in future model responses.

  2. Line cap: each section is capped at _MAX_LINES_PER_SECTION = 60 lines. Overflow goes to a labeled [historical, truncated — <section> exceeded 60-line cap; N older lines demoted] block. Prevents a single polluted section from blowing up the prompt cache for every turn.

The existing imperative-shape filter from PR #66754 is preserved unchanged. Backward-compatible: clean sections render identically.

Why

Symptom observed on 2026-07-18: the Telegram bot's response to a logic puzzle included a side note that re-narrated what the actual answer already established ("Side note: the memory-context block this turn shows the sanitized User Peer Card format..."). Tracing back, the side note was triggered by the bot pattern-matching hermes says X observations from its own AI Self-Representation, even though the actual response didn't need the sidebar. PR #66754's imperative-shape filter (catch INSTRUCTION:, RULE:, etc.) didn't catch this because the noise was in hermes says form, not imperative form.

The same pattern was visible across this debugging session's AI Self-Representation, which had ~70 [2026-07-18 ...] hermes says X lines by the end. Each one became a future re-assertion target. Patching the renderer to demote these lines closes the vector at the surface layer regardless of upstream Honcho state.

Repro

A session's AI Self-Representation contains dozens of hermes says X / hermes said Y lines from prior debugging. Without the patch, the model reads them as authoritative and re-narrates them in every response, even when the response doesn't need it. With the patch, those lines are demoted to a clearly-labeled [historical, demoted] block at the end of the section so the model can see them for context but doesn't quote them as live facts.

Diff scope

  • plugins/memory/honcho/__init__.py: extends _sanitize_card_lines with self-narration filter, line cap, and updated trailer-block formatting. Adds _SELF_NARRATION_PREFIXES tuple and _MAX_LINES_PER_SECTION constant.
  • 1 file changed, +138 / -4
  • No changes to upstream-facing API
  • No changes to Honcho client behavior
  • No changes to gateway / Telegram routing

Test coverage

In-process unit tests (verify with python -c "..." after this PR lands):

  • All three filter types in one input: imperative-shape line → [untrusted injection] block; lowercase hermes says X[historical, demoted] block; uppercase HERMES SAID: Y[historical, demoted] block; [AUTO-NARRATED] Z[historical, demoted] block; legitimate clean lines stay in main body
  • Line cap: 70-line input → 60 in main body + 10 in [historical, truncated] block with explicit count annotation
  • Cross-case sensitivity: lowercase hermes says and uppercase HERMES SAYS: both caught (matched case-insensitively because Honcho stores these in lowercase)

Reviewer ask

  • Confirm the prefix list (hermes says, hermes said, HERMES SAYS:, HERMES SAID:, [AUTO-NARRATED], [DEBUG-LOG], [SELF-TRACE]) is the right set — open to additions if there are other historical/debug-tag prefixes in production Honcho data
  • Confirm the 60-line cap is the right balance: too low and legitimate short sessions lose context; too high and the cache-fill benefit is muted. 60 is a starting point; can be tuned via future PRs.
  • Confirm demoted-as-labeled-block (vs silent drop) is the right UX — the model gets to see what was filtered, just clearly labeled as historical

Out of scope

  • Cleaning up the existing ~70 polluted observations in our install's Honcho store — those require honcho_conclude delete per-row, which is a separate work item. The renderer fix is the durable prevention.
  • Switching peer-card format to a typed schema — that's an Honcho-side change.
  • Adding a per-turn refresh that bypasses the first-turn cache — that would break prompt caching.

Cross-references

…dows

_run_bash() built [bash, "-c", cmd_string] unconditionally for every
local-backend command on Windows -- including when the command targets
a different interpreter entirely. bash -c parses the whole string as
bash source, expanding $-prefixed content inside double-quoted portions
before the target program ever sees it, so `powershell -Command
"...$_.Path..."` never reached PowerShell intact.

terminal, write_file, and execute_code's generated hermes_tools.terminal()
RPC stub all funnel through this one function, so this was one root cause
surfacing in three places, not three separate bugs.

Fix: detect when cmd_string is *only* a powershell/pwsh invocation (no
other bash constructs mixed in, checked on tokenized output so a `|`
inside a quoted PowerShell pipeline isn't mistaken for a bash pipe) and
invoke the resolved interpreter directly via subprocess.Popen, skipping
bash's parser entirely. Every other command is unchanged.

Also wired the existing _looks_like_msys_spawn_failure()/_git_bash_aslr_help()
diagnostic into write_file's failure path -- previously only the terminal
tool's own _find_bash() call site used it, so the same rare MSYS crash
class surfaced as a raw dump instead of the actionable remediation
message when it hit during a write_file call.

Verified: full existing tools/environments/local.py test suite (199
tests) identical before/after via git stash -- zero regressions.
Add _is_minimax_global_anthropic_base_url helper and extend
build_api_kwargs_extras to emit thinking:{type:adaptive} (or
disabled) on the Anthropic-compatible route at
api.minimax.io/anthropic. Previously only the OpenAI-compatible
/v1 route received the reasoning control, leaving every install
that uses the standard minimax-oauth or default minimax provider
with M3 reasoning silently off despite reasoning_effort being set.

Complements NousResearch#42560 (which handles the reasoning_config=None case
in agent/anthropic_adapter.py) — together the two cover both the
explicit-config and silent-default call patterns.
…hropic

Cherry-picked from NousResearch#42560 (kapelame) with
manual hunk adaptation since local main has moved past the PR's base.

Three changes in agent/anthropic_adapter.py:

1. _supports_adaptive_thinking now returns True for MiniMax M3 (was
   False, which made the code path fall into the manual
   enabled+budget_tokens branch — MiniMax's /anthropic endpoint only
   accepts adaptive/disabled per the official docs and silently
   ignores manual thinking.type=enabled, producing reasoning_tokens=0).

2. New _is_minimax_m3(model) helper.

3. build_anthropic_kwargs defaults reasoning_config to {enabled: True}
   for M3 when caller passes None (M3 returns content=null without
   thinking enabled).

After this, build_anthropic_kwargs emits thinking.type=adaptive for M3
on api.minimax.io/anthropic, which the endpoint actually accepts.

End-to-end verified in-process: M3+None→adaptive medium, M3+enabled
True effort high→adaptive high, M3+enabled False→no thinking,
claude-opus-4.6 unchanged (still adaptive), M2.7 unchanged (still
manual enabled+budget_tokens).
…tation

The agent's own AI Self-Representation can accumulate hundreds of
`hermes says X` / `hermes said X` lines from prior debugging sessions.
Once surfaced, they re-assert themselves as present-tense facts in
future model responses even when the underlying state has changed.

This patch adds a second filter pass that demotes these self-narration
lines to a labeled `[historical, demoted from <section>]` block at the
end of the section, alongside the existing imperative-shape filter
that demotes INSTRUCTION:/RULE:/DIRECTIVE:/COMMAND: lines.

Also adds a 60-line hard cap per section. Lines past the cap go to a
`[historical, truncated]` block. Prevents a single polluted section
from blowing up the prompt cache for every turn of every session.

Diff: extends PR NousResearch#66754 sanitizer with self-narration + line-cap passes.
Backward compatible: clean sections render unchanged.
Copilot AI review requested due to automatic review settings July 18, 2026 06:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds additional sanitization and truncation logic to Honcho-rendered memory context to reduce prompt-injection/self-trust-loop pollution, and also includes unrelated fixes in local command execution and MiniMax M3 reasoning defaults.

Changes:

  • Add Honcho context sanitization for imperative-shaped lines, self-narration/debug prefixes, and a per-section line cap with demoted “historical” trailers.
  • Adjust local command execution to directly exec standalone PowerShell/pwsh invocations (bypassing bash -c) to avoid $ expansion/path mangling.
  • Fix MiniMax M3 thinking defaults on Anthropic-format routes and expand MiniMax provider route detection.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plugins/memory/honcho/__init__.py Sanitizes/demotes injected or self-narrated lines and caps rendered context per section.
tools/environments/local.py Adds direct-exec path for standalone PowerShell/pwsh commands to avoid Git Bash parsing issues.
tools/file_operations.py Improves write-file error detail by reusing Git Bash ASLR remediation messaging on Windows/MSYS spawn failures.
plugins/model-providers/minimax/__init__.py Adjusts MiniMax M3 reasoning controls for both /v1 and /anthropic routes.
agent/anthropic_adapter.py Defaults MiniMax M3 thinking on (when unspecified) and treats M3 as adaptive-thinking capable.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +710 to +715
stripped = line.lstrip()
lower = stripped.lower()
if any(stripped.startswith(prefix) for prefix in cls._IMPERATIVE_LINE_PREFIXES):
filtered_injection.append(line)
elif any(lower.startswith(prefix) for prefix in cls._SELF_NARRATION_PREFIXES):
filtered_historical.append(line)
_MAX_LINES_PER_SECTION = 60

@classmethod
def _sanitize_card_lines(cls, card_text: str, section_name: str) -> str:
rep = ctx.get("representation", "")
if rep:
parts.append(f"## User Representation\n{rep}")
sanitized_rep = self._sanitize_representation_lines(rep, "User Representation")
# count of what was truncated.
_MAX_LINES_PER_SECTION = 60

@classmethod
Comment on lines +2667 to +2668
if reasoning_config is None and _is_minimax_m3(model):
reasoning_config = {"enabled": True}
Comment on lines +645 to +649
def _direct_interpreter_argv(cmd_string: str) -> "list[str] | None":
"""Detect a command that is ONLY a direct PowerShell/pwsh invocation and
return a ready-to-exec argv list for it, or None if this command should
still go through bash -c.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets tool/memory Memory tool and memory providers tool/file File tools (read, write, patch, search) provider/minimax MiniMax (Anthropic transport) backend/local Local shell execution sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state needs-decision Awaiting maintainer decision before any implementation labels Jul 18, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #66754 for the Honcho context-sanitization work, but this branch also changes MiniMax transport behavior, local PowerShell execution, and file operations. Please split or explicitly review those independent changes; it is not a duplicate.

…ervations

The self-narration filter introduced in NousResearch#66770 only catches lines whose
first token is 'hermes says' / 'hermes said' / '[AUTO-NARRATED]' / etc.
User-peer observations of the form

    [2026-07-18 06:13:36] austin shared that Hermes said 'Vee'...

survive the prefix filter because they start with a timestamp. But the
quoted self-narration token still seeds the self-trust loop when it
lands in model context — observed live with three surviving lines in
the user-peer representation referencing the Vee debugging session.

Add a fourth filter pass: a word-boundary-anchored regex matching
'hermes says' or 'hermes said' anywhere in the line, case-insensitive.
Demotes to the same [historical, demoted from <section>] block as the
prefix filter.

False-positive analysis against the live user-peer corpus (12,377 chars,
125 lines):
- 3 surviving-contamination lines: caught
- 9 legitimate 'Hermes' mentions ('PC-Hermes-class', 'desktop hermes',
  'hermes-update-now.ps1', 'quoted Hermes saying that...', 'austin says...',
  'Hermes to', 'hermes-says-X'): all kept, zero false positives

The word-boundary anchors prevent matching inside compound tokens like
'hermes-says-X' (hyphenated) — only free-standing 'hermes says/hermes said'
phrases trigger.

Tests: 9 new tests in test_self_narration_filter.py covering all four
filter passes, line cap, word-boundary edge cases, case-insensitivity,
and the prefix-vs-phrase interaction. All 9 pass; full Honcho test
suite still green (425 passed, 5 pre-existing unrelated path-resolution
failures confirmed via git stash).

Stacks on PR NousResearch#66770 — third commit on fix/honcho-self-narration-demote.
@bbasketballer75 bbasketballer75 changed the title fix(honcho-memory): demote self-narration lines from AI Self-Representation fix(honcho-memory): three-pass sanitizer for imperative, self-narration prefix, and self-narration phrase lines Jul 18, 2026
@bbasketballer75

Copy link
Copy Markdown
Author

Third commit added: 6b22c9424 extends the self-narration filter to substring-match hermes says / hermes said anywhere in the line, not just at the start.

Why: user-peer observations of the form [YYYY-MM-DD HH:MM:SS] austin shared that Hermes said 'Vee'… survive the prefix filter (they start with a timestamp), but the quoted phrasing still seeds the self-trust loop. Three surviving lines in the live user-peer representation were the actual reason the contaminated <memory-context> block kept showing up in Telegram responses.

Fix: word-boundary-anchored regex \bhermes (?:says|said)\b, case-insensitive, applied as a fourth filter pass in _sanitize_card_lines (which is also called by _sanitize_representation_lines). Same [historical, demoted from <section>] block.

False-positive analysis against the live user-peer corpus (12,377 chars, 125 lines):

  • 3 surviving-contamination lines: caught
  • 9 legitimate Hermes mentions (PC-Hermes-class, desktop hermes, hermes-update-now.ps1, quoted Hermes saying that…, austin says…, Hermes to, hermes-says-X): all kept, zero false positives. Word-boundary anchors prevent matching inside compound tokens (hermes-says-X is hyphenated and stays intact).

Tests: 9 new tests in tests/honcho_plugin/test_self_narration_filter.py — all four filter passes, line cap, word-boundary edge cases, case-insensitivity, prefix-vs-phrase interaction. 9/9 passing; full Honcho suite green (425 passed, 5 pre-existing path-resolution failures unrelated to this PR).

PR title updated to reflect three-pass scope. Patch artifact at ~/.hermes/patches/honcho-self-narration-demote.patch (sha256 aae95611…3dc98, 16,885 bytes) — single combined delta from origin/main, applies cleanly on a fresh clone.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Hermes Agent

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Hermes Agent

@bbasketballer75

Copy link
Copy Markdown
Author

Closing and re-opening as PR #66771 from a clean branch that contains only the 2 self-narration commits. The original PR head branch had picked up MiniMax transport and PowerShell bypass commits that don't belong with the Honcho context-sanitization work — keeping them out of the new PR scope. The install-tree hash and live install behavior are unchanged; the patch is functionally identical, just isolated to the right concerns.

bbasketballer75 added a commit to bbasketballer75/hermes-agent that referenced this pull request Jul 31, 2026
Assistant output is dominated by self-narration, status reports, and
tool-call traces. The Honcho deriver's extraction prompt reads
assistant output as facts about the hermes peer, which inflates the AI
Self-Representation with debug breadcrumbs and re-asserting "hermes
said X" lines on every turn — the exact pattern that fed the
2026-07-18 self-trust loop and that PR NousResearch#66770's renderer only partially
mitigates. Source-side fix is the right layer.

User messages still go in (legitimate). AI identity / config / system
seeds go through seed_ai_identity in the same module and are unaffected.

This is the Hermes-side half of a two-part fix:
- Honcho deriver prompt (exclusions) — addresses new pollution at extract time
- This patch — stops new pollution at write time

Both needed because:
1. Prompt exclusions only catch lines that match; some debug-style content
   slips through.
2. Stopping the write at source is structurally simpler and prevents
   the deriver from spending compute on assistant content at all.

Refs PR NousResearch#66770 (renderer, defense-in-depth).
bbasketballer75 added a commit to bbasketballer75/hermes-agent that referenced this pull request Jul 31, 2026
The sync_turn patch (commit bff9bc635 in this repo) only blocks assistant
content from being added to the session object via the runtime chat write
path. The _flush_session path that drains queued messages to Honcho is
called independently from:

  - on_session_end hook (which calls manager.flush_all() -> _flush_session)
  - the async_writer_loop background daemon thread
  - direct flush_all() invocations from tools/ call sites

Without filtering on the flush side, any assistant messages added to the
session BEFORE the sync_turn patch took effect would still drain to Honcho
during a gateway restart or session-end, then deriver would extract them
as third-person facts about the hermes peer (hermes said X / hermes
reported Y), reproducing the AI Self-Representation pollution that
PR NousResearch#66770's renderer only partially mitigates on read.

The 96 new pollution documents observed after the gateway restart on
2026-07-18 08:42 were from exactly this path: in-memory queue draining.

Fix: filter m.get('role') == 'assistant' inside _flush_session's
new_messages list comprehension, mirroring sync_turn's stance. User
messages and the rare legitimate assistant_message (e.g., identity
seeds via seed_ai_identity path which doesn't go through session._flush_session)
are unaffected. The seed_ai_identity path was already separate and not
in scope here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend/local Local shell execution comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists provider/minimax MiniMax (Anthropic transport) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/file File tools (read, write, patch, search) tool/memory Memory tool and memory providers type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants