# Todo 44.1 Tool Transcript Hardening Plan **Status:** done ## Result - Rejected duplicate call IDs within and across assistant messages. - Protected provider request fields from `extra_body` overrides and validated forced choices locally. - Projected EventAgent history without unresolved provider protocol state. - Kept public pre-messages user-facing by rejecting internal tool protocol messages. - Completed role-field and test-double validation; full suite passes (`108 passed`). ## Goal Close identity, payload-precedence, history-projection, and request-boundary gaps found by Todo 44 code review before direct Runtime tool mode builds on the transcript layer. ## Scope - Reject duplicate assistant tool-call IDs before serialization. - Prevent `extra_body` from overriding reserved provider request fields, especially explicit `tool_choice`. - Validate forced tool choices locally against the supplied tools. - Project EventAgent conversation history without unresolved assistant tool-call protocol state. - Keep public `pre_messages` user-facing only by rejecting both tool replies and assistant tool calls. - Complete role-specific field validation and update all affected fakes. ## Files - Modify: `src/agent_lab/domain/messages.py` - Modify: `src/agent_lab/application/contracts.py` - Modify: `src/agent_lab/application/event_agent.py` - Modify: `src/agent_lab/infrastructure/chat_client.py` - Modify: `tests/test_websocket_api.py` - Modify: `tests/test_event_agent.py` - Modify: `tests/test_debug_runtime.py` ## Plan ### Plan 1. Add failing negative tests for every review finding. 2. Verify failures are caused by current acceptance/override behavior, not test setup. 3. Implement the smallest domain/client/history/request-boundary hardening. 4. Run focused and full regressions. ### Act - Add tests rejecting duplicate tool-call IDs within one assistant message and across a transcript. - Add tests proving `extra_body` cannot override `messages`, `tools`, `tool_choice`, model, streaming, or token/sampling controls. - Add tests rejecting forced tool choice with no tools or a function name absent from the supplied list. - Add an EventAgent test with a prior complete assistant/tool round and assert its argument prompt contains visible assistant content without unresolved `tool_calls`. - Add request-contract tests rejecting `pre_messages` that contain either tool replies or assistant tool calls. - Update ChatMessage role-field tests so irrelevant `tool_call_id`/`tool_calls` fields fail instead of being ignored. - Update all fake `stream_chat` signatures and assertions for optional `tool_choice`. - Run RED, apply minimum fixes, then run focused/full verification. ### Observe Confirm transcript identity is unique, explicit controlled fields win over provider extras, EventAgent receives a provider-valid projected history, and public pre-messages cannot inject partial internal protocol state. ### Update - Mark 44 and 44.1 `done` only after spec and quality re-review. - Commit separately with the malformed transcript and provider override risks 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`