Mead.Deck.Channel (mead v0.1.1)

Copy Markdown View Source

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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

client_message(channel, socket, message)

@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.

policy_level(channel)

The current permission policy level.

resume(channel, socket, last_sequence, device_id \\ nil)

@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.

start_link(options)