# Todo 44 Provider Tool Transcript Plan **Status:** done ## Result - Added structured assistant tool calls and provider-compatible tool replies. - Added local transcript-state validation and explicit tool-choice behavior. - Removed implicit forcing for a single ChatAgent tool while EventAgent explicitly forces its selected function. - Todo 44.1 hardened identity, payload precedence, history projection, and public request boundaries. - Verified with focused tests (`97 passed`) and the full suite (`108 passed`, one existing warning). ## Goal Represent and serialize a valid OpenAI-compatible assistant tool-call transcript so a later Runtime todo can continue ChatAgent after native tool execution without weakening the existing dual-agent path. ## Scope - Add structured assistant `tool_calls` to the internal `ChatMessage` model. - Serialize assistant tool calls and matched `tool` replies into provider-compatible request messages. - Reject orphaned, mismatched, duplicate, or incomplete tool transcripts before network I/O. - Replace implicit “one tool means forced invocation” behavior with an explicit `tool_choice` argument. - EventAgent explicitly forces its one selected tool; ordinary ChatAgent requests keep automatic/no forced choice behavior. - Do not enable ChatAgent tools in Runtime in this todo. ## Files - Modify: `src/agent_lab/domain/messages.py` - Modify: `src/agent_lab/infrastructure/chat_client.py` - Modify: `src/agent_lab/application/runtime.py` - Modify: `src/agent_lab/application/event_agent.py` - Modify: `src/agent_lab/application/contracts.py` - Modify: `tests/test_websocket_api.py` - Modify: `tests/test_event_agent.py` - Modify: `tests/test_debug_runtime.py` only for protocol signature compatibility if required - Modify: `docs/plans/todos.md` ## Plan ### Plan 1. Define the desired internal transcript and tool-choice API through failing tests. 2. Verify failures occur because `ChatMessage` lacks assistant tool calls, the client rejects all tool messages, and a single tool is implicitly forced. 3. Add the minimum typed message fields and transcript validation/serialization. 4. Add explicit optional `tool_choice` to the chat-client protocols and implementation. 5. Make EventAgent pass a forced function choice; keep Runtime ChatAgent calls unchanged with `tools=[]` and no tool choice. 6. Run focused and full regressions. ### Act Follow TDD in small RED/GREEN slices: 1. **Message shape RED** - Add a test constructing an assistant `ChatMessage` with two `ToolCallEvent` entries and asserting `model_dump()` preserves IDs, names, parsed arguments, and raw arguments. - Add validation tests rejecting `tool_calls` on non-assistant roles and rejecting a `tool` role without `tool_call_id`. 2. **Provider serialization RED** - Add a MockTransport request test containing: - assistant content plus two tool calls; - two matching tool replies; - a final user or assistant-visible message after replies. - Assert the provider payload uses standard `assistant.tool_calls[].function.arguments` raw JSON and `tool.tool_call_id`. 3. **Transcript guard RED** - Add separate tests rejecting before network I/O: - orphan tool reply; - mismatched call ID; - duplicate tool reply; - a non-tool message while assistant calls remain unresolved. 4. **Tool choice RED** - Add a test showing one ChatAgent tool with no explicit choice does not add forced `tool_choice`. - Add a test showing an explicit forced function choice is serialized exactly. - Update EventAgent tests to assert its argument-generation call supplies the selected function choice. 5. Run focused tests and confirm expected failures. 6. Implement minimal domain validation, transcript serialization, explicit choice plumbing, and EventAgent forcing. 7. Re-run focused tests and then the full suite. ### Observe Confirm that: - the legal transcript matches OpenAI-compatible message ordering; - visible assistant content can coexist with tool calls; - invalid histories fail locally before HTTP calls; - single-tool ChatAgent requests are no longer accidentally forced; - EventAgent retains deterministic selected-tool invocation; - Runtime still sends `tools=[]` in the existing dual-agent mode. ### Update - Mark only todo 44 `done` after spec and quality reviews pass. - If review finds post-commit defects, add `44.1` directly below todo 44 and do not edit the completed row. - Commit code with the problem and any compatibility risk in the body. ## Verification - `uv run pytest tests/test_websocket_api.py tests/test_event_agent.py tests/test_debug_runtime.py -q` - `uv run pytest` - `git diff --check`