One narrow boundary
cwkPippa has exactly one place where polymorphism is intentional: the streaming-API boundary in backend/adapters/base.py. Everything above that boundary — routes, store, services, frontend — is Claude-shaped.
Why narrow
The wire protocols genuinely differ. Claude has thinking blocks. Codex has a function-calling loop without server-side resume. Gemini has thoughtSignature preservation and dual-path auth. Ollama has its own tool schema wrapping. Below that boundary, we accept the difference in how a brain is spoken to. Above it, we don't let the wire leak. What does reach the top is what Dad genuinely chooses per brain: each brain's reasoning knob has its own vocabulary (Claude's effort runs low to max, Gemini's thinking level is low or high, Codex has its own reasoning effort), so routes resolve those settings per brain and the frontend shows each brain its own control. That is a difference in what can be asked of a brain, not in how the conversation is stored or shown.
What's above the boundary
- Routes:
chat.pyassumes Claude-shape. It once carried a prefix-check escape hatch for variant session ids; since 2026-05-14 every brain full-replays history, so there is nothing left to escape. - Store:
conversationstable hasclaude_session_idcolumn. Storing Codex/Gemini/Ollama ids in it is fine — they fit Claude's shape (string id + history reconstructed elsewhere). - JSONL ground truth: every message line is in Claude-Code-compatible message format, with cwkPippa-specific fields in a nested
pippasub-object — Rule 2 expressed in the data format itself. Beside the messages, the logger writes event lines of its own, typedpippa_*(text and thinking deltas, tool use and results, usage, a sealed segment), with their payload at the top level. Those never pretend to be messages; they are the record of how the turn streamed.