Files
hermes-agent/tests/agent/test_codex_echo_ownership.py
Teknium a9ef4a7625 fix(codex): keep transport echoes out of durable user history
Port the exact submitted-wire-text ownership boundary from #93546 onto
current topical runtime code. Do not add the candidate's mocked-result
fallback or storage-level content deduplication. Preserve later distinct
and identical user events, separate identical accepted turns, and keyless
inputs. Add two regression invariants and offline subprocess-wire A/B.

Local wire A/B: 4/8 control matrix passing on base, 8/8 after.
Broader tests queued behind campaign lock; not ready for merge.

Refs #104653
Original diagnosis: @gitszabolcs (#38254)
Original implementation: #43127, submitted by @vashkartik
Focused salvage and wire-text correction: @fancyboi999 (#93546)
Current-main carry-forward considered: #104698

Co-authored-by: Xinmin Zeng <135568692+fancyboi999@users.noreply.github.com>
Co-authored-by: VECTOR <vector.hq@outlook.com>
2026-09-07 08:09:57 -07:00

51 lines
2.6 KiB
Python

"""The accepted input owns its row; transport echoes do not own new rows."""
import threading
from types import SimpleNamespace
import pytest
from agent.codex_runtime import _persist_projected_messages
from agent.message_metadata import append_message
from agent.session_persistence import SessionPersistenceMixin
from agent.transports.codex_event_projector import CodexEventProjector
from hermes_state import SessionDB
@pytest.mark.parametrize("platform_id", ["2146", None])
@pytest.mark.parametrize("projection", ["echo", "assistant_only", "different", "later_equal"])
def test_only_submitted_leading_echo_is_excluded(tmp_path, platform_id, projection):
db = SessionDB(tmp_path / "state.db")
try:
db.create_session(session_id="echo", source="telegram", model="codex")
agent = SessionPersistenceMixin()
agent.session_id = "echo"
agent._session_db = db
agent._session_db_created = True
agent._last_flushed_db_idx = 0
agent._session_persist_lock = threading.RLock()
messages = []
# Two independently accepted identical inputs must both survive.
for turn_index in range(2):
append_message(messages, {"role": "user", "content": "accepted", "platform_message_id": platform_id})
assert agent._flush_messages_to_session_db(messages)
projector = CodexEventProjector()
items = []
if projection != "assistant_only":
items.append({"type": "userMessage", "id": f"u{turn_index}", "content": [
{"type": "text", "text": "different" if projection == "different" else "wire caption"}]})
items.append({"type": "agentMessage", "id": f"a{turn_index}", "text": "reply"})
if projection == "later_equal":
items.append({"type": "userMessage", "id": f"s{turn_index}", "content": [
{"type": "text", "text": "wire caption"}]})
projected = []
for item in items:
projected.extend(projector.project({"method": "item/completed", "params": {"item": item}}).messages)
_persist_projected_messages(agent, SimpleNamespace(
projected_messages=projected, submitted_user_text="wire caption"), messages)
assert agent._flush_messages_to_session_db(messages)
users = [row["content"] for row in db.get_messages_as_conversation("echo") if row["role"] == "user"]
extra = {"different": ["different"], "later_equal": ["wire caption"]}.get(projection, [])
assert users == (["accepted"] + extra) * 2
finally:
db.close()