Files
hermes-agent/agent/conversation_compression_telemetry.py
kshitijk4poor a62979dc36 fix(compression): a refused candidate's attempt record claims no effect
Review thread on #136050 (agent/context_compressor_telemetry.py, "Refusals
report unsaved effects"). The compressor records a candidate's effect
fields before the commit checks run. When the growth guard refused the
candidate, or a database failure rolled the transcript back, the aborted
record still reported its `messages_after`, `tokens_after`,
`tokens_reclaimed` and `items_dropped`. That claimed reclaimed tokens for a
conversation that stayed unchanged.

Drop those fields from any record that neither committed nor rewrote the
history. The input-side counts stay.
2026-10-10 20:18:10 +05:30

209 lines
11 KiB
Python

"""Content-free per-attempt compression telemetry (attempt log line, Relay mark, shared metric).
Sibling of ``agent/conversation_compression.py`` (the facade), which imports it at module level; this
module must never import the facade (import cycle). It logs under the facade's logger name so log
consumers keep one source.
"""
from __future__ import annotations
import copy
import json
import logging
import time
import uuid
from typing import Any
from agent.compaction_events import publish_attempt
logger = logging.getLogger("agent.conversation_compression")
# Caller-side abort verdicts that only restate an outcome the compressor already classified more precisely:
# ``no_progress`` ("the transcript came back unchanged") covers the structural no-ops
# (``no_compressible_window``, ``insufficient_messages``, ``empty_post_handoff_window``) and
# ``summary_generation_aborted`` covers the terminal summary failures (``summary_auth_failure``,
# ``summary_overload_failure``, ...). The emitter keeps the compressor's class for these so the attempt log
# says WHY, not just THAT (#131412). Every other caller label names an event the compressor cannot see
# (fence cancelled, superseded, rollback, pool saturated, ...) and still wins.
_GENERIC_ABORT_VERDICTS = frozenset({"no_progress", "summary_generation_aborted"})
def _snapshot_compression_effect_input(messages: list, verbatim_tail: list | None) -> list | None:
"""Content-only snapshot for final effect accounting; telemetry must never break compression."""
try:
if not verbatim_tail:
return messages
from hermes_cli.partial_compress import rejoin_compressed_head_and_tail
return rejoin_compressed_head_and_tail(messages, copy.deepcopy(verbatim_tail))
except Exception:
logger.debug("failed to snapshot compression effect input", exc_info=True)
return None
def _record_committed_attempt_effect(
agent: Any, messages_before: list | None, messages_after: list, verbatim_tail: list | None, split_status: str,
) -> None:
"""Finalize effect fields from the transcript shape that crossed the commit boundary."""
if messages_before is None:
return
try:
if verbatim_tail and split_status == "not_applicable":
from hermes_cli.partial_compress import rejoin_compressed_head_and_tail
# Without a SessionDB, the manual-compression caller performs this rejoin after compress_context returns.
messages_after = rejoin_compressed_head_and_tail(messages_after, verbatim_tail)
recorder = getattr(agent.context_compressor, "_record_committed_compression_effect", None)
if callable(recorder):
recorder(messages_before, messages_after)
except Exception:
logger.debug("failed to record committed compression effect", exc_info=True)
def _attempt_seed(
agent: Any, *, attempt_began: bool = True, attempt_seed: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""This attempt's id, the session it started in, and its trigger.
``attempt_seed`` is the attempt's own copy and wins: the agent's current-attempt fields belong to the newest
attempt, so a stalled attempt unwinding after its fallback began would otherwise log the fallback's identity.
An emit with no attempt begun (pool saturation) gets a fresh id and an unknown trigger."""
if attempt_seed:
return dict(attempt_seed)
attempt_id = getattr(agent, "_compression_attempt_id", None) if attempt_began else None
return {
"attempt_id": attempt_id or uuid.uuid4().hex, "session_id": getattr(agent, "session_id", "") or "",
"trigger_source": "unknown",
}
def _log_attempt_record(payload: dict[str, Any]) -> None:
"""The one attempt log line; ``scripts/micro_compaction_report.py`` parses this prefix."""
logger.info("context compression attempt telemetry: %s", json.dumps(payload, sort_keys=True, separators=(",", ":")))
# Fields describing the rewritten transcript; the input-side counts (messages_before, tokens_before) stay.
_CANDIDATE_EFFECT_FIELDS = ("messages_after", "tokens_after", "tokens_reclaimed", "items_dropped")
def _emit_compression_attempt_telemetry(
agent: Any, *, started_at: float, commit_status: str, split_status: str, failure_class: str | None = None,
commit_started_at: float | None = None, include_last_telemetry: bool = True,
attempt_seed: dict[str, Any] | None = None, history_rewritten: bool | None = None,
) -> None:
"""Emit one content-free JSON log line for a compression attempt.
``include_last_telemetry=False`` is for emits that fire without an attempt having begun
(pool-saturation refusals): they must not hydrate from the previous attempt's numbers.
``attempt_seed`` is the emitting attempt's own seed (see ``_attempt_seed``).
``history_rewritten`` (commit path only) says whether the returned transcript replaced the input, which
a split that failed after publishing the compacted history still did."""
try:
compressor = agent.context_compressor
telemetry = getattr(compressor, "_last_compression_telemetry", None) if include_last_telemetry else None
seed = _attempt_seed(agent, attempt_began=include_last_telemetry, attempt_seed=attempt_seed)
own = isinstance(telemetry, dict) and telemetry.get("attempt_id") == seed["attempt_id"]
if not own:
# Before dispatch the compressor still holds the previous attempt's dict, a pre-commit restore puts
# it back, and a newer attempt may have seeded its own: describe THIS attempt from its seed, never
# another's numbers.
telemetry = {**seed, "method": "none"}
payload = dict(telemetry)
payload.setdefault("event", "compression_attempt")
payload.setdefault("route", "hermes")
payload.setdefault("attempt_id", seed["attempt_id"])
payload.setdefault("session_id", seed["session_id"])
payload.update(
total_duration_ms=int((time.monotonic() - started_at) * 1000), commit_status=commit_status,
split_status=split_status,
)
if history_rewritten is not None:
payload["history_rewritten"] = history_rewritten
if commit_status != "committed" and not history_rewritten:
# A refused or rolled-back candidate never reached the transcript: report no effect it did not have.
for key in _CANDIDATE_EFFECT_FIELDS:
payload.pop(key, None)
if commit_started_at is not None:
telemetry["commit_ms"] = payload["commit_ms"] = max(0, int((time.monotonic() - commit_started_at) * 1000))
# Defer only to THIS attempt's class: an abort restore can put the previous attempt's telemetry back.
if failure_class and not (failure_class in _GENERIC_ABORT_VERDICTS and own and payload.get("failure_class")):
payload["failure_class"] = failure_class
payload.setdefault("chunking", False)
payload.setdefault("chunk_count", 0)
# The compressor's fallback flags are this attempt's only when its telemetry is.
payload["fallback_used"] = bool(
payload.get("fallback_used")
or own and (
getattr(compressor, "_last_summary_fallback_used", False)
or getattr(compressor, "_last_aux_model_failure_model", None)
)
)
_log_attempt_record(payload)
publish_attempt(agent, payload)
from hermes_cli.observability.shared_metrics_events import finish_compression_attempt
finish_compression_attempt(
commit_status, payload.get("failure_class"), getattr(agent.context_compressor, "context_length", None), agent=agent,
)
except Exception as exc:
logger.debug("failed to emit compression attempt telemetry: %s", exc, exc_info=True)
def _emit_aborted_attempt_telemetry(
agent: Any, started_at: float, failure_class: str | None, attempt_seed: dict[str, Any] | None = None,
) -> None:
_emit_compression_attempt_telemetry(
agent, started_at=started_at, commit_status="aborted", split_status="aborted", failure_class=failure_class,
attempt_seed=attempt_seed,
)
def _emit_bypassed_attempt_telemetry(
agent: Any, started_at: float, *, commit_status: str, failure_class: str | None, approx_tokens: Any,
route: str = "hermes", method: str = "none", attempt_seed: dict[str, Any] | None = None,
) -> None:
"""Log one attempt that never reached the local compressor: an automatic gate blocked it, Codex owns the
thread, or the session lease showed another path already owns the work. The compressor's telemetry still
describes an earlier attempt, so this record starts from the attempt seed. Shared metrics have never
counted these exits and still do not."""
try:
compressor = getattr(agent, "context_compressor", None)
payload = {
"event": "compression_attempt", "route": route, "method": method, "failure_class": failure_class,
**_attempt_seed(agent, attempt_seed=attempt_seed),
"main_provider": getattr(agent, "provider", "") or "", "main_model": getattr(agent, "model", "") or "",
# Private caches only: the public properties can trigger a synchronous context-length probe.
"main_context_limit": getattr(compressor, "_resolved_context_length", None),
"effective_threshold": getattr(compressor, "_threshold_tokens", None),
"current_estimated_tokens": approx_tokens if isinstance(approx_tokens, int) else None,
"total_duration_ms": int((time.monotonic() - started_at) * 1000), "commit_status": commit_status,
"split_status": "not_applicable", "fallback_used": False,
}
_log_attempt_record(payload)
publish_attempt(agent, payload)
except Exception as exc:
logger.debug("failed to emit compression attempt telemetry: %s", exc, exc_info=True)
finally:
# compress_context opened the thread-local shared-metric lifecycle before reaching this exit.
# Bypassed attempts are log-only, so retire that lifecycle explicitly; otherwise an unrelated
# pool-saturation emit on the same thread can consume its trigger and token count.
try:
from hermes_cli.observability.shared_metrics_events import discard_compression_attempt
discard_compression_attempt()
except Exception:
logger.debug("failed to discard bypassed compression metric", exc_info=True)
def _emit_blocked_attempt_telemetry(
agent: Any, started_at: float, approx_tokens: Any, attempt_seed: dict[str, Any] | None, reason: str,
) -> None:
"""Record an automatic attempt the breaker gate refused for ``reason`` (the gate's block reason). The class keeps
only the guard's name (``blocked:cooldown``, ``blocked:structural_backoff``, ``blocked:ineffective``), never its
seconds."""
_emit_bypassed_attempt_telemetry(
agent, started_at, commit_status="blocked", failure_class=f"blocked:{reason.split(':', 1)[0]}",
approx_tokens=approx_tokens, attempt_seed=attempt_seed,
)