hermes - ✅(Solved) Fix [Bug] context compaction summaries still emit function-call-shaped pseudo tool calls with truncated arguments [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#12586Fetched 2026-04-20 12:18:04
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
cross-referenced ×1

Root Cause

This summary text is not the real stored tool_calls history, but it is injected back into model context after compaction. On weaker local tool-calling models, function-call-shaped pseudo examples with truncated raw arguments are risky because they can be imitated as if they were valid tool-call output.

Fix Action

Fix / Workaround

import json
from unittest.mock import patch
from agent.context_compressor import ContextCompressor

with patch("agent.context_compressor.get_model_context_length", return_value=100000):
    c = ContextCompressor(model="test", quiet_mode=True)

PR fix notes

PR #12588: fix(context_compressor): harden tool-call previews in summaries

Description (problem / solution / changelog)

Summary

  • harden context compaction summary rendering for assistant tool calls
  • replace function-call-shaped summary text with JSON-safe argument previews
  • add regression tests covering long multiline tool arguments in summaries

Problem

Current context_compressor._serialize_for_summary() still renders assistant tool calls in a function-call-shaped text form, for example:

execute_code({"code": "import os\nimport subprocess\n...)

This is only summary/reference text, not real tool_calls history, but it is still injected back into model context after compaction. For weaker local tool-calling models, function-call-shaped pseudo examples with truncated raw arguments are risky because they can be imitated as if they were valid tool-call output.

The pruning-path bug that corrupted real tool_calls[].function.arguments JSON is already fixed upstream. This PR is narrower: it hardens the summary serializer so compaction summaries no longer show pseudo executable tool calls.

Changes

  • add _preview_tool_args_for_summary()
    • parse JSON arguments when possible
    • shorten only long string leaves
    • preserve dict/list structure in the preview
    • emit a compact JSON-safe preview with json.dumps(..., ensure_ascii=False)
    • fall back to a descriptive object for non-JSON argument strings
  • change _serialize_for_summary() from name(args) to name args={preview}
  • add regression tests for:
    • preserving short JSON args in readable form
    • summarizing long multiline string values safely
    • ensuring serialized summary contains execute_code args=
    • ensuring serialized summary does not contain execute_code({

Why this helps

  • avoids putting function-call-shaped pseudo examples into compaction summaries
  • reduces the chance that local models imitate malformed or truncated summary text as real tool calls
  • keeps the compaction summary readable while making it safer for downstream tool-calling models

Test Plan

Ran:

scripts/run_tests.sh tests/agent/test_context_compressor.py

Result:

  • 51 passed

Scope

This PR is intentionally narrow. It does not modify the already-fixed Pass-3 pruning logic for real historical tool_calls[].function.arguments. It only changes the compaction summary serializer and its tests.

Closes #12586

Changed files

  • agent/context_compressor.py (modified, +73/-5)
  • tests/agent/test_context_compressor.py (modified, +53/-1)

Code Example

execute_code({"code": "import os\nimport subprocess\n...)

---

if len(args) > self._TOOL_ARGS_MAX:
    args = args[:self._TOOL_ARGS_HEAD] + "..."
tc_parts.append(f"  {name}({args})")

---

execute_code args={"code": "<...>"}

---

import json
from unittest.mock import patch
from agent.context_compressor import ContextCompressor

long_code = ("import os\nimport subprocess\nimport json\n\n# check something\n" * 20)
raw_args = json.dumps({"code": long_code})

with patch("agent.context_compressor.get_model_context_length", return_value=100000):
    c = ContextCompressor(model="test", quiet_mode=True)

print(c._serialize_for_summary([
    {
        "role": "assistant",
        "content": "",
        "tool_calls": [{
            "id": "call1",
            "type": "function",
            "function": {"name": "execute_code", "arguments": raw_args},
        }],
    }
]))
RAW_BUFFERClick to expand / collapse

Bug Description

Current main preserves valid JSON when shrinking real historical tool_calls[].function.arguments, but the compaction summary serializer still renders assistant tool calls in a function-call-shaped text form such as:

execute_code({"code": "import os\nimport subprocess\n...)

This summary text is not the real stored tool_calls history, but it is injected back into model context after compaction. On weaker local tool-calling models, function-call-shaped pseudo examples with truncated raw arguments are risky because they can be imitated as if they were valid tool-call output.

Why this is distinct from the already-fixed pruning bug

The already-fixed pruning bug affected real historical tool_calls[].function.arguments JSON and could directly poison a session by making the provider reject replayed messages.

This issue is narrower:

  • real stored tool_calls JSON remains valid
  • the compaction summary still contains pseudo executable tool-call text with truncated raw arguments
  • downstream local models may imitate that malformed-looking pattern

Current Behavior

In agent/context_compressor.py, _serialize_for_summary() still does:

if len(args) > self._TOOL_ARGS_MAX:
    args = args[:self._TOOL_ARGS_HEAD] + "..."
tc_parts.append(f"  {name}({args})")

Expected Behavior

Compaction summaries should avoid function-call-shaped pseudo examples.

A safer shape would be something like:

execute_code args={"code": "<...>"}

That preserves useful context while avoiding the appearance of an executable tool call.

Minimal Reproduction

import json
from unittest.mock import patch
from agent.context_compressor import ContextCompressor

long_code = ("import os\nimport subprocess\nimport json\n\n# check something\n" * 20)
raw_args = json.dumps({"code": long_code})

with patch("agent.context_compressor.get_model_context_length", return_value=100000):
    c = ContextCompressor(model="test", quiet_mode=True)

print(c._serialize_for_summary([
    {
        "role": "assistant",
        "content": "",
        "tool_calls": [{
            "id": "call1",
            "type": "function",
            "function": {"name": "execute_code", "arguments": raw_args},
        }],
    }
]))

Suggested Fix

  • Build a compact JSON-safe preview for summary rendering
  • Parse JSON arguments when possible
  • Shorten only long string leaves
  • Emit descriptive summary text like name args={...} instead of name(args)
  • For non-JSON argument strings, emit a descriptive fallback object rather than raw pseudo-call text

Suggested Tests

  • short JSON args remain readable in the preview
  • long multiline string values are summarized safely
  • serialized summary contains execute_code args=
  • serialized summary does not contain execute_code({

Related

This is related to the broader malformed-tool-call / poisoned-session family, but specifically targets compaction summary rendering rather than corruption of real historical tool_calls JSON.

extent analysis

TL;DR

Modify the _serialize_for_summary() method in agent/context_compressor.py to emit descriptive summary text instead of function-call-shaped pseudo examples.

Guidance

  • Review the current implementation of _serialize_for_summary() and identify where the function-call-shaped text is being generated.
  • Update the code to parse JSON arguments when possible and shorten only long string leaves.
  • Implement a compact JSON-safe preview for summary rendering, emitting descriptive summary text like name args={...} instead of name(args).
  • Add tests to ensure short JSON args remain readable, long multiline string values are summarized safely, and the serialized summary contains the expected format.

Example

def _serialize_for_summary(self, tool_calls):
    # ...
    if len(args) > self._TOOL_ARGS_MAX:
        try:
            # Attempt to parse JSON arguments
            args_json = json.loads(args)
            # Shorten long string leaves
            args_json = self._shorten_long_strings(args_json)
            args = f"args={json.dumps(args_json)}"
        except json.JSONDecodeError:
            # Emit a descriptive fallback object for non-JSON argument strings
            args = f"args=<...>"
    tc_parts.append(f"  {name} {args}")

Notes

The suggested fix requires careful handling of JSON parsing and string shortening to ensure the summary text is both compact and safe.

Recommendation

Apply the suggested fix to modify the _serialize_for_summary() method, as it directly addresses the issue of function-call-shaped pseudo examples in the compaction summary.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix [Bug] context compaction summaries still emit function-call-shaped pseudo tool calls with truncated arguments [1 pull requests, 1 participants]