From d53abcea0edf1fabf4273902cc69337aa7a7afa7 Mon Sep 17 00:00:00 2001 From: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Date: Fri, 28 Aug 2026 22:33:37 +0000 Subject: [PATCH 1/3] test(sdk): remove change-detector tests Remove the 49 implementation-coupled tests identified in the audit so behavior-preserving refactors do not require mechanical updates. Co-authored-by: open-swe[bot] --- .../backends/test_composite_backend.py | 18 ------ .../unit_tests/backends/test_file_format.py | 6 -- .../backends/test_filesystem_backend.py | 7 +-- .../backends/test_langsmith_sandbox.py | 16 ----- .../backends/test_local_shell_backend.py | 13 ---- .../unit_tests/backends/test_protocol.py | 5 -- .../backends/test_sandbox_backend.py | 23 -------- .../backends/test_timeout_compat.py | 6 -- .../middleware/test_compact_tool.py | 32 ---------- .../middleware/test_memory_middleware.py | 29 --------- .../middleware/test_rubric_middleware.py | 25 -------- .../middleware/test_skills_middleware.py | 31 ---------- .../test_subagent_middleware_init.py | 37 ------------ .../middleware/test_summarization_factory.py | 45 +------------- .../test_summarization_middleware.py | 59 ------------------- .../tests/unit_tests/test_artifacts_root.py | 57 ------------------ .../tests/unit_tests/test_local_shell.py | 16 +---- .../tests/unit_tests/test_middleware.py | 26 -------- .../tests/unit_tests/test_models.py | 5 -- .../unit_tests/test_nemotron_ultra_profile.py | 35 ----------- .../tests/unit_tests/test_rubric_example.py | 22 ------- .../tests/unit_tests/test_version.py | 7 --- 22 files changed, 3 insertions(+), 517 deletions(-) diff --git a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py index 524d786bdb..473f827e23 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_composite_backend.py @@ -683,24 +683,6 @@ def test_composite_backend_execute_without_sandbox_default(): comp.execute("ls -la") -def test_composite_backend_supports_execution_check(): - """Test the isinstance check works correctly for CompositeBackend.""" - mem_store = InMemoryStore() - - # CompositeBackend with sandbox default should pass isinstance check - sandbox = MockSandboxBackend(store=mem_store, namespace=lambda _rt: ("default",)) - comp_with_sandbox = CompositeBackend(default=sandbox, routes={}) - # Note: CompositeBackend itself has execute() method, so isinstance will pass - # but the actual support depends on the default backend - assert hasattr(comp_with_sandbox, "execute") - - # CompositeBackend with non-sandbox default should still have execute() method - # but will raise NotImplementedError when called - state = StoreBackend(store=mem_store, namespace=lambda _rt: ("default",)) - comp_without_sandbox = CompositeBackend(default=state, routes={}) - assert hasattr(comp_without_sandbox, "execute") - - def test_composite_backend_execute_with_routed_backends(): """Test that execution doesn't interfere with file routing.""" mem_store = InMemoryStore() diff --git a/libs/deepagents/tests/unit_tests/backends/test_file_format.py b/libs/deepagents/tests/unit_tests/backends/test_file_format.py index 47881c8407..f7f6f36576 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_file_format.py +++ b/libs/deepagents/tests/unit_tests/backends/test_file_format.py @@ -7,7 +7,6 @@ from deepagents.backends.store import StoreBackend from deepagents.backends.utils import ( - compile_grep_include_glob, create_file_data, file_data_to_string, grep_matches_from_files, @@ -139,11 +138,6 @@ def test_grep_glob_matches_nothing() -> None: assert result.matches == [] -def test_compile_glob_is_cached() -> None: - assert compile_grep_include_glob("*.py") is compile_grep_include_glob("*.py") - assert compile_grep_include_glob("*.py") is not compile_grep_include_glob("*.md") - - def test_grep_glob_repeated_pattern_stays_correct() -> None: first = {"/x.py": create_file_data("hit"), "/x.md": create_file_data("hit")} second = {"/y.py": create_file_data("hit"), "/y.txt": create_file_data("hit")} diff --git a/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py b/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py index 523c8695b0..c3bb9b8fd3 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py @@ -19,7 +19,7 @@ from deepagents.backends.filesystem import FilesystemBackend from deepagents.backends.protocol import DeleteResult, EditResult, GrepMatch, ReadResult, WriteResult from deepagents.backends.utils import format_grep_matches -from deepagents.middleware.filesystem import GLOB_TIMEOUT, FilesystemMiddleware +from deepagents.middleware.filesystem import FilesystemMiddleware def require_ripgrep() -> None: @@ -1158,11 +1158,6 @@ def test_glob_mid_iteration_oserror_is_error_not_truncated(tmp_path: Path, monke assert result.truncated is False -def test_glob_backend_budget_below_middleware_deadline() -> None: - """The backend glob budget must stay below the middleware's outer deadline so partial results win first.""" - assert fs_module._DEFAULT_GLOB_TIMEOUT < GLOB_TIMEOUT - - def test_glob_supports_brace_expansion(tmp_path: Path) -> None: """Glob enables brace expansion via `wcmatch`, diverging from stdlib `rglob` (which is literal).""" (tmp_path / "a.py").write_text("x") diff --git a/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py b/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py index d18f90fb66..cd2a36ddfc 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py +++ b/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py @@ -9,7 +9,6 @@ import pytest from langsmith.sandbox import ResourceNotFoundError, SandboxClientError -from deepagents.backends import sandbox as base_sandbox from deepagents.backends.langsmith import LangSmithSandbox from deepagents.backends.sandbox import MAX_BINARY_BYTES, MAX_OUTPUT_BYTES, TRUNCATION_MSG @@ -542,21 +541,6 @@ def test_write_preflight_runs_existence_check() -> None: assert base64.b64encode(b"/app/test.txt").decode("ascii") in cmd_arg -def test_max_binary_bytes_constant_matches_template() -> None: - """Python `MAX_BINARY_BYTES` constant stays in lockstep with the heredoc literal. - - Drift here would silently desync `LangSmithSandbox.read()` from - `BaseSandbox.read()` because the template does not import the constant. - """ - assert "MAX_BINARY_BYTES = 500 * 1024" in base_sandbox._READ_COMMAND_TEMPLATE - assert MAX_BINARY_BYTES == 500 * 1024 - - -def test_max_output_bytes_constant_matches_template() -> None: - assert "MAX_OUTPUT_BYTES = 500 * 1024" in base_sandbox._READ_COMMAND_TEMPLATE - assert MAX_OUTPUT_BYTES == 500 * 1024 - - def _make_async_sandbox() -> tuple[LangSmithSandbox, MagicMock, MagicMock]: sb, mock_sdk = _make_sandbox() async_sdk = MagicMock() diff --git a/libs/deepagents/tests/unit_tests/backends/test_local_shell_backend.py b/libs/deepagents/tests/unit_tests/backends/test_local_shell_backend.py index 9887245da5..8825b6dd06 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_local_shell_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_local_shell_backend.py @@ -1,12 +1,10 @@ """Unit tests for LocalShellBackend.""" import os -import subprocess import sys import tempfile import warnings from pathlib import Path -from unittest.mock import patch import pytest @@ -69,17 +67,6 @@ def test_local_shell_backend_execute_simple_command() -> None: assert result.truncated is False -def test_local_shell_backend_execute_starts_new_session() -> None: - """Test that commands cannot access the parent's controlling terminal.""" - completed = subprocess.CompletedProcess(args="echo hello", returncode=0, stdout="hello\n", stderr="") - with tempfile.TemporaryDirectory() as tmpdir: - backend = LocalShellBackend(root_dir=tmpdir) - with patch("subprocess.run", return_value=completed) as run: - backend.execute("echo hello") - - assert run.call_args.kwargs["start_new_session"] is True - - def test_local_shell_backend_cannot_open_parent_controlling_terminal(tmp_path: Path) -> None: """Test a command cannot open the controlling terminal owned by its parent.""" exit_code, output = _run_controlling_terminal_probe(tmp_path) diff --git a/libs/deepagents/tests/unit_tests/backends/test_protocol.py b/libs/deepagents/tests/unit_tests/backends/test_protocol.py index a5053e96a4..7c47bb07c5 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_protocol.py +++ b/libs/deepagents/tests/unit_tests/backends/test_protocol.py @@ -13,7 +13,6 @@ from deepagents.backends.filesystem import _map_exception_to_standard_error from deepagents.backends.protocol import ( ASYNC_GREP_TIMEOUT, - DEFAULT_GREP_TIMEOUT, BackendProtocol, DeleteResult, GrepResult, @@ -155,10 +154,6 @@ async def test_aexecute(self, sandbox_backend: BareSandboxBackend) -> None: class TestAgrepTimeout: """Tests for `agrep` async timeout safety net.""" - def test_agrep_timeout_exceeds_two_sync_grep_phases(self) -> None: - """`agrep` gives `FilesystemBackend` headroom for `rg` timeout plus fallback timeout.""" - assert ASYNC_GREP_TIMEOUT > (2 * DEFAULT_GREP_TIMEOUT) - async def test_agrep_returns_error_on_timeout(self, backend: BareBackend) -> None: """`agrep` catches `TimeoutError` and returns `GrepResult` with error.""" seen_timeout = None diff --git a/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py b/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py index 85c2042588..f35c6d96f2 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py @@ -683,24 +683,6 @@ def test_grep_path_glob_is_routed_for_slash_in_glob() -> None: assert "--include=" not in sandbox.last_command -def test_grep_path_glob_template_strips_leading_slash() -> None: - """Anchored globs (leading /) stay relative to the search root, not the filesystem root.""" - assert "lstrip" in _GREP_PATH_GLOB_TEMPLATE - assert "rel_glob" in _GREP_PATH_GLOB_TEMPLATE - # The raw glob_pat must not be passed directly to glob.glob — only rel_glob. - # Verify the template uses rel_glob in the glob() call, not glob_pat. - assert "glob.glob(rel_glob" in _GREP_PATH_GLOB_TEMPLATE - assert "glob.glob(glob_pat" not in _GREP_PATH_GLOB_TEMPLATE - - -def test_grep_path_glob_template_terminates_each_record() -> None: - """Each match record is explicitly newline-terminated to prevent concatenation.""" - # The template must strip the line's trailing newline and add an explicit one - # so a file whose last line lacks a final newline doesn't merge with the next. - assert "rstrip" in _GREP_PATH_GLOB_TEMPLATE - assert "line.rstrip" in _GREP_PATH_GLOB_TEMPLATE - - def test_grep_path_glob_parses_multiple_matches_no_trailing_newline() -> None: """Two matches where the first line has no trailing newline parse correctly.""" # Simulate the fixed template output: each record explicitly newline-terminated. @@ -1117,11 +1099,6 @@ def test_edit_command_template_format() -> None: assert "__DEEPAGENTS_EDIT_EOF__" in cmd -def test_edit_command_template_ends_with_newline() -> None: - """Test that _EDIT_COMMAND_TEMPLATE preserves the trailing newline after EOF.""" - assert _EDIT_COMMAND_TEMPLATE.endswith("\n") - - def test_edit_tmpfile_template_format() -> None: """Test that _EDIT_TMPFILE_TEMPLATE can be formatted without KeyError.""" old_b64 = base64.b64encode(b"/tmp/old").decode("ascii") diff --git a/libs/deepagents/tests/unit_tests/backends/test_timeout_compat.py b/libs/deepagents/tests/unit_tests/backends/test_timeout_compat.py index 17ddf1c080..85f60570ad 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_timeout_compat.py +++ b/libs/deepagents/tests/unit_tests/backends/test_timeout_compat.py @@ -73,12 +73,6 @@ def test_kwargs_backend_returns_false(self) -> None: """A backend with **kwargs does not have a named `timeout` param.""" assert execute_accepts_timeout(KwargsBackend) is False - def test_result_is_cached(self) -> None: - execute_accepts_timeout(ModernBackend) - execute_accepts_timeout(ModernBackend) - info = execute_accepts_timeout.cache_info() - assert info.hits >= 1 - def test_logs_warning_on_inspect_failure(self, caplog: pytest.LogCaptureFixture) -> None: """If inspect.signature raises, a warning is logged and False returned.""" diff --git a/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py b/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py index 9d0b71ea1f..f41608cd1a 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py @@ -11,7 +11,6 @@ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage from langgraph.types import Command -from deepagents.backends.state import StateBackend from deepagents.middleware.summarization import ( SummarizationMiddleware, SummarizationToolMiddleware, @@ -471,32 +470,6 @@ def test_cutoff_exceeds_message_count(self) -> None: assert result[0] is summary_msg -class TestCompactBackendUsage: - """Test backend use for compact offloading.""" - - def test_static_backend_is_passed_to_offload(self) -> None: - """Should pass the configured backend instance to offload.""" - backend = StateBackend() - mw = _make_middleware(backend=backend) - messages = _make_messages(10) - runtime = _make_runtime(messages) - - with ( - patch.object(mw._summarization, "_determine_cutoff_index", return_value=4), - patch.object( - mw._summarization, - "_partition_messages", - side_effect=lambda msgs, idx: (msgs[:idx], msgs[idx:]), - ), - patch.object(mw._summarization, "_create_summary", return_value="Summary."), - patch.object(mw._summarization, "_offload_to_backend", return_value=None) as offload, - ): - mw._run_compact(runtime) - - offload.assert_called_once() - assert offload.call_args.args[0] is backend - - class TestComputeStateCutoff: """Tests for _compute_state_cutoff arithmetic.""" @@ -650,11 +623,6 @@ def test_trigger_clauses_are_preferred_over_legacy_conditions(self) -> None: result = mw._run_compact(runtime) assert "_summarization_event" in result.update - def test_dict_trigger_constructs_langchain_trigger_clauses(self) -> None: - """Dict trigger input should populate LangChain's canonical trigger clauses.""" - mw = _make_middleware_with_trigger({"tokens": 100_000, "messages": 6}) - assert mw._summarization._lc_helper._trigger_clauses == [{"tokens": 100_000, "messages": 6}] - def test_dict_clause_list_uses_or_semantics(self) -> None: """Multiple dict trigger clauses use OR semantics for compact eligibility.""" mw = _make_middleware_with_trigger(("tokens", 100_000)) diff --git a/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py index ae63d42176..0628ef031b 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py @@ -616,35 +616,6 @@ async def test_agent_with_memory_middleware_async(tmp_path: Path) -> None: assert "Test async loading" in content -def test_memory_middleware_with_state_backend() -> None: - """Test that MemoryMiddleware can be initialized with StateBackend instance.""" - sources: list[str] = ["/memory/AGENTS.md"] - middleware = MemoryMiddleware( - backend=StateBackend(), - sources=sources, - ) - - # Verify the middleware was created successfully - assert middleware is not None - assert isinstance(middleware._backend, StateBackend) - assert len(middleware.sources) == 1 - assert middleware.sources[0] == "/memory/AGENTS.md" - - -def test_memory_middleware_with_store_backend_instance() -> None: - """Test that MemoryMiddleware can be initialized with StoreBackend instance.""" - store = InMemoryStore() - sources: list[str] = ["/memory/AGENTS.md"] - middleware = MemoryMiddleware( - backend=StoreBackend(store=store, namespace=_assistant_id_namespace), - sources=sources, - ) - - # Verify the middleware was created successfully - assert middleware is not None - assert isinstance(middleware._backend, StoreBackend) - - def test_memory_middleware_with_store_backend_assistant_id() -> None: """Test namespace isolation: each assistant_id gets its own memory namespace.""" # Setup diff --git a/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py index 68b8f8afd8..b84cf6bb81 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py @@ -159,14 +159,6 @@ async def _ainvoke( class TestConstruction: - def test_defaults(self) -> None: - mw = RubricMiddleware(model=_STUB_MODEL) - assert mw.max_iterations == 3 - assert mw._model == _STUB_MODEL - assert mw._tools == [] - # `system_prompt` defaults to the built-in grader prompt. - assert "grader" in mw._system_prompt.lower() - def test_missing_model_raises(self) -> None: # `model` is keyword-only and required -- omitting it is a TypeError # from the function signature itself. @@ -204,23 +196,6 @@ def test_max_iterations_non_int_rejected(self) -> None: with pytest.raises(TypeError): RubricMiddleware(model=_STUB_MODEL, max_iterations="3") # type: ignore[arg-type] - def test_tools_default_to_empty(self) -> None: - mw = RubricMiddleware(model=_STUB_MODEL) - assert mw._tools == [] - - def test_tools_propagated(self) -> None: - @tool - def my_tool(query: str) -> str: - """A tool.""" - return query - - mw = RubricMiddleware(model=_STUB_MODEL, tools=[my_tool]) - assert mw._tools == [my_tool] - - def test_custom_system_prompt_stored(self) -> None: - mw = RubricMiddleware(model=_STUB_MODEL, system_prompt="be strict") - assert mw._system_prompt == "be strict" - # ---------------------------------------------------------------------- # # before_agent semantics diff --git a/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py index 10095b5322..2eaed4aaee 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py @@ -1601,37 +1601,6 @@ def test_agent_with_skills_middleware_system_prompt(tmp_path: Path) -> None: assert "test-skill" in content, "System prompt should mention the skill name" -def test_skills_middleware_with_state_backend() -> None: - """Test that SkillsMiddleware can be initialized with StateBackend instance.""" - sources = ["/skills/user"] - middleware = SkillsMiddleware( - backend=StateBackend(), - sources=sources, - ) - - # Verify the middleware was created successfully - assert middleware is not None - assert isinstance(middleware._backend, StateBackend) - assert len(middleware.sources) == 1 - assert middleware.sources[0] == "/skills/user" - - -def test_skills_middleware_with_store_backend_instance() -> None: - """Test that SkillsMiddleware can be initialized with StoreBackend instance.""" - store = InMemoryStore() - sources = ["/skills/user"] - middleware = SkillsMiddleware( - backend=StoreBackend(store=store, namespace=_assistant_id_namespace), - sources=sources, - ) - - # Verify the middleware was created successfully - assert middleware is not None - assert isinstance(middleware._backend, StoreBackend) - assert len(middleware.sources) == 1 - assert middleware.sources[0] == "/skills/user" - - async def test_agent_with_skills_middleware_async(tmp_path: Path) -> None: """Test that skills middleware works with async agent invocation.""" backend = FilesystemBackend(root_dir=str(tmp_path), virtual_mode=False) diff --git a/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py b/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py index 3a1ae72e54..00601784a2 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py @@ -439,43 +439,6 @@ def invoke( assert beta.update["messages"][0].content == "agent-beta" assert graph.config is None - def test_middleware_delegates_to_create_sub_agent(self, monkeypatch: pytest.MonkeyPatch) -> None: - """Middleware should use the shared entrypoint for declarative subagents.""" - graph = self._make_echo_graph() - calls: list[tuple[object, type | None]] = [] - - class CustomState(MessagesState): - pass - - def fake_create_sub_agent( - spec: object, - *, - state_schema: type | None = None, - response_format: object = None, - ) -> object: - del response_format - calls.append((spec, state_schema)) - return graph - - monkeypatch.setattr("deepagents.middleware.subagents.create_sub_agent", fake_create_sub_agent) - - SubAgentMiddleware( - backend=StateBackend(), - subagents=[ - { - "name": "agent-alpha", - "description": "First binding", - "system_prompt": "Work on the task.", - "model": "test-model", - "tools": [], - }, - ], - state_schema=CustomState, - ) - - assert len(calls) == 1 - assert calls[0][1] is CustomState - def test_multiple_subagents_with_interrupt_on(self) -> None: """Test creating agent with multiple subagents that have interrupt_on configured.""" agent = create_agent( diff --git a/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py b/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py index fa8e351a07..bf14803b8d 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py @@ -1,12 +1,11 @@ """Unit tests for the summarization middleware factory.""" -from collections.abc import Iterable from inspect import Parameter, signature from typing import Any, cast from unittest.mock import MagicMock import pytest -from langchain_core.messages import AIMessage, MessageLikeRepresentation +from langchain_core.messages import AIMessage from deepagents.middleware.summarization import create_summarization_middleware from tests.unit_tests.chat_model import GenericFakeChatModel @@ -22,28 +21,6 @@ def _make_model(*, with_profile_limit: int | None) -> GenericFakeChatModel: return model -def test_factory_uses_profile_based_defaults() -> None: - """Uses fraction-based defaults when model profile has `max_input_tokens`.""" - model = _make_model(with_profile_limit=120_000) - middleware = create_summarization_middleware(model, cast("Any", MagicMock())) - - assert middleware._lc_helper.trigger == ("fraction", 0.85) - assert middleware._lc_helper.keep == ("fraction", 0.10) - assert middleware._truncate_args_trigger == ("fraction", 0.85) - assert middleware._truncate_args_keep == ("fraction", 0.10) - - -def test_factory_uses_fallback_defaults_without_profile() -> None: - """Uses fixed token/message defaults when no model profile is available.""" - model = _make_model(with_profile_limit=None) - middleware = create_summarization_middleware(model, cast("Any", MagicMock())) - - assert middleware._lc_helper.trigger == ("tokens", 170000) - assert middleware._lc_helper.keep == ("messages", 6) - assert middleware._truncate_args_trigger == ("messages", 20) - assert middleware._truncate_args_keep == ("messages", 20) - - def test_factory_default_prompt_explains_media_references() -> None: """Explains preserved media tags in the default summary prompt.""" model = _make_model(with_profile_limit=None) @@ -59,26 +36,6 @@ def test_factory_default_prompt_explains_media_references() -> None: assert "call `read_file` on the referenced path" in rendered -def test_factory_surfaces_summarization_knobs() -> None: - """Passes explicit summarization settings through to the middleware.""" - model = _make_model(with_profile_limit=120_000) - - def token_counter(messages: Iterable[MessageLikeRepresentation]) -> int: - return len(list(messages)) - - middleware = create_summarization_middleware( - model, - cast("Any", MagicMock()), - summary_prompt="custom summary prompt: {messages}", - trim_tokens_to_summarize=123, - token_counter=token_counter, - ) - - assert middleware._lc_helper.summary_prompt == "custom summary prompt: {messages}" - assert middleware._lc_helper.trim_tokens_to_summarize == 123 - assert middleware._lc_helper.token_counter is token_counter - - def test_factory_summarization_knobs_are_keyword_only() -> None: """Requires optional factory controls to be passed by name.""" params = signature(create_summarization_middleware).parameters diff --git a/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py index 7065c37d4f..d0565af778 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py @@ -326,19 +326,6 @@ async def handler(req: ModelRequest) -> "ModelResponse": class TestSummarizationMiddlewareInit: """Tests for middleware initialization.""" - def test_init_with_backend(self) -> None: - """Test initialization with a backend instance.""" - backend = MockBackend() - middleware = SummarizationMiddleware( - model=make_mock_model(), - backend=backend, - trigger=("messages", 5), - keep=("messages", 3), - ) - - assert middleware._backend is backend - assert middleware._history_path_prefix == "/conversation_history" - def test_langchain_deprecated_kwargs_are_forwarded(self) -> None: """LangChain-owned deprecated arguments retain their upstream behavior.""" with pytest.warns(DeprecationWarning, match="(?:max_tokens_before_summary|messages_to_keep) is deprecated"): @@ -3127,24 +3114,6 @@ def counting_token_counter(_messages: list[BaseMessage], **_kwargs: Any) -> int: ) return middleware, calls - def test_token_counter_called_once_per_model_call(self) -> None: - middleware, calls = self._make_counting_middleware() - state = cast("AgentState[Any]", {"messages": make_conversation_messages()}) - - _, captured_request = call_wrap_model_call(middleware, state, make_mock_runtime()) - - assert captured_request is not None # Handler ran; nothing was summarized. - assert calls["count"] == 1 - - async def test_token_counter_called_once_per_model_call_async(self) -> None: - middleware, calls = self._make_counting_middleware() - state = cast("AgentState[Any]", {"messages": make_conversation_messages()}) - - _, captured_request = await call_awrap_model_call(middleware, state, make_mock_runtime()) - - assert captured_request is not None # Handler ran; nothing was summarized. - assert calls["count"] == 1 - def _make_truncating_counting_middleware( self, ) -> tuple[SummarizationMiddleware, dict[str, int]]: @@ -3195,34 +3164,6 @@ def _truncatable_state(self) -> "AgentState[Any]": ] return cast("AgentState[Any]", {"messages": messages}) - def test_token_counter_recounts_when_truncation_modifies_messages(self) -> None: - middleware, calls = self._make_truncating_counting_middleware() - - _, captured_request = call_wrap_model_call(middleware, self._truncatable_state(), make_mock_runtime()) - - assert captured_request is not None # Handler ran; nothing was summarized. - # Truncation changed the message set, so the count is refreshed: - # once before truncation, once after. - assert calls["count"] == 2 - # Confirm the modify path was genuinely taken (not a vacuous recount). - first_ai = captured_request.messages[0] - assert isinstance(first_ai, AIMessage) - assert first_ai.tool_calls[0]["args"]["content"] == "x" * 20 + "...(argument truncated)" - - async def test_token_counter_recounts_when_truncation_modifies_messages_async(self) -> None: - middleware, calls = self._make_truncating_counting_middleware() - - _, captured_request = await call_awrap_model_call(middleware, self._truncatable_state(), make_mock_runtime()) - - assert captured_request is not None # Handler ran; nothing was summarized. - # Truncation changed the message set, so the count is refreshed: - # once before truncation, once after. - assert calls["count"] == 2 - # Confirm the modify path was genuinely taken (not a vacuous recount). - first_ai = captured_request.messages[0] - assert isinstance(first_ai, AIMessage) - assert first_ai.tool_calls[0]["args"]["content"] == "x" * 20 + "...(argument truncated)" - class _OpaqueCounter: """Wraps a counter so its signature cannot be introspected. diff --git a/libs/deepagents/tests/unit_tests/test_artifacts_root.py b/libs/deepagents/tests/unit_tests/test_artifacts_root.py index f41d969d32..6bc06f707d 100644 --- a/libs/deepagents/tests/unit_tests/test_artifacts_root.py +++ b/libs/deepagents/tests/unit_tests/test_artifacts_root.py @@ -7,8 +7,6 @@ from deepagents.backends.state import StateBackend from deepagents.backends.store import StoreBackend from deepagents.middleware.filesystem import FilesystemMiddleware -from deepagents.middleware.summarization import create_summarization_middleware -from tests.unit_tests.chat_model import GenericFakeChatModel as FakeChatModel def _make_store_backend(): @@ -35,28 +33,6 @@ def test_custom_artifacts_root(self) -> None: class TestFilesystemMiddlewareArtifactsRoot: - def test_default_prefixes(self) -> None: - mw = FilesystemMiddleware() - assert mw._large_tool_results_prefix == "/large_tool_results" - assert mw._conversation_history_prefix == "/conversation_history" - - def test_custom_artifacts_root_from_composite_backend(self) -> None: - backend = _make_composite_backend(artifacts_root="/workspace") - mw = FilesystemMiddleware(backend=backend) - assert mw._large_tool_results_prefix == "/workspace/large_tool_results" - assert mw._conversation_history_prefix == "/workspace/conversation_history" - - def test_trailing_slash_normalized(self) -> None: - backend = _make_composite_backend(artifacts_root="/workspace/") - mw = FilesystemMiddleware(backend=backend) - assert mw._large_tool_results_prefix == "/workspace/large_tool_results" - assert mw._conversation_history_prefix == "/workspace/conversation_history" - - def test_root_slash_no_double_slash(self) -> None: - mw = FilesystemMiddleware() - assert mw._large_tool_results_prefix == "/large_tool_results" - assert mw._conversation_history_prefix == "/conversation_history" - def test_large_tool_result_eviction_uses_artifacts_root(self) -> None: backend = _make_composite_backend(artifacts_root="/workspace") mw = FilesystemMiddleware(backend=backend, tool_token_limit_before_evict=100) @@ -88,32 +64,6 @@ def test_large_tool_result_eviction_default_root(self) -> None: assert resp.content == b"x" * 5000 -class TestCreateSummarizationMiddlewareArtifactsRoot: - def test_default_history_path_prefix(self) -> None: - backend = _make_store_backend() - model = FakeChatModel(messages=iter([])) - mw = create_summarization_middleware(model, backend) - assert mw._history_path_prefix == "/conversation_history" - - def test_custom_artifacts_root_from_composite_backend(self) -> None: - backend = _make_composite_backend(artifacts_root="/workspace") - model = FakeChatModel(messages=iter([])) - mw = create_summarization_middleware(model, backend) - assert mw._history_path_prefix == "/workspace/conversation_history" - - def test_trailing_slash_normalized(self) -> None: - backend = _make_composite_backend(artifacts_root="/workspace/") - model = FakeChatModel(messages=iter([])) - mw = create_summarization_middleware(model, backend) - assert mw._history_path_prefix == "/workspace/conversation_history" - - def test_root_slash_no_double_slash(self) -> None: - backend = _make_store_backend() - model = FakeChatModel(messages=iter([])) - mw = create_summarization_middleware(model, backend) - assert mw._history_path_prefix == "/conversation_history" - - class TestCompositeBackendEvictionArtifactsRoot: """Tests for eviction with CompositeBackend and custom artifacts_root.""" @@ -135,13 +85,6 @@ def test_large_tool_result_eviction(self) -> None: [resp] = backend.download_files(["/large_tool_results/evict_ws"]) assert resp.content is None - def test_summarization_history_prefix(self) -> None: - """Summarization middleware uses the correct history prefix from artifacts_root.""" - backend = _make_composite_backend(artifacts_root="/workspace") - model = FakeChatModel(messages=iter([])) - mw = create_summarization_middleware(model, backend) - assert mw._history_path_prefix == "/workspace/conversation_history" - class TestAsyncEvictionArtifactsRoot: """Tests for async eviction paths with custom artifacts_root.""" diff --git a/libs/deepagents/tests/unit_tests/test_local_shell.py b/libs/deepagents/tests/unit_tests/test_local_shell.py index 6952e74a70..5c7a40df33 100644 --- a/libs/deepagents/tests/unit_tests/test_local_shell.py +++ b/libs/deepagents/tests/unit_tests/test_local_shell.py @@ -6,26 +6,12 @@ import pytest -from deepagents.backends.local_shell import DEFAULT_EXECUTE_TIMEOUT, LocalShellBackend - - -class TestDefaultTimeoutConstant: - """Tests for the named default timeout constant.""" - - def test_default_timeout_uses_constant(self) -> None: - """Backend created without explicit timeout should use the default constant.""" - backend = LocalShellBackend() - assert backend._default_timeout == DEFAULT_EXECUTE_TIMEOUT +from deepagents.backends.local_shell import LocalShellBackend class TestInitTimeoutValidation: """Tests for timeout validation in __init__.""" - def test_custom_timeout_accepted(self) -> None: - """Custom positive timeout should be stored.""" - backend = LocalShellBackend(timeout=300) - assert backend._default_timeout == 300 - def test_zero_timeout_raises(self) -> None: """Zero timeout should raise ValueError.""" with pytest.raises(ValueError, match="timeout must be positive"): diff --git a/libs/deepagents/tests/unit_tests/test_middleware.py b/libs/deepagents/tests/unit_tests/test_middleware.py index 170f8e84da..506a70272d 100644 --- a/libs/deepagents/tests/unit_tests/test_middleware.py +++ b/libs/deepagents/tests/unit_tests/test_middleware.py @@ -135,32 +135,6 @@ def test_multiple_middleware(self): class TestFilesystemMiddleware: - def test_init_default(self): - middleware = FilesystemMiddleware() - assert isinstance(middleware.backend, StateBackend) - assert middleware._custom_system_prompt is None - assert len(middleware.tools) == 8 # All tools including execute and delete - - def test_init_with_composite_backend(self): - backend = CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend(namespace=lambda _rt: ("filesystem",))}) - middleware = FilesystemMiddleware(backend=backend) - assert isinstance(middleware.backend, CompositeBackend) - assert middleware._custom_system_prompt is None - assert len(middleware.tools) == 8 # All tools including execute and delete - - def test_init_custom_system_prompt_default(self): - middleware = FilesystemMiddleware(system_prompt="Custom system prompt") - assert isinstance(middleware.backend, StateBackend) - assert middleware._custom_system_prompt == "Custom system prompt" - assert len(middleware.tools) == 8 # All tools including execute and delete - - def test_init_custom_system_prompt_with_composite(self): - backend = CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend(namespace=lambda _rt: ("filesystem",))}) - middleware = FilesystemMiddleware(backend=backend, system_prompt="Custom system prompt") - assert isinstance(middleware.backend, CompositeBackend) - assert middleware._custom_system_prompt == "Custom system prompt" - assert len(middleware.tools) == 8 # All tools including execute and delete - def test_init_custom_tool_descriptions_default(self): middleware = FilesystemMiddleware(custom_tool_descriptions={"ls": "Custom ls tool description"}) assert isinstance(middleware.backend, StateBackend) diff --git a/libs/deepagents/tests/unit_tests/test_models.py b/libs/deepagents/tests/unit_tests/test_models.py index c3b51598d7..2853ace14b 100644 --- a/libs/deepagents/tests/unit_tests/test_models.py +++ b/libs/deepagents/tests/unit_tests/test_models.py @@ -34,7 +34,6 @@ from deepagents.profiles.provider._nvidia import ( _NVIDIA_APP_ORIGIN, _NVIDIA_BILLING_ORIGIN_HEADER, - _nvidia_attribution_kwargs, ) from deepagents.profiles.provider._openrouter import ( _OPENROUTER_ALLOW_AZURE_ENV, @@ -1166,10 +1165,6 @@ def test_openrouter_provider_profile_has_pre_init_and_factory(self) -> None: assert profile.pre_init is not None assert profile.init_kwargs_factory is not None - def test_nvidia_provider_profile_has_attribution_factory(self) -> None: - profile = get_provider_profile("nvidia:nvidia/nemotron-3-super-120b-a12b") - assert profile.init_kwargs_factory is _nvidia_attribution_kwargs - def test_openai_has_no_built_in_harness_profile(self) -> None: assert _get_harness_profile("openai:gpt-5") is None diff --git a/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py b/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py index 1738827376..78c1070a4b 100644 --- a/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py +++ b/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py @@ -13,7 +13,6 @@ from deepagents.profiles.harness._nvidia_nemotron_3_ultra import ( _DEFAULT_READ_LIMIT, _EMPTY_TOOL_PLACEHOLDER, - _HARNESS_PROFILE_SUFFIX_MARKER, ChatNVIDIAMessageCompatibilityMiddleware, EntityResolutionGuardMiddleware, FinalAnswerGuardMiddleware, @@ -27,9 +26,7 @@ ReadFileContinuationNoticeMiddleware, _tool_name_is_domain, _tool_name_is_mutation, - register, ) -from deepagents.profiles.harness.harness_profiles import _HARNESS_PROFILES if TYPE_CHECKING: from pathlib import Path @@ -933,35 +930,3 @@ def test_entity_resolution_guard_keeps_current_entity_branch_bound() -> None: assert update["nemotron_entity_guard_fired"] is True assert update["jump_to"] == "model" assert "service_id 8514" in update["messages"][0].content - - -def test_register_adds_ultra3_profiles_for_supported_providers() -> None: - """Every supported Nemotron Ultra spec should receive profile data.""" - original = dict(_HARNESS_PROFILES) - try: - _HARNESS_PROFILES.clear() - register() - - for spec in _EXPECTED_NEMOTRON_ULTRA_MODEL_SPECS: - profile = _HARNESS_PROFILES[spec] - middleware = profile.materialize_extra_middleware() - - assert _HARNESS_PROFILE_SUFFIX_MARKER in (profile.system_prompt_suffix or "") - assert "whole/full file" in profile.tool_description_overrides["read_file"] - assert [entry.name for entry in middleware] == [ - "NemotronProgressBudgetMiddleware", - "NemotronPolicyNudgeMiddleware", - "NemotronToolCallShim", - "ReadFileContinuationNoticeMiddleware", - "ToolRetryMiddleware", - "ModelRateLimitRetryMiddleware", - "ChatNVIDIAMessageCompatibilityMiddleware", - "NemotronReasoningTagCleanupMiddleware", - "NemotronTextToolCallParser", - "FollowupDisciplineMiddleware", - "EntityResolutionGuardMiddleware", - "FinalAnswerGuardMiddleware", - ] - finally: - _HARNESS_PROFILES.clear() - _HARNESS_PROFILES.update(original) diff --git a/libs/deepagents/tests/unit_tests/test_rubric_example.py b/libs/deepagents/tests/unit_tests/test_rubric_example.py index 2e8dbdc90e..eedcf02ec9 100644 --- a/libs/deepagents/tests/unit_tests/test_rubric_example.py +++ b/libs/deepagents/tests/unit_tests/test_rubric_example.py @@ -6,13 +6,9 @@ import sys from pathlib import Path from types import ModuleType -from typing import TYPE_CHECKING, cast import pytest -if TYPE_CHECKING: - from collections.abc import Callable - class _DotenvModule(ModuleType): """Minimal `dotenv` replacement for importing the example.""" @@ -36,21 +32,3 @@ def _load_example(monkeypatch: pytest.MonkeyPatch) -> ModuleType: module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module - - -def test_project_is_resolved_after_dotenv_load(monkeypatch: pytest.MonkeyPatch) -> None: - module = _load_example(monkeypatch) - - def load_dotenv(dotenv_path: str) -> bool: - assert dotenv_path == "settings" - monkeypatch.setenv("LANGSMITH_PROJECT", "project-from-dotenv") - return True - - monkeypatch.delenv("LANGSMITH_PROJECT", raising=False) - monkeypatch.setattr(module, "load_dotenv", load_dotenv) - load_environment = cast( - "Callable[[str | None], str]", - module._load_environment, - ) - - assert load_environment("settings") == "project-from-dotenv" diff --git a/libs/deepagents/tests/unit_tests/test_version.py b/libs/deepagents/tests/unit_tests/test_version.py index 92ed8becc4..a2a343085a 100644 --- a/libs/deepagents/tests/unit_tests/test_version.py +++ b/libs/deepagents/tests/unit_tests/test_version.py @@ -496,10 +496,3 @@ def test_plain_release_install(self) -> None: def test_editable_install_gets_local_segment(self) -> None: with patch("deepagents._version._is_editable_install", return_value=True): assert _lc_version() == f"{__version__}+editable" - - def test_caches_editable_install_lookup(self) -> None: - with patch("deepagents._version._is_editable_install", return_value=False) as is_editable_install: - assert _lc_version() == __version__ - assert _lc_version() == __version__ - - is_editable_install.assert_called_once_with() From 39772a3d50a45100e8178204c4d8a968b9931a1a Mon Sep 17 00:00:00 2001 From: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:21:18 +0000 Subject: [PATCH 2/3] test(sdk): retain meaningful regression guards Restore coverage for requirements that the audit marked for rewriting while keeping only redundant implementation checks removed. Relax the Nemotron middleware assertion to preserve capabilities without pinning exact ordering.\n\nCo-authored-by: open-swe[bot] --- .../backends/test_filesystem_backend.py | 7 ++- .../backends/test_langsmith_sandbox.py | 16 +++++ .../unit_tests/backends/test_protocol.py | 5 ++ .../backends/test_sandbox_backend.py | 5 ++ .../middleware/test_memory_middleware.py | 29 +++++++++ .../middleware/test_rubric_middleware.py | 21 +++++++ .../middleware/test_skills_middleware.py | 31 ++++++++++ .../test_subagent_middleware_init.py | 37 ++++++++++++ .../middleware/test_summarization_factory.py | 45 +++++++++++++- .../test_summarization_middleware.py | 59 +++++++++++++++++++ .../tests/unit_tests/test_artifacts_root.py | 28 +++++++++ .../tests/unit_tests/test_local_shell.py | 16 ++++- .../tests/unit_tests/test_middleware.py | 26 ++++++++ .../tests/unit_tests/test_models.py | 5 ++ .../unit_tests/test_nemotron_ultra_profile.py | 29 +++++++++ .../tests/unit_tests/test_rubric_example.py | 21 +++++++ .../tests/unit_tests/test_version.py | 7 +++ 17 files changed, 384 insertions(+), 3 deletions(-) diff --git a/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py b/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py index c3bb9b8fd3..523c8695b0 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py @@ -19,7 +19,7 @@ from deepagents.backends.filesystem import FilesystemBackend from deepagents.backends.protocol import DeleteResult, EditResult, GrepMatch, ReadResult, WriteResult from deepagents.backends.utils import format_grep_matches -from deepagents.middleware.filesystem import FilesystemMiddleware +from deepagents.middleware.filesystem import GLOB_TIMEOUT, FilesystemMiddleware def require_ripgrep() -> None: @@ -1158,6 +1158,11 @@ def test_glob_mid_iteration_oserror_is_error_not_truncated(tmp_path: Path, monke assert result.truncated is False +def test_glob_backend_budget_below_middleware_deadline() -> None: + """The backend glob budget must stay below the middleware's outer deadline so partial results win first.""" + assert fs_module._DEFAULT_GLOB_TIMEOUT < GLOB_TIMEOUT + + def test_glob_supports_brace_expansion(tmp_path: Path) -> None: """Glob enables brace expansion via `wcmatch`, diverging from stdlib `rglob` (which is literal).""" (tmp_path / "a.py").write_text("x") diff --git a/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py b/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py index cd2a36ddfc..d18f90fb66 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py +++ b/libs/deepagents/tests/unit_tests/backends/test_langsmith_sandbox.py @@ -9,6 +9,7 @@ import pytest from langsmith.sandbox import ResourceNotFoundError, SandboxClientError +from deepagents.backends import sandbox as base_sandbox from deepagents.backends.langsmith import LangSmithSandbox from deepagents.backends.sandbox import MAX_BINARY_BYTES, MAX_OUTPUT_BYTES, TRUNCATION_MSG @@ -541,6 +542,21 @@ def test_write_preflight_runs_existence_check() -> None: assert base64.b64encode(b"/app/test.txt").decode("ascii") in cmd_arg +def test_max_binary_bytes_constant_matches_template() -> None: + """Python `MAX_BINARY_BYTES` constant stays in lockstep with the heredoc literal. + + Drift here would silently desync `LangSmithSandbox.read()` from + `BaseSandbox.read()` because the template does not import the constant. + """ + assert "MAX_BINARY_BYTES = 500 * 1024" in base_sandbox._READ_COMMAND_TEMPLATE + assert MAX_BINARY_BYTES == 500 * 1024 + + +def test_max_output_bytes_constant_matches_template() -> None: + assert "MAX_OUTPUT_BYTES = 500 * 1024" in base_sandbox._READ_COMMAND_TEMPLATE + assert MAX_OUTPUT_BYTES == 500 * 1024 + + def _make_async_sandbox() -> tuple[LangSmithSandbox, MagicMock, MagicMock]: sb, mock_sdk = _make_sandbox() async_sdk = MagicMock() diff --git a/libs/deepagents/tests/unit_tests/backends/test_protocol.py b/libs/deepagents/tests/unit_tests/backends/test_protocol.py index 7c47bb07c5..a5053e96a4 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_protocol.py +++ b/libs/deepagents/tests/unit_tests/backends/test_protocol.py @@ -13,6 +13,7 @@ from deepagents.backends.filesystem import _map_exception_to_standard_error from deepagents.backends.protocol import ( ASYNC_GREP_TIMEOUT, + DEFAULT_GREP_TIMEOUT, BackendProtocol, DeleteResult, GrepResult, @@ -154,6 +155,10 @@ async def test_aexecute(self, sandbox_backend: BareSandboxBackend) -> None: class TestAgrepTimeout: """Tests for `agrep` async timeout safety net.""" + def test_agrep_timeout_exceeds_two_sync_grep_phases(self) -> None: + """`agrep` gives `FilesystemBackend` headroom for `rg` timeout plus fallback timeout.""" + assert ASYNC_GREP_TIMEOUT > (2 * DEFAULT_GREP_TIMEOUT) + async def test_agrep_returns_error_on_timeout(self, backend: BareBackend) -> None: """`agrep` catches `TimeoutError` and returns `GrepResult` with error.""" seen_timeout = None diff --git a/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py b/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py index f35c6d96f2..88d0616295 100644 --- a/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py +++ b/libs/deepagents/tests/unit_tests/backends/test_sandbox_backend.py @@ -1099,6 +1099,11 @@ def test_edit_command_template_format() -> None: assert "__DEEPAGENTS_EDIT_EOF__" in cmd +def test_edit_command_template_ends_with_newline() -> None: + """Test that _EDIT_COMMAND_TEMPLATE preserves the trailing newline after EOF.""" + assert _EDIT_COMMAND_TEMPLATE.endswith("\n") + + def test_edit_tmpfile_template_format() -> None: """Test that _EDIT_TMPFILE_TEMPLATE can be formatted without KeyError.""" old_b64 = base64.b64encode(b"/tmp/old").decode("ascii") diff --git a/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py index 0628ef031b..ae63d42176 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_memory_middleware.py @@ -616,6 +616,35 @@ async def test_agent_with_memory_middleware_async(tmp_path: Path) -> None: assert "Test async loading" in content +def test_memory_middleware_with_state_backend() -> None: + """Test that MemoryMiddleware can be initialized with StateBackend instance.""" + sources: list[str] = ["/memory/AGENTS.md"] + middleware = MemoryMiddleware( + backend=StateBackend(), + sources=sources, + ) + + # Verify the middleware was created successfully + assert middleware is not None + assert isinstance(middleware._backend, StateBackend) + assert len(middleware.sources) == 1 + assert middleware.sources[0] == "/memory/AGENTS.md" + + +def test_memory_middleware_with_store_backend_instance() -> None: + """Test that MemoryMiddleware can be initialized with StoreBackend instance.""" + store = InMemoryStore() + sources: list[str] = ["/memory/AGENTS.md"] + middleware = MemoryMiddleware( + backend=StoreBackend(store=store, namespace=_assistant_id_namespace), + sources=sources, + ) + + # Verify the middleware was created successfully + assert middleware is not None + assert isinstance(middleware._backend, StoreBackend) + + def test_memory_middleware_with_store_backend_assistant_id() -> None: """Test namespace isolation: each assistant_id gets its own memory namespace.""" # Setup diff --git a/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py index b84cf6bb81..eed124c501 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_rubric_middleware.py @@ -159,6 +159,14 @@ async def _ainvoke( class TestConstruction: + def test_defaults(self) -> None: + mw = RubricMiddleware(model=_STUB_MODEL) + assert mw.max_iterations == 3 + assert mw._model == _STUB_MODEL + assert mw._tools == [] + # `system_prompt` defaults to the built-in grader prompt. + assert "grader" in mw._system_prompt.lower() + def test_missing_model_raises(self) -> None: # `model` is keyword-only and required -- omitting it is a TypeError # from the function signature itself. @@ -196,6 +204,19 @@ def test_max_iterations_non_int_rejected(self) -> None: with pytest.raises(TypeError): RubricMiddleware(model=_STUB_MODEL, max_iterations="3") # type: ignore[arg-type] + def test_tools_propagated(self) -> None: + @tool + def my_tool(query: str) -> str: + """A tool.""" + return query + + mw = RubricMiddleware(model=_STUB_MODEL, tools=[my_tool]) + assert mw._tools == [my_tool] + + def test_custom_system_prompt_stored(self) -> None: + mw = RubricMiddleware(model=_STUB_MODEL, system_prompt="be strict") + assert mw._system_prompt == "be strict" + # ---------------------------------------------------------------------- # # before_agent semantics diff --git a/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py index 2eaed4aaee..10095b5322 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_skills_middleware.py @@ -1601,6 +1601,37 @@ def test_agent_with_skills_middleware_system_prompt(tmp_path: Path) -> None: assert "test-skill" in content, "System prompt should mention the skill name" +def test_skills_middleware_with_state_backend() -> None: + """Test that SkillsMiddleware can be initialized with StateBackend instance.""" + sources = ["/skills/user"] + middleware = SkillsMiddleware( + backend=StateBackend(), + sources=sources, + ) + + # Verify the middleware was created successfully + assert middleware is not None + assert isinstance(middleware._backend, StateBackend) + assert len(middleware.sources) == 1 + assert middleware.sources[0] == "/skills/user" + + +def test_skills_middleware_with_store_backend_instance() -> None: + """Test that SkillsMiddleware can be initialized with StoreBackend instance.""" + store = InMemoryStore() + sources = ["/skills/user"] + middleware = SkillsMiddleware( + backend=StoreBackend(store=store, namespace=_assistant_id_namespace), + sources=sources, + ) + + # Verify the middleware was created successfully + assert middleware is not None + assert isinstance(middleware._backend, StoreBackend) + assert len(middleware.sources) == 1 + assert middleware.sources[0] == "/skills/user" + + async def test_agent_with_skills_middleware_async(tmp_path: Path) -> None: """Test that skills middleware works with async agent invocation.""" backend = FilesystemBackend(root_dir=str(tmp_path), virtual_mode=False) diff --git a/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py b/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py index 00601784a2..3a1ae72e54 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_subagent_middleware_init.py @@ -439,6 +439,43 @@ def invoke( assert beta.update["messages"][0].content == "agent-beta" assert graph.config is None + def test_middleware_delegates_to_create_sub_agent(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Middleware should use the shared entrypoint for declarative subagents.""" + graph = self._make_echo_graph() + calls: list[tuple[object, type | None]] = [] + + class CustomState(MessagesState): + pass + + def fake_create_sub_agent( + spec: object, + *, + state_schema: type | None = None, + response_format: object = None, + ) -> object: + del response_format + calls.append((spec, state_schema)) + return graph + + monkeypatch.setattr("deepagents.middleware.subagents.create_sub_agent", fake_create_sub_agent) + + SubAgentMiddleware( + backend=StateBackend(), + subagents=[ + { + "name": "agent-alpha", + "description": "First binding", + "system_prompt": "Work on the task.", + "model": "test-model", + "tools": [], + }, + ], + state_schema=CustomState, + ) + + assert len(calls) == 1 + assert calls[0][1] is CustomState + def test_multiple_subagents_with_interrupt_on(self) -> None: """Test creating agent with multiple subagents that have interrupt_on configured.""" agent = create_agent( diff --git a/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py b/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py index bf14803b8d..fa8e351a07 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_summarization_factory.py @@ -1,11 +1,12 @@ """Unit tests for the summarization middleware factory.""" +from collections.abc import Iterable from inspect import Parameter, signature from typing import Any, cast from unittest.mock import MagicMock import pytest -from langchain_core.messages import AIMessage +from langchain_core.messages import AIMessage, MessageLikeRepresentation from deepagents.middleware.summarization import create_summarization_middleware from tests.unit_tests.chat_model import GenericFakeChatModel @@ -21,6 +22,28 @@ def _make_model(*, with_profile_limit: int | None) -> GenericFakeChatModel: return model +def test_factory_uses_profile_based_defaults() -> None: + """Uses fraction-based defaults when model profile has `max_input_tokens`.""" + model = _make_model(with_profile_limit=120_000) + middleware = create_summarization_middleware(model, cast("Any", MagicMock())) + + assert middleware._lc_helper.trigger == ("fraction", 0.85) + assert middleware._lc_helper.keep == ("fraction", 0.10) + assert middleware._truncate_args_trigger == ("fraction", 0.85) + assert middleware._truncate_args_keep == ("fraction", 0.10) + + +def test_factory_uses_fallback_defaults_without_profile() -> None: + """Uses fixed token/message defaults when no model profile is available.""" + model = _make_model(with_profile_limit=None) + middleware = create_summarization_middleware(model, cast("Any", MagicMock())) + + assert middleware._lc_helper.trigger == ("tokens", 170000) + assert middleware._lc_helper.keep == ("messages", 6) + assert middleware._truncate_args_trigger == ("messages", 20) + assert middleware._truncate_args_keep == ("messages", 20) + + def test_factory_default_prompt_explains_media_references() -> None: """Explains preserved media tags in the default summary prompt.""" model = _make_model(with_profile_limit=None) @@ -36,6 +59,26 @@ def test_factory_default_prompt_explains_media_references() -> None: assert "call `read_file` on the referenced path" in rendered +def test_factory_surfaces_summarization_knobs() -> None: + """Passes explicit summarization settings through to the middleware.""" + model = _make_model(with_profile_limit=120_000) + + def token_counter(messages: Iterable[MessageLikeRepresentation]) -> int: + return len(list(messages)) + + middleware = create_summarization_middleware( + model, + cast("Any", MagicMock()), + summary_prompt="custom summary prompt: {messages}", + trim_tokens_to_summarize=123, + token_counter=token_counter, + ) + + assert middleware._lc_helper.summary_prompt == "custom summary prompt: {messages}" + assert middleware._lc_helper.trim_tokens_to_summarize == 123 + assert middleware._lc_helper.token_counter is token_counter + + def test_factory_summarization_knobs_are_keyword_only() -> None: """Requires optional factory controls to be passed by name.""" params = signature(create_summarization_middleware).parameters diff --git a/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py b/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py index d0565af778..7065c37d4f 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_summarization_middleware.py @@ -326,6 +326,19 @@ async def handler(req: ModelRequest) -> "ModelResponse": class TestSummarizationMiddlewareInit: """Tests for middleware initialization.""" + def test_init_with_backend(self) -> None: + """Test initialization with a backend instance.""" + backend = MockBackend() + middleware = SummarizationMiddleware( + model=make_mock_model(), + backend=backend, + trigger=("messages", 5), + keep=("messages", 3), + ) + + assert middleware._backend is backend + assert middleware._history_path_prefix == "/conversation_history" + def test_langchain_deprecated_kwargs_are_forwarded(self) -> None: """LangChain-owned deprecated arguments retain their upstream behavior.""" with pytest.warns(DeprecationWarning, match="(?:max_tokens_before_summary|messages_to_keep) is deprecated"): @@ -3114,6 +3127,24 @@ def counting_token_counter(_messages: list[BaseMessage], **_kwargs: Any) -> int: ) return middleware, calls + def test_token_counter_called_once_per_model_call(self) -> None: + middleware, calls = self._make_counting_middleware() + state = cast("AgentState[Any]", {"messages": make_conversation_messages()}) + + _, captured_request = call_wrap_model_call(middleware, state, make_mock_runtime()) + + assert captured_request is not None # Handler ran; nothing was summarized. + assert calls["count"] == 1 + + async def test_token_counter_called_once_per_model_call_async(self) -> None: + middleware, calls = self._make_counting_middleware() + state = cast("AgentState[Any]", {"messages": make_conversation_messages()}) + + _, captured_request = await call_awrap_model_call(middleware, state, make_mock_runtime()) + + assert captured_request is not None # Handler ran; nothing was summarized. + assert calls["count"] == 1 + def _make_truncating_counting_middleware( self, ) -> tuple[SummarizationMiddleware, dict[str, int]]: @@ -3164,6 +3195,34 @@ def _truncatable_state(self) -> "AgentState[Any]": ] return cast("AgentState[Any]", {"messages": messages}) + def test_token_counter_recounts_when_truncation_modifies_messages(self) -> None: + middleware, calls = self._make_truncating_counting_middleware() + + _, captured_request = call_wrap_model_call(middleware, self._truncatable_state(), make_mock_runtime()) + + assert captured_request is not None # Handler ran; nothing was summarized. + # Truncation changed the message set, so the count is refreshed: + # once before truncation, once after. + assert calls["count"] == 2 + # Confirm the modify path was genuinely taken (not a vacuous recount). + first_ai = captured_request.messages[0] + assert isinstance(first_ai, AIMessage) + assert first_ai.tool_calls[0]["args"]["content"] == "x" * 20 + "...(argument truncated)" + + async def test_token_counter_recounts_when_truncation_modifies_messages_async(self) -> None: + middleware, calls = self._make_truncating_counting_middleware() + + _, captured_request = await call_awrap_model_call(middleware, self._truncatable_state(), make_mock_runtime()) + + assert captured_request is not None # Handler ran; nothing was summarized. + # Truncation changed the message set, so the count is refreshed: + # once before truncation, once after. + assert calls["count"] == 2 + # Confirm the modify path was genuinely taken (not a vacuous recount). + first_ai = captured_request.messages[0] + assert isinstance(first_ai, AIMessage) + assert first_ai.tool_calls[0]["args"]["content"] == "x" * 20 + "...(argument truncated)" + class _OpaqueCounter: """Wraps a counter so its signature cannot be introspected. diff --git a/libs/deepagents/tests/unit_tests/test_artifacts_root.py b/libs/deepagents/tests/unit_tests/test_artifacts_root.py index 6bc06f707d..679503031d 100644 --- a/libs/deepagents/tests/unit_tests/test_artifacts_root.py +++ b/libs/deepagents/tests/unit_tests/test_artifacts_root.py @@ -7,6 +7,8 @@ from deepagents.backends.state import StateBackend from deepagents.backends.store import StoreBackend from deepagents.middleware.filesystem import FilesystemMiddleware +from deepagents.middleware.summarization import create_summarization_middleware +from tests.unit_tests.chat_model import GenericFakeChatModel as FakeChatModel def _make_store_backend(): @@ -33,6 +35,12 @@ def test_custom_artifacts_root(self) -> None: class TestFilesystemMiddlewareArtifactsRoot: + def test_trailing_slash_normalized(self) -> None: + backend = _make_composite_backend(artifacts_root="/workspace/") + mw = FilesystemMiddleware(backend=backend) + assert mw._large_tool_results_prefix == "/workspace/large_tool_results" + assert mw._conversation_history_prefix == "/workspace/conversation_history" + def test_large_tool_result_eviction_uses_artifacts_root(self) -> None: backend = _make_composite_backend(artifacts_root="/workspace") mw = FilesystemMiddleware(backend=backend, tool_token_limit_before_evict=100) @@ -64,6 +72,26 @@ def test_large_tool_result_eviction_default_root(self) -> None: assert resp.content == b"x" * 5000 +class TestCreateSummarizationMiddlewareArtifactsRoot: + def test_default_history_path_prefix(self) -> None: + backend = _make_store_backend() + model = FakeChatModel(messages=iter([])) + mw = create_summarization_middleware(model, backend) + assert mw._history_path_prefix == "/conversation_history" + + def test_trailing_slash_normalized(self) -> None: + backend = _make_composite_backend(artifacts_root="/workspace/") + model = FakeChatModel(messages=iter([])) + mw = create_summarization_middleware(model, backend) + assert mw._history_path_prefix == "/workspace/conversation_history" + + def test_root_slash_no_double_slash(self) -> None: + backend = _make_store_backend() + model = FakeChatModel(messages=iter([])) + mw = create_summarization_middleware(model, backend) + assert mw._history_path_prefix == "/conversation_history" + + class TestCompositeBackendEvictionArtifactsRoot: """Tests for eviction with CompositeBackend and custom artifacts_root.""" diff --git a/libs/deepagents/tests/unit_tests/test_local_shell.py b/libs/deepagents/tests/unit_tests/test_local_shell.py index 5c7a40df33..6952e74a70 100644 --- a/libs/deepagents/tests/unit_tests/test_local_shell.py +++ b/libs/deepagents/tests/unit_tests/test_local_shell.py @@ -6,12 +6,26 @@ import pytest -from deepagents.backends.local_shell import LocalShellBackend +from deepagents.backends.local_shell import DEFAULT_EXECUTE_TIMEOUT, LocalShellBackend + + +class TestDefaultTimeoutConstant: + """Tests for the named default timeout constant.""" + + def test_default_timeout_uses_constant(self) -> None: + """Backend created without explicit timeout should use the default constant.""" + backend = LocalShellBackend() + assert backend._default_timeout == DEFAULT_EXECUTE_TIMEOUT class TestInitTimeoutValidation: """Tests for timeout validation in __init__.""" + def test_custom_timeout_accepted(self) -> None: + """Custom positive timeout should be stored.""" + backend = LocalShellBackend(timeout=300) + assert backend._default_timeout == 300 + def test_zero_timeout_raises(self) -> None: """Zero timeout should raise ValueError.""" with pytest.raises(ValueError, match="timeout must be positive"): diff --git a/libs/deepagents/tests/unit_tests/test_middleware.py b/libs/deepagents/tests/unit_tests/test_middleware.py index 506a70272d..170f8e84da 100644 --- a/libs/deepagents/tests/unit_tests/test_middleware.py +++ b/libs/deepagents/tests/unit_tests/test_middleware.py @@ -135,6 +135,32 @@ def test_multiple_middleware(self): class TestFilesystemMiddleware: + def test_init_default(self): + middleware = FilesystemMiddleware() + assert isinstance(middleware.backend, StateBackend) + assert middleware._custom_system_prompt is None + assert len(middleware.tools) == 8 # All tools including execute and delete + + def test_init_with_composite_backend(self): + backend = CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend(namespace=lambda _rt: ("filesystem",))}) + middleware = FilesystemMiddleware(backend=backend) + assert isinstance(middleware.backend, CompositeBackend) + assert middleware._custom_system_prompt is None + assert len(middleware.tools) == 8 # All tools including execute and delete + + def test_init_custom_system_prompt_default(self): + middleware = FilesystemMiddleware(system_prompt="Custom system prompt") + assert isinstance(middleware.backend, StateBackend) + assert middleware._custom_system_prompt == "Custom system prompt" + assert len(middleware.tools) == 8 # All tools including execute and delete + + def test_init_custom_system_prompt_with_composite(self): + backend = CompositeBackend(default=StateBackend(), routes={"/memories/": StoreBackend(namespace=lambda _rt: ("filesystem",))}) + middleware = FilesystemMiddleware(backend=backend, system_prompt="Custom system prompt") + assert isinstance(middleware.backend, CompositeBackend) + assert middleware._custom_system_prompt == "Custom system prompt" + assert len(middleware.tools) == 8 # All tools including execute and delete + def test_init_custom_tool_descriptions_default(self): middleware = FilesystemMiddleware(custom_tool_descriptions={"ls": "Custom ls tool description"}) assert isinstance(middleware.backend, StateBackend) diff --git a/libs/deepagents/tests/unit_tests/test_models.py b/libs/deepagents/tests/unit_tests/test_models.py index 2853ace14b..c3b51598d7 100644 --- a/libs/deepagents/tests/unit_tests/test_models.py +++ b/libs/deepagents/tests/unit_tests/test_models.py @@ -34,6 +34,7 @@ from deepagents.profiles.provider._nvidia import ( _NVIDIA_APP_ORIGIN, _NVIDIA_BILLING_ORIGIN_HEADER, + _nvidia_attribution_kwargs, ) from deepagents.profiles.provider._openrouter import ( _OPENROUTER_ALLOW_AZURE_ENV, @@ -1165,6 +1166,10 @@ def test_openrouter_provider_profile_has_pre_init_and_factory(self) -> None: assert profile.pre_init is not None assert profile.init_kwargs_factory is not None + def test_nvidia_provider_profile_has_attribution_factory(self) -> None: + profile = get_provider_profile("nvidia:nvidia/nemotron-3-super-120b-a12b") + assert profile.init_kwargs_factory is _nvidia_attribution_kwargs + def test_openai_has_no_built_in_harness_profile(self) -> None: assert _get_harness_profile("openai:gpt-5") is None diff --git a/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py b/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py index 78c1070a4b..f476fb0c57 100644 --- a/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py +++ b/libs/deepagents/tests/unit_tests/test_nemotron_ultra_profile.py @@ -13,6 +13,7 @@ from deepagents.profiles.harness._nvidia_nemotron_3_ultra import ( _DEFAULT_READ_LIMIT, _EMPTY_TOOL_PLACEHOLDER, + _HARNESS_PROFILE_SUFFIX_MARKER, ChatNVIDIAMessageCompatibilityMiddleware, EntityResolutionGuardMiddleware, FinalAnswerGuardMiddleware, @@ -26,7 +27,9 @@ ReadFileContinuationNoticeMiddleware, _tool_name_is_domain, _tool_name_is_mutation, + register, ) +from deepagents.profiles.harness.harness_profiles import _HARNESS_PROFILES if TYPE_CHECKING: from pathlib import Path @@ -930,3 +933,29 @@ def test_entity_resolution_guard_keeps_current_entity_branch_bound() -> None: assert update["nemotron_entity_guard_fired"] is True assert update["jump_to"] == "model" assert "service_id 8514" in update["messages"][0].content + + +def test_register_adds_ultra3_profiles_for_supported_providers() -> None: + """Every supported Nemotron Ultra spec should receive profile data.""" + original = dict(_HARNESS_PROFILES) + try: + _HARNESS_PROFILES.clear() + register() + + for spec in _EXPECTED_NEMOTRON_ULTRA_MODEL_SPECS: + profile = _HARNESS_PROFILES[spec] + middleware = profile.materialize_extra_middleware() + + assert _HARNESS_PROFILE_SUFFIX_MARKER in (profile.system_prompt_suffix or "") + assert "whole/full file" in profile.tool_description_overrides["read_file"] + assert {entry.name for entry in middleware} >= { + "NemotronToolCallShim", + "ReadFileContinuationNoticeMiddleware", + "ChatNVIDIAMessageCompatibilityMiddleware", + "NemotronTextToolCallParser", + "EntityResolutionGuardMiddleware", + "FinalAnswerGuardMiddleware", + } + finally: + _HARNESS_PROFILES.clear() + _HARNESS_PROFILES.update(original) diff --git a/libs/deepagents/tests/unit_tests/test_rubric_example.py b/libs/deepagents/tests/unit_tests/test_rubric_example.py index eedcf02ec9..835c136c19 100644 --- a/libs/deepagents/tests/unit_tests/test_rubric_example.py +++ b/libs/deepagents/tests/unit_tests/test_rubric_example.py @@ -6,9 +6,13 @@ import sys from pathlib import Path from types import ModuleType +from typing import TYPE_CHECKING, cast import pytest +if TYPE_CHECKING: + from collections.abc import Callable + class _DotenvModule(ModuleType): """Minimal `dotenv` replacement for importing the example.""" @@ -32,3 +36,20 @@ def _load_example(monkeypatch: pytest.MonkeyPatch) -> ModuleType: module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module + + +def test_project_is_resolved_after_dotenv_load(monkeypatch: pytest.MonkeyPatch) -> None: + module = _load_example(monkeypatch) + + def load_dotenv(_dotenv_path: str) -> bool: + monkeypatch.setenv("LANGSMITH_PROJECT", "project-from-dotenv") + return True + + monkeypatch.delenv("LANGSMITH_PROJECT", raising=False) + monkeypatch.setattr(module, "load_dotenv", load_dotenv) + load_environment = cast( + "Callable[[str | None], str]", + module._load_environment, + ) + + assert load_environment("settings") == "project-from-dotenv" diff --git a/libs/deepagents/tests/unit_tests/test_version.py b/libs/deepagents/tests/unit_tests/test_version.py index a2a343085a..92ed8becc4 100644 --- a/libs/deepagents/tests/unit_tests/test_version.py +++ b/libs/deepagents/tests/unit_tests/test_version.py @@ -496,3 +496,10 @@ def test_plain_release_install(self) -> None: def test_editable_install_gets_local_segment(self) -> None: with patch("deepagents._version._is_editable_install", return_value=True): assert _lc_version() == f"{__version__}+editable" + + def test_caches_editable_install_lookup(self) -> None: + with patch("deepagents._version._is_editable_install", return_value=False) as is_editable_install: + assert _lc_version() == __version__ + assert _lc_version() == __version__ + + is_editable_install.assert_called_once_with() From 8327f172673a4ec3759ce17a6fc31c58033dbc0c Mon Sep 17 00:00:00 2001 From: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:26:28 +0000 Subject: [PATCH 3/3] test(sdk): cover compact backend writes Replace the deleted backend-identity assertion with an observable compact-history write through the configured backend.\n\nCo-authored-by: open-swe[bot] --- .../middleware/test_compact_tool.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py b/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py index f41608cd1a..e409bf4f8b 100644 --- a/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py +++ b/libs/deepagents/tests/unit_tests/middleware/test_compact_tool.py @@ -470,6 +470,30 @@ def test_cutoff_exceeds_message_count(self) -> None: assert result[0] is summary_msg +class TestCompactBackendUsage: + """Test backend use for compact offloading.""" + + def test_compact_writes_history_to_configured_backend(self) -> None: + """Compacted history is written through the configured backend.""" + backend = _make_mock_backend() + summarization = SummarizationMiddleware( + model=_make_mock_model(), + backend=backend, + trigger=("fraction", 0.85), + keep=("messages", 2), + ) + middleware = SummarizationToolMiddleware(summarization) + messages = [HumanMessage(content=f"Message {index}") for index in range(9)] + messages.append(_ai_message_with_usage(120_000)) + result = middleware._run_compact(_make_runtime(messages)) + + event = result.update["_summarization_event"] + backend.write.assert_called_once() + path, content = backend.write.call_args.args + assert path == event["file_path"] + assert "Message 0" in content + + class TestComputeStateCutoff: """Tests for _compute_state_cutoff arithmetic."""