Release exp-v0.52.197: preserve api_content for agent replay (#6757, @franksong2702)

Ships PR #6757 with a maintainer hardening pass folded in (authorship preserved).

Preserves the exact provider-facing reply text (api_content) through the internal
session store for byte-accurate agent replay, while guaranteeing it never reaches
a browser: every client-facing surface strips it via the public projection.

Maintainer fixes (2 defects my adversarial re-gate reproduced):
- Selective session redaction restored: only transcript fields are scrubbed;
  operational fields (e.g. workspace path) pass through unchanged, so a path
  containing credential-shaped text is no longer masked into an invalid path.
- Runner-backed SSE relay now projects payloads before emit, closing an
  api_content leak through _stream_runner_run_events.
+5 regression tests. Codex adversarial re-gate: SAFE TO SHIP. Full suite clean.

Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
This commit is contained in:
nesquena-hermesandfranksong2702 committed 2026-08-11 02:10:21 +00:00
1 parent 728ec10e66
commit cf7eb48e56
9 files changed
+3187 -112

No files matched your search

+729 -14
View File
@@ -7986,6 +7986,11 @@ def get_state_db_session_messages(
'codex_reasoning_items',
'reasoning_content',
'codex_message_items',
# Hermes Agent stores the exact provider-facing text here when
# it differs from the clean transcript content. Keep this
# sidecar in the WebUI's internal history; the provider-safe
# projection strips it before any direct API request.
'api_content',
]
id_col = ['id'] if 'id' in available else []
selected = id_col + ['role', 'content', 'timestamp'] + [c for c in optional if c in available]
@@ -8090,6 +8095,11 @@ def get_state_db_session_messages(
'content': row['content'],
'timestamp': row['timestamp'],
}
# ``id`` is the durable SQLite row identity, not the WebUI's
# session-local stable message id. Keep it in a private
# provenance field so duplicate reconciliation can align a
# state.db sidecar without changing the existing ``id`` key
# used by WebUI transcript merge/dedup logic.
for col in optional:
if col not in row.keys():
continue
@@ -8099,6 +8109,16 @@ def get_state_db_session_messages(
if col in {'tool_calls', 'reasoning_details', 'codex_reasoning_items', 'codex_message_items'}:
value = _json_loads_if_string(value)
msg[col] = value
# Keep durable provenance only alongside a real Agent replay
# sidecar. Ordinary state.db rows must retain their historic
# shape and must not acquire internal bookkeeping fields.
if (
id_col
and row['id'] is not None
and isinstance(msg.get('api_content'), str)
and msg['api_content']
):
msg['_state_db_row_id'] = row['id']
if msg.get('role') == 'tool' and msg.get('tool_name') and not msg.get('name'):
msg['name'] = msg['tool_name']
msgs.append(msg)
@@ -8131,6 +8151,8 @@ def get_state_db_session_message_prefix_summary(
before_ts = float(before_timestamp)
except (TypeError, ValueError):
return None
if not math.isfinite(before_ts):
return None
if isinstance(profile, str) and profile:
db_path = _get_profile_home(profile) / 'state.db'
@@ -8219,12 +8241,13 @@ def get_state_db_session_message_keys_before_timestamp(
available = {str(row['name']) for row in cur.fetchall()}
if not {'id', 'session_id', 'role', 'content', 'timestamp', 'tool_calls'}.issubset(available):
return None
api_content_select = ", api_content" if "api_content" in available else ""
cur.execute(
"""
f"""
SELECT
COALESCE(role, '') AS role,
COALESCE(content, '') AS content,
tool_calls
tool_calls{api_content_select}
FROM messages
WHERE session_id = ? AND timestamp IS NOT NULL AND timestamp < ?
ORDER BY timestamp ASC, id ASC
@@ -8237,6 +8260,7 @@ def get_state_db_session_message_keys_before_timestamp(
"role": row["role"],
"content": row["content"],
"tool_calls": _json_loads_if_string(row["tool_calls"]),
"api_content": row["api_content"] if "api_content" in available else None,
}
)
for row in cur.fetchall()
@@ -8299,6 +8323,11 @@ def _normalized_message_timestamp_for_key(value):
timestamp = float(value)
except (TypeError, ValueError):
return str(value)
if not math.isfinite(timestamp):
# Keep malformed metadata from crashing key construction. The
# reconciliation layer separately marks this row invalid so it cannot
# fall through to a weaker metadata-free match.
return f"<invalid:{value!r}>"
# Truncate to second-level granularity so that sub-second drift between
# the sidecar JSON write and the state.db created_at write does not cause
# the legacy dedup key to differ for the same logical message.
@@ -8312,9 +8341,29 @@ def _message_timestamp_as_float(msg):
if value is None or value == "":
return None
try:
return float(value)
timestamp = float(value)
except (TypeError, ValueError):
return None
return timestamp if math.isfinite(timestamp) else None
def _session_message_api_content_key(msg: dict):
"""Return the exact trusted provider sidecar used in duplicate identity."""
if not isinstance(msg, dict):
return None
value = msg.get("api_content")
return value if isinstance(value, str) and value else None
def _session_message_key_with_sidecar(base_key: tuple, msg: dict) -> tuple:
"""Append provider sidecar identity only when one is actually present.
The no-sidecar key shape is an internal compatibility surface used by
reconciliation tests and callers. A present sidecar must extend that
identity so different provider bytes cannot collapse into one duplicate.
"""
sidecar = _session_message_api_content_key(msg)
return base_key if sidecar is None else (*base_key, sidecar)
def _session_message_merge_key(msg: dict):
@@ -8322,7 +8371,9 @@ def _session_message_merge_key(msg: dict):
return ("non_dict", repr(msg))
message_identity = msg.get("id") or msg.get("message_id")
if message_identity:
return ("message_id", str(message_identity))
return _session_message_key_with_sidecar(
("message_id", str(message_identity)), msg
)
# Include tool_calls so assistant messages that invoke different tools
# (but share identical empty content and same-second timestamp) are not
# collapsed by the merge-key guard at line ~4216. Without this,
@@ -8331,7 +8382,7 @@ def _session_message_merge_key(msg: dict):
# every state.db tool-call after the first one registered by the sidecar.
_tc = msg.get("tool_calls")
_tc_key = json.dumps(_tc, sort_keys=True, default=str) if _tc else ""
return (
return _session_message_key_with_sidecar((
"legacy",
str(msg.get("role") or ""),
str(msg.get("content") or ""),
@@ -8339,7 +8390,7 @@ def _session_message_merge_key(msg: dict):
str(msg.get("tool_call_id") or ""),
str(msg.get("tool_name") or msg.get("name") or ""),
_tc_key,
)
), msg)
def _session_messages_have_prefix(messages, prefix) -> bool:
@@ -8386,6 +8437,585 @@ def _merge_session_display_metadata(target: dict | None, source: dict | None) ->
target[key] = copy.deepcopy(value)
def _state_db_row_identity_details(message: dict | None) -> tuple[str | None, bool]:
"""Return ``(row_id, valid)`` for private state.db provenance aliases.
A message carrying two different aliases is contradictory provenance. It
must not silently fall through to timestamp/sequence matching, because that
would turn an identity conflict into a guessed provider-side payload.
"""
if not isinstance(message, dict):
return None, True
values = set()
for key in ("_state_db_row_id", "_db_row_id", "state_db_row_id"):
if key not in message or message.get(key) in (None, ""):
continue
value = message.get(key)
if isinstance(value, bool):
return None, False
if isinstance(value, int):
normalized = str(value) if value >= 0 else None
elif isinstance(value, float):
normalized = str(int(value)) if math.isfinite(value) and value >= 0 and value.is_integer() else None
elif isinstance(value, str):
text = value.strip()
normalized = text if text.isdigit() else None
if normalized is not None:
normalized = str(int(normalized))
else:
normalized = None
if normalized is None:
return None, False
values.add(normalized)
if len(values) > 1:
return None, False
return (next(iter(values)) if values else None), True
def _state_db_row_identity(message: dict | None):
"""Return durable state.db provenance without treating WebUI ``id`` as it."""
identity, valid = _state_db_row_identity_details(message)
return identity if valid else None
def _stable_message_identity_details(message: dict | None) -> tuple[str | None, bool]:
"""Return a canonical stable message id and whether its aliases are valid."""
if not isinstance(message, dict):
return None, True
values = set()
for key in ("id", "message_id"):
if key not in message or message.get(key) in (None, ""):
continue
value = message.get(key)
if isinstance(value, bool):
return None, False
if isinstance(value, (int, str)):
normalized = str(value).strip()
elif isinstance(value, float):
normalized = str(value) if math.isfinite(value) else ""
else:
normalized = ""
if not normalized:
return None, False
values.add(normalized)
if len(values) > 1:
return None, False
return (next(iter(values)) if values else None), True
def _message_identity_compatible(target: dict | None, source: dict | None) -> bool:
"""Return whether private identities do not contradict one another."""
target_stable, target_stable_valid = _stable_message_identity_details(target)
source_stable, source_stable_valid = _stable_message_identity_details(source)
if not target_stable_valid or not source_stable_valid:
return False
if target_stable is not None and source_stable is not None and target_stable != source_stable:
return False
target_row_id, target_row_id_valid = _state_db_row_identity_details(target)
source_row_id, source_row_id_valid = _state_db_row_identity_details(source)
if not target_row_id_valid or not source_row_id_valid:
return False
if target_row_id is not None and source_row_id is not None and target_row_id != source_row_id:
return False
return _visible_content_compatible(target, source)
def _message_exact_timestamp(message: dict | None):
"""Return a numeric transcript timestamp, preserving ``0`` as valid."""
if not isinstance(message, dict):
return None
parsed, valid = _message_exact_timestamp_details(message)
return parsed if valid else None
def _message_exact_timestamp_details(message: dict | None) -> tuple[float | None, bool]:
"""Return ``(timestamp, valid)`` while distinguishing absent metadata."""
if not isinstance(message, dict):
return None, True
for key in ("timestamp", "_ts"):
if key not in message or message.get(key) in (None, ""):
continue
try:
parsed = float(message.get(key))
except (TypeError, ValueError):
return None, False
if not math.isfinite(parsed):
return None, False
return parsed, True
return None, True
def _message_sidecar_role(message: dict | None):
if not isinstance(message, dict):
return None
role = str(message.get("role") or "").strip().lower()
return role if role in {"user", "assistant"} else None
_WORKSPACE_PREFIX_RE = re.compile(r"^\s*\[Workspace(?:::v1)?:[^\]]+\]\s*")
def _message_visible_content_key(message: dict | None):
"""Return a canonical visible-content key for sidecar reconciliation."""
if not isinstance(message, dict):
return None
role = _message_sidecar_role(message)
if role is None:
return None
def _normalize(value, *, strip_prefix=False):
if isinstance(value, str):
text = value
if strip_prefix:
text = _WORKSPACE_PREFIX_RE.sub("", text, count=1)
return " ".join(text.split())
if isinstance(value, list):
return [_normalize(item, strip_prefix=strip_prefix) for item in value]
if isinstance(value, dict):
return {
key: _normalize(child, strip_prefix=False)
for key, child in value.items()
if key not in {"api_content", "_state_db_row_id", "_db_row_id", "state_db_row_id"}
}
return value
normalized = _normalize(message.get("content"), strip_prefix=(role == "user"))
try:
content = json.dumps(normalized, ensure_ascii=False, sort_keys=True, default=str)
except (TypeError, ValueError):
content = repr(normalized)
return role, content
def _visible_content_compatible(target: dict | None, source: dict | None) -> bool:
"""Return True only when role and visible content agree exactly."""
return _message_visible_content_key(target) == _message_visible_content_key(source)
def _copy_api_content_sidecar(target: dict | None, source: dict | None) -> bool:
"""Copy a non-empty internal sidecar without replacing an existing one."""
if not isinstance(target, dict) or not isinstance(source, dict):
return False
target_role = _message_sidecar_role(target)
source_role = _message_sidecar_role(source)
if target_role is None or target_role != source_role:
return False
if not _visible_content_compatible(target, source):
return False
if target.get("api_content") not in (None, ""):
return True
api_content = source.get("api_content")
if isinstance(api_content, str) and api_content:
target["api_content"] = api_content
return True
return False
def _reconcile_api_content_sidecars(sidecar_messages: list, state_messages: list) -> None:
"""Attach state.db ``api_content`` to the matching sidecar transcript rows.
Matching is intentionally stricter than visible transcript dedupe. A
unique stable message id is preferred, followed by a unique durable
state.db row id, exact timestamps, same-second timestamp drift, and finally
a mutually-unique role/content pair that may have mixed timestamp metadata.
Duplicate, malformed, or contradictory provenance is consumed without a
fallback guess. Repeated visible rows without unique provenance remain
unattached. If both sides already carry different non-empty provider
sidecars, neither is attached; the append-only merge preserves those rows
as distinct.
"""
sidecar = [message for message in sidecar_messages or () if isinstance(message, dict)]
state = [
message
for message in state_messages or ()
if isinstance(message, dict)
and _message_sidecar_role(message) is not None
and isinstance(message.get("api_content"), str)
and message.get("api_content")
]
if not sidecar or not state:
return
used_targets: set[int] = set()
used_sources: set[int] = set()
# Invalid provenance is never treated as absent metadata. Consume those
# rows up front so no weaker tier can attach an arbitrary provider sidecar.
for index, message in enumerate(sidecar):
_, row_id_valid = _state_db_row_identity_details(message)
_, timestamp_valid = _message_exact_timestamp_details(message)
_, stable_id_valid = _stable_message_identity_details(message)
if not row_id_valid or not timestamp_valid or not stable_id_valid:
used_targets.add(index)
for source_index, message in enumerate(state):
_, row_id_valid = _state_db_row_identity_details(message)
_, timestamp_valid = _message_exact_timestamp_details(message)
_, stable_id_valid = _stable_message_identity_details(message)
if not row_id_valid or not timestamp_valid or not stable_id_valid:
used_sources.add(source_index)
# 1. Stable message identity. This is the strongest WebUI-side identity
# and must run before timestamp/content fallbacks. Duplicate or conflicting
# aliases are consumed above and cannot fall through to a guess.
targets_by_stable_id = {}
state_by_stable_id = {}
for index, message in enumerate(sidecar):
if index in used_targets:
continue
stable_id, valid = _stable_message_identity_details(message)
if valid and stable_id is not None:
targets_by_stable_id.setdefault(stable_id, []).append(index)
for source_index, message in enumerate(state):
if source_index in used_sources:
continue
stable_id, valid = _stable_message_identity_details(message)
if valid and stable_id is not None:
state_by_stable_id.setdefault(stable_id, []).append(source_index)
stable_ids = list(targets_by_stable_id)
stable_ids.extend(
stable_id
for stable_id in state_by_stable_id
if stable_id not in targets_by_stable_id
)
duplicate_stable_target_indexes = set()
duplicate_stable_source_indexes = set()
for stable_id in stable_ids:
target_indexes = targets_by_stable_id.get(stable_id, [])
source_indexes = state_by_stable_id.get(stable_id, [])
if len(target_indexes) > 1 or len(source_indexes) > 1:
# Durable row ids are stronger provenance than a repeated stable
# message id. Defer this bucket's quarantine until after the row-id
# tier so unique row-id pairs can still resolve it one-to-one.
duplicate_stable_target_indexes.update(target_indexes)
duplicate_stable_source_indexes.update(source_indexes)
for stable_id in stable_ids:
target_indexes = [
index
for index in targets_by_stable_id.get(stable_id, ())
if index not in used_targets
]
source_indexes = [
index
for index in state_by_stable_id.get(stable_id, ())
if index not in used_sources
]
# A singleton stable id present on only one side remains eligible for
# stronger durable-row, timestamp, or content matching below.
if len(target_indexes) != 1 or len(source_indexes) != 1:
continue
target_index = target_indexes[0]
source_index = source_indexes[0]
if not _message_identity_compatible(sidecar[target_index], state[source_index]):
used_targets.add(target_index)
used_sources.add(source_index)
continue
target_api_content = _session_message_api_content_key(sidecar[target_index])
source_api_content = _session_message_api_content_key(state[source_index])
used_targets.add(target_index)
used_sources.add(source_index)
if (
target_api_content is not None
and source_api_content is not None
and target_api_content != source_api_content
):
continue
_copy_api_content_sidecar(sidecar[target_index], state[source_index])
# 2. Durable row identity. This path is unambiguous only when every
# alias agrees, each row id occurs once on each side, and visible content
# is compatible. Any duplicate/conflicting id is consumed and rejected;
# it must not fall through to a weaker tier. A unique row id with
# conflicting non-empty sidecars is left for the append-only merge, which
# can preserve both authoritative payloads without guessing.
targets_by_row_id = {}
for index, message in enumerate(sidecar):
if index in used_targets:
continue
row_id, valid = _state_db_row_identity_details(message)
if not valid:
used_targets.add(index)
elif row_id is not None:
targets_by_row_id.setdefault(row_id, []).append(index)
state_by_row_id = {}
for source_index, message in enumerate(state):
if source_index in used_sources:
continue
row_id, valid = _state_db_row_identity_details(message)
if not valid:
used_sources.add(source_index)
elif row_id is not None:
state_by_row_id.setdefault(row_id, []).append(source_index)
# Reject duplicate source buckets independently of the target map. A
# source-only duplicate id has no target loop iteration to consume it, so
# without this pre-pass it would descend into timestamp/metadata-free
# matching and attach one arbitrary provider sidecar.
for source_indexes in state_by_row_id.values():
if len(source_indexes) > 1:
used_sources.update(source_indexes)
for row_id, original_target_indexes in targets_by_row_id.items():
original_source_indexes = state_by_row_id.get(row_id, [])
target_indexes = [
index for index in original_target_indexes if index not in used_targets
]
source_indexes = [
index for index in original_source_indexes if index not in used_sources
]
if len(original_target_indexes) != 1 or len(original_source_indexes) != 1:
used_targets.update(target_indexes)
used_sources.update(source_indexes)
continue
if len(target_indexes) != 1 or len(source_indexes) != 1:
continue
target_index = target_indexes[0]
source_index = source_indexes[0]
if not _message_identity_compatible(sidecar[target_index], state[source_index]):
# Row ids never override contradictory stable ids, aliases, roles,
# or visible content. Quarantine both sides before fallback.
used_targets.add(target_index)
used_sources.add(source_index)
continue
target_api_content = _session_message_api_content_key(sidecar[target_index])
source_api_content = _session_message_api_content_key(state[source_index])
if (
target_api_content is not None
and source_api_content is not None
and target_api_content != source_api_content
):
# A durable row id is not sufficient to reconcile two already
# authoritative provider payloads. Quarantine both rows so a
# weaker timestamp/content tier cannot attach one payload to the
# other row.
used_targets.add(target_index)
used_sources.add(source_index)
continue
used_targets.add(target_index)
used_sources.add(source_index)
_copy_api_content_sidecar(sidecar[target_index], state[source_index])
# Any duplicate stable-id rows left unresolved by the authoritative row-id
# tier are ambiguous and must not fall through to timestamps/content.
used_targets.update(
index for index in duplicate_stable_target_indexes if index not in used_targets
)
used_sources.update(
index for index in duplicate_stable_source_indexes if index not in used_sources
)
# 3. Exact role + timestamp. Equal timestamps are not provenance; resolve
# only mutually-unique compatible pairs inside a bucket and never guess by
# list order when a relationship is ambiguous.
sidecar_by_exact_timestamp = {}
state_by_exact_timestamp = {}
for index, message in enumerate(sidecar):
if index in used_targets:
continue
role = _message_sidecar_role(message)
timestamp = _message_exact_timestamp(message)
_, valid = _state_db_row_identity_details(message)
if valid and role is not None and timestamp is not None:
sidecar_by_exact_timestamp.setdefault((role, timestamp), []).append(index)
for source_index, message in enumerate(state):
if source_index in used_sources:
continue
role = _message_sidecar_role(message)
timestamp = _message_exact_timestamp(message)
_, valid = _state_db_row_identity_details(message)
if valid and role is not None and timestamp is not None:
state_by_exact_timestamp.setdefault((role, timestamp), []).append((source_index, message))
for key, state_rows in state_by_exact_timestamp.items():
target_indexes = [
index
for index in sidecar_by_exact_timestamp.get(key, ())
if index not in used_targets
]
if not target_indexes:
# No exact-timestamp target is not an ambiguity: leave the source
# row available for the documented same-second/content tiers.
continue
# Use the same mutually-unique compatibility graph as the same-second
# tier. This resolves distinct content pairs inside a repeated exact
# timestamp bucket without guessing by list order.
compatible_targets_by_source = {}
for source_index, source_message in state_rows:
if source_index in used_sources:
continue
compatible_targets_by_source[source_index] = [
target_index
for target_index in target_indexes
if target_index not in used_targets
and _message_identity_compatible(sidecar[target_index], source_message)
and (
_session_message_api_content_key(sidecar[target_index]) is None
or _session_message_api_content_key(sidecar[target_index])
== _session_message_api_content_key(source_message)
)
]
compatible_sources_by_target = {}
for source_index, candidates in compatible_targets_by_source.items():
for target_index in candidates:
compatible_sources_by_target.setdefault(target_index, []).append(source_index)
for source_index, candidates in compatible_targets_by_source.items():
if len(candidates) != 1:
continue
target_index = candidates[0]
if len(compatible_sources_by_target.get(target_index, ())) != 1:
continue
if source_index in used_sources or target_index in used_targets:
continue
used_sources.add(source_index)
used_targets.add(target_index)
_copy_api_content_sidecar(sidecar[target_index], state[source_index])
# 4. Same-second sub-second drift. The sidecar JSON and state.db can
# record one logical turn with different fractional timestamps while still
# landing in the same wall-clock second. Do not use the truncated second
# as identity by itself: require a unique *compatible* visible row on each
# side. A candidate is compatible only when role + normalized visible
# content and all available provenance agree, and neither side carries a
# conflicting provider sidecar.
# This keeps repeated text, contradictory provenance, and conflicting wire
# payloads fail-closed instead of guessing by list order.
sidecar_by_same_second = {}
state_by_same_second = {}
for index, message in enumerate(sidecar):
if index in used_targets:
continue
role = _message_sidecar_role(message)
timestamp = _message_exact_timestamp(message)
row_id, valid = _state_db_row_identity_details(message)
if valid and role is not None and timestamp is not None:
second = _normalized_message_timestamp_for_key(timestamp)
sidecar_by_same_second.setdefault((role, second), []).append(index)
for source_index, message in enumerate(state):
if source_index in used_sources:
continue
role = _message_sidecar_role(message)
timestamp = _message_exact_timestamp(message)
row_id, valid = _state_db_row_identity_details(message)
if valid and role is not None and timestamp is not None:
second = _normalized_message_timestamp_for_key(timestamp)
state_by_same_second.setdefault((role, second), []).append(source_index)
for key, state_indexes in state_by_same_second.items():
target_indexes = [
index
for index in sidecar_by_same_second.get(key, ())
if index not in used_targets
]
if not target_indexes:
continue
compatible_targets_by_source = {}
for source_index in state_indexes:
if source_index in used_sources:
continue
source_message = state[source_index]
compatible_targets_by_source[source_index] = [
target_index
for target_index in target_indexes
if target_index not in used_targets
and _message_identity_compatible(sidecar[target_index], source_message)
and (
_session_message_api_content_key(sidecar[target_index]) is None
or _session_message_api_content_key(sidecar[target_index])
== _session_message_api_content_key(source_message)
)
]
compatible_sources_by_target = {}
for source_index, candidates in compatible_targets_by_source.items():
for target_index in candidates:
compatible_sources_by_target.setdefault(target_index, []).append(source_index)
# Accept only one-to-one compatible pairs. This deliberately does not
# zip a bucket: repeated same-text rows or equal timestamps remain
# distinct until durable provenance resolves them.
for source_index, candidates in compatible_targets_by_source.items():
if len(candidates) != 1:
continue
target_index = candidates[0]
if len(compatible_sources_by_target.get(target_index, ())) != 1:
continue
if source_index in used_sources or target_index in used_targets:
continue
used_sources.add(source_index)
used_targets.add(target_index)
_copy_api_content_sidecar(sidecar[target_index], state[source_index])
# 5. Role + visible-content fallback tolerates mixed timestamp metadata,
# but only when the candidate relationship is mutually unique. Repeated
# identical rows have no principled ordering and remain unattached.
sidecar_by_visible_content = {}
state_by_visible_content = {}
for index, message in enumerate(sidecar):
if index in used_targets:
continue
role = _message_sidecar_role(message)
_, row_id_valid = _state_db_row_identity_details(message)
_, stable_id_valid = _stable_message_identity_details(message)
_, timestamp_valid = _message_exact_timestamp_details(message)
if (
row_id_valid
and stable_id_valid
and timestamp_valid
and role is not None
and _message_visible_content_key(message) is not None
):
sidecar_by_visible_content.setdefault(_message_visible_content_key(message), []).append(index)
for source_index, message in enumerate(state):
if source_index in used_sources:
continue
role = _message_sidecar_role(message)
_, row_id_valid = _state_db_row_identity_details(message)
_, stable_id_valid = _stable_message_identity_details(message)
_, timestamp_valid = _message_exact_timestamp_details(message)
if (
row_id_valid
and stable_id_valid
and timestamp_valid
and role is not None
and _message_visible_content_key(message) is not None
):
state_by_visible_content.setdefault(_message_visible_content_key(message), []).append(source_index)
for visible_key, target_indexes in sidecar_by_visible_content.items():
source_indexes = state_by_visible_content.get(visible_key, [])
if len(target_indexes) != 1 or len(source_indexes) != 1:
continue
target_index = target_indexes[0]
source_index = source_indexes[0]
target_timestamp, target_timestamp_valid = _message_exact_timestamp_details(
sidecar[target_index]
)
source_timestamp, source_timestamp_valid = _message_exact_timestamp_details(
state[source_index]
)
if (
not target_timestamp_valid
or not source_timestamp_valid
or (target_timestamp is not None and source_timestamp is not None)
):
continue
if not _message_identity_compatible(sidecar[target_index], state[source_index]):
continue
target_api_content = _session_message_api_content_key(sidecar[target_index])
source_api_content = _session_message_api_content_key(state[source_index])
if (
target_api_content is not None
and source_api_content is not None
and target_api_content != source_api_content
):
continue
used_targets.add(target_index)
used_sources.add(source_index)
_copy_api_content_sidecar(sidecar[target_index], state[source_index])
def _session_message_dedup_key(msg: dict):
"""Like _session_message_merge_key but preserves full-precision timestamp.
@@ -8398,13 +9028,15 @@ def _session_message_dedup_key(msg: dict):
return ("non_dict", repr(msg))
message_identity = msg.get("id") or msg.get("message_id")
if message_identity:
return ("message_id", str(message_identity))
return _session_message_key_with_sidecar(
("message_id", str(message_identity)), msg
)
# Include tool_calls in the key so assistant messages that carry
# different tool invocations (but identical empty content/timestamp)
# are never collapsed into one. (#3346 regression)
_tc = msg.get("tool_calls")
_tc_key = json.dumps(_tc, sort_keys=True, default=str) if _tc else ""
return (
return _session_message_key_with_sidecar((
"legacy",
str(msg.get("role") or ""),
str(msg.get("content") or ""),
@@ -8412,7 +9044,7 @@ def _session_message_dedup_key(msg: dict):
str(msg.get("tool_call_id") or ""),
str(msg.get("tool_name") or msg.get("name") or ""),
_tc_key,
)
), msg)
def _normalized_session_message_content(msg: dict) -> str:
@@ -8450,12 +9082,12 @@ def _session_message_content_key(msg: dict):
content = " ".join(
_strip_workspace_prefix(content, include_legacy=True).split()
)
return (
return _session_message_key_with_sidecar((
role,
content,
str(msg.get("tool_call_id") or ""),
str(msg.get("tool_name") or msg.get("name") or ""),
)
), msg)
def _session_message_visible_key(msg: dict):
@@ -8467,11 +9099,11 @@ def _session_message_visible_key(msg: dict):
# ("assistant", "") and the merge treats state.db rows as replays.
_tc = msg.get("tool_calls")
_tc_key = json.dumps(_tc, sort_keys=True, default=str) if _tc else ""
return (
return _session_message_key_with_sidecar((
str(msg.get("role") or ""),
_normalized_session_message_content(msg),
_tc_key,
)
), msg)
def _build_visible_duplicate_lookup(visible_keys: set[tuple]) -> dict:
@@ -8496,6 +9128,7 @@ def _matching_visible_duplicate(visible_key: tuple, visible_keys: set[tuple], lo
return visible_key
role = visible_key[0]
content = visible_key[1] if len(visible_key) > 1 else ""
sidecar = visible_key[3] if len(visible_key) > 3 else None
if not content:
return None
if lookup is None:
@@ -8505,7 +9138,8 @@ def _matching_visible_duplicate(visible_key: tuple, visible_keys: set[tuple], lo
for existing_key in lookup.get("by_role", {}).get(role, []):
existing_role = existing_key[0]
existing_content = existing_key[1] if len(existing_key) > 1 else ""
if role != existing_role or not existing_content:
existing_sidecar = existing_key[3] if len(existing_key) > 3 else None
if role != existing_role or sidecar != existing_sidecar or not existing_content:
continue
# Exact visible-key equality was checked above. For very large payloads
# (tool logs / request dumps), Python-in substring and fuzzy-token
@@ -8582,6 +9216,12 @@ def state_db_delta_after_context(sidecar_context: list, state_messages: list) ->
if not sidecar_context or not state_messages:
return state_messages
# Attach trusted state.db sidecars before computing context keys. A missing
# sidecar is not a wildcard: once attached, it participates in every
# duplicate/prefix decision below and distinct provider bytes remain
# distinct rows.
_reconcile_api_content_sidecars(sidecar_context, state_messages)
# Recovered interrupted turns are special: the visible interruption marker
# is synthetic, so the recovered user turn should still count as a mirrored
# prefix when it is the actual aligned prefix row.
@@ -8836,6 +9476,46 @@ def merge_session_messages_append_only(
"""
sidecar_messages = list(sidecar_messages or [])
state_messages = list(state_messages or [])
_reconcile_api_content_sidecars(sidecar_messages, state_messages)
# The reconciler's quarantine sets are invocation-local. Mirror the
# identity-bucket guards here because this append-only merge has its own
# row-id fast path that must not re-admit a row isolated above.
sidecar_row_id_counts = collections.Counter()
state_row_id_counts = collections.Counter()
for message in sidecar_messages:
row_id, row_id_valid = _state_db_row_identity_details(message)
if row_id_valid and row_id is not None:
sidecar_row_id_counts[row_id] += 1
for message in state_messages:
row_id, row_id_valid = _state_db_row_identity_details(message)
if row_id_valid and row_id is not None:
state_row_id_counts[row_id] += 1
def _row_id_fast_path_allowed(existing, incoming) -> bool:
if not isinstance(existing, dict) or not isinstance(incoming, dict):
return False
existing_row_id, existing_row_id_valid = _state_db_row_identity_details(existing)
incoming_row_id, incoming_row_id_valid = _state_db_row_identity_details(incoming)
if (
not existing_row_id_valid
or not incoming_row_id_valid
or existing_row_id is None
or existing_row_id != incoming_row_id
):
return False
if (
sidecar_row_id_counts.get(existing_row_id, 0) > 1
or state_row_id_counts.get(existing_row_id, 0) > 1
):
return False
_, existing_timestamp_valid = _message_exact_timestamp_details(existing)
_, incoming_timestamp_valid = _message_exact_timestamp_details(incoming)
return (
existing_timestamp_valid
and incoming_timestamp_valid
and _message_identity_compatible(existing, incoming)
)
# Per-invocation cache keyed by message identity. Sidecar/state message objects
# are retained for this call, and this function does not mutate key-defining
# fields before each helper call.
@@ -8977,6 +9657,8 @@ def merge_session_messages_append_only(
merged_by_message_key = {}
merged_by_dedup_key = {}
merged_by_visible_key = {}
merged_by_row_id = {}
ambiguous_row_ids = set()
max_sidecar_timestamp = None
def _remember_merged_message(message):
@@ -8985,6 +9667,12 @@ def merge_session_messages_append_only(
merged_by_message_key.setdefault(_cached_message_key(message, "merge"), message)
merged_by_dedup_key.setdefault(_cached_message_key(message, "dedup"), message)
merged_by_visible_key.setdefault(_cached_message_key(message, "visible"), message)
row_id, row_id_valid = _state_db_row_identity_details(message)
if row_id_valid and row_id is not None:
if row_id in merged_by_row_id:
ambiguous_row_ids.add(row_id)
else:
merged_by_row_id[row_id] = message
for msg in sidecar_messages:
timestamp = _message_timestamp_as_float(msg)
@@ -9051,6 +9739,32 @@ def merge_session_messages_append_only(
# are caught by the dedup guard (#3346).
seen_dedup_keys.add(dedup_key)
continue
row_id, row_id_valid = _state_db_row_identity_details(msg)
row_id_sidecar_conflict = False
existing = (
merged_by_row_id.get(row_id)
if row_id_valid and row_id is not None
else None
)
if (
row_id_valid
and row_id is not None
and existing is not None
and row_id not in ambiguous_row_ids
and _row_id_fast_path_allowed(existing, msg)
):
existing_api_content = _session_message_api_content_key(existing)
incoming_api_content = _session_message_api_content_key(msg)
row_id_sidecar_conflict = (
existing_api_content is not None
and incoming_api_content is not None
and existing_api_content != incoming_api_content
)
if not row_id_sidecar_conflict:
if existing_api_content is None and incoming_api_content is not None:
_copy_api_content_sidecar(existing, msg)
_merge_session_display_metadata(existing, msg)
continue
# Skip rows ABOVE the watermark only while the sidecar has NOT advanced
# past the watermark. Because Session.save() no longer auto-clears the
# watermark, an unconditional `timestamp > watermark` skip would become
@@ -9185,6 +9899,7 @@ def merge_session_messages_append_only(
and max_sidecar_timestamp is not None
and timestamp is not None
and timestamp <= max_sidecar_timestamp
and not row_id_sidecar_conflict
):
# When a truncation watermark is active and the sidecar holds only
# the edited user checkpoint, state.db may contain an assistant/tool