Both Main and ResearchCanvas call useCoAgent<AgentState>({ name: agent }) against the
same coagent (agent-name-contract), but passed
different initialState: Main.tsx seeded all five fields (model, research_question,
resources, report, logs), while ResearchCanvas.tsx seeded only { model }. Whichever
hook initialized the shared state first won, so the canvas's starting shape was order-dependent
rather than declared. It worked because Main mounts around ResearchCanvas, but it was a
latent footgun: reorder the tree and resources/report/logs start undefined, which the
render code papers over with || [] / || "" guards. Define the initial state once and use it in
both places so the contract is explicit, not incidental.
Resolution (2026-07-30)
Closed by a shared factory rather than a duplicated literal. src/lib/types.ts:32 now exports
createInitialAgentState(model: string): AgentState, seeding all six fields (the five above
plus citations). Both hooks call it — Main.tsx:12 and ResearchCanvas.tsx:24 — so the two
seeds cannot drift by construction, and the return type makes a missing field a compile error
rather than a mount-order surprise.
A factory rather than a constant because model is only known at render time from
useModelSelectorContext; the comment above the function records that reason and the
same-shape requirement, so the next reader doesn't inline it back. Verified against current
source this pass. Note the seed now includes citations, which the original finding predates
— the field set tracks
agent-state-shape-contract, so a new state key
has to be added here too.