# Todo 2 Domain Runtime Plan **Status:** done ## Result Implemented by subagent and locally verified with: ```bash uv run pytest tests/test_openai_stream_parser.py tests/test_event_agent.py tests/test_debug_runtime.py ``` Result: `3 passed in 0.07s`. Evaluation note: the runtime currently streams message deltas to the caller but does not yet persist the aggregated assistant message into conversation history. Todo 3 should cover transcript persistence when wiring the WebSocket/API surface. ## Goal Implement the core domain/runtime loop that passes the existing Todo 1 tests without adding WebSocket, FastAPI routes, or UI. ## Scope This todo covers: - Domain objects for chat messages, tool-call events, stream items, and usage. - Application contracts for debug run requests and agent parameters. - EventAgent execution for enabled tools. - OpenAI Chat Completions compatible stream parsing. - DebugRuntime loop that routes ChatAgent tool events through EventAgent and resumes ChatAgent with tool replies. This todo does not cover: - Real HTTP calls to an LLM endpoint. - pydantic-settings config. - WebSocket protocol. - Static frontend. ## Files - Create: `src/agent_lab/domain/events.py` - Create: `src/agent_lab/domain/messages.py` - Create: `src/agent_lab/application/contracts.py` - Create: `src/agent_lab/application/event_agent.py` - Create: `src/agent_lab/application/runtime.py` - Create: `src/agent_lab/infrastructure/openai_compatible.py` - Do not modify: `docs/plans/todos.md` - Avoid modifying tests unless a test is internally inconsistent. ## Design Use small Pydantic models for contracts that cross application boundaries and dataclasses for internal stream items. Keep tool handling intentionally simple: - `ToolCallEvent.name` is the ChatAgent tool name. - EventAgent treats `ToolCallEvent.name` as the tool identifier. - The MVP ships one built-in event tool, `handoff_note`, that returns a JSON tool reply. - Disabled tools return a JSON error tool reply instead of raising. `DebugRuntime.run()` is an async generator. It builds initial messages from system prompts, pre-messages, and the user message. It calls `chat_client.stream_chat()`, yields message deltas and events, sends events to EventAgent, appends the returned tool message, and calls ChatAgent again until no event is produced or `max_event_loops` is reached. ## Steps 1. Implement domain models in `events.py` and `messages.py`. 2. Implement request/parameter contracts in `contracts.py`. 3. Implement `EventAgent.handle()`. 4. Implement `ChatCompletionStreamParser.feed()` for OpenAI-compatible streamed chunks: - content deltas become `StreamItem.message_delta`. - tool call chunks are accumulated by `index`. - on `finish_reason == "tool_calls"`, emit `StreamItem.event` for each completed tool call. - usage becomes `StreamItem.usage`. 5. Implement `DebugRuntime.run()` around the chat client protocol. 6. Run `uv run pytest tests/test_openai_stream_parser.py tests/test_event_agent.py tests/test_debug_runtime.py`. 7. Fix only Todo 2 scope failures. ## Verification Run: ```bash uv run pytest tests/test_openai_stream_parser.py tests/test_event_agent.py tests/test_debug_runtime.py ``` Expected result: - 3 tests pass. - No FastAPI/WebSocket/UI code is required for this todo.