Tasks: User Story 1 — Serve a Claude-Backed Agent via REST/AG-UI¶
Feature: 024-claude-serve-deploy | Story: US1 | Date: 2026-03-20 Spec: spec.md | Plan: plan.md | Data Model: data-model.md
User Story: A platform operator configures an agent with provider: anthropic and runs holodeck serve agent.yaml. The server starts, performs pre-flight validation (Node.js availability, API credentials), and begins accepting requests. Clients interact with the agent through REST or AG-UI protocol endpoints identically to how they would with an OpenAI-backed agent.
Acceptance Scenarios:
1. Given agent config with provider: anthropic and Node.js installed, When operator runs holodeck serve agent.yaml, Then server starts successfully and health endpoint reports ready.
2. Given running Claude-backed server, When client sends message to REST endpoint, Then server returns ExecutionResult-compatible response.
3. Given running Claude-backed server, When client opens streaming session, Then text chunks delivered progressively.
4. Given agent config with provider: anthropic but no Node.js, When operator runs holodeck serve, Then server fails fast with clear error.
5. Given agent config with provider: anthropic but no credentials, When operator runs holodeck serve, Then server fails fast with clear error.
FR Coverage: FR-001, FR-006, FR-008, FR-012, FR-013, FR-014
Phase 1: Setup — Shared Infrastructure¶
These tasks create the data model extensions and shared constants that all US1 tasks depend on. No story label — they are foundational.
-
T001 Add
max_concurrent_sessionsfield toClaudeConfigin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/models/claude_config.py. Addmax_concurrent_sessions: int | None = Field(default=10, ge=1, le=100, description="Maximum concurrent Claude SDK subprocesses per serve instance"). Place it after the existingmax_turnsfield. This enables the session cap configured via YAML (claude.max_concurrent_sessions). -
T002 [P] Add
backend_readyandbackend_diagnosticsfields toHealthResponsein/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/models.py. Add:backend_ready: bool = Field(default=True, description="Whether backend prerequisites are satisfied")andbackend_diagnostics: list[str] = Field(default_factory=list, description="Diagnostic messages when backend is degraded/unhealthy"). These are additive, backward-compatible fields. Update thestatusfield description to include"degraded"as a valid value. -
T003 [P] Define
NODEJS_MIN_VERSIONconstant in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/validators.py. AddNODEJS_MIN_VERSION: int = 18at module level after the existing env var candidate tuples. This constant is used by the enhancedvalidate_nodejs()(T005) and health check validators.
Phase 2: Foundational — Blocking Prerequisites¶
These tasks implement the core validation logic, error mapping, and server lifecycle changes that must exist before the serve path works end-to-end.
-
T004 [US1] Enhance
validate_nodejs()in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/lib/backends/validators.pyto verify Node.js version >= 18 (FR-012). After the existingshutil.which("node")check passes, runsubprocess.run(["node", "--version"], capture_output=True, text=True, timeout=5). Parse the output (format:v22.1.0) — strip thevprefix, split on., extract the major version as int. If major <NODEJS_MIN_VERSION, raiseConfigError("nodejs", f"Node.js version {version} found but >= {NODEJS_MIN_VERSION} is required by Claude Agent SDK. ..."). Handlesubprocess.TimeoutExpiredandsubprocess.CalledProcessErrorby raisingConfigErrorwith an actionable message. Importsubprocessat the top of the file. -
T005 [US1] Add
_validate_backend_prerequisites()method toAgentServerin/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/server.py(FR-001, FR-008). Implementasync def _validate_backend_prerequisites(self) -> Nonethat: (a) checksself.agent_config.model.provider == ProviderEnum.ANTHROPIC, (b) if Anthropic, callsvalidate_nodejs()andvalidate_credentials(self.agent_config.model)fromholodeck.lib.backends.validators, (c) onConfigError, raisesBackendInitErrorwith the error message. The CLI layer (serve command) catches this and exits. (d) for non-Anthropic providers, returns immediately (no-op). ImportBackendInitErrorfromholodeck.lib.backends,ProviderEnumfromholodeck.models.llm, and the validator functions. -
T006 [US1] Integrate
_validate_backend_prerequisites()into the server startup lifecycle in/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/server.py. Locate the point where the server transitions toRUNNINGstate (currently increate_app()which setsREADY, then uvicorn starts). Add a call toawait self._validate_backend_prerequisites()in thestart()method (or the appropriate startup hook) BEFORE the state transitions toRUNNING. The startup lifecycle catchesBackendInitErrorfrom_validate_backend_prerequisites(), logs the error withlogger.error(), and exits. If validation fails, the server never reachesRUNNINGstate. Log a success message on success:"Backend prerequisites validated for {provider} provider". -
T007 [US1] Pass Claude session cap to
SessionStorein/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/server.py(FR-014). InAgentServer.__init__(), detect ifself.agent_config.model.provider == ProviderEnum.ANTHROPICandself.agent_config.claudeis not None. If so, readself.agent_config.claude.max_concurrent_sessionsand pass it toSessionStore(max_sessions=cap)instead of the default 1000. Non-Anthropic providers keep the existing 1000 default. ImportProviderEnumfromholodeck.models.llm. -
T008 [US1] Map
BackendSessionErrorto HTTP 502 in REST protocol at/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/protocols/rest.py(FR-014, crash handling). In the chat endpoint handler(s), wrap theAgentExecutorinvocation with a try/except that catchesBackendSessionError(import fromholodeck.lib.backends). When caught, return aJSONResponsewith status 502 and body:{"error": "backend_error", "message": "Claude Agent SDK subprocess terminated unexpectedly. Start a new session to retry.", "retriable": true}. Remove the crashed session from the session store. -
T009 [US1] Map capacity exceeded to HTTP 503 in REST protocol at
/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/protocols/rest.py(FR-014, session cap). WhenSessionStore.create()raisesRuntimeErrordue to max sessions exceeded, catch it and return aJSONResponsewith status 503,Retry-After: 5header, and body matching the session-cap contract:{"error": "capacity_exceeded", "message": "Maximum concurrent Claude sessions ({max}) reached. Retry after existing sessions complete.", "active_sessions": N, "max_sessions": N}. -
T010 [US1] Map
BackendSessionErrorto SSE error event in AG-UI protocol at/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/protocols/agui.py(FR-014, crash handling). In the AG-UI endpoint handler, wrap theAgentExecutorinvocation with a try/except that catchesBackendSessionError(import fromholodeck.lib.backends). When caught, emit an SSE error event:event: error\ndata: {"type": "backend_error", "message": "Claude Agent SDK subprocess terminated unexpectedly.", "retriable": true}. Remove the crashed session from the session store. -
T011 [US1] Map capacity exceeded to SSE error event in AG-UI protocol at
/Users/justinbarias/Documents/Git/python/agentlab/src/holodeck/serve/protocols/agui.py(FR-014, session cap). WhenSessionStore.create()raisesRuntimeErrordue to max sessions exceeded, catch it and emit an SSE error event:event: error\ndata: {"type": "capacity_exceeded", "message": "Maximum concurrent Claude sessions ({max}) reached."}.
Phase 3: User Story 1 — Unit Tests¶
These tasks verify all US1 production code with comprehensive unit tests.
-
T012 [US1] Write unit tests for
ClaudeConfig.max_concurrent_sessionsin/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/models/test_claude_config.py. Add tests: (a) default value is 10, (b)max_concurrent_sessions: 50is valid, (c)max_concurrent_sessions: 0raisesValidationError(ge=1), (d)max_concurrent_sessions: 101raisesValidationError(le=100), (e)max_concurrent_sessions: nullis valid (None). Run withpytest tests/unit/models/test_claude_config.py -n auto. -
T013 [US1] Write unit tests for enhanced
validate_nodejs()version check in/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/lib/backends/test_validators.py. Add tests: (a) Node.js v22.1.0 passes validation (mocksubprocess.runreturningv22.1.0), (b) Node.js v18.0.0 passes (boundary), (c) Node.js v16.20.0 raisesConfigErrorwith version message, (d)node --versiontimes out raisesConfigError, (e)node --versionreturns non-zero exit code raisesConfigError, (f) node not found on PATH raisesConfigError(existing behavior preserved), (g) unparseable version output (e.g., garbage string) raisesConfigError. Mockshutil.whichandsubprocess.runappropriately. Run withpytest tests/unit/lib/backends/test_validators.py -n auto. -
T014 [US1] Write unit tests for
_validate_backend_prerequisites()in/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/serve/test_server.py. Add tests: (a) Anthropic provider with valid Node.js + credentials — no exception raised, (b) Anthropic provider with missing Node.js — raisesBackendInitError, (c) Anthropic provider with missing credentials — raisesBackendInitError, (d) non-Anthropic provider (e.g., openai) — skips validation entirely, (e) Node.js version too old — raisesBackendInitError. Mockvalidate_nodejsandvalidate_credentials. Run withpytest tests/unit/serve/test_server.py -n auto. -
T015 [US1] Write unit tests for session cap enforcement in
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/serve/test_server.py. Add tests: (a) Anthropic provider withmax_concurrent_sessions: 5— SessionStore created withmax_sessions=5, (b) non-Anthropic provider — SessionStore created with defaultmax_sessions=1000, (c) Anthropic provider withoutclaudeconfig section — SessionStore uses default 10. Run withpytest tests/unit/serve/test_server.py -n auto. -
T016 [US1] Write unit tests for REST protocol error mapping in
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/serve/test_protocols_rest.py(or existing test file for REST protocol). Add tests: (a)BackendSessionErrorduring chat returns 502 with{"error": "backend_error", "retriable": true}, (b) capacity exceeded (RuntimeError from SessionStore) returns 503 with{"error": "capacity_exceeded"}andRetry-Afterheader, (c) crashed session is removed from session store after 502 response. MockAgentExecutorto raise the respective exceptions. Run withpytest tests/unit/serve/ -n auto. -
T017 [US1] Write unit tests for AG-UI protocol error mapping in
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/serve/test_protocols_agui.py(or existing test file for AG-UI protocol). Add tests: (a)BackendSessionErrorduring AG-UI streaming emits SSE error event with"type": "backend_error", (b) capacity exceeded emits SSE error event with"type": "capacity_exceeded", (c) crashed session is removed from session store after error event. MockAgentExecutorto raise the respective exceptions. Run withpytest tests/unit/serve/ -n auto. -
T018 [US1] Write unit test verifying env var pass-through for proxy credential injection (FR-006) in
/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/lib/backends/test_claude_backend.py. Verify that whenANTHROPIC_BASE_URL,HTTP_PROXY, andHTTPS_PROXYare set in the process environment, thebuild_options()method onClaudeBackendproducesClaudeAgentOptions.envthat merges with (not replaces) the inherited environment. Set env vars viamonkeypatch.setenv(), callbuild_options(), and assert the env dict contains the proxy vars. This confirms FR-006 without any new production code. Run withpytest tests/unit/lib/backends/test_claude_backend.py -n auto.
Phase 4: User Story 1 — Integration Tests & OTel Verification¶
-
T019 [US1] Create Claude agent fixture for serve integration tests at
/Users/justinbarias/Documents/Git/python/agentlab/tests/fixtures/claude_agent/agent.yaml. Define a minimal Claude agent config:name: claude-serve-test,model: {provider: anthropic, name: claude-sonnet-4-20250514},instructions: {inline: "You are a helpful test assistant."},claude: {permission_mode: acceptAll, max_concurrent_sessions: 3}. -
T020 [US1] Write integration test for Claude serve startup and health endpoint in new file
/Users/justinbarias/Documents/Git/python/agentlab/tests/integration/serve/test_server_claude.py(FR-001, FR-005, FR-010). Test: (a)AgentServerwith Claude agent fixture starts and health endpoint returnsbackend_ready: true(requires Node.js + credentials in CI), (b) health endpoint returnsbackend_diagnostics: []when all prerequisites met, (c) server transitions to RUNNING state after successful pre-flight validation. Mark with@pytest.mark.integration. Usehttpx.AsyncClientwith the FastAPI test client pattern. Run withpytest tests/integration/serve/test_server_claude.py -n auto. -
T021 [US1] Write integration test for Claude serve request handling in
/Users/justinbarias/Documents/Git/python/agentlab/tests/integration/serve/test_server_claude.py(FR-010). Test: (a) POST to REST chat endpoint with Claude agent returns 200 withExecutionResult-compatible response (hascontent,session_id,tool_callsfields), (b) streaming endpoint returns SSE events with progressive text chunks, (c) session is created and tracked in SessionStore. Mark with@pytest.mark.integration. Run withpytest tests/integration/serve/test_server_claude.py -n auto. -
T022 [US1] Write integration test verifying OTel instrumentation in serve mode in
/Users/justinbarias/Documents/Git/python/agentlab/tests/integration/serve/test_server_claude.py(FR-013). Test: (a) configure anInMemorySpanExporter, (b) start Claude serve, send a request, (c) assert at least one span withgen_ai.systemattribute is emitted, (d) verify span hasgen_ai.request.modelattribute matching the configured model. This confirms FR-013 — GenAI semantic convention instrumentation works in serve mode without new code. Mark with@pytest.mark.integration.
Phase 5: Polish — Validation & Quality¶
-
T023 [US1] Validate quickstart Scenario 1 (Serve a Claude Agent Locally) from
/Users/justinbarias/Documents/Git/python/agentlab/docs/product-specs/024-claude-serve-deploy/quickstart.md. Create a unit test in/Users/justinbarias/Documents/Git/python/agentlab/tests/unit/serve/test_serve_quickstart.pythat: (a) parses the quickstart YAML config through theAgentPydantic model and asserts validation passes, (b) verifiesagent.claude.max_concurrent_sessions == 5, (c) verifiesagent.model.provider == ProviderEnum.ANTHROPIC. This does NOT require a running server — config parsing only. -
T024 [US1] Run full test suite and code quality checks. Execute
make test-unit,make lint,make format-check, andmake type-checkfrom/Users/justinbarias/Documents/Git/python/agentlab. Fix any failures introduced by US1 changes. Ensure all existing tests still pass (zero regressions per FR-009). All new files must pass Black formatting, Ruff linting, and MyPy type checking.
Dependencies & Execution Order¶
T001 (ClaudeConfig field)
└─ no dependencies — start immediately
T002 (HealthResponse fields)
└─ no dependencies — start immediately
T003 (NODEJS_MIN_VERSION constant)
└─ no dependencies — start immediately
T001 + T002 + T003 can all run in parallel (Phase 1).
T004 (validate_nodejs enhancement) ── depends on T003
T005 (_validate_backend_prerequisites) ── depends on T004
T006 (startup lifecycle integration) ── depends on T005
T007 (session cap in __init__) ── depends on T001
T008 (REST 502 mapping) ── depends on T006
T009 (REST 503 mapping) ── depends on T007
T010 (AG-UI 502 mapping) ── depends on T006
T011 (AG-UI 503 mapping) ── depends on T007
T008 + T009 can run in parallel (REST protocol changes).
T010 + T011 can run in parallel (AG-UI protocol changes).
REST and AG-UI groups can run in parallel with each other.
T012 (ClaudeConfig tests) ── depends on T001
T013 (validator tests) ── depends on T004
T014 (prereq tests) ── depends on T005, T006
T015 (session cap tests) ── depends on T007
T016 (REST error tests) ── depends on T008, T009
T017 (AG-UI error tests) ── depends on T010, T011
T018 (env var pass-through test) ── no production deps, can run after Phase 1
T012 + T013 + T015 + T018 can run in parallel (independent test files).
T014 can run in parallel with T016 + T017 (independent test files).
T019 (fixture) ── no dependencies — can start in Phase 1
T020 (integration: startup) ── depends on T006, T019
T021 (integration: request handling) ── depends on T008, T009, T019
T022 (integration: OTel) ── depends on T020
T023 (quickstart validation) ── depends on T001
T024 (full suite) ── depends on ALL previous tasks
Parallel Execution Examples¶
The following groups can be executed in parallel within each phase:
Phase 1 parallel group: - T001 (ClaudeConfig field) | T002 (HealthResponse fields) | T003 (NODEJS_MIN_VERSION) — all independent, no cross-file dependencies
Phase 2 first wave: - T004 (validate_nodejs) | T007 (session cap) — T004 depends on T003, T007 depends on T001, no overlap
Phase 2 second wave (after T004, T005): - T008 (REST 502) + T009 (REST 503) | T010 (AG-UI 502) + T011 (AG-UI 503) — all independent protocol/endpoint changes
Phase 3 first wave: - T012 (config tests) | T013 (validator tests) | T015 (session cap tests) | T018 (env var tests) — independent test files
Phase 3 second wave: - T014 (server tests) | T016 (REST tests) | T017 (AG-UI tests) — independent test files
Phase 4: - T019 (fixture, can start early) | T020, T021 (integration, after Phase 2) | T022 (OTel, after T020)
Phase 5 sequential: - T023 → T024
Implementation Strategy¶
Guiding Principles¶
-
Fail-fast validation: All Claude backend prerequisites (Node.js >= 18, API credentials) MUST be checked at server startup in
AgentServer.start(), before transitioning to RUNNING state. The server must never appear healthy while lacking prerequisites. -
Backward compatibility: All changes to
HealthResponseand health endpoints are additive. Existing consumers that checkstatus == "healthy"continue to work. The new"degraded"status is only relevant to Claude-aware consumers. -
Contract compliance: REST error responses (502 for crashes, 503 for capacity) and AG-UI SSE error events MUST match the contracts defined in
/Users/justinbarias/Documents/Git/python/agentlab/docs/product-specs/024-claude-serve-deploy/contracts/session-cap.mdexactly. -
No new backend code: The
ClaudeBackendandClaudeSessionalready implement theAgentBackend/AgentSessionprotocols. US1 only adds server-level validation, error mapping, and lifecycle management around the existing backend. -
OTel for free: GenAI semantic convention instrumentation activates automatically via
ClaudeBackend.initialize()in serve mode. No new instrumentation code is needed — only an integration test to verify (T022). -
Error handling:
BackendInitErroris raised by_validate_backend_prerequisites()on pre-flight failure; the CLI layer (serve command) catches it and exits.BackendSessionErrormaps to 502 (retriable), capacity exceeded maps to 503 (withRetry-After). Crashed sessions are removed fromSessionStoreto free the slot immediately.
Key Files Reference¶
| File | Action | Tasks | FR Coverage |
|---|---|---|---|
src/holodeck/models/claude_config.py |
MODIFY | T001, T012 | FR-014 |
src/holodeck/serve/models.py |
MODIFY | T002 | FR-005 |
src/holodeck/lib/backends/validators.py |
MODIFY | T003, T004, T013 | FR-012 |
src/holodeck/serve/server.py |
MODIFY | T005, T006, T007, T014, T015 | FR-001, FR-008, FR-014 |
src/holodeck/serve/protocols/rest.py |
MODIFY | T008, T009, T016 | FR-014 |
src/holodeck/serve/protocols/agui.py |
MODIFY | T010, T011, T017 | FR-014 |
tests/unit/models/test_claude_config.py |
MODIFY | T012 | FR-014 |
tests/unit/lib/backends/test_validators.py |
MODIFY | T013 | FR-012 |
tests/unit/serve/test_server.py |
MODIFY | T014, T015 | FR-001, FR-014 |
tests/unit/serve/test_protocols_rest.py |
MODIFY | T016 | FR-014 |
tests/unit/serve/test_protocols_agui.py |
MODIFY | T017 | FR-014 |
tests/unit/lib/backends/test_claude_backend.py |
MODIFY | T018 | FR-006 |
tests/fixtures/claude_agent/agent.yaml |
NEW | T019 | FR-010 |
tests/integration/serve/test_server_claude.py |
NEW | T020, T021, T022 | FR-010, FR-013 |
tests/unit/serve/test_serve_quickstart.py |
NEW | T023 | — |