diff --git a/navi/core/agent.py b/navi/core/agent.py index 42f6168..7274e82 100644 --- a/navi/core/agent.py +++ b/navi/core/agent.py @@ -217,7 +217,12 @@ if not note: return session.context.append( - Message(role="system", content=note, metadata={"source": "task_note"}) + Message( + role="system", + content=note, + metadata={"source": "task_note"}, + is_display=False, + ) ) await self._sessions.save(session) log.info("agent.task_notes_drained", session_id=session_id) diff --git a/navi/core/context_builder.py b/navi/core/context_builder.py index e3bb65f..cc8c50c 100644 --- a/navi/core/context_builder.py +++ b/navi/core/context_builder.py @@ -435,7 +435,14 @@ f"prefer this directory when no explicit working_dir is given." ) system_msg = Message(role="system", content=system_prompt) - conv = [m for m in session_context if m.role != "system"] + # System-role history is normally infrastructure-only (hidden from the + # LLM), but background-task completion notes must stay visible — they + # are the agent's only guaranteed delivery of detached tool results. + conv = [ + m for m in session_context + if m.role != "system" + or (m.metadata or {}).get("source") == "task_note" + ] conv = self._truncate_oversized(conv) result: list[Message] = [system_msg] if mem: diff --git a/tests/unit/core/test_task_notes_injection.py b/tests/unit/core/test_task_notes_injection.py index 8ab78e7..d5e36de 100644 --- a/tests/unit/core/test_task_notes_injection.py +++ b/tests/unit/core/test_task_notes_injection.py @@ -68,6 +68,28 @@ # session was persisted assert agent._sessions.saved == [session] + async def test_note_survives_context_build_system_filter(self): + """Regression: build() used to drop ALL system-role history, so a + drained note was logged as delivered but never reached the LLM.""" + from navi.core.context_builder import ContextBuilder + from tests.conftest_factory import make_profile, make_profile_registry + + agent = make_agent_with_store() + session = make_session() + job = SimpleNamespace( + task_id="bt-ab12", session_id="s1", tool="terminal", status="completed", + subagent_tokens=None, preview=lambda limit=800: "BG-DONE-42", + ) + await task_notes.add_note(job) + await agent._drain_task_notes("s1", session) + + builder = ContextBuilder(profile_registry=make_profile_registry()) + built = builder.build(session.context, make_profile("test"), None) + + joined = "\n".join((m.content or "") for m in built) + assert "[Background task results]" in joined + assert "bt-ab12" in joined + async def test_no_notes_nothing_injected(self): agent = make_agent_with_store() session = make_session()