Summary
Running a real task in the agent chat crashes the stream:
Error: Stream error: 'dict' object has no attribute 'conversation_id'
Root cause
AgentService._deserialize_messages (app/features/agents/service.py) returns the raw stored dicts unchanged (return data # type: ignore), on the assumption that PydanticAI's run() accepts dict message history. That is false for PydanticAI 1.96 — ModelRequest / ModelResponse now carry a conversation_id field, and PydanticAI accesses msg.conversation_id on every history item. A plain dict has no such attribute → crash.
_serialize_messages is the matching half of the bug: it hand-rolls dataclasses.asdict() + a str() fallback, producing lossy dicts that cannot round-trip back into ModelMessage objects.
Net effect: any session turn that runs with non-empty message history crashes.
Fix
Use PydanticAI's own ModelMessagesTypeAdapter:
_serialize_messages → ModelMessagesTypeAdapter.dump_python(messages, mode='json')
_deserialize_messages → ModelMessagesTypeAdapter.validate_python(data), falling back to an empty history (with a warning) if stored data is in the old lossy format.
Summary
Running a real task in the agent chat crashes the stream:
Root cause
AgentService._deserialize_messages(app/features/agents/service.py) returns the raw stored dicts unchanged (return data # type: ignore), on the assumption that PydanticAI'srun()accepts dict message history. That is false for PydanticAI 1.96 —ModelRequest/ModelResponsenow carry aconversation_idfield, and PydanticAI accessesmsg.conversation_idon every history item. A plain dict has no such attribute → crash._serialize_messagesis the matching half of the bug: it hand-rollsdataclasses.asdict()+ astr()fallback, producing lossy dicts that cannot round-trip back intoModelMessageobjects.Net effect: any session turn that runs with non-empty message history crashes.
Fix
Use PydanticAI's own
ModelMessagesTypeAdapter:_serialize_messages→ModelMessagesTypeAdapter.dump_python(messages, mode='json')_deserialize_messages→ModelMessagesTypeAdapter.validate_python(data), falling back to an empty history (with a warning) if stored data is in the old lossy format.