test(agent): add tests to ensure goal state does not leak across sessions

This commit is contained in:
Xubin Ren 2026-05-16 11:14:56 +00:00
parent f97b960433
commit 387724c355
2 changed files with 78 additions and 0 deletions

View File

@ -299,6 +299,31 @@ class TestBuildMessages:
assert "Goal (active):" in user_msg
assert "Finish docs migration." in user_msg
def test_goal_state_does_not_leak_without_session_metadata(self, tmp_path):
builder = _builder(tmp_path)
other_session_meta = {
GOAL_STATE_KEY: {"status": "active", "objective": "Other chat goal."},
}
with_goal = builder.build_messages(
[],
"hi",
channel="websocket",
chat_id="chat-a",
session_metadata=other_session_meta,
)
without_goal = builder.build_messages(
[],
"hi",
channel="websocket",
chat_id="chat-b",
session_metadata={},
)
assert "Other chat goal." in str(with_goal[-1]["content"])
assert "Other chat goal." not in str(without_goal[-1]["content"])
assert "Goal (active):" not in str(without_goal[-1]["content"])
def test_consecutive_same_role_merged(self, tmp_path):
builder = _builder(tmp_path)
history = [{"role": "user", "content": "previous user message"}]

View File

@ -9,6 +9,7 @@ from nanobot.agent.loop import AgentLoop
from nanobot.bus.events import InboundMessage
from nanobot.bus.queue import MessageBus
from nanobot.providers.base import LLMResponse
from nanobot.session.goal_state import GOAL_STATE_KEY
from nanobot.session.manager import Session
from nanobot.utils.webui_titles import (
WEBUI_SESSION_METADATA_KEY,
@ -493,6 +494,58 @@ async def test_process_message_uses_context_chat_id_for_runtime_prompt(tmp_path:
assert loop._run_agent_loop.call_args.kwargs["chat_id"] == "thread-777"
@pytest.mark.asyncio
async def test_process_message_uses_explicit_session_metadata_for_goal_context(
tmp_path: Path,
) -> None:
loop = _make_full_loop(tmp_path)
loop.consolidator.maybe_consolidate_by_tokens = AsyncMock(return_value=False) # type: ignore[method-assign]
chat_session = loop.sessions.get_or_create("websocket:chat-with-goal")
chat_session.metadata[GOAL_STATE_KEY] = {
"status": "active",
"objective": "This chat goal must not leak into heartbeat.",
}
loop.sessions.save(chat_session)
system_session = loop.sessions.get_or_create("heartbeat")
system_session.metadata = {}
loop.sessions.save(system_session)
loop.context.build_messages = MagicMock( # type: ignore[method-assign]
return_value=[
{"role": "system", "content": "system"},
{"role": "user", "content": "runtime + heartbeat"},
]
)
loop._run_agent_loop = AsyncMock(return_value=( # type: ignore[method-assign]
"ok",
[],
[
{"role": "system", "content": "system"},
{"role": "user", "content": "runtime + heartbeat"},
{"role": "assistant", "content": "ok"},
],
"stop",
False,
))
result = await loop._process_message(
InboundMessage(
channel="websocket",
sender_id="heartbeat",
chat_id="chat-with-goal",
content="heartbeat work",
),
session_key="heartbeat",
)
assert result is not None
assert result.content == "ok"
kwargs = loop.context.build_messages.call_args.kwargs
assert kwargs["chat_id"] == "chat-with-goal"
assert kwargs["session_metadata"] is system_session.metadata
assert GOAL_STATE_KEY not in kwargs["session_metadata"]
def test_set_tool_context_uses_effective_key_for_spawn_tool(tmp_path: Path) -> None:
loop = _make_full_loop(tmp_path)
spawn_tool = loop.tools.get("spawn")