Tasks: User Story 1 — Configure a Google ADK Agent via YAML¶
Feature: 023-choose-your-backend | Story: US1 | Date: 2026-03-15 Spec: spec.md | Plan: plan.md | Data Model: data-model.md
User Story: A platform user wants to use Google's Gemini models through the Google ADK backend. They create an agent.yaml specifying provider: google and a Gemini model name. HoloDeck auto-detects the ADK backend, initializes the agent, and executes the request.
Acceptance Scenarios:
1. Valid agent.yaml with model.provider: google and model.name: gemini-2.5-flash → auto-detect ADK, invoke, return response with text and token usage.
2. agent.yaml with provider: google and vectorstore tools → tools initialized and available to ADK agent.
3. agent.yaml with provider: google and MCP tools → MCP tools invoked, results returned with tool call/result records.
4. agent.yaml with provider: google → holodeck chat establishes multi-turn session with context maintained.
Phase 1: Setup — Shared Infrastructure¶
These tasks create the foundational enums, dependencies, and routing changes that US1 (and future stories) depend on.
-
T001 [US1] Add
google-adk>=1.2.0,<2.0.0as an optional dependency group inpyproject.tomlat/Users/justinbarias/Documents/Git/python/agentlab/pyproject.toml. Add a new[project.optional-dependencies]entry:google-adk = ["google-adk>=1.2.0,<2.0.0"]. Do NOT add it to the coredependencieslist (lazy import only). -
T002 [US1] Add
GOOGLE = "google"toProviderEnumin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/llm.py. Place it after the existingOLLAMAentry. This enablesmodel.provider: googlein agent.yaml. -
T003 [US1] Create
BackendEnumin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/llm.py. Add a newBackendEnum(str, Enum)class with four values:SEMANTIC_KERNEL = "semantic_kernel",CLAUDE = "claude",GOOGLE_ADK = "google_adk",AGENT_FRAMEWORK = "agent_framework". Place it afterProviderEnum. This enum identifies the agent runtime, decoupled from the LLM provider. -
T004 [US1] Add optional
backendfield to theAgentmodel in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/agent.py. ImportBackendEnumfromholodeck.models.llm. Add field:backend: BackendEnum | None = Field(default=None, description="Agent runtime override. When None, auto-detected from model.provider."). Place it after themodelfield. -
T005 [US1] Add optional
google_adkconfig field to theAgentmodel in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/agent.py. ImportGoogleADKConfigfromholodeck.models.google_adk_config(will be created in T008). Add field:google_adk: GoogleADKConfig | None = Field(default=None, description="Google ADK-specific settings. Applicable when resolved backend is google_adk."). Place it after theclaudefield. -
T006 [US1] Write unit tests for
ProviderEnum.GOOGLEandBackendEnumin/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/models/test_llm.py. Add tests verifying: (a)ProviderEnum("google") == ProviderEnum.GOOGLE, (b) all fourBackendEnumvalues are accessible and have correct string values, (c) invalid enum values raiseValueError. Run withpytest tests/unit/models/test_llm.py -n auto.
Phase 2: Foundational — Blocking Prerequisites¶
These tasks implement the BackendSelector routing changes and Agent model validators that must exist before the ADK backend can be wired up.
-
T007 [US1] Update
BackendSelector.select()in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/selector.pyto route by thebackendfield first, falling back to auto-detect frommodel.provider. ImportBackendEnumfromholodeck.models.llm. Whenagent.backendis explicitly set, use it directly. Whenagent.backend is None, resolve frommodel.providerusing the default routing table:openai→agent_framework,azure_openai→agent_framework,anthropic→claude,ollama→claude,google→google_adk. This is a clean break from the legacy SK routing — users who need Semantic Kernel must setbackend: semantic_kernelexplicitly. ForBackendEnum.GOOGLE_ADK, lazily importADKBackendfromholodeck.lib.backends.adk_backendand instantiate it. ForBackendEnum.AGENT_FRAMEWORK, lazily importAFBackendfromholodeck.lib.backends.af_backendand instantiate it (stub — implemented in US2). ForBackendEnum.CLAUDE, use existingClaudeBackend. Wrap the import in atry/except ImportErrorthat raisesBackendInitErrorwith message:"Google ADK backend requires the 'google-adk' package. Install it with: pip install holodeck[google-adk]". -
T008 [US1] Create
GoogleADKConfigPydantic model in new file/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/google_adk_config.py. DefineStreamingMode(str, Enum)with valuesNONE = "none"andSSE = "sse". DefineGoogleADKConfig(BaseModel)withConfigDict(extra="forbid")and fields:streaming_mode: StreamingMode = Field(default=StreamingMode.NONE),code_execution: bool = Field(default=False),max_iterations: int | None = Field(default=None, ge=1),output_key: str = Field(default="output"). Follow the pattern ofClaudeConfigin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/claude_config.py. -
T009 [US1] Create
@model_validator(mode='after')in theAgentmodel at/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/agent.pythat enforcesembedding_provideris set when the resolved backend isgoogle_adk(orclaude) AND vectorstore or hierarchical_document tools are configured. ImportBackendEnumandProviderEnum. Resolve the effective backend: ifself.backendis set, use it; otherwise auto-detect fromself.model.providerusing the default routing table (same as T007:openai→agent_framework,azure_openai→agent_framework,anthropic→claude,ollama→claude,google→google_adk). Check if any tool inself.toolshastype == "vectorstore"ortype == "hierarchical_document". If so, andself.embedding_provider is None, raiseValueErrorwith message:"embedding_provider is required when using vectorstore or hierarchical_document tools with the {backend} backend". This fixes the pre-existing gap foranthropicprovider. -
T010 [US1] Create backend/provider compatibility validator as a
@model_validator(mode='after')in theAgentmodel at/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/agent.py. Whenself.backendis explicitly set, validate thatself.model.provideris compatible using the compatibility matrix:google_adksupportsgoogle,openai,azure_openai,anthropic,ollama;claudesupportsanthropiconly;semantic_kernelsupportsopenai,azure_openai,ollama. RaiseValueErrorwith a clear message listing compatible providers for the chosen backend if validation fails. -
T011 [US1] Write unit tests for
GoogleADKConfigin new file/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/models/test_google_adk_config.py. Test: (a) default values are correct, (b)streaming_mode: "sse"parses toStreamingMode.SSE, (c)max_iterations: 0raisesValidationError(ge=1), (d)max_iterations: nullis valid, (e) extra fields are rejected (extra="forbid"), (f)code_execution: trueis accepted. Run withpytest tests/unit/models/test_google_adk_config.py -n auto. -
T012 [US1] Write unit tests for updated
BackendSelectorrouting in/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/lib/backends/test_selector.py. Add tests: (a)provider: googleauto-detects to ADK backend (mockADKBackend), (b) explicitbackend: google_adkroutes to ADK, (c) explicitbackend: google_adkwithprovider: openairoutes to ADK (cross-provider), (d) missinggoogle-adkpackage raisesBackendInitErrorwith install instructions, (e)provider: openaiauto-routes toagent_framework(NOTsemantic_kernel— clean break), (f)provider: azure_openaiauto-routes toagent_framework, (g)provider: ollamaauto-routes toclaude, (h) existingprovider: anthropicrouting still works (Claude), (i) explicitbackend: semantic_kernelwithprovider: openairoutes to SK (opt-in only). Run withpytest tests/unit/lib/backends/test_selector.py -n auto. -
T013 [US1] Write unit tests for Agent model validators (embedding_provider and backend/provider compatibility) in
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/models/test_agent.py. Add tests: (a)provider: google+ vectorstore tool + noembedding_provider→ValueError, (b)provider: google+ vectorstore tool +embedding_providerset → valid, (c)provider: anthropic+ vectorstore tool + noembedding_provider→ValueError, (d)backend: claude+provider: openai→ValueErrorwith compatible provider list, (e)backend: google_adk+provider: openai→ valid, (f)backend: google_adk+provider: google→ valid. Run withpytest tests/unit/models/test_agent.py -n auto.
Phase 3: User Story 1 Implementation — ADK Backend¶
These tasks implement the ADK backend, session, tool adapters, and OTel instrumentation.
-
T014 [US1] Create
ADKBackendclass in new file/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/adk_backend.py. The class must implement theAgentBackendprotocol fromholodeck.lib.backends.base. Use lazy imports:from google.adk.agents import LlmAgent,from google.adk.runners import Runner,from google.adk.sessions import InMemorySessionService. Constructor acceptsagent_config: Agentandtool_instances: dict[str, Any] | None. State attributes:_agent: LlmAgent | None,_runner: Runner | None,_session_service: InMemorySessionService | None,_tools: list[Any],_config: GoogleADKConfig | None(fromagent_config.google_adk). Implementinitialize(): createInMemorySessionService, build theLlmAgentwith name fromagent_config.name, model fromagent_config.model.name(forprovider: google) or LiteLLM prefix{provider}/{name}(for cross-provider), instruction from resolved instructions, and adapted tools. CreateRunner(agent=agent, session_service=session_service). Implementteardown(): set references to None. -
T015 [US1] Implement
invoke_once()onADKBackendin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/adk_backend.py. Create a temporary session viasession_service.create_session(app_name=agent_name, user_id=unique_id). Buildnew_messageusinggoogle.genai.types.Content(role="user", parts=[Part.from_text(message)]). Callrunner.run_async(user_id, session_id, new_message)and iterate theAsyncGenerator[Event]. Collect events: extract final response text fromevent.content.partswhenevent.is_final_response(), collect tool calls fromevent.get_function_calls(), extract token usage fromevent.usage_metadata(map toTokenUsage(prompt_tokens, completion_tokens, total_tokens)). Count turns by tracking function call/response cycles. ReturnExecutionResultwith all fields populated. Wrap errors inBackendSessionError. -
T016 [US1] Create
ADKSessionclass in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/adk_backend.py. ImplementsAgentSessionprotocol. Constructor acceptsrunner: Runner,session_service: InMemorySessionService,app_name: str,user_id: str,session_id: str. Implementsend(): build Content message, callrunner.run_async(), collect events same asinvoke_once(), returnExecutionResult. Conversation history is automatically maintained by theInMemorySessionServiceacross calls with the samesession_id. Implementsend_streaming(): useRunConfig(streaming_mode=StreamingMode.SSE)passed torunner.run_async(), yield partial text from non-final events. Implementclose(): no-op (in-memory session cleanup is handled by garbage collection). Implementcreate_session()onADKBackend: generate uniqueuser_idandsession_idusingpython-ulid, create session viasession_service.create_session(), returnADKSessioninstance. -
T017 [US1] Create ADK tool adapter module at
/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/adk_tool_adapters.py. Implementadapt_tools_for_adk(tools: list[ToolUnion], tool_instances: dict[str, Any] | None) -> list[Any]that converts HoloDeck tool definitions to ADK-compatible tools. Handle each tool type: (a) VectorstoreTool: Create an async callable wrappingtool_instances[name].search(query)that takes aquery: strparameter and returns search results as a string. ADK auto-wraps plain callables asFunctionTool. (b) HierarchicalDocumentTool: Same pattern as VectorstoreTool. (c) FunctionTool: Load the Python function fromtool.moduleandtool.functionusingimportlib, pass the callable directly (ADK auto-wraps). (d) MCPTool (stdio): CreateMcpToolset(connection_params=StdioConnectionParams(command=tool.command, args=tool.args)). Import fromgoogle.adk.tools.mcp_tool.mcp_toolset. (e) MCPTool (sse): CreateMcpToolset(connection_params=SseConnectionParams(url=tool.url)). (f) MCPTool (http): CreateMcpToolset(connection_params=StreamableHTTPConnectionParams(url=tool.url)). All ADK imports must be lazy (inside the function body). Note: SkillTool (type: skill) adapter is OUT OF SCOPE for US1 — it is deferred to US7 (tasks T730–T738). US1 builds adapters for 4 tool types only: vectorstore, hierarchical_document, function, and MCP. If a SkillTool is encountered, raiseToolErrorwith message:"SkillTool adapter is not yet supported for the ADK backend (planned for US7)". -
T018 [US1] Create basic OTel span wrapping for ADK backend in new file
/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/adk_otel.py. Implement a decorator or context manageradk_span(operation_name: str)that creates an OpenTelemetry span wrapping ADK operations. Useopentelemetry.trace.get_tracer("holodeck.backends.adk"). Wrap the following operations:invoke_once,session.send, and tool calls. Add span attributes:gen_ai.system = "google_adk",gen_ai.request.model,gen_ai.response.model,gen_ai.usage.prompt_tokens,gen_ai.usage.completion_tokens. Apply the decorator/context manager inadk_backend.pyinvoke_once()andADKSession.send()methods. -
T019 [US1] Update
/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/__init__.pyto conditionally export ADK types. Add a try/except block that importsADKBackendandADKSessionfromholodeck.lib.backends.adk_backendand adds them to__all__. IfImportErroroccurs (google-adk not installed), skip the exports silently. This ensures existing imports work when google-adk is not installed. -
T020 [US1] Write unit tests for
ADKBackendin new file/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/lib/backends/test_adk_backend.py. Mock allgoogle.adkimports usingpytest-mock. Test: (a)initialize()createsInMemorySessionService,LlmAgent, andRunnerwith correct parameters, (b)invoke_once()sends message and returnsExecutionResultwith response text, token usage, and turn count, (c)invoke_once()with tool calls populatestool_callsandtool_resultsinExecutionResult, (d)create_session()returns anADKSessionwith unique user_id and session_id, (e)teardown()cleans up references, (f)ADKSession.send()returnsExecutionResultwith conversation context maintained, (g)ADKSession.close()completes without error, (h) cross-provider model name construction (e.g.,provider: openai+name: gpt-4o→ model string"openai/gpt-4o"), (i)provider: google+name: gemini-2.5-flash→ model string"gemini-2.5-flash"(no prefix). Use@pytest.mark.asynciofor all async tests. Run withpytest tests/unit/lib/backends/test_adk_backend.py -n auto. -
T021 [US1] Write unit tests for ADK tool adapters in new file
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/lib/backends/test_adk_tool_adapters.py. Mockgoogle.adkimports. Test: (a) VectorstoreTool adaptation creates a callable that wrapsinstance.search(), (b) HierarchicalDocumentTool adaptation creates a callable that wrapsinstance.search(), (c) FunctionTool adaptation loads the Python function and returns it directly, (d) MCPTool stdio createsMcpToolsetwithStdioConnectionParams, (e) MCPTool sse createsMcpToolsetwithSseConnectionParams, (f) empty tools list returns empty list, (g) unknown tool type raisesToolError, (h) SkillTool (type: skill) raisesToolErrorindicating deferral to US7. Note: US1 only covers 4 tool types (vectorstore, hierarchical_document, function, MCP). SkillTool adapter is deferred to US7 (tasks T730–T738). Run withpytest tests/unit/lib/backends/test_adk_tool_adapters.py -n auto.
Phase 4: Polish — Docs, Cleanup, Quickstart Validation¶
-
T022 [US1] Validate quickstart examples work end-to-end. Verify the "Google ADK with Gemini" example from
/Users/justinbarias/Documents/Git/python/agentlab/docs/product-specs/023-choose-your-backend/quickstart.mdcan be loaded as a validAgentconfig. Create a simple integration-style unit test in/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/models/test_adk_quickstart_configs.pythat parses the three ADK quickstart YAML examples (basic Gemini, cross-provider OpenAI, streaming withgoogle_adksection) through theAgentPydantic model and asserts they validate without errors. This does NOT require google-adk installed — it only tests config parsing. -
T023 [US1] Run full test suite and code quality checks. Execute
make test-unit,make lint,make format-check, andmake type-checkfrom the project root at/Users/justinbarias/Documents/Git/python/agentlab. Fix any failures introduced by US1 changes. Ensure all existing tests still pass (zero regressions). All new files must pass Black formatting, Ruff linting, and MyPy type checking.
Dependencies & Execution Order¶
T001 (pyproject.toml deps)
└─ no dependencies — start immediately
T002 (ProviderEnum.GOOGLE) ──┐
T003 (BackendEnum) ──────────┤
├──→ T004 (Agent.backend field)
├──→ T007 (BackendSelector routing)
└──→ T009 (embedding_provider validator)
T010 (backend/provider compatibility validator)
T008 (GoogleADKConfig model) ──→ T005 (Agent.google_adk field)
└──→ T014 (ADKBackend constructor uses config)
T004 + T005 ──→ T009, T010, T013 (Agent validators + tests)
T006 (enum tests) ── can run after T002 + T003
T011 (GoogleADKConfig tests) ── can run after T008
T012 (BackendSelector tests) ── can run after T007
T007 (BackendSelector) ──→ T014 (ADKBackend)
T017 (tool adapters) ──→ T014 (ADKBackend uses adapted tools)
T014 ──→ T015 (invoke_once)
T014 ──→ T016 (ADKSession + create_session)
T014 ──→ T018 (OTel spans applied to backend)
T014 ──→ T019 (__init__.py exports)
T014 + T015 + T016 ──→ T020 (ADKBackend tests)
T017 ──→ T021 (tool adapter tests)
T020 + T021 + T013 ──→ T022 (quickstart validation)
T022 ──→ T023 (full suite + quality checks)
Parallel Execution Example¶
The following groups can be executed in parallel within each phase:
Phase 1 parallel group: - T001 (pyproject.toml) | T002 (ProviderEnum) | T003 (BackendEnum) | T008 (GoogleADKConfig) — all independent
Phase 2 parallel group (after Phase 1): - T004 + T005 (Agent model fields) — depend on T002, T003, T008 - T006 (enum tests) — depends on T002, T003 - T011 (config tests) — depends on T008 - T007 (BackendSelector) — depends on T002, T003
Phase 2 second wave: - T009 + T010 (Agent validators) — depend on T004 - T012 (BackendSelector tests) — depends on T007 - T013 (Agent validator tests) — depends on T009, T010
Phase 3 parallel group (after Phase 2): - T017 (tool adapters) | T018 (OTel) — independent of each other, both depend on Phase 2 - T014 + T015 + T016 (backend + session) — depend on T007, T008, T017
Phase 3 second wave: - T019 (init exports) | T020 (backend tests) | T021 (adapter tests) — after T014-T017
Phase 4 sequential: - T022 → T023
Implementation Strategy¶
Guiding Principles¶
-
Lazy imports only: All
google.adk.*imports MUST be inside function/method bodies or guarded bytry/except ImportError. The ADK package must never cause import-time failures when not installed. -
Protocol compliance:
ADKBackendmust satisfyAgentBackendprotocol andADKSessionmust satisfyAgentSessionprotocol from/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/base.py. Useisinstance()checks in tests to verify protocol compliance. -
ExecutionResult completeness: Every
invoke_once()andsession.send()call must return a fully populatedExecutionResultwith:response(text),tool_calls(list of dicts with tool name/args),tool_results(list of dicts with tool output),token_usage(TokenUsage dataclass),num_turns(int),is_error(bool),error_reason(str | None). -
Cross-provider model naming: For
provider: google, pass model name directly to ADK (e.g.,"gemini-2.5-flash"). For other providers withbackend: google_adk, construct LiteLLM prefix:"{provider}/{model_name}"(e.g.,"openai/gpt-4o"). See research.md R1. -
Error handling: Wrap all ADK exceptions in HoloDeck backend errors:
BackendInitErrorfor initialization failures,BackendSessionErrorfor runtime invocation failures,BackendTimeoutErrorfor timeouts. -
Test isolation: All unit tests must mock
google.adkimports entirely. No test should require the google-adk package to be installed. Useunittest.mock.patchorpytest-mockto mock thegoogle.adkmodule hierarchy.
Key Files Reference¶
| File | Action | Purpose |
|---|---|---|
pyproject.toml |
MODIFY | Add google-adk optional dep |
src/holodeck/models/llm.py |
MODIFY | Add GOOGLE to ProviderEnum, create BackendEnum |
src/holodeck/models/agent.py |
MODIFY | Add backend, google_adk fields + validators |
src/holodeck/models/google_adk_config.py |
NEW | GoogleADKConfig Pydantic model |
src/holodeck/lib/backends/selector.py |
MODIFY | Route by backend field, lazy ADK import |
src/holodeck/lib/backends/adk_backend.py |
NEW | ADKBackend + ADKSession |
src/holodeck/lib/backends/adk_tool_adapters.py |
NEW | HoloDeck → ADK tool conversion |
src/holodeck/lib/backends/adk_otel.py |
NEW | Basic OTel span wrapping |
src/holodeck/lib/backends/__init__.py |
MODIFY | Conditional ADK exports |
tests/unit/models/test_llm.py |
MODIFY | Enum tests |
tests/unit/models/test_google_adk_config.py |
NEW | Config model tests |
tests/unit/models/test_agent.py |
MODIFY | Validator tests |
tests/unit/lib/backends/test_selector.py |
MODIFY | Routing tests |
tests/unit/lib/backends/test_adk_backend.py |
NEW | Backend + session tests |
tests/unit/lib/backends/test_adk_tool_adapters.py |
NEW | Tool adapter tests |
tests/unit/models/test_adk_quickstart_configs.py |
NEW | Quickstart YAML validation |