From acb0c59ada6875d0f2c540c10ee0c4b4055a5547 Mon Sep 17 00:00:00 2001 From: Michael Kuehl Date: Fri, 17 Apr 2026 10:05:59 +0200 Subject: [PATCH] fix(harvest): pass embedder to extract_and_store_knowledge (fixes #453) --- agent_fox/engine/session_lifecycle.py | 1 + tests/unit/cli/test_knowledge_wiring.py | 47 +++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/agent_fox/engine/session_lifecycle.py b/agent_fox/engine/session_lifecycle.py index 42b86fb3..7e5fd0c9 100644 --- a/agent_fox/engine/session_lifecycle.py +++ b/agent_fox/engine/session_lifecycle.py @@ -574,6 +574,7 @@ async def _extract_knowledge_and_findings( knowledge_db=self._knowledge_db, sink_dispatcher=self._sink, run_id=self._run_id, + embedder=self._embedder, causal_context_limit=self._config.orchestrator.causal_context_limit, ) except Exception: diff --git a/tests/unit/cli/test_knowledge_wiring.py b/tests/unit/cli/test_knowledge_wiring.py index fab5ca8a..7b7e54c3 100644 --- a/tests/unit/cli/test_knowledge_wiring.py +++ b/tests/unit/cli/test_knowledge_wiring.py @@ -95,6 +95,51 @@ async def test_extract_called_on_completed_session(self, tmp_path: Path) -> None call_args = mock_extract.call_args assert call_args.kwargs["spec_name"] == "test_spec" + @pytest.mark.asyncio + async def test_embedder_passed_to_extract(self, tmp_path: Path) -> None: + """Regression: embedder must be forwarded to extract_and_store_knowledge (fixes #453).""" + workspace = _make_workspace(tmp_path) + outcome = _make_outcome(status="completed") + + summary = {"summary": "Implemented feature X.", "tests_added_or_modified": []} + (tmp_path / ".agent-fox").mkdir(exist_ok=True) + (tmp_path / ".agent-fox" / "session-summary.json").write_text(json.dumps(summary)) + + spec_dir = Path.cwd() / ".specs" / "test_spec" + spec_dir.mkdir(parents=True, exist_ok=True) + + config = AgentFoxConfig() + mock_embedder = MagicMock() + runner = NodeSessionRunner("test_spec:1", config, knowledge_db=_MOCK_KB, embedder=mock_embedder) + + mock_extract = AsyncMock() + + with ( + patch( + "agent_fox.engine.session_lifecycle.run_session", + new_callable=AsyncMock, + return_value=outcome, + ), + patch("agent_fox.engine.session_lifecycle.harvest", new_callable=AsyncMock), + patch( + "agent_fox.engine.session_lifecycle.create_worktree", + new_callable=AsyncMock, + return_value=workspace, + ), + patch( + "agent_fox.engine.session_lifecycle.destroy_worktree", + new_callable=AsyncMock, + ), + patch( + "agent_fox.engine.session_lifecycle.extract_and_store_knowledge", + mock_extract, + ), + ): + await runner.execute("test_spec:1", 1) + + mock_extract.assert_called_once() + assert mock_extract.call_args.kwargs["embedder"] is mock_embedder + @pytest.mark.asyncio async def test_extract_not_called_on_failed_session(self, tmp_path: Path) -> None: """extract_and_store_knowledge is NOT invoked when the session fails.""" @@ -177,5 +222,3 @@ async def test_extract_failure_does_not_block_session(self, tmp_path: Path) -> N # Session is still completed despite extraction failure assert record.status == "completed" - -