fix(slack,trajectory): mint the compression marker for agent-facing truncations

The Slack Block Kit payload dump and the nested-attachment text budget
are both fed to the agent, and trajectory_compressor's summarizer input
becomes training data; all three still used the imitable bare
"... [truncated]" idiom. Route them through elide()/elide_middle() with
module-level imports and extend the no-idiom invariant to scan
plugins/platforms/slack and trajectory_compressor.py.

Co-authored-by: salch-cred <salch-cred@users.noreply.github.com>
This commit is contained in:
kshitijk4poor
2026-09-26 18:37:21 +05:30
committed by kshitij
parent 21edc762d5
commit f7122daaab
5 changed files with 20 additions and 10 deletions

View File

@@ -33,6 +33,7 @@ from pathlib import Path as _Path
sys.path.insert(0, str(_Path(__file__).resolve().parents[3]))
from agent.compression_marker import elide
from agent.retry_utils import parse_retry_after_seconds
from agent.secret_scope import get_secret
from gateway.config import Platform, PlatformConfig
@@ -764,8 +765,7 @@ def _serialize_slack_blocks_for_agent(blocks: list, max_chars: int = 6000) -> st
payload = json.dumps(_sanitize(inspectable), ensure_ascii=False, indent=2)
except Exception:
payload = repr(inspectable)
if len(payload) > max_chars:
payload = payload[: max_chars - 18].rstrip() + "\n... [truncated]"
payload = elide(payload, max_chars)
return f"[Slack Block Kit payload for this message]\n```json\n{payload}\n```"
@@ -4267,8 +4267,7 @@ class SlackAdapter(BasePlatformAdapter):
nested_text = ""
if blocks_budget > 0:
nested_text = _extract_text_from_slack_blocks(att.get("blocks") or [])
if len(nested_text) > blocks_budget:
nested_text = nested_text[:blocks_budget].rstrip() + "\n... [truncated]"
nested_text = elide(nested_text, blocks_budget)
if nested_text and nested_text not in body:
blocks_budget -= len(nested_text)
body = f"{body}\n{nested_text}".strip() if body else nested_text

View File

@@ -19,7 +19,14 @@ from agent.compression_marker import (
elide_middle,
)
AGENT_ROOT = pathlib.Path(__file__).resolve().parents[2] / "agent"
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
AGENT_ROOT = REPO_ROOT / "agent"
# Agent-facing renderers outside agent/: Slack payload dumps and trajectory training data.
SCANNED_PATHS = (
*sorted(AGENT_ROOT.rglob("*.py")),
*sorted((REPO_ROOT / "plugins" / "platforms" / "slack").rglob("*.py")),
REPO_ROOT / "trajectory_compressor.py",
)
# Any "...[<words> truncated]" variant, not just the bare one (e.g. "...[fallback summary truncated]").
IMITABLE_MARKER_RE = re.compile(r"(?:\.\.\.|…)\s?\[[^\]\n]*truncated\]")
@@ -68,12 +75,12 @@ def test_no_imitable_truncation_marker_in_agent_strings():
helpers now, so any such literal in a string token is a new imitation surface.
"""
offenders = []
for path in sorted(AGENT_ROOT.rglob("*.py")):
for path in SCANNED_PATHS:
with tokenize.open(str(path)) as fh:
for tok in tokenize.generate_tokens(fh.readline):
if tok.type != tokenize.STRING:
continue
for match in IMITABLE_MARKER_RE.finditer(tok.string):
rel = path.relative_to(AGENT_ROOT.parent)
rel = path.relative_to(REPO_ROOT)
offenders.append(f"{rel}:{tok.start[0]}: {match.group()}")
assert not offenders, "imitable truncation markers in agent/ strings:\n" + "\n".join(offenders)

View File

@@ -6,6 +6,7 @@ each carrying its own blocks, so without a shared ceiling the projection grows l
attachment count while the top-level ``blocks`` path is capped once.
"""
from agent.compression_marker import _COMPRESSION_MARKER_RE
from plugins.platforms.slack.adapter import SlackAdapter
@@ -27,7 +28,7 @@ def test_nested_block_text_stops_growing_with_attachment_count():
per_attachment_growth = (sizes[20] - sizes[5]) / 15
assert per_attachment_growth < 200, sizes
assert sizes[20] < 3 * sizes[1], sizes
assert "[truncated]" in SlackAdapter._append_link_unfurls("intro", [_attachment(i) for i in range(20)])
assert _COMPRESSION_MARKER_RE.search(SlackAdapter._append_link_unfurls("intro", [_attachment(i) for i in range(20)]))
def test_first_attachment_keeps_its_body_and_headers_survive_exhaustion():

View File

@@ -8,6 +8,8 @@ from unittest.mock import AsyncMock, patch, MagicMock
import pytest
from agent.compression_marker import _COMPRESSION_MARKER_RE
from trajectory_compressor import (
CompressionConfig,
TrajectoryMetrics,
@@ -268,7 +270,7 @@ class TestExtractTurnContent:
{"from": "tool", "value": "x" * 5000},
]
content = tc._extract_turn_content_for_summary(trajectory, 0, 1)
assert "...[truncated]..." in content
assert _COMPRESSION_MARKER_RE.search(content)
assert len(content) < 5000
def test_empty_range(self):

View File

@@ -30,6 +30,7 @@ import fire
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn, TimeRemainingColumn
from rich.console import Console
from hermes_constants import OPENROUTER_BASE_URL, get_hermes_home
from agent.compression_marker import elide_middle
from agent.retry_utils import jittered_backoff
from hermes_cli.env_loader import load_hermes_dotenv
@@ -401,7 +402,7 @@ class TrajectoryCompressor:
turn = trajectory[i]
value = turn.get("value", "")
if len(value) > 3000:
value = value[:1500] + "\n...[truncated]...\n" + value[-500:]
value = elide_middle(value, 1500, 500)
parts.append(f"[Turn {i} - {turn.get('from', 'unknown').upper()}]:\n{value}")
return "\n\n".join(parts)