From 206c4ffcf2e948591e6433deda92101a8a5cc078 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 22 Apr 2026 15:04:24 +0300 Subject: [PATCH] address review: top-level AnalysisRunDeps import, asserts, e2e tests - Move AnalysisRunDeps to the module-level import in _tools.py. - Swap the RuntimeError guards in _require_rag and execute_code for plain asserts with the same diagnostic messages. Apply the same treatment to the ty-reachability guard in Sandbox._ensure_initialized. - Add end-to-end lifespan tests for both skills that drive _run_skill with TestModel, covering the sub-agent entry path that the existing tool-level tests bypass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../haiku/rag/agents/analysis/sandbox.py | 7 ++----- haiku_rag_slim/haiku/rag/skills/_tools.py | 17 +++++++---------- tests/skills/test_analysis.py | 13 +++++++++++++ tests/skills/test_rag.py | 13 +++++++++++++ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py b/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py index 3232a03d..a62516ca 100644 --- a/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py +++ b/haiku_rag_slim/haiku/rag/agents/analysis/sandbox.py @@ -277,11 +277,8 @@ class Sandbox: external_functions=self._build_external_functions(), os=self._vfs, ) - repl = self._repl - vfs = self._vfs - if repl is None or vfs is None: - raise RuntimeError("Sandbox initialization failed") - return repl, vfs + assert self._repl is not None and self._vfs is not None + return self._repl, self._vfs async def execute(self, code: str) -> SandboxResult: """Execute Python code in the Monty REPL. diff --git a/haiku_rag_slim/haiku/rag/skills/_tools.py b/haiku_rag_slim/haiku/rag/skills/_tools.py index 0869c16c..d406fcca 100644 --- a/haiku_rag_slim/haiku/rag/skills/_tools.py +++ b/haiku_rag_slim/haiku/rag/skills/_tools.py @@ -7,7 +7,7 @@ from pydantic_ai import RunContext from haiku.rag.agents.research.models import Citation from haiku.rag.client import HaikuRAG from haiku.rag.config.models import AppConfig -from haiku.rag.skills._deps import RAGRunDeps +from haiku.rag.skills._deps import AnalysisRunDeps, RAGRunDeps from haiku.rag.store.models.chunk import SearchResult @@ -76,10 +76,9 @@ def _get_state(ctx: RunContext[RAGRunDeps], state_type: type[BaseModel]) -> Any: def _require_rag(ctx: RunContext[RAGRunDeps]) -> HaikuRAG: - if ctx.deps is None or ctx.deps.rag is None: - raise RuntimeError( - "RAGRunDeps.rag is not set — skill lifespan must run before tools." - ) + assert ctx.deps is not None and ctx.deps.rag is not None, ( + "RAGRunDeps.rag is not set — skill lifespan must run before tools." + ) return ctx.deps.rag @@ -227,7 +226,6 @@ def create_skill_tools( tools["get_document"] = get_document if "execute_code" in tool_names: - from haiku.rag.skills._deps import AnalysisRunDeps async def execute_code(ctx: RunContext[AnalysisRunDeps], code: str) -> str: """Execute Python code in a sandboxed interpreter. @@ -242,10 +240,9 @@ def create_skill_tools( Args: code: Python code to execute. """ - if ctx.deps is None or ctx.deps.sandbox is None: - raise RuntimeError( - "AnalysisRunDeps.sandbox is not set — skill lifespan must run before execute_code." - ) + assert ctx.deps is not None and ctx.deps.sandbox is not None, ( + "AnalysisRunDeps.sandbox is not set — skill lifespan must run before execute_code." + ) sandbox = ctx.deps.sandbox result = await sandbox.execute(code) diff --git a/tests/skills/test_analysis.py b/tests/skills/test_analysis.py index 273c6d67..266c5b8b 100644 --- a/tests/skills/test_analysis.py +++ b/tests/skills/test_analysis.py @@ -266,6 +266,19 @@ class TestAnalysisLifespan: assert skill.deps_type is AnalysisRunDeps assert skill.lifespan is not None + async def test_run_skill_end_to_end_opens_client_and_sandbox( + self, allow_model_requests, rag_db + ): + """Full sub-agent path: lifespan builds client + sandbox, tools see them.""" + from pydantic_ai.models.test import TestModel + + from haiku.rag.skills.analysis import create_skill + from haiku.skills.agent import _run_skill + + skill = create_skill(db_path=rag_db) + result, *_ = await _run_skill(TestModel(), skill, "Print the document count.") + assert result + async def test_lifespan_clears_executions_citations_searches(self, rag_db): from haiku.rag.agents.research.models import Citation from haiku.rag.skills._deps import AnalysisRunDeps, make_analysis_lifespan diff --git a/tests/skills/test_rag.py b/tests/skills/test_rag.py index 4890583f..6fbd009d 100644 --- a/tests/skills/test_rag.py +++ b/tests/skills/test_rag.py @@ -359,6 +359,19 @@ class TestLifespan: assert skill.deps_type is RAGRunDeps assert skill.lifespan is not None + async def test_run_skill_end_to_end_opens_and_closes_client( + self, allow_model_requests, rag_db + ): + """Full sub-agent path: lifespan opens the client, tools see it, lifespan closes it.""" + from pydantic_ai.models.test import TestModel + + from haiku.rag.skills.rag import create_skill + from haiku.skills.agent import _run_skill + + skill = create_skill(db_path=rag_db) + result, *_ = await _run_skill(TestModel(), skill, "List the documents.") + assert result + async def test_lifespan_clears_citations_and_searches_but_keeps_index(self, rag_db): from haiku.rag.agents.research.models import Citation from haiku.rag.skills._deps import RAGRunDeps, make_rag_lifespan