Files
hermes-agent/evals/codex_echo/probe.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

62 lines
3.1 KiB
Python

"""Run from the checkout: PYTHONPATH=. python evals/codex_echo/probe.py.
Real subprocess pipes, protocol parser/projector, persistence and SQLite replay.
The peer is an offline fixture, not a Codex/provider validation.
"""
import json
import os
from pathlib import Path
import tempfile
import threading
def main():
with tempfile.TemporaryDirectory(prefix="hermes-echo-") as tmp:
os.environ.update(HOME=tmp, HERMES_HOME=tmp, CODEX_HOME=tmp, HERMES_DISABLE_PLUGINS="1")
from agent import codex_runtime
from agent.message_metadata import append_message
from agent.session_persistence import SessionPersistenceMixin
from agent.transports.codex_app_server_session import CodexAppServerSession
from hermes_state import SessionDB
observations = []
for mode in ["echo", "assistant_only", "different", "later_equal"]:
for rich in [False, True]:
os.environ["ECHO_FIXTURE_MODE"] = mode
sid = f"{mode}-{rich}"
db = SessionDB(Path(tmp) / f"{sid}.db")
db.create_session(session_id=sid, source="telegram", model="fixture")
agent = SessionPersistenceMixin()
agent.session_id = sid
agent._session_db = db
agent._session_db_created = True
agent._last_flushed_db_idx = 0
agent._session_persist_lock = threading.RLock()
session = CodexAppServerSession(cwd=tmp, codex_home=tmp,
codex_bin=str(Path(__file__).with_name("fixture_server.py").resolve()))
messages = []
wire_input = [{"type": "text", "text": "caption"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,abc"}}] if rich else "caption"
try:
for accepted in range(2):
append_message(messages, {"role": "user", "content": "caption",
"platform_message_id": str(accepted) if not rich else None})
assert agent._flush_messages_to_session_db(messages)
turn = session.run_turn(wire_input, turn_timeout=10)
assert turn.error is None, turn.error
assert turn.final_text == "fixture reply", turn
codex_runtime._persist_projected_messages(agent, turn, messages)
assert agent._flush_messages_to_session_db(messages)
users = [m["content"] for m in db.get_messages_as_conversation(sid) if m["role"] == "user"]
expected_count = 4 if mode in {"different", "later_equal"} else 2
observations.append({"mode": mode, "rich_keyless": rich, "users": users,
"expected_user_rows": expected_count, "user_rows": len(users), "pass": len(users) == expected_count})
finally:
session.close()
db.close()
print(json.dumps({"module": codex_runtime.__file__, "cases": observations}, indent=2))
if __name__ == "__main__":
main()