Edit tool rows now carry display_metadata.tool_result_metadata.inline_diff
(~9KB of ANSI per edit) on the live message dict. build_api_messages strips
display_kind/display_metadata/_row_id before the wire, but the local
estimator's wire shadow only dropped PERSISTENCE_ONLY_MESSAGE_FIELDS
({"timestamp"}), so estimate_messages_tokens_rough and
estimate_request_tokens_rough priced the diff: one edit row went from 32 to
2527 estimated tokens (100 edits: 3100 -> 252600). That inflates compaction
preflight, post-tool checks and turn-overflow scoring, compacts early and
breaks the prompt cache.
PERSISTENCE_ONLY_MESSAGE_FIELDS (agent/message_metadata.py) is now the single
set of local-only fields: timestamp, display_kind, display_metadata, _row_id.
build_api_messages pops exactly that set and the estimator shadow drops it,
so the two can no longer drift. The iteration-limit summary path
(_iteration_summary_api_messages) hand-builds its wire messages and stripped
timestamp but not display_*; its key set now unions the same constant, so
the inline diff never reaches the provider there either (strict gateways
reject unknown keys). The tail-budget walk (_estimate_msg_budget_tokens) is
allowlist-based and already ignored these fields.
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
"""Internal metadata attached to durable conversation messages."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from time import time as wall_time
|
|
from typing import Any, MutableMapping, Optional, TypeVar
|
|
|
|
|
|
# These fields describe Hermes' durable record and timeline display, not
|
|
# provider-visible message content. The request builder strips them from every
|
|
# outgoing copy and the token estimator ignores them: one set, so an estimate
|
|
# never prices bytes the provider never receives (an edit's inline_diff in
|
|
# display_metadata is ~9KB and would trigger premature compaction).
|
|
PERSISTENCE_ONLY_MESSAGE_FIELDS = frozenset({"timestamp", "display_kind", "display_metadata", "_row_id"})
|
|
|
|
_Message = TypeVar("_Message", bound=MutableMapping[str, Any])
|
|
|
|
|
|
def stamp_message_timestamp(
|
|
message: _Message,
|
|
*,
|
|
timestamp: Optional[float] = None,
|
|
) -> _Message:
|
|
"""Attach a creation timestamp without replacing source-provided time.
|
|
|
|
Gateway adapters can supply the platform event time; all other callers use
|
|
the local wall clock. Returns the same mapping for use at append sites.
|
|
"""
|
|
if message.get("timestamp") is None:
|
|
message["timestamp"] = wall_time() if timestamp is None else timestamp
|
|
return message
|
|
|
|
|
|
def append_message(
|
|
messages: list[Any],
|
|
message: _Message,
|
|
*,
|
|
timestamp: Optional[float] = None,
|
|
) -> _Message:
|
|
"""Stamp and append one live transcript message."""
|
|
messages.append(stamp_message_timestamp(message, timestamp=timestamp))
|
|
return message
|