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. Above it, we don't.
What's above the boundary
- Routes:
chat.pyassumes Claude-shape (with prefix-check escape hatch). - 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 line is Claude-Code-compatible message format. cwkPippa-specific fields live in a nested
pippasub-object — Rule 2 expressed in the data format itself.
The principle: A 'narrow boundary' isn't a compromise. It's an admission that some differences are unavoidable (wire protocols) and others are imagined (everything else). Drawing the boundary in the right place is the architectural skill.