The deck's view of one session: one process per session that owns the event sequence, the event log, the prompt queue and the turn that runs.
A socket (Mead.Deck.Socket) is only a translator between frames and calls on this process, so
a deck that reconnects, or a second device, finds the same state. Every sequenced event is
written to the log before it is sent to anyone: a deck can only have seen what a later replay
can repeat, and a log that cannot be written stops the channel instead of sending an event the
server could not stand behind.
Turns
prompt.send is idempotent on client_message_id: a repeated id gets its original
prompt.accepted again and starts nothing. A new prompt is accepted, queued behind at most
queue_limit others (a prompt over the limit is dropped without any event), and the queue runs
one turn at a time through Mead.submit_turn/3 in a task under Mead.TurnSupervisor. When a
turn ends, its text arrives as a text.delta (unless it was already streamed), and
metric.updated reports the session's token totals from the ledger. A turn that ends in an
error emits turn.failed; one that succeeds with an empty or invalid answer emits neither text
nor failure.
Permissions
Every turn the channel starts carries Mead.Deck.Interaction, so the router asks the channel
before each tool call. The policy level decides: allow_all emits permission.requested and,
at once, permission.resolved with decided_by_policy; ask_for_risky lets a :read call run
silently; anything else emits permission.requested, pauses the turn clock
(Mead.Session.Server.pause_clock/1) and waits for the first valid permission.respond or for
the deadline, which resolves as expired. A later answer gets the existing permission.resolved
sent again to the answering socket alone. A request left open by a channel that died is resolved
as expired when the next channel starts, before it serves anyone. The details of a request come
from the model's arguments and are never logged.
allow_always answers allow the call and remember the tool ("<server>__<tool>"): the
permission.resolved carries remember, and from then on the same tool is allowed at once with
decided_by_rule, whatever the level but allow_all, which keeps decided_by_policy. The rules
are rebuilt from the log, and permission.forget with the tool name removes one
(permission.forgotten).
The channel monitors the session and stops with it. It is registered in Mead.Registry as
{:deck, session_id}.
Summary
Functions
Returns a specification to start this module under a supervisor.
Hands a validated client message from socket to the channel; unattached sockets are ignored.
The current permission policy level.
Attaches socket (a pid that will receive {:deck_event, binary} messages), sends it the
snapshot, and replays every sequenced event after last_sequence. All inside one call, so no
live event can reach the socket before its snapshot.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec client_message(GenServer.server(), pid(), Mead.Deck.Events.client_message()) :: :ok
Hands a validated client message from socket to the channel; unattached sockets are ignored.
@spec policy_level(GenServer.server()) :: Mead.Deck.Configuration.policy_level()
The current permission policy level.
@spec resume(GenServer.server(), pid(), non_neg_integer(), String.t() | nil) :: :ok
Attaches socket (a pid that will receive {:deck_event, binary} messages), sends it the
snapshot, and replays every sequenced event after last_sequence. All inside one call, so no
live event can reach the socket before its snapshot.