Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Tests for background-review session-store isolation (hermes_state).
|
||||
|
||||
The background skill/memory review fork shares the parent's ``session_id`` for
|
||||
prompt-cache warmth. Without the ``_persist_disabled`` isolation it wrote its
|
||||
harness turn ("Review the conversation above and update the skill library…")
|
||||
plus its curator-mode reply into the user's REAL session, and the next live
|
||||
turn re-read that injected user message as a standing instruction — the agent
|
||||
"became" the curator and refused the actual task.
|
||||
|
||||
``_strip_background_review_harness`` is the load-on-read defense-in-depth that
|
||||
removes any such stray harness message (and the assistant reply that followed
|
||||
it) so a polluted session resumes clean.
|
||||
"""
|
||||
|
||||
from hermes_state import (
|
||||
_is_background_review_harness_message,
|
||||
_strip_background_review_harness,
|
||||
)
|
||||
|
||||
|
||||
class TestIsBackgroundReviewHarnessMessage:
|
||||
def test_matches_skill_review_prompt(self):
|
||||
msg = {"role": "user", "content": "Review the conversation above and update the skill library now."}
|
||||
assert _is_background_review_harness_message(msg) is True
|
||||
|
||||
def test_matches_memory_review_prompt(self):
|
||||
msg = {"role": "system", "content": "Review the conversation above and consider saving to memory."}
|
||||
assert _is_background_review_harness_message(msg) is True
|
||||
|
||||
|
||||
def test_ignores_normal_user_message(self):
|
||||
msg = {"role": "user", "content": "Please review my PR and update the changelog."}
|
||||
assert _is_background_review_harness_message(msg) is False
|
||||
|
||||
def test_ignores_assistant_role(self):
|
||||
# An assistant message that quotes the harness text is not itself a harness prompt.
|
||||
msg = {"role": "assistant", "content": "Review the conversation above and update the skill library"}
|
||||
assert _is_background_review_harness_message(msg) is False
|
||||
|
||||
|
||||
|
||||
|
||||
class TestStripBackgroundReviewHarness:
|
||||
def test_strips_harness_and_following_assistant_reply(self):
|
||||
messages = [
|
||||
{"role": "user", "content": "What's the weather?"},
|
||||
{"role": "assistant", "content": "It's sunny."},
|
||||
{"role": "user", "content": "Review the conversation above and update the skill library."},
|
||||
{"role": "assistant", "content": "Nothing to save."},
|
||||
{"role": "user", "content": "Thanks, now book a flight."},
|
||||
]
|
||||
out = _strip_background_review_harness(messages)
|
||||
contents = [m["content"] for m in out]
|
||||
assert contents == ["What's the weather?", "It's sunny.", "Thanks, now book a flight."]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TestGetMessagesAsConversationStripsHarness:
|
||||
"""The load-on-read wiring: get_messages_as_conversation must actually call
|
||||
_strip_background_review_harness, so a session polluted with stray harness
|
||||
rows resumes clean end-to-end (not just the pure helper in isolation)."""
|
||||
|
||||
def test_polluted_session_resumes_without_harness(self):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from hermes_state import SessionDB
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db = SessionDB(db_path=Path(tmp) / "t.db")
|
||||
try:
|
||||
db.create_session(session_id="s1", source="cli")
|
||||
db.append_message("s1", role="user", content="What's the weather?")
|
||||
db.append_message("s1", role="assistant", content="It's sunny.")
|
||||
# Stray background-review pollution written by an older build.
|
||||
db.append_message(
|
||||
"s1", role="user",
|
||||
content="Review the conversation above and update the skill library with anything useful.",
|
||||
)
|
||||
db.append_message("s1", role="assistant", content="I'll act as the curator now.")
|
||||
db.append_message("s1", role="user", content="Thanks, now book a flight.")
|
||||
|
||||
conv = db.get_messages_as_conversation("s1")
|
||||
contents = [m["content"] for m in conv]
|
||||
|
||||
# Harness user turn AND its curator-mode assistant reply are gone.
|
||||
assert not any(
|
||||
isinstance(c, str) and c.lstrip().startswith("Review the conversation above")
|
||||
for c in contents
|
||||
)
|
||||
assert "I'll act as the curator now." not in contents
|
||||
# Genuine turns survive in order.
|
||||
assert contents == ["What's the weather?", "It's sunny.", "Thanks, now book a flight."]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
class TestPersistDisabledHardStop:
|
||||
"""The isolation wiring: a _persist_disabled agent must never write to the
|
||||
session store via _flush_messages_to_session_db, even with a live db set."""
|
||||
|
||||
def test_flush_is_a_noop_when_persist_disabled(self):
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from hermes_state import SessionDB
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db = SessionDB(db_path=Path(tmp) / "t.db")
|
||||
try:
|
||||
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
|
||||
from run_agent import AIAgent
|
||||
agent = AIAgent(
|
||||
api_key="test-key",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model="test/model",
|
||||
quiet_mode=True,
|
||||
session_db=db,
|
||||
session_id="s-review",
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
agent._ensure_db_session()
|
||||
agent._persist_disabled = True
|
||||
|
||||
agent._flush_messages_to_session_db(
|
||||
[{"role": "user", "content": "Review the conversation above and update the skill library."},
|
||||
{"role": "assistant", "content": "curator reply"}],
|
||||
[],
|
||||
)
|
||||
|
||||
# Nothing written: the hard-stop fired before any append.
|
||||
assert db.get_messages("s-review") == []
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user