Tasks: Configure a Microsoft Agent Framework Agent via YAML (US2)¶
Input: Design documents from /docs/product-specs/023-choose-your-backend/
Prerequisites: plan.md (required), spec.md (required), research.md, data-model.md, quickstart.md
Tests: TDD approach — write tests FIRST, verify they FAIL, then implement.
Dependency: US2 depends on US1's setup phase (BackendEnum, ProviderEnum, BackendSelector routing). Tasks below assume those foundational changes are already merged.
Organization: Tasks are grouped by phase to enable incremental delivery. All tasks belong to User Story 2.
Format: [ID] [P?] [US2] Description¶
- [P]: Can run in parallel (different files, no dependencies)
- [US2]: All tasks belong to User Story 2
- Include exact file paths in descriptions
Path Conventions¶
- Single project:
src/holodeck/,tests/at repository root
Phase 1: Setup (Shared Infrastructure)¶
Purpose: Add AF optional dependency groups to pyproject.toml. BackendEnum, ProviderEnum, and BackendSelector changes are assumed complete from US1.
CRITICAL: US1 setup phase MUST be complete before starting. Specifically: BackendEnum (in src/holodeck/models/llm.py), ProviderEnum updates, and BackendSelector routing (in src/holodeck/lib/backends/selector.py) must already exist.
- T001 [US2] Add
agent-frameworkoptional dependency group topyproject.tomlunder[project.optional-dependencies]withagent-framework-core==1.0.0rc4. Addagent-framework-anthropicsub-extra group withagent-framework-core==1.0.0rc4andagent-framework-anthropic==1.0.0rc4. Addagent-framework-ollamasub-extra group withagent-framework-core==1.0.0rc4andagent-framework-ollama==1.0.0rc4. Runuv lockto regenerate the lock file - T002 [US2] Verify AF package installs correctly by running
uv pip install -e ".[agent-framework]"and confirming thatfrom agent_framework import Agent(or verified import path from P0-1 prerequisite) is importable. Fix package names inpyproject.tomlif verification reveals different names
Phase 2: Foundational (AF-Specific Prerequisites)¶
Purpose: Create the AF-specific config model and add the agent_framework field to the Agent model. If US1 already added the agent_framework field stub to src/holodeck/models/agent.py, skip T004.
CRITICAL: Phase 1 must be complete before starting.
- T003 [US2] Create
src/holodeck/models/af_config.pywith theAgentFrameworkConfigPydantic model. Include:CompactionStrategyenum (none,sliding_window,summarization— usestr, Enumbase classes),compaction_strategy: CompactionStrategyfield (defaultCompactionStrategy.NONE),max_tool_rounds: int | Nonefield (defaultNone,ge=1constraint). Setmodel_config = ConfigDict(extra="forbid"). Add docstrings per PEP 257 and type hints per project standards - T004 [US2] Add
agent_framework: AgentFrameworkConfig | None = Nonefield to theAgentmodel insrc/holodeck/models/agent.py. ImportAgentFrameworkConfigfromholodeck.models.af_config. Add Field with description "Agent Framework backend-specific configuration". This field is only validated when resolved backend isagent_framework; silently ignored otherwise - T005 [P] [US2] Create unit test file
tests/unit/models/test_af_config.pywith tests forAgentFrameworkConfig: (1) test default values (compaction_strategy=none,max_tool_rounds=None), (2) test validcompaction_strategyvalues (none,sliding_window,summarization), (3) test invalidcompaction_strategyvalue raisesValidationError, (4) testmax_tool_roundswith valid value (e.g.,5), (5) testmax_tool_rounds=0raisesValidationError(ge=1), (6) testextra="forbid"rejects unknown fields. Import fromholodeck.models.af_config - T006 [US2] Run tests in
tests/unit/models/test_af_config.pywithpytest tests/unit/models/test_af_config.py -n auto -vand verify all pass
Checkpoint: AF config model ready. Agent model accepts agent_framework config section.
Phase 3: User Story 2 Implementation¶
Purpose: Implement the AF backend, session, tool adapters, OTel wrapping, and provider client selection. This is the core of US2.
3a: AF Tool Adapters¶
- T007 [P] [US2] Create unit test file
tests/unit/lib/backends/test_af_tool_adapters.pywith tests for all tool type conversions. Tests MUST be written FIRST and verified to FAIL before implementation. Include: (1)test_vectorstore_tool_creates_function_tool— mock a VectorstoreTool instance, call adapter, assert returns AFFunctionToolwrapping the search function, (2)test_hierarchical_document_tool_creates_function_tool— same pattern for HierarchicalDocumentTool, (3)test_function_tool_creates_af_function_tool— load a Python callable, assert wrapped as AFFunctionTool, (4)test_mcp_stdio_creates_mcp_stdio_tool— MCPConfig withtransport: stdio,command,args, assert createsMCPStdioTool(name, command, args), (5)test_mcp_sse_creates_streamable_http_tool— MCPConfig withtransport: sseandurl, assert createsMCPStreamableHTTPTool(name, url), (6)test_mcp_websocket_creates_websocket_tool— MCPConfig withtransport: websocketandurl, assert createsMCPWebsocketTool(name, url). Use lazy import mocking for all AF SDK types - T008 [US2] Create
src/holodeck/lib/backends/af_tool_adapters.pywithadapt_tools_for_af(tool_configs: list[ToolUnion], tool_instances: dict[str, Any]) -> list[Any]function. Use LAZY IMPORTS for all AF SDK types (guard with try/except ImportError raisingBackendInitErrorwith install instructions). Implement adapter logic per research.md R4: VectorstoreTool/HierarchicalDocumentTool →FunctionTool(func=instance.search), FunctionTool →FunctionTool(func=loaded_fn), MCPConfig stdio →MCPStdioTool(name, command, args), MCPConfig sse/http →MCPStreamableHTTPTool(name, url), MCPConfig websocket →MCPWebsocketTool(name, url). Add docstrings and type hints - T009 [US2] Run tests in
tests/unit/lib/backends/test_af_tool_adapters.pywithpytest tests/unit/lib/backends/test_af_tool_adapters.py -n auto -vand verify all pass
3b: AF OTel Span Wrapping¶
- T010 [P] [US2] Create
src/holodeck/lib/backends/af_otel.pywith basic OTel span wrapping for AF backend operations. Implement: (1)trace_af_invoke(func)async decorator that wrapsinvoke_once()calls in an OTel span namedholodeck.af.invokewith attributesaf.model_name,af.provider, (2)trace_af_session_send(func)async decorator that wrapssession.send()calls in an OTel span namedholodeck.af.session.send, (3)trace_af_tool_call(func)async decorator for tool invocations with span nameholodeck.af.tool. Use lazy import foropentelemetry.trace— if not available, decorators should be no-ops (return the unwrapped function). Add docstrings
3c: AF Backend and Session¶
- T011 [P] [US2] Create unit test file
tests/unit/lib/backends/test_af_backend.pywith tests for AFBackend and AFSession. Tests MUST be written FIRST and verified to FAIL. Mock ALL AF SDK imports lazily. Include: (1)test_initialize_creates_openai_client— agent withmodel.provider=openai,model.name=gpt-4o, callinitialize(), assertOpenAIChatClientinstantiated with correct model and API key from env, (2)test_initialize_creates_azure_client— agent withmodel.provider=azure_openai, assertAzureOpenAIChatClientinstantiated with endpoint, api_version, api_key, (3)test_initialize_creates_anthropic_client— agent withmodel.provider=anthropic, assertAnthropicClientinstantiated, (4)test_initialize_creates_ollama_client— agent withmodel.provider=ollama, assertOllamaChatClientinstantiated with endpoint, (5)test_initialize_google_provider_raises— agent withmodel.provider=googleandbackend=agent_framework, assert raisesBackendInitErrorwith message about Google not being supported by AF, (6)test_invoke_once_returns_execution_result— mockagent.run()to return a mockAgentResponsewith.text,.usage_details,.messages, callinvoke_once("hello"), assertExecutionResulthas correctresponse,token_usage,tool_calls,is_error=False, (7)test_invoke_once_populates_tool_calls— mockagent.run()response with tool call messages, assertExecutionResult.tool_callsandExecutionResult.tool_resultspopulated correctly, (8)test_invoke_once_handles_error— mockagent.run()to raise exception, assertExecutionResultreturned withis_error=Trueanderror_reasonset, (9)test_create_session_returns_af_session— callcreate_session(), assert returned object hassend,send_streaming,closemethods, (10)test_session_send_maintains_history— create session, send two messages, verify AF native session passed toagent.run()on both calls, (11)test_session_close_cleans_up— create and close session, verify resources released, (12)test_teardown_cleans_backend— initialize and teardown, verify_agentand_clientset to None, (13)test_af_import_error_gives_install_instructions— mock AF import to raise ImportError, callinitialize(), assertBackendInitErrorraised with message containingpip install "holodeck[agent-framework]" - T012 [US2] Create
src/holodeck/lib/backends/af_backend.pyimplementingAFBackendclass that satisfies theAgentBackendprotocol fromsrc/holodeck/lib/backends/base.py. Use LAZY IMPORTS for all AF SDK types. Use import aliasfrom agent_framework import AgentSession as AFNativeSessionto avoid collision with HoloDeck'sAgentSessionprotocol. Constructor takesagent: Agent,tool_instances: dict[str, Any]. Implementinitialize(): (a) try-import AF packages, raiseBackendInitErrorwith install instructions on ImportError, (b) select client class based onagent.model.providerper research.md R5 mapping (openai →OpenAIChatClient, azure_openai →AzureOpenAIChatClient, anthropic →AnthropicClient, ollama →OllamaChatClient), (c) raiseBackendInitErrorforgoogleprovider with clear error message, (d) instantiate client with model name, API key, endpoint etc from agent config and environment, (e) calladapt_tools_for_af()to convert tools, (f) create agent viaclient.as_agent(name=agent.name, instructions=resolved_instructions, tools=adapted_tools), (g) applyAgentFrameworkConfigsettings (compaction_strategy, max_tool_rounds) if present. Implementinvoke_once(message: str) -> ExecutionResult: (a) callawait self._agent.run(message), (b) extract response text fromresult.text, (c) extract token usage fromresult.usage_detailsintoTokenUsage, (d) extract tool calls/results fromresult.messages, (e) return populatedExecutionResult. Implementcreate_session() -> AFSession. Implementteardown(): cleanup agent and client references - T013 [US2] In the same file
src/holodeck/lib/backends/af_backend.py, implementAFSessionclass satisfying theAgentSessionprotocol. Constructor takesagentand creates AF native session viaagent.create_session(). Implementsend(message: str) -> ExecutionResult: callawait self._agent.run(message, session=self._session), extract results same asinvoke_once(). Implementsend_streaming(message: str) -> AsyncGenerator[str, None]: callself._agent.run(message, session=self._session, stream=True), iterateResponseStreamyielding text chunks. Implementclose(): release session reference. Note: MCP tools requireasync with self._agent:context manager — handle this ininitialize()by entering the context and storing the exit forteardown() - T014 [US2] Run tests in
tests/unit/lib/backends/test_af_backend.pywithpytest tests/unit/lib/backends/test_af_backend.py -n auto -vand verify all pass
3d: Backend Exports and Selector Integration¶
- T015 [US2] Update
src/holodeck/lib/backends/__init__.pyto exportAFBackendandAFSessiontypes. Use conditional/lazy import pattern: only import fromaf_backendwhen needed (e.g., inside a function or behind TYPE_CHECKING guard) to avoid import-time AF dependency - T016 [US2] Verify BackendSelector routing for AF in
src/holodeck/lib/backends/selector.py— confirm thatBackendEnum.AGENT_FRAMEWORKroutes toAFBackendand that auto-detection mapsopenaiandazure_openaiproviders toagent_frameworkbackend (clean break, no deprecation warning).ollamaauto-detects toclaudebackend (clean break, no deprecation warning). If US1 left placeholder routing, replace with actualAFBackendimport (lazy). Add test intests/unit/lib/backends/test_selector.py: (1)test_select_af_backend_for_openai_provider— agent withprovider=openai, no explicit backend, assert AF backend selected directly with no deprecation warning emitted, (2)test_select_af_backend_for_azure_provider— agent withprovider=azure_openai, assert AF backend selected directly with no deprecation warning emitted, (3)test_select_af_backend_explicit— agent withbackend=agent_framework, assert AF backend selected, (4)test_select_af_backend_google_provider_rejected— agent withprovider=google,backend=agent_framework, assert raises validation error, (5)test_select_claude_backend_for_ollama_provider— agent withprovider=ollama, no explicit backend, assert Claude backend selected directly with no deprecation warning emitted
3e: Acceptance Scenario Verification Tests¶
- T017 [P] [US2] Write acceptance test
test_openai_provider_autodetects_af_backendintests/unit/lib/backends/test_af_backend.py— create a full Agent config withmodel.provider=openai,model.name=gpt-4o, instructions (inline), no explicit backend field. Mock AF SDK. CallBackendSelector.select(), assert returns AFBackend directly (no deprecation warning emitted — usewarnings.catch_warnings()to assert zero warnings). Callinitialize(), mockOpenAIChatClient, assert client created. Callinvoke_once("What is AI?"), mockagent.run()to return response text "AI is...". AssertExecutionResult.response == "AI is..."andis_error is False - T018 [P] [US2] Write acceptance test
test_azure_openai_uses_af_backendintests/unit/lib/backends/test_af_backend.py— create Agent config withmodel.provider=azure_openai,model.name=gpt-4o,model.endpoint=https://myendpoint.openai.azure.com,model.api_version=2024-02-01. Mock AF SDK. AssertAzureOpenAIChatClientcreated with correct endpoint, api_version, and API key - T019 [P] [US2] Write acceptance test
test_mcp_stdio_tools_via_afintests/unit/lib/backends/test_af_backend.py— create Agent config withprovider=openaiand an MCP tool config (transport=stdio, command="node", args=["server.js"]). Mock AF SDK. Initialize backend, assertMCPStdioToolcreated in adapted tools list. Mockagent.run()returning a response with tool call records. AssertExecutionResult.tool_callscontains the MCP tool invocation - T020 [P] [US2] Write acceptance test
test_multiturn_chat_via_af_sessionintests/unit/lib/backends/test_af_backend.py— create Agent config withprovider=openai. Mock AF SDK. Create session viabackend.create_session(). Send message 1, assert response returned. Send message 2, assert AF native session object was passed toagent.run()on the second call (verifying conversation state maintained). Close session, assert cleanup completed
Phase 4: Polish¶
Purpose: Code quality, documentation, and final validation.
- T021 [P] [US2] Run
make formatto format all new and modified files with Black + Ruff - T022 [P] [US2] Run
make lintand fix any Ruff + Bandit violations insrc/holodeck/models/af_config.py,src/holodeck/lib/backends/af_backend.py,src/holodeck/lib/backends/af_tool_adapters.py,src/holodeck/lib/backends/af_otel.py - T023 [US2] Run
make type-checkand fix any MyPy errors — ensure all AF lazy imports have proper type annotations (useAnyfor unresolvable AF SDK types behindTYPE_CHECKING) - T024 [US2] Run full test suite
make testto verify no regressions across entire codebase - T025 [US2] Verify quickstart.md examples for AF backend match implementation — confirm YAML examples with
provider: openaiandprovider: azure_openaiare accurate and includeagent_frameworkconfig section examples - T026 [US2] Add unit test
test_af_execution_result_has_evaluator_required_fieldsintests/unit/lib/backends/test_af_backend.py— verify ExecutionResult returned by AFBackend contains all fields required for evaluator compatibility (FR-016). Mock AF SDK andagent.run()to return a realistic response. Callinvoke_once(), then assert field presence and types on the returned ExecutionResult:response(str),tool_calls(list),tool_results(list),token_usage(TokenUsage),num_turns(int),is_error(bool),error_reason(str | None). This is a lightweight field-presence/type check, not a full evaluator integration test
Dependencies & Execution Order¶
Cross-Story Dependencies¶
- US1 Setup (REQUIRED BEFORE US2): BackendEnum, ProviderEnum updates, BackendSelector routing infrastructure in
src/holodeck/models/llm.pyandsrc/holodeck/lib/backends/selector.pymust be completed in US1 before US2 can begin. - US2 is independent of US1's Google ADK implementation — only US1's shared setup phase is required.
Phase Dependencies¶
- Phase 1 (Setup): Depends on US1 setup completion — add AF deps to pyproject.toml
- Phase 2 (Foundational): Depends on Phase 1 — AF config model + Agent model field
- Phase 3 (Implementation): Depends on Phase 2 — all AF backend components
- Phase 3a (Tool Adapters) and 3b (OTel) can run in parallel — different files, no dependencies
- Phase 3c (Backend/Session) depends on 3a (uses tool adapters) and 3b (uses OTel decorators)
- Phase 3d (Exports/Selector) depends on 3c (exports the backend classes)
- Phase 3e (Acceptance Tests) depends on 3d (needs selector routing working)
- Phase 4 (Polish): Depends on all Phase 3 sub-phases being complete
Within Each Sub-Phase¶
- Tests MUST be written and FAIL before implementation (TDD)
- Implementation after tests
- Verify tests PASS after implementation
Parallel Example: Phase 3a + 3b¶
# These tasks can run in parallel (different files, no dependencies):
Task T007: "Write tool adapter tests in tests/unit/lib/backends/test_af_tool_adapters.py"
Task T010: "Create OTel span wrappers in src/holodeck/lib/backends/af_otel.py"
Parallel Example: Acceptance Scenarios¶
# These tasks can run in parallel (independent test functions, same file):
Task T017: "Write test_openai_provider_autodetects_af_backend"
Task T018: "Write test_azure_openai_uses_af_backend"
Task T019: "Write test_mcp_stdio_tools_via_af"
Task T020: "Write test_multiturn_chat_via_af_session"
Implementation Strategy¶
MVP First (Phases 1-3c Only)¶
- Complete Phase 1: Setup (T001-T002) — AF deps installed
- Complete Phase 2: Foundational (T003-T006) — AF config model ready
- Complete Phase 3a: Tool Adapters (T007-T009) — tools convert correctly
- Complete Phase 3c: Backend/Session (T011-T014) — core AF backend works
- STOP and VALIDATE: Run
make test— AF backend initializes, invokes, sessions work - Deploy/demo if ready
Incremental Delivery¶
- Phase 1 → AF dependency available
- Phase 2 → AF config model validated, Agent model accepts it
- Phase 3a → Tool adapters tested independently
- Phase 3b → OTel wrapping available (no-op if OTel not installed)
- Phase 3c → Core backend functional (MVP!)
- Phase 3d → Integrated into BackendSelector routing
- Phase 3e → All acceptance scenarios verified
- Phase 4 → Code quality validated, no regressions
Key Files Created¶
| File | Phase | Purpose |
|---|---|---|
src/holodeck/models/af_config.py |
Phase 2 | AgentFrameworkConfig Pydantic model with CompactionStrategy enum |
src/holodeck/lib/backends/af_tool_adapters.py |
Phase 3a | HoloDeck tool → AF tool conversion for all 5 tool types + MCP transports |
src/holodeck/lib/backends/af_otel.py |
Phase 3b | Basic OTel span decorators for invoke, session.send, tool calls |
src/holodeck/lib/backends/af_backend.py |
Phase 3c | AFBackend + AFSession implementing AgentBackend/AgentSession protocols |
tests/unit/models/test_af_config.py |
Phase 2 | Unit tests for AF config model |
tests/unit/lib/backends/test_af_tool_adapters.py |
Phase 3a | Unit tests for tool adapters |
tests/unit/lib/backends/test_af_backend.py |
Phase 3c | Unit tests for backend + session + acceptance scenarios |
Key Files Modified¶
| File | Phase | Changes |
|---|---|---|
pyproject.toml |
Phase 1 | [agent-framework], [agent-framework-anthropic], [agent-framework-ollama] optional groups |
src/holodeck/models/agent.py |
Phase 2 | agent_framework: AgentFrameworkConfig \| None field |
src/holodeck/lib/backends/__init__.py |
Phase 3d | Export AFBackend, AFSession |
src/holodeck/lib/backends/selector.py |
Phase 3d | Verify/update AF routing (may already be done by US1) |
tests/unit/lib/backends/test_selector.py |
Phase 3d | AF-specific routing tests |
Notes¶
- [P] tasks = different files, no dependencies between them
- [US2] label on every task for traceability
- All AF SDK imports MUST be lazy (inside functions/methods, not at module level) to prevent import-time failures when AF is not installed
- Import alias required:
from agent_framework import AgentSession as AFNativeSessionto avoid collision with HoloDeck'sAgentSessionprotocol inbase.py provider: googleis NOT supported by AF — must raise clear error with message directing user to usebackend: google_adkinstead- AF's
MCPStdioToolrequiresasync with agent:context manager — AFBackend.initialize() must enter this context and store the exit for teardown() - Commit after each task or logical group
- Stop at any checkpoint to validate independently