"""Kernel persistence seam (docs/kernel_spec.md §2).

The agent loop touches conversation storage only through ``ConversationStore``.
The kernel defines the seam; it does not rewrite storage. The production
implementation wraps today's ``chat_storage`` (Phase 3b). The kernel ships an
in-memory reference store used by the golden-path tests.

Messages are native OpenAI-shaped dicts (``assistant.tool_calls`` + the
matching ``role: tool`` results) — the same shape ``chat_storage`` already
persists, per the native-tool-calls invariant.

Boundary: stdlib only.
"""
from __future__ import annotations

from typing import Protocol, runtime_checkable


@runtime_checkable
class ConversationStore(Protocol):
    """Minimal load/append seam the loop depends on."""

    def load(self, thread_id: str) -> list[dict]:
        """Return the persisted conversation for ``thread_id`` (oldest first)."""
        ...

    def append(self, thread_id: str, messages: list[dict]) -> None:
        """Append new messages produced this run, in order."""
        ...


class MemoryStore:
    """In-process ``ConversationStore``: reference implementation + test double.

    Stores defensive copies so callers can't mutate persisted state by holding
    onto the lists they passed in.
    """

    def __init__(self) -> None:
        self._threads: dict[str, list[dict]] = {}

    def load(self, thread_id: str) -> list[dict]:
        return [dict(m) for m in self._threads.get(thread_id, [])]

    def append(self, thread_id: str, messages: list[dict]) -> None:
        if not messages:
            return
        bucket = self._threads.setdefault(thread_id, [])
        bucket.extend(dict(m) for m in messages)
