Tasks: Seamless Tool Portability Across Backends (US3)¶
Input: Design documents from /docs/product-specs/023-choose-your-backend/
Prerequisites: plan.md (required), spec.md (required), research.md, data-model.md
Tests: TDD approach — write tests FIRST, verify they FAIL, then implement.
Organization: Tasks are grouped by phase. US3 is primarily an integration testing and validation story that verifies the tool adapters built in US1 (ADK) and US2 (AF) produce semantically identical results across all four backends.
Format: [ID] [P?] [US3] Description¶
- [P]: Can run in parallel (different files, no dependencies)
- [US3]: All tasks belong to User Story 3
- Include exact file paths in descriptions
Path Conventions¶
- Single project:
src/holodeck/,tests/at repository root
Phase 1: Setup — Test Fixtures and Shared Test Infrastructure¶
Purpose: Create shared test fixtures and helpers that provide identical tool configurations across all backends, enabling direct portability comparison.
- T301 [P] [US3] Create shared tool fixture module
tests/fixtures/tool_portability_fixtures.pywith factory functions that return canonical HoloDeck tool configurations for each tool type:make_vectorstore_config()returning aVectorstoreToolconfig dict,make_function_tool_config()returning a function tool config dict,make_mcp_stdio_config()returning an MCP stdio tool config dict,make_mcp_sse_config()returning an MCP SSE/HTTP tool config dict, andmake_hierarchical_doc_config()returning aHierarchicalDocumentToolConfigdict. Each factory must return the same canonical config regardless of which backend test imports it. - T302 [P] [US3] Create shared mock helpers in
tests/fixtures/tool_portability_fixtures.py:make_mock_vectorstore_instance()returning a mockVectorStoreToolwith a.search()async method that returns deterministic results (3 fixed documents with scores), andmake_mock_hierarchical_doc_instance()returning a mockHierarchicalDocumentToolwith identical.search()behavior. These mocks simulate the initialized tool instances that adapters wrap. - T303 [P] [US3] Create shared mock helpers in
tests/fixtures/tool_portability_fixtures.py:make_mock_embedding_service()returning a mockEmbeddingServicethat returns deterministic embedding vectors (fixed 3-dimensional vectors for reproducibility). This ensures embedding behavior is identical across backend tests. - T304 [US3] Register the fixture module in
tests/conftest.pyby addingpytest_pluginsentry or importing fixtures so they are available to both unit and integration test files. Verify fixtures are discoverable by runningpytest --collect-only tests/fixtures/tool_portability_fixtures.py.
Checkpoint: Shared fixtures ready — portability tests can reference canonical tool configurations.
Phase 2: Foundational — Tool Adapter Interface Consistency¶
Purpose: Ensure all four backend tool adapters expose a consistent interface pattern so portability tests can compare outputs uniformly. Refactor if needed.
- T305 [P] [US3] Add a
normalize_tool_output(raw_output: Any) -> dict[str, Any]helper function totests/fixtures/tool_portability_fixtures.pythat takes the raw output from any backend's tool invocation and normalizes it to a common schema:{"results": list[dict], "query": str, "tool_name": str}. This enables direct comparison of tool outputs across backends regardless of native format differences. - T306 [P] [US3] Review and document (as code comments in
tests/fixtures/tool_portability_fixtures.py) the tool adapter entry points for each backend: (1)src/holodeck/lib/backends/tool_adapters.py— Claude:adapt_tools()returns list ofSdkMcpTool, (2)src/holodeck/lib/backends/adk_tool_adapters.py— ADK:adapt_tools_for_adk()returns list of ADK-native tools, (3)src/holodeck/lib/backends/af_tool_adapters.py— AF:adapt_tools_for_af()returns list of AFFunctionTool/MCP tools, (4) SK backend usestool_initializer.pydirectly. Document the expected input/output contract for each. - T307 [US3] Verify that all four tool adapter modules (
tool_adapters.py,adk_tool_adapters.py,af_tool_adapters.py, and the SK path insk_backend.py) accept the samelist[ToolUnion]input type along with initialized tool instances. If any adapter deviates, add an adapter shim or refactor the entry point to accept(tool_configs: list[ToolUnion], tool_instances: dict[str, Any])consistently. File paths:src/holodeck/lib/backends/tool_adapters.py,src/holodeck/lib/backends/adk_tool_adapters.py,src/holodeck/lib/backends/af_tool_adapters.py,src/holodeck/lib/backends/sk_backend.py.
Checkpoint: All adapters have consistent interfaces — portability comparison tests can proceed.
Phase 3: User Story 3 — Cross-Backend Validation Tests¶
Purpose: Validate that switching backend preserves tool behavior. Tests are organized by tool type, then by backend pair.
3a. VectorStore Tool Portability¶
- T308 [P] [US3] Write unit test
test_vectorstore_adk_adapter_output_matches_claudeintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical vectorstore config from T301 usingadapt_tools_for_adk(), invoke the adapted tool's search with query"test query", verify the response contains the same 3 documents with identical content and scores as the mock from T302. Compare against Claude adapter output (adapt same config viatool_adapters.py, invoke, normalize both outputs via T305). - T309 [P] [US3] Write unit test
test_vectorstore_af_adapter_output_matches_claudeintests/unit/lib/backends/test_af_tool_adapters.py— same pattern as T308 but adapt viaadapt_tools_for_af()and compare AF output against Claude adapter output. VerifyFunctionToolwrapping preserves search semantics. - T310 [P] [US3] Write unit test
test_vectorstore_adk_adapter_output_matches_afintests/unit/lib/backends/test_adk_tool_adapters.py— adapt canonical vectorstore config via both ADK and AF adapters, invoke both with same query, normalize outputs, assert results are semantically identical (same documents, same scores, same ordering). - T311 [P] [US3] Write unit test
test_hierarchical_doc_adk_adapter_output_matches_claudeintests/unit/lib/backends/test_adk_tool_adapters.py— same pattern as T308 but usingmake_hierarchical_doc_config()andmake_mock_hierarchical_doc_instance()from T301/T302. - T312 [P] [US3] Write unit test
test_hierarchical_doc_af_adapter_output_matches_claudeintests/unit/lib/backends/test_af_tool_adapters.py— same pattern as T309 but for hierarchical document tools.
3b. Function Tool Portability¶
- T313 [P] [US3] Write unit test
test_function_tool_adk_adapter_callableintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical function tool config from T301 usingadapt_tools_for_adk(), verify the adapted tool is a plain callable (ADK auto-wraps), invoke it with test arguments, assert return value matches expected output. The function under test should be a simple deterministic function (e.g.,def add(a: int, b: int) -> int: return a + b). - T314 [P] [US3] Write unit test
test_function_tool_af_adapter_callableintests/unit/lib/backends/test_af_tool_adapters.py— adapt the same canonical function tool config usingadapt_tools_for_af(), verify result is an AFFunctionTool, invoke it with the same test arguments, assert return value is identical to T313. - T315 [P] [US3] Write unit test
test_function_tool_cross_backend_identical_outputintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical function tool via ADK, AF, and Claude adapters, invoke all three with identical arguments, assert all three return the same result. This validates FR-006 (tool adapters translate definitions to native format) for function tools.
3c. MCP Tool Portability¶
- T316 [P] [US3] Write unit test
test_mcp_stdio_adk_adapter_creates_toolsetintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical MCP stdio config from T301 usingadapt_tools_for_adk(), verify the result is anMcpToolsetinstance withStdioConnectionParamscontaining the correctcommandandargs. Mock the MCP process to avoid spawning real subprocesses. - T317 [P] [US3] Write unit test
test_mcp_stdio_af_adapter_creates_mcp_toolintests/unit/lib/backends/test_af_tool_adapters.py— adapt the same canonical MCP stdio config usingadapt_tools_for_af(), verify the result is anMCPStdioToolinstance with matchingcommandandargs. Mock the MCP process. - T318 [P] [US3] Write unit test
test_mcp_sse_adk_adapter_creates_toolsetintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical MCP SSE/HTTP config from T301, verifyMcpToolsetwithSseConnectionParamsorStreamableHTTPConnectionParamscontaining the correct URL. - T319 [P] [US3] Write unit test
test_mcp_sse_af_adapter_creates_http_toolintests/unit/lib/backends/test_af_tool_adapters.py— adapt the same MCP SSE/HTTP config, verifyMCPStreamableHTTPToolwith the correct URL.
3d. End-to-End Portability (Integration)¶
- T320 [US3] Create integration test file
tests/integration/test_tool_portability.pywith module docstring explaining this file validates SC-001 (switching backends preserves tool behavior). Addpytest.importorskip()guards forgoogle.adkandagent_frameworkpackages. Import shared fixtures from T301-T303. - T321 [P] [US3] Write integration test
test_vectorstore_portability_across_all_backendsintests/integration/test_tool_portability.py— for each backend (SK, Claude, ADK, AF): create an agent config withbackendset explicitly and a vectorstore tool, initialize the backend with mocked LLM responses, invokeinvoke_once()with a query that triggers the vectorstore tool, capture theExecutionResult.tool_results, normalize outputs, assert all four backends return semantically identical search results. Skip backends whose packages are not installed. - T322 [P] [US3] Write integration test
test_function_tool_portability_across_all_backendsintests/integration/test_tool_portability.py— same pattern as T321 but with a function tool. For each backend, invoke with the same query, assert the function is called with identical arguments and returns the same result inExecutionResult.tool_results. - T323 [P] [US3] Write integration test
test_mcp_stdio_portability_across_backendsintests/integration/test_tool_portability.py— same pattern as T321 but with MCP stdio tools. Mock the MCP subprocess to return deterministic tool results. Verify all backends (that support MCP) produce identicalExecutionResult.tool_results. Note: SK does not support MCP tools natively, so skip SK for this test. - T324 [P] [US3] Write integration test
test_backend_switch_preserves_tool_configintests/integration/test_tool_portability.py— load a single agent.yaml fixture with multiple tool types (vectorstore + function + MCP), run it withbackend: google_adk, then reload the same config withbackend: agent_framework, then withbackend: claude. For each run, assert that all tools are initialized without errors and the agent responds without tool-related failures.
3e. Edge Cases and Error Handling¶
- T325 [P] [US3] Write unit test
test_unsupported_tool_type_raises_errorintests/unit/lib/backends/test_adk_tool_adapters.py— pass a tool config with an unsupported type to the ADK adapter, verify it raises a clear error message listing supported tool types. Repeat for AF adapter intests/unit/lib/backends/test_af_tool_adapters.py. - T326 [P] [US3] Write unit test
test_empty_tool_list_adapts_cleanlyintests/unit/lib/backends/test_adk_tool_adapters.py— pass an empty tool list to ADK and AF adapters, verify both return empty lists without errors. This covers agents configured with no tools switching backends. - T327 [P] [US3] Write unit test
test_tool_adapter_preserves_tool_name_and_descriptionintests/unit/lib/backends/test_adk_tool_adapters.py— for each tool type (vectorstore, function, MCP), adapt via ADK adapter, extract the tool name and description from the adapted object, assert they match the original HoloDeck config. Repeat for AF adapter intests/unit/lib/backends/test_af_tool_adapters.py. - T328 [P] [US3] Write unit test
test_vectorstore_tool_requires_embedding_serviceintests/unit/lib/backends/test_adk_tool_adapters.py— attempt to adapt a vectorstore tool without providing anEmbeddingServiceinstance, verify the adapter raisesToolInitializerErroror equivalent with a message mentioningembedding_provider. Repeat for AF adapter. - T329 [US3] Run all US3 unit tests:
pytest tests/unit/lib/backends/test_adk_tool_adapters.py tests/unit/lib/backends/test_af_tool_adapters.py -n auto -v -k "portability or cross_backend or matches"and verify they PASS. - T330 [US3] Run all US3 integration tests:
pytest tests/integration/test_tool_portability.py -n auto -vand verify they PASS (skipping backends whose packages are not installed).
Checkpoint: All cross-backend tool portability tests pass — US3 acceptance scenarios validated.
3f. Skill Tool Portability¶
Blocked by: US7 (SkillTool model + backend adapters T730-T738)
- T338 [P] [US3] Add
make_skill_config()fixture (inline skill withinstructions+allowed_tools) intests/fixtures/tool_portability_fixtures.py. The factory should return a canonicalSkillToolconfig dict with a deterministic instruction string and a fixed list of allowed tools, consistent with the pattern established in T301. - T339 [P] [US3] Write unit test
test_skill_tool_adk_adapter_output_matches_claudeintests/unit/lib/backends/test_adk_tool_adapters.py— adapt the canonical skill config from T338 usingadapt_tools_for_adk(), compare the adapted tool's structure and invocation output against Claude adapter output (adapt same config viatool_adapters.py). Normalize both outputs via T305 and assert semantic equivalence. - T340 [P] [US3] Write unit test
test_skill_tool_af_adapter_output_matches_claudeintests/unit/lib/backends/test_af_tool_adapters.py— same pattern as T339 but adapt viaadapt_tools_for_af()and compare AF output against Claude adapter output. VerifyFunctionToolwrapping preserves skill invocation semantics. - T341 [US3] Write unit test
test_skill_tool_adk_adapter_output_matches_afintests/unit/lib/backends/test_cross_backend_skill.py— adapt canonical skill config via both ADK and AF adapters, invoke both with the same input, normalize outputs, assert results are semantically identical. - T342 [US3] Write integration test
test_skill_tool_portability_across_backendsintests/integration/test_tool_portability.py— configure an agent with a skill tool, run it withbackend: google_adk, thenbackend: agent_framework, thenbackend: claude. For each backend, verify skill invocation works without errors and produces semantically consistent results. Skip backends whose packages are not installed.
Checkpoint: Skill tool portability validated across all three backends.
Phase 4: Polish — Documentation and Final Validation¶
Purpose: Document tool portability guarantees and ensure code quality.
- T331 [P] [US3] Add a docstring block to
tests/integration/test_tool_portability.pydocumenting the portability guarantee: "HoloDeck guarantees that switching thebackendfield while keeping tool configurations unchanged produces semantically identical tool behavior. This file validates that guarantee per SC-001 and FR-006." - T332 [P] [US3] Add inline comments to
src/holodeck/lib/backends/adk_tool_adapters.pyat each tool type handler documenting the portability contract: input format (HoloDeck ToolUnion), output format (ADK-native), and semantic equivalence expectation. - T333 [P] [US3] Add inline comments to
src/holodeck/lib/backends/af_tool_adapters.pyat each tool type handler documenting the same portability contract. - T334 [US3] Run
make formatto format all new and modified files with Black + Ruff. - T335 [US3] Run
make lintand fix any Ruff + Bandit violations in US3 files. - T336 [US3] Run
make type-checkand fix any MyPy errors in US3 files. - T337 [US3] Run full test suite
make testto verify no regressions from US3 changes.
Checkpoint: US3 complete — tool portability is validated, documented, and code quality verified.
Dependencies & Execution Order¶
External Dependencies (BLOCKING)¶
- US1 (ADK Backend): Must be complete before US3. US3 validates
adk_tool_adapters.pybuilt in US1. - US2 (AF Backend): Must be complete before US3. US3 validates
af_tool_adapters.pybuilt in US2. - US6 (EmbeddingService): Must be complete before US3. Vectorstore/hierarchical_document tool portability tests depend on
EmbeddingServiceprotocol andLiteLLMEmbeddingAdapter. - US7 (SkillTool): Must be complete before Phase 3f only. Skill tool portability tests depend on
SkillToolmodel and backend adapters (T730-T738). Other US3 phases can proceed without US7.
Phase Dependencies¶
- Phase 1 (Setup): No internal dependencies — can start immediately after US1 + US2 + US6 are complete
- Phase 2 (Foundational): Depends on Phase 1 completion (needs fixtures)
- Phase 3 (Validation Tests): Depends on Phase 2 completion (needs consistent adapter interfaces)
- Phase 4 (Polish): Depends on Phase 3 completion (all tests must pass first)
Within Phase 3¶
- 3a (VectorStore), 3b (Function), 3c (MCP) can run in parallel — they test independent tool types
- 3d (Integration) depends on 3a, 3b, 3c being complete — integration tests exercise all tool types together
- 3e (Edge Cases) can run in parallel with 3a-3c
- 3f (Skill Tool Portability) depends on US7 completion — can run in parallel with 3a-3e once US7 is done
Parallel Example: Phase 3a + 3b + 3c¶
# All tool-type-specific portability tests can run in parallel:
Task: "Write test_vectorstore_adk_adapter_output_matches_claude in tests/unit/lib/backends/test_adk_tool_adapters.py"
Task: "Write test_vectorstore_af_adapter_output_matches_claude in tests/unit/lib/backends/test_af_tool_adapters.py"
Task: "Write test_function_tool_adk_adapter_callable in tests/unit/lib/backends/test_adk_tool_adapters.py"
Task: "Write test_function_tool_af_adapter_callable in tests/unit/lib/backends/test_af_tool_adapters.py"
Task: "Write test_mcp_stdio_adk_adapter_creates_toolset in tests/unit/lib/backends/test_adk_tool_adapters.py"
Task: "Write test_mcp_stdio_af_adapter_creates_mcp_tool in tests/unit/lib/backends/test_af_tool_adapters.py"
Implementation Strategy¶
Test-Driven Validation (US3 is primarily a testing story)¶
Unlike US1 and US2 which build new adapters, US3 creates the cross-backend validation layer that proves portability works. The strategy is:
- Phase 1: Build shared fixtures that represent the "ground truth" tool configurations and expected outputs
- Phase 2: Verify all four backends' tool adapters have consistent entry points (refactor if needed)
- Phase 3: Write exhaustive cross-backend comparison tests:
- Unit tests compare adapter outputs pairwise (ADK vs Claude, AF vs Claude, ADK vs AF)
- Integration tests run the same tool config through all backends end-to-end
- Edge case tests validate error paths and boundary conditions
- Phase 4: Document the portability guarantee and run full quality checks
Key Files Modified¶
| File | Phase | Changes |
|---|---|---|
tests/fixtures/tool_portability_fixtures.py |
Phase 1 | NEW: Shared fixtures, mocks, normalization helpers |
tests/conftest.py |
Phase 1 | MODIFY: Register fixture module |
tests/unit/lib/backends/test_adk_tool_adapters.py |
Phase 3 | MODIFY: Add portability-focused comparison tests |
tests/unit/lib/backends/test_af_tool_adapters.py |
Phase 3 | MODIFY: Add portability-focused comparison tests |
tests/integration/test_tool_portability.py |
Phase 3 | NEW: End-to-end cross-backend portability tests (incl. skill tool T342) |
tests/unit/lib/backends/test_cross_backend_skill.py |
Phase 3f | NEW: ADK vs AF skill tool portability comparison |
src/holodeck/lib/backends/adk_tool_adapters.py |
Phase 2, 4 | MODIFY: Interface consistency + portability docs |
src/holodeck/lib/backends/af_tool_adapters.py |
Phase 2, 4 | MODIFY: Interface consistency + portability docs |
Acceptance Scenario Traceability¶
| Acceptance Scenario | Validating Tasks |
|---|---|
| AS-1: Vectorstore tools adapted to ADK format, identical results | T308, T310, T321 |
| AS-2: MCP tools adapted to AF format, function correctly | T317, T319, T323 |
| AS-3: Switch AF to SK, tool behavior identical | T321, T322, T324 |
| AS-4: Function tools loadable and callable across backends | T313, T314, T315, T322 |
| AS-4 (extended): Skill tools portable across any two supported backends | T338, T339, T340, T341, T342 |
Notes¶
- [P] tasks = different files or independent test functions, no dependencies
- [US3] label maps all tasks to User Story 3 for traceability
- US3 is a validation/testing story — minimal production code changes expected
- Mock LLM responses in integration tests to avoid real API calls
- Use
pytest.importorskip()for backends that may not be installed - Commit after each phase or logical group of tasks
- Stop at any checkpoint to validate progress independently
- Tool portability is validated by comparing normalized outputs, not internal representations