Tasks: US4 — Backend-Specific Configuration¶
Input: Design documents from /docs/product-specs/023-choose-your-backend/
Prerequisites: plan.md (required), spec.md (required), data-model.md (required), US1 (ADK config model + backend), US2 (AF config model + backend)
Tests: TDD approach — write tests FIRST, verify they FAIL, then implement.
User Story: A platform user wants to tune backend-specific features. For Google ADK: streaming mode, code execution. For Agent Framework: message compaction strategy, client type. These settings are in optional YAML sections (google_adk: or agent_framework:) that are ignored when using other backends.
Format: [ID] [P?] [Story] Description¶
- [P]: Can run in parallel (different files, no dependencies)
- [US4]: All tasks belong to User Story 4
- Include exact file paths in descriptions
Path Conventions¶
- Single project:
src/holodeck/,tests/at repository root
Phase 1: Setup¶
Purpose: N/A — config models (GoogleADKConfig, AgentFrameworkConfig) and backend files (adk_backend.py, af_backend.py) are built in US1 and US2. No new files or dependencies needed for US4.
Phase 2: Foundational — Config Wiring Infrastructure¶
Purpose: Ensure the backend initialize() methods read their respective config sections from the Agent model and store them for runtime use. This is the prerequisite for all US4 feature wiring.
Tests (TDD — write FIRST, verify they FAIL)¶
NOTE: Write these tests FIRST, ensure they FAIL before implementation. All tests calling
initialize()MUST mock external SDK imports and validators to isolate config wiring logic.
-
T001 [P] [US4] Write unit test
test_adk_backend_reads_google_adk_configintests/unit/lib/backends/test_adk_backend.py— create anAgentwithbackend: google_adk,model.provider: google, andgoogle_adk: { streaming_mode: sse, code_execution: true, max_iterations: 5, output_key: result }. Mock ADK SDK imports. CallADKBackend.initialize(). Assertself._configis aGoogleADKConfiginstance withstreaming_mode == StreamingMode.SSE,code_execution == True,max_iterations == 5,output_key == "result" -
T002 [P] [US4] Write unit test
test_adk_backend_uses_defaults_when_no_configintests/unit/lib/backends/test_adk_backend.py— create anAgentwithbackend: google_adk,model.provider: google, and NOgoogle_adksection. CallADKBackend.initialize(). Assertself._configisNoneor defaults are applied (streaming_mode=none, code_execution=False, max_iterations=None, output_key="output") -
T003 [P] [US4] Write unit test
test_af_backend_reads_agent_framework_configintests/unit/lib/backends/test_af_backend.py— create anAgentwithbackend: agent_framework,model.provider: openai, andagent_framework: { compaction_strategy: sliding_window, max_tool_rounds: 10 }. Mock AF SDK imports. CallAFBackend.initialize(). Assertself._configis anAgentFrameworkConfiginstance withcompaction_strategy == CompactionStrategy.SLIDING_WINDOW,max_tool_rounds == 10 -
T004 [P] [US4] Write unit test
test_af_backend_uses_defaults_when_no_configintests/unit/lib/backends/test_af_backend.py— create anAgentwithbackend: agent_framework,model.provider: openai, and NOagent_frameworksection. CallAFBackend.initialize(). Assertself._configisNoneor defaults are applied (compaction_strategy=none, max_tool_rounds=None)
Implementation¶
-
T005 [US4] Ensure
ADKBackend.__init__()insrc/holodeck/lib/backends/adk_backend.pystoresself._config = agent.google_adk(the optionalGoogleADKConfigfrom the Agent model). If US1 already does this, verify and mark as done -
T006 [US4] Ensure
AFBackend.__init__()insrc/holodeck/lib/backends/af_backend.pystoresself._config = agent.agent_framework(the optionalAgentFrameworkConfigfrom the Agent model). If US2 already does this, verify and mark as done. Run tests T001–T004 and verify they PASS
Checkpoint: Config wiring infrastructure confirmed — backends read their config sections correctly.
Phase 3: US4 Implementation — Feature Wiring and Ignore Behavior¶
Purpose: Wire backend-specific config values to actual runtime behavior (streaming, code execution, compaction) and validate that mismatched config sections are silently ignored.
3A: ADK Streaming Mode Wiring¶
Wire
GoogleADKConfig.streaming_modeto ADK runner invocation behavior.
Tests (TDD)¶
-
T008 [P] [US4] Write unit test
test_adk_streaming_mode_sse_delivers_progressivelyintests/unit/lib/backends/test_adk_backend.py— createAgentwithgoogle_adk: { streaming_mode: sse }. Mock ADKRunner. CallADKBackend.invoke_once()orADKSession.send_streaming(). Assert the runner is invoked with SSE streaming enabled (verify the ADKrun_asyncor equivalent is called with streaming parameters). VerifyExecutionResultis returned successfully -
T009 [P] [US4] Write unit test
test_adk_streaming_mode_none_returns_complete_responseintests/unit/lib/backends/test_adk_backend.py— createAgentwithgoogle_adk: { streaming_mode: none }(or no config). CallADKBackend.invoke_once(). Assert the runner is invoked WITHOUT streaming. VerifyExecutionResult.responsecontains the full response
Implementation¶
- T010 [US4] Wire
streaming_modeinADKBackend.invoke_once()andADKSession.send()/ADKSession.send_streaming()insrc/holodeck/lib/backends/adk_backend.py— whenself._configis not None andself._config.streaming_mode == StreamingMode.SSE, use ADK's streaming invocation path (e.g.,runner.run_async()with streaming events). Whennone, use standard synchronous invocation. Default tononewhenself._configisNone. Run tests T008–T009 and verify they PASS
3B: ADK Code Execution Wiring¶
Wire
GoogleADKConfig.code_executionto ADK agent construction.
Tests (TDD)¶
-
T012 [P] [US4] Write unit test
test_adk_code_execution_enabledintests/unit/lib/backends/test_adk_backend.py— createAgentwithgoogle_adk: { code_execution: true }. CallADKBackend.initialize(). Assert the ADKLlmAgentis constructed with code execution capability enabled (verify the constructor args or tools list includes code execution) -
T013 [P] [US4] Write unit test
test_adk_code_execution_disabled_by_defaultintests/unit/lib/backends/test_adk_backend.py— createAgentwith nogoogle_adksection orcode_execution: false. CallADKBackend.initialize(). Assert the ADKLlmAgentdoes NOT have code execution capability
Implementation¶
- T014 [US4] Wire
code_executioninADKBackend.initialize()insrc/holodeck/lib/backends/adk_backend.py— whenself._configis not None andself._config.code_execution == True, add ADK's code execution tool/capability to the agent construction. When False or config is None, omit it. Run tests T012–T013 and verify they PASS
3C: ADK Max Iterations and Output Key Wiring¶
Wire
GoogleADKConfig.max_iterationsandoutput_keyto ADK runner/session behavior.
Tests (TDD)¶
-
T016 [P] [US4] Write unit test
test_adk_max_iterations_limits_agent_loopintests/unit/lib/backends/test_adk_backend.py— createAgentwithgoogle_adk: { max_iterations: 3 }. Mock ADK runner. CallADKBackend.invoke_once(). Assert the runner is configured with max iterations of 3 (verify constructor arg or run parameter) -
T017 [P] [US4] Write unit test
test_adk_output_key_extracts_correct_valueintests/unit/lib/backends/test_adk_backend.py— createAgentwithgoogle_adk: { output_key: "final_answer" }. Mock ADK session state to contain{ "final_answer": "42" }. CallADKBackend.invoke_once(). AssertExecutionResult.responseis"42"(extracted from session state using the configured output key)
Implementation¶
-
T018 [US4] Wire
max_iterationsinADKBackend.initialize()orinvoke_once()insrc/holodeck/lib/backends/adk_backend.py— passmax_iterationsto the ADKRunnerorLlmAgentconstruction when configured. WhenNone, use ADK's default behavior -
T019 [US4] Wire
output_keyinADKBackend.invoke_once()andADKSession.send()insrc/holodeck/lib/backends/adk_backend.py— extract the final response from ADK session state usingself._config.output_key(default:"output"). Fall back to"output"whenself._configisNone. Run tests T016–T017 and verify they PASS
3D: AF Compaction Strategy Wiring¶
Wire
AgentFrameworkConfig.compaction_strategyto AF session/agent behavior.
Tests (TDD)¶
-
T021 [P] [US4] Write unit test
test_af_compaction_sliding_windowintests/unit/lib/backends/test_af_backend.py— createAgentwithagent_framework: { compaction_strategy: sliding_window }. Mock AF SDK. CallAFBackend.create_session()and thenAFSession.send()multiple times. Assert the AF agent or session is configured with sliding window compaction (verify constructor args or session setup includes the compaction strategy) -
T022 [P] [US4] Write unit test
test_af_compaction_summarizationintests/unit/lib/backends/test_af_backend.py— createAgentwithagent_framework: { compaction_strategy: summarization }. Mock AF SDK. CallAFBackend.create_session(). Assert the AF session is configured with summarization compaction strategy -
T023 [P] [US4] Write unit test
test_af_compaction_none_by_defaultintests/unit/lib/backends/test_af_backend.py— createAgentwith noagent_frameworksection. CallAFBackend.create_session(). Assert no compaction is applied (default behavior, no compaction strategy set on the AF session)
Implementation¶
- T024 [US4] Wire
compaction_strategyinAFBackend.create_session()orAFSession.__init__()insrc/holodeck/lib/backends/af_backend.py— whenself._configis not None andself._config.compaction_strategy != CompactionStrategy.NONE, configure the AF session with the appropriate compaction strategy (sliding_window or summarization). Whennoneor config is None, leave session with default (no compaction). Run tests T021–T023 and verify they PASS
3E: AF Max Tool Rounds Wiring¶
Wire
AgentFrameworkConfig.max_tool_roundsto AF agent invocation behavior.
Tests (TDD)¶
-
T026 [P] [US4] Write unit test
test_af_max_tool_rounds_limits_invocationsintests/unit/lib/backends/test_af_backend.py— createAgentwithagent_framework: { max_tool_rounds: 5 }. Mock AF SDK. CallAFBackend.invoke_once(). Assert the AF agent's invocation is configured with max_tool_rounds=5 (verify constructor arg or invocation parameter) -
T027 [P] [US4] Write unit test
test_af_max_tool_rounds_unlimited_by_defaultintests/unit/lib/backends/test_af_backend.py— createAgentwith noagent_frameworksection. CallAFBackend.invoke_once(). Assert no max_tool_rounds limit is applied (default behavior)
Implementation¶
- T028 [US4] Wire
max_tool_roundsinAFBackend.invoke_once()andAFSession.send()insrc/holodeck/lib/backends/af_backend.py— whenself._configis not None andself._config.max_tool_roundsis set, pass it to the AF agent invocation. WhenNoneor config is None, use AF's default (unlimited). Run tests T026–T027 and verify they PASS
3F: Ignore-When-Mismatched Behavior¶
Validate that backend-specific config sections are silently ignored when a different backend is active.
Tests (TDD)¶
-
T030 [P] [US4] Write unit test
test_google_adk_section_ignored_when_backend_is_openaiintests/unit/models/test_agent_config_ignore.py(new file) — createAgentwithmodel.provider: openai, NO explicitbackendfield, andgoogle_adk: { streaming_mode: sse, code_execution: true }. Assert the Agent model is created successfully withoutValidationError. Assertagent.google_adkis a validGoogleADKConfig(Pydantic parses it), but it is not used at runtime. VerifyBackendSelector.select()routes toAFBackend(not ADKBackend) -
T031 [P] [US4] Write unit test
test_agent_framework_section_ignored_when_backend_is_claudeintests/unit/models/test_agent_config_ignore.py— createAgentwithmodel.provider: anthropic, NO explicitbackendfield, andagent_framework: { compaction_strategy: sliding_window }. Assert model creation succeeds. AssertBackendSelector.select()routes toClaudeBackend. Assert theagent_frameworkconfig section is parsed but not consumed -
T032 [P] [US4] Write unit test
test_google_adk_section_ignored_when_backend_is_agent_frameworkintests/unit/models/test_agent_config_ignore.py— createAgentwithbackend: agent_framework,model.provider: openai, andgoogle_adk: { streaming_mode: sse }. Assert model creation succeeds. AssertBackendSelector.select()routes toAFBackend -
T033 [P] [US4] Write unit test
test_agent_framework_section_ignored_when_backend_is_google_adkintests/unit/models/test_agent_config_ignore.py— createAgentwithbackend: google_adk,model.provider: google, andagent_framework: { compaction_strategy: summarization }. Assert model creation succeeds. AssertBackendSelector.select()routes toADKBackend -
T034 [P] [US4] Write unit test
test_both_config_sections_present_only_active_one_usedintests/unit/models/test_agent_config_ignore.py— createAgentwithbackend: google_adk,model.provider: google,google_adk: { streaming_mode: sse }, ANDagent_framework: { compaction_strategy: sliding_window }. Assert model creation succeeds. AssertBackendSelector.select()routes toADKBackend. Assert ADKBackend reads onlygoogle_adkconfig, notagent_framework
Implementation¶
- T035 [US4] Verify that
BackendSelector.select()insrc/holodeck/lib/backends/selector.pydoes NOT validate or reject non-matching config sections. The selector should only read thebackendfield (or auto-detect frommodel.provider) and instantiate the matching backend. Non-matching config sections remain on the Agent model but are never accessed by the selected backend. If the selector currently rejects them, fix it to ignore them. Run tests T030–T034 and verify they PASS
Note: Strict model validation (extra='forbid', invalid enum values) is tested in US1 (test_google_adk_config.py) and US2 (test_af_config.py). Not duplicated here.
Checkpoint: All US4 features wired — streaming, code execution, compaction, max tool rounds, and ignore behavior all verified.
Phase 4: Polish¶
Purpose: Code quality and final validation.
- T036 Run
make formatto format all modified/new files with Black + Ruff - T037 Run
make lintand fix any Ruff + Bandit violations in files touched by US4 - T038 Run
make type-checkand fix any MyPy errors inadk_backend.py,af_backend.py,google_adk_config.py,af_config.py, and new test files - T039 Run full test suite
make testto verify no regressions - T040 Verify acceptance scenarios from user story:
- Scenario 1: agent.yaml with
backend: google_adkandgoogle_adk: { streaming_mode: sse }results in streaming responses delivered progressively - Scenario 2: agent.yaml with
backend: agent_frameworkandagent_framework: { compaction_strategy: sliding_window }results in message history compacted - Scenario 3: agent.yaml with
provider: openaiand agoogle_adk:section present results in section ignored without errors, auto-detected backend used
Dependencies & Execution Order¶
External Dependencies (MUST be complete before US4 begins)¶
- US1:
GoogleADKConfigmodel (src/holodeck/models/google_adk_config.py),ADKBackend+ADKSession(src/holodeck/lib/backends/adk_backend.py),google_adkfield on Agent model (src/holodeck/models/agent.py), unit tests (tests/unit/models/test_google_adk_config.py) - US2:
AgentFrameworkConfigmodel (src/holodeck/models/af_config.py),AFBackend+AFSession(src/holodeck/lib/backends/af_backend.py),agent_frameworkfield on Agent model (src/holodeck/models/agent.py), unit tests (tests/unit/models/test_af_config.py)
Phase Dependencies¶
- Foundational (Phase 2): No external dependencies beyond US1/US2 completion — can start immediately after US1/US2
- Implementation (Phase 3): Depends on Phase 2 completion
- 3A (ADK streaming), 3B (ADK code exec), 3C (ADK iterations/output key) can proceed in parallel
- 3D (AF compaction), 3E (AF max tool rounds) can proceed in parallel
- 3F (ignore behavior) can proceed in parallel with 3A–3E (tests different files)
- Polish (Phase 4): Depends on all Phase 3 sub-phases being complete
Parallel Opportunities¶
- T001, T002, T003, T004 can run in parallel (different test files)
- T008, T009, T012, T013, T016, T017 can run in parallel (same file, independent tests)
- T021, T022, T023, T026, T027 can run in parallel (same file, independent tests)
- T030, T031, T032, T033, T034 can run in parallel (same file, independent tests)
- T036, T037, T038 can run in parallel (independent quality checks)
Key Files Modified/Created¶
| File | Phase | Changes |
|---|---|---|
src/holodeck/lib/backends/adk_backend.py |
Phase 2, 3A–3C | Config wiring, streaming mode, code execution, max iterations, output key |
src/holodeck/lib/backends/af_backend.py |
Phase 2, 3D–3E | Config wiring, compaction strategy, max tool rounds |
src/holodeck/lib/backends/selector.py |
Phase 3F | Verify ignore-when-mismatched behavior (likely no change needed) |
tests/unit/lib/backends/test_adk_backend.py |
Phase 2, 3A–3C | Config wiring + feature wiring tests |
tests/unit/lib/backends/test_af_backend.py |
Phase 2, 3D–3E | Config wiring + feature wiring tests |
tests/unit/models/test_agent_config_ignore.py |
Phase 3F | NEW: Ignore-when-mismatched behavior tests |
Notes¶
- [P] tasks = different files, no dependencies
- [US4] label maps all tasks to User Story 4
- TDD: Verify tests fail before implementing
- Commit after each task or logical group
- Stop at any checkpoint to validate progress
- US4 is primarily a wiring story — most config models and backend skeletons already exist from US1/US2
- The "ignore behavior" tests (Phase 3F) are the most important acceptance tests for this story — they validate that non-matching config sections never cause errors
- Strict model validation (
extra='forbid', invalid enum values) is covered by US1/US2 tests — not duplicated here