"""Backend-agnostic model interface. Concrete backends (Claude Code, Ollama, Anthropic API direct, OpenCode, etc.) implement `ModelBackend.call`. The orchestration code only ever talks to this interface, never to a specific backend. Persona is NOT carried in ModelRequest; that's a backend-level configuration concern (some backends autodiscover persona from a workspace, others need to be told). What flows through here is per-turn DYNAMIC context only. """ from abc import ABC, abstractmethod from dataclasses import dataclass, field from typing import Literal Role = Literal["system", "user", "assistant", "tool"] @dataclass class Message: role: Role content: str @dataclass class ModelRequest: """Everything a backend needs to produce one response.""" messages: list[Message] turn_context: str | None = None extras: dict = field(default_factory=dict) @dataclass class ModelResponse: """What a backend returns. Kept narrow now, extended later.""" content: str extras: dict = field(default_factory=dict) class ModelBackend(ABC): @abstractmethod def call(self, request: ModelRequest) -> ModelResponse: ...