b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
111 lines
4.2 KiB
Python
111 lines
4.2 KiB
Python
"""Verification-loop synthetic scaffolding must never reach durable session state.
|
|
|
|
verify_on_stop / pre_verify append a synthetic assistant "done" plus a synthetic
|
|
user nudge to keep the agent going one more turn before it can claim completion.
|
|
These messages exist only to drive the loop; persisting them poisons the resumed
|
|
transcript and breaks prompt-prefix cache reuse on later turns (#55733).
|
|
|
|
Both persistence sinks (SQLite flush + JSON snapshot) route through the single
|
|
``_is_ephemeral_scaffolding`` chokepoint, which is driven by
|
|
``_EPHEMERAL_SCAFFOLDING_FLAGS``. These tests assert that the verification-loop
|
|
flags are registered there and that both sinks drop the flagged messages while
|
|
keeping the real conversation.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
|
|
def _fresh_run_agent(hermes_home):
|
|
for mod in list(sys.modules):
|
|
if mod == "run_agent" or mod.startswith("agent.") or mod.startswith("tools.") or mod.startswith("hermes_"):
|
|
del sys.modules[mod]
|
|
import run_agent # noqa: F401
|
|
return sys.modules["run_agent"]
|
|
|
|
|
|
def test_verification_flags_registered_as_ephemeral(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
|
ra = _fresh_run_agent(tmp_path)
|
|
|
|
assert "_verification_stop_synthetic" in ra._EPHEMERAL_SCAFFOLDING_FLAGS
|
|
assert "_pre_verify_synthetic" in ra._EPHEMERAL_SCAFFOLDING_FLAGS
|
|
|
|
# The central classifier drives both persistence sinks.
|
|
assert ra._is_ephemeral_scaffolding(
|
|
{"role": "assistant", "content": "done", "_verification_stop_synthetic": True}
|
|
)
|
|
assert ra._is_ephemeral_scaffolding(
|
|
{"role": "user", "content": "[System: run tests]", "_pre_verify_synthetic": True}
|
|
)
|
|
# Real messages are not scaffolding.
|
|
assert not ra._is_ephemeral_scaffolding({"role": "user", "content": "hi"})
|
|
|
|
|
|
def _make_agent(ra, session_id, tmp_path):
|
|
agent = ra.AIAgent(
|
|
session_id=session_id,
|
|
api_key="test-key",
|
|
base_url="http://127.0.0.1:8000/v1",
|
|
provider="openai-compat",
|
|
model="test-model",
|
|
quiet_mode=True,
|
|
skip_context_files=True,
|
|
skip_memory=True,
|
|
)
|
|
agent._session_db = MagicMock()
|
|
agent._session_db_created = True
|
|
agent._session_json_enabled = True
|
|
agent.logs_dir = tmp_path / "logs"
|
|
agent.logs_dir.mkdir(parents=True, exist_ok=True)
|
|
return agent
|
|
|
|
|
|
def test_db_flush_drops_verification_scaffolding(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
|
ra = _fresh_run_agent(tmp_path)
|
|
agent = _make_agent(ra, "sess_db", tmp_path)
|
|
|
|
messages = [
|
|
{"role": "user", "content": "hi"},
|
|
{"role": "assistant", "content": "premature done", "_verification_stop_synthetic": True},
|
|
{"role": "user", "content": "[System: run tests]", "_verification_stop_synthetic": True},
|
|
{"role": "assistant", "content": "verified and clean"},
|
|
]
|
|
|
|
agent._flush_messages_to_session_db(messages, conversation_history=[])
|
|
|
|
persisted = [
|
|
kwargs.get("content")
|
|
for _args, kwargs in agent._session_db.append_message.call_args_list
|
|
]
|
|
assert "hi" in persisted
|
|
assert "verified and clean" in persisted
|
|
assert "premature done" not in persisted
|
|
assert "[System: run tests]" not in persisted
|
|
|
|
|
|
def test_json_log_drops_verification_scaffolding(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
|
ra = _fresh_run_agent(tmp_path)
|
|
agent = _make_agent(ra, "sess_json", tmp_path)
|
|
|
|
messages = [
|
|
{"role": "user", "content": "hi"},
|
|
{"role": "assistant", "content": "premature done", "_pre_verify_synthetic": True},
|
|
{"role": "user", "content": "[System: run tests]", "_pre_verify_synthetic": True},
|
|
{"role": "assistant", "content": "verified and clean"},
|
|
]
|
|
|
|
agent._save_session_log(messages)
|
|
|
|
log_file = agent.logs_dir / "session_sess_json.json"
|
|
assert log_file.exists()
|
|
data = json.loads(log_file.read_text(encoding="utf-8"))
|
|
contents = [m.get("content") for m in data["messages"]]
|
|
assert contents == ["hi", "verified and clean"]
|
|
assert all(not m.get("_pre_verify_synthetic") for m in data["messages"])
|