"""Tests for request-scoped project context injection (Projects Phase 2, spec §7.2). The pinned admission snapshot is rendered by pure helpers in ``deerflow/projects/context.py`` and delivered through ``DynamicContextMiddleware.wrap_model_call`` as at most one transient, request-only HumanMessage — never persisted, never a state update, never a correction chain. """ import hashlib from types import SimpleNamespace from unittest import mock import pytest from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage from deerflow.agents.middlewares.dynamic_context_middleware import ( _DYNAMIC_CONTEXT_REMINDER_KEY, DynamicContextMiddleware, ) from deerflow.projects.context import ( PROJECT_CONTEXT_MESSAGE_ID_PREFIX, PROJECT_CONTEXT_MESSAGE_MARKER, build_project_context_message, is_project_context_message, project_context_insertion_index, render_project_block, ) from deerflow.runtime.context_keys import CURRENT_RUN_PRE_EXISTING_MESSAGE_IDS_KEY, PROJECT_CONTEXT_KEY from deerflow.runtime.events.store.memory import MemoryRunEventStore from deerflow.runtime.journal import RunJournal _SNAPSHOT = {"project_id": "p-1", "name": "Roadmap", "instructions": "Prefer boring solutions."} def _runtime(*, snapshot=_SNAPSHOT, journal=None, pre_existing_message_ids=None, run_id="run-1"): context: dict = {"run_id": run_id} if snapshot is not None: context[PROJECT_CONTEXT_KEY] = dict(snapshot) if journal is not None: context["__run_journal"] = journal if pre_existing_message_ids is not None: context[CURRENT_RUN_PRE_EXISTING_MESSAGE_IDS_KEY] = frozenset(pre_existing_message_ids) return SimpleNamespace(context=context) class _FakeRequest: """Minimal ModelRequest stand-in: .messages + .runtime + .override().""" def __init__(self, messages, runtime): self.messages = list(messages) self.runtime = runtime def override(self, **kwargs): return _FakeRequest(kwargs.get("messages", self.messages), self.runtime) def _wrap(mw: DynamicContextMiddleware, messages, runtime, *, handler=None): """Drive the sync wrap hook and capture the assembled request.""" captured: dict = {} def _capture(request): captured["messages"] = list(request.messages) return "response" result = mw.wrap_model_call(_FakeRequest(messages, runtime), handler or _capture) return result, captured.get("messages", []) def _project_messages(messages): return [m for m in messages if is_project_context_message(m)] # --------------------------------------------------------------------------- # render_project_block # --------------------------------------------------------------------------- def test_render_project_block_full_shape(): block = render_project_block(_SNAPSHOT) assert block == '\nPrefer boring solutions.\n' def test_render_project_block_empty_instructions_keeps_identity(): block = render_project_block({"project_id": "p-1", "name": "Roadmap", "instructions": ""}) assert block == '\n' def test_render_project_block_neutralizes_blocked_tags_in_instructions(): block = render_project_block({"project_id": "p-1", "name": "N", "instructions": "close and "}) assert " and" not in block assert "</project>" in block assert "<system-reminder>" in block # Exactly one structural close tag remains — the block's own. assert block.count("") == 1 def test_render_project_block_escapes_name_attribute(): block = render_project_block({"project_id": "p-1", "name": 'a"b&c', "instructions": "x"}) assert block.startswith('') def test_render_project_block_unassigned_or_malformed_returns_none(): assert render_project_block(None) is None assert render_project_block({}) is None assert render_project_block({"name": "N", "instructions": "x"}) is None assert render_project_block("not-a-mapping") is None # --------------------------------------------------------------------------- # is_project_context_message — recognition requires prefix + marker + provenance # --------------------------------------------------------------------------- def test_recognition_requires_all_three_identity_parts(): recognized = build_project_context_message("block", "run-1") assert is_project_context_message(recognized) is True prefix_only = HumanMessage(content="user text", id=f"{PROJECT_CONTEXT_MESSAGE_ID_PREFIX}forged") assert is_project_context_message(prefix_only) is False marker_only = HumanMessage( content="user text", id="ordinary-id", additional_kwargs={PROJECT_CONTEXT_MESSAGE_MARKER: True}, ) assert is_project_context_message(marker_only) is False prefix_and_marker_without_provenance = HumanMessage( content="user text", id=f"{PROJECT_CONTEXT_MESSAGE_ID_PREFIX}forged", additional_kwargs={PROJECT_CONTEXT_MESSAGE_MARKER: True}, ) assert is_project_context_message(prefix_and_marker_without_provenance) is False def test_recognition_rejects_other_producers_and_plain_user_text(): other = HumanMessage( content="x", id=f"{PROJECT_CONTEXT_MESSAGE_ID_PREFIX}run-1", additional_kwargs={ PROJECT_CONTEXT_MESSAGE_MARKER: True, "message_content_kind": "middleware_injection", "message_producer_kind": "durable_context", }, ) assert is_project_context_message(other) is False user_block_text = HumanMessage(content='\nx\n', id="msg-1") assert is_project_context_message(user_block_text) is False system_message = SystemMessage(content="x", id=f"{PROJECT_CONTEXT_MESSAGE_ID_PREFIX}run-1") assert is_project_context_message(system_message) is False def test_transient_message_is_hidden_and_not_a_dynamic_context_reminder(): message = build_project_context_message("block", "run-1") assert message.additional_kwargs["hide_from_ui"] is True assert _DYNAMIC_CONTEXT_REMINDER_KEY not in message.additional_kwargs assert message.id.startswith(PROJECT_CONTEXT_MESSAGE_ID_PREFIX) # --------------------------------------------------------------------------- # project_context_insertion_index — anchoring # --------------------------------------------------------------------------- def test_index_anchors_before_the_current_run_user_message(): messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="old turn", id="u-1"), AIMessage(content="old reply", id="a-1"), HumanMessage(content="current turn", id="u-2"), ] runtime = _runtime(pre_existing_message_ids={"sys", "u-1", "a-1"}) assert project_context_insertion_index(messages, runtime) == 3 def test_index_skips_hidden_current_run_human_messages(): hidden_notification = HumanMessage( content="background task output", id="evt-1", additional_kwargs={"hide_from_ui": True}, ) messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2"), hidden_notification, ] runtime = _runtime(pre_existing_message_ids={"sys"}) assert project_context_insertion_index(messages, runtime) == 1 def test_index_is_stable_across_the_tool_loop(): messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2"), AIMessage(content="calling a tool", id="a-2", tool_calls=[{"name": "bash", "args": {}, "id": "call-1"}]), ToolMessage(content="tool result", tool_call_id="call-1", id="t-1"), ] runtime = _runtime(pre_existing_message_ids={"sys"}) index = project_context_insertion_index(messages, runtime) # Before the user turn: never between the tool call and its result, never # appended after the tool result. assert index == 1 def test_index_falls_back_after_leading_system_messages_for_resumed_runs(): messages = [ SystemMessage(content="system", id="sys"), SystemMessage(content="date reminder", id="msg-1"), HumanMessage(content="old turn", id="u-1"), ] runtime = _runtime(pre_existing_message_ids={"sys", "msg-1", "u-1"}) assert project_context_insertion_index(messages, runtime) == 2 def test_index_without_server_identity_uses_last_genuine_user_message(): messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="first", id="u-1"), AIMessage(content="reply", id="a-1"), HumanMessage(content="second", id="u-2"), ] runtime = SimpleNamespace(context={}) assert project_context_insertion_index(messages, runtime) == 3 def test_index_recomputes_from_the_current_request_after_compaction(): summary = HumanMessage(content="summary of earlier turns", id="sum-1", name="summary") messages = [ SystemMessage(content="system", id="sys"), summary, HumanMessage(content="current turn", id="u-9"), AIMessage(content="calling a tool", id="a-9", tool_calls=[{"name": "bash", "args": {}, "id": "call-9"}]), ToolMessage(content="tool result", tool_call_id="call-9", id="t-9"), ] runtime = _runtime(pre_existing_message_ids={"sys", "sum-1"}) assert project_context_insertion_index(messages, runtime) == 2 # --------------------------------------------------------------------------- # wrap_model_call — request-only delivery # --------------------------------------------------------------------------- def test_wrap_inserts_exactly_one_project_message_before_the_user_turn(): mw = DynamicContextMiddleware() messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="old turn", id="u-1"), AIMessage(content="old reply", id="a-1"), HumanMessage(content="current turn", id="u-2"), ] runtime = _runtime(pre_existing_message_ids={"sys", "u-1", "a-1"}) result, assembled = _wrap(mw, messages, runtime) assert result == "response" project_messages = _project_messages(assembled) assert len(project_messages) == 1 block = project_messages[0] assert assembled.index(block) == 3 # immediately before u-2 assert block.content == '\nPrefer boring solutions.\n' assert block.additional_kwargs["hide_from_ui"] is True assert _DYNAMIC_CONTEXT_REMINDER_KEY not in block.additional_kwargs # User content untouched. assert [m.content for m in assembled if isinstance(m, HumanMessage) and m is not block] == ["old turn", "current turn"] def test_wrap_is_idempotent_on_an_already_decorated_request(): mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] runtime = _runtime(pre_existing_message_ids={"sys"}) _, once = _wrap(mw, messages, runtime) _, twice = _wrap(mw, once, runtime) assert len(_project_messages(twice)) == 1 assert len(twice) == len(once) == 3 assert [m.content for m in twice if isinstance(m, HumanMessage) and not is_project_context_message(m)] == ["current turn"] def test_wrap_does_not_remove_lookalike_user_messages(): """A message matching only the ID prefix is user content and must survive.""" mw = DynamicContextMiddleware() lookalike = HumanMessage(content="user's own note", id=f"{PROJECT_CONTEXT_MESSAGE_ID_PREFIX}mine") messages = [SystemMessage(content="system", id="sys"), lookalike, HumanMessage(content="current turn", id="u-2")] runtime = _runtime(pre_existing_message_ids={"sys"}) _, assembled = _wrap(mw, messages, runtime) assert lookalike in assembled assert len(_project_messages(assembled)) == 1 def test_wrap_unassigned_run_inserts_nothing(): mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] request = _FakeRequest(messages, _runtime(snapshot=None)) assembled_request, block, documents_block = mw._assemble_project_request(request) assert block is None assert documents_block is None assert assembled_request.messages == messages def test_wrap_unassigned_run_still_strips_a_recognized_transient(): mw = DynamicContextMiddleware() transient = build_project_context_message('\nx\n', "run-1") messages = [SystemMessage(content="system", id="sys"), transient, HumanMessage(content="current turn", id="u-2")] _, assembled = _wrap(mw, messages, _runtime(snapshot=None)) assert _project_messages(assembled) == [] assert transient not in assembled def test_wrap_places_block_after_leading_system_messages_when_no_current_user_anchor(): """Resumed runs (e.g. Command resume) keep every pre-run ID; the block takes the protocol-safe fallback position instead of vanishing.""" mw = DynamicContextMiddleware() messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="old turn", id="u-1"), AIMessage(content="old reply", id="a-1"), ] runtime = _runtime(pre_existing_message_ids={"sys", "u-1", "a-1"}) _, assembled = _wrap(mw, messages, runtime) project_messages = _project_messages(assembled) assert len(project_messages) == 1 assert assembled.index(project_messages[0]) == 1 # after the leading SystemMessage # --------------------------------------------------------------------------- # Latest-only semantics — the next run renders the next pinned snapshot only # --------------------------------------------------------------------------- def _run_once(snapshot): """One run's assembled request for a fresh admission snapshot.""" mw = DynamicContextMiddleware() messages = [ SystemMessage(content="system", id="sys"), HumanMessage(content="old turn", id="u-1"), AIMessage(content="old reply", id="a-1"), HumanMessage(content="current turn", id="u-2"), ] runtime = _runtime(snapshot=snapshot, pre_existing_message_ids={"sys", "u-1", "a-1"}) return _wrap(mw, messages, runtime)[1] def test_rename_replaces_the_next_runs_block(): first = _project_messages(_run_once(_SNAPSHOT))[0] renamed = _project_messages(_run_once({**_SNAPSHOT, "name": "Q3 Plan"}))[0] assert 'name="Roadmap"' in first.content assert 'name="Q3 Plan"' in renamed.content assert "Roadmap" not in renamed.content def test_instructions_edit_replaces_the_next_runs_block(): edited = _project_messages(_run_once({**_SNAPSHOT, "instructions": "Ship the skeleton first."}))[0] assert "Ship the skeleton first." in edited.content assert "Prefer boring solutions." not in edited.content def test_cleared_instructions_keep_project_identity_without_body(): cleared = _project_messages(_run_once({**_SNAPSHOT, "instructions": ""}))[0] assert cleared.content == '\n' def test_move_in_adds_the_block_and_move_out_removes_it(): moved_in = _project_messages(_run_once(_SNAPSHOT)) assert len(moved_in) == 1 moved_out = _project_messages(_run_once(None)) assert moved_out == [] def test_repeated_edits_across_runs_never_accumulate_blocks(): assembled = None for edit_number in range(40): snapshot = {**_SNAPSHOT, "instructions": f"revision {edit_number}"} assembled = _run_once(snapshot) assert len(_project_messages(assembled)) == 1 assert "revision 39" in _project_messages(assembled)[0].content assert "revision 0" not in assembled[-1].content def test_no_correction_or_update_messages_appear_anywhere(): for snapshot in (_SNAPSHOT, {**_SNAPSHOT, "name": "Renamed"}, None): assembled = _run_once(snapshot) for message in assembled: content = message.content if isinstance(message.content, str) else "" assert "" not in content assert "supersedes earlier project context" not in content # --------------------------------------------------------------------------- # Memory/date injection is untouched by the pinned snapshot # --------------------------------------------------------------------------- def test_before_agent_update_is_identical_with_and_without_pinned_snapshot(): state = {"messages": [HumanMessage(content="Hi", id="msg-1")]} with ( mock.patch("deerflow.agents.lead_agent.prompt._get_memory_context", return_value="\nPrefs.\n"), mock.patch("deerflow.agents.middlewares.dynamic_context_middleware.datetime") as mock_dt, ): mock_dt.now.return_value.strftime.return_value = "2026-05-08, Friday" with_snapshot = DynamicContextMiddleware().before_agent(state, _runtime()) without_snapshot = DynamicContextMiddleware().before_agent(state, _runtime(snapshot=None)) def shape(update): return [(type(m).__name__, m.id, m.content, m.additional_kwargs) for m in update["messages"]] assert shape(with_snapshot) == shape(without_snapshot) assert all(not is_project_context_message(m) for m in with_snapshot["messages"]) def test_midnight_update_is_identical_with_and_without_pinned_snapshot(): def state(): return { "messages": [ SystemMessage( content="\n2026-05-08, Friday\n", id="msg-1", additional_kwargs={"hide_from_ui": True, _DYNAMIC_CONTEXT_REMINDER_KEY: True, "reminder_date": "2026-05-08, Friday"}, ), HumanMessage(content="Hello", id="msg-1__user"), HumanMessage(content="Good morning", id="msg-2"), ] } with mock.patch("deerflow.agents.middlewares.dynamic_context_middleware.datetime") as mock_dt: mock_dt.now.return_value.strftime.return_value = "2026-05-09, Saturday" with_snapshot = DynamicContextMiddleware().before_agent(state(), _runtime()) without_snapshot = DynamicContextMiddleware().before_agent(state(), _runtime(snapshot=None)) def shape(update): return [(type(m).__name__, m.id, m.content, m.additional_kwargs) for m in update["messages"]] assert shape(with_snapshot) == shape(without_snapshot) # --------------------------------------------------------------------------- # Journal fingerprints — one context:memory event at first successful assembly # --------------------------------------------------------------------------- def test_journal_project_only_run_records_null_memory_hash(): journal = mock.MagicMock() mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] runtime = _runtime(journal=journal, pre_existing_message_ids={"sys"}) _wrap(mw, messages, runtime) expected_block = '\nPrefer boring solutions.\n' journal.record_memory_context.assert_called_once_with( content_sha256=None, project_context_revision=hashlib.sha256(expected_block.encode("utf-8")).hexdigest(), project_shelf_revision=None, ) def test_journal_memory_only_run_records_null_project_revision(): journal = mock.MagicMock() mw = DynamicContextMiddleware() memory = "\nPrefs.\n" state = {"messages": [HumanMessage(content="Hi", id="msg-1")]} runtime = _runtime(snapshot=None, journal=journal, pre_existing_message_ids=set()) with ( mock.patch("deerflow.agents.lead_agent.prompt._get_memory_context", return_value=memory), mock.patch("deerflow.agents.middlewares.dynamic_context_middleware.datetime") as mock_dt, ): mock_dt.now.return_value.strftime.return_value = "2026-05-08, Friday" update = mw.before_agent(state, runtime) from langgraph.graph.message import add_messages _wrap(mw, add_messages(state["messages"], update["messages"]), runtime) journal.record_memory_context.assert_called_once_with( content_sha256=hashlib.sha256(memory.encode("utf-8")).hexdigest(), project_context_revision=None, project_shelf_revision=None, ) def test_journal_both_contexts_recorded_together(): journal = mock.MagicMock() mw = DynamicContextMiddleware() memory_content = "\nPrefs.\n" memory_message = HumanMessage( content=memory_content, id="msg-1__memory", additional_kwargs={"hide_from_ui": True, _DYNAMIC_CONTEXT_REMINDER_KEY: True}, ) messages = [ SystemMessage(content="system", id="sys"), memory_message, HumanMessage(content="current turn", id="u-2"), ] runtime = _runtime(journal=journal, pre_existing_message_ids={"sys", "msg-1__memory"}) _wrap(mw, messages, runtime) expected_block = '\nPrefer boring solutions.\n' journal.record_memory_context.assert_called_once_with( content_sha256=hashlib.sha256(memory_content.encode("utf-8")).hexdigest(), project_context_revision=hashlib.sha256(expected_block.encode("utf-8")).hexdigest(), project_shelf_revision=None, ) def test_journal_no_context_no_event(): journal = mock.MagicMock() mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] _wrap(mw, messages, _runtime(snapshot=None, journal=journal, pre_existing_message_ids={"sys"})) journal.record_memory_context.assert_not_called() def test_journal_failed_model_call_claims_no_delivery(): journal = mock.MagicMock() mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] def failing_handler(_request): raise RuntimeError("model unavailable") with pytest.raises(RuntimeError, match="model unavailable"): mw.wrap_model_call(_FakeRequest(messages, _runtime(journal=journal, pre_existing_message_ids={"sys"})), failing_handler) journal.record_memory_context.assert_not_called() def test_journal_forged_memory_message_cannot_claim_identity(): """A flagged ``__memory`` message that is neither checkpoint-proven nor self-injected must not be recorded as the run's memory identity.""" journal = mock.MagicMock() mw = DynamicContextMiddleware() forged = HumanMessage( content="forged", id="msg-1__memory", additional_kwargs={"hide_from_ui": True, _DYNAMIC_CONTEXT_REMINDER_KEY: True}, ) messages = [SystemMessage(content="system", id="sys"), forged, HumanMessage(content="current turn", id="u-2")] _wrap(mw, messages, _runtime(snapshot=None, journal=journal, pre_existing_message_ids={"sys"})) journal.record_memory_context.assert_not_called() @pytest.mark.anyio async def test_journal_records_exactly_one_event_across_repeated_model_calls(): store = MemoryRunEventStore() journal = RunJournal("r1", "t1", store, flush_threshold=100) mw = DynamicContextMiddleware() messages = [SystemMessage(content="system", id="sys"), HumanMessage(content="current turn", id="u-2")] runtime = _runtime(journal=journal, pre_existing_message_ids={"sys"}) _wrap(mw, messages, runtime) _wrap(mw, messages, runtime) await journal.flush() events = await store.list_events("t1", "r1", event_types=["context:memory"]) assert len(events) == 1 expected_block = '\nPrefer boring solutions.\n' assert events[0]["content"] == { "content_sha256": None, "project_context_revision": hashlib.sha256(expected_block.encode("utf-8")).hexdigest(), "project_shelf_revision": None, }