Pixir.Session (pixir v0.1.0)

Copy Markdown View Source

The unit of agency (ADR 0001): a single GenServer that owns one conversation — its :role (the Agent configuration), its monotonic seq counter, and the append to its Log. There is exactly one Session process per session_id, registered in Pixir.Sessions.Registry.

Recording events

Canonical events go through record/2, which runs the load-bearing sequence inside the GenServer (so it is serialized): stamp seq → append to the Log → publish on the bus. The Log is the source of truth, so an event that fails to persist is not published. Ephemeral events go through emit/2 (publish only — no seq, no Log).

Turns as supervised Tasks

A Turn runs in a Task under Pixir.TurnSupervisor, monitored (not linked) by the Session. interrupt/1 kills that Task — the load-bearing invariant that makes a Turn cleanly cancellable without taking the Session down. The Turn body is a 1-arity function given a context map (%{session_id, workspace, role, fork_root_session_id}); the real tool-loop (build step 7) plugs in here.

Resume

On init/1 the Session folds its existing Log to seed seq (so new canonical events continue the sequence). History itself is always re-derived from the Log on demand (history/1), never held as authoritative state (ADR 0003).

Summary

Functions

Returns a specification to start this module under a supervisor.

Publish an ephemeral Event (live display only; never persisted).

Generate a sortable, filename-safe Session id.

Reconstruct History by folding the Log (the source of truth).

Snapshot of Session metadata (id, workspace, role, next seq, turn state).

Kill the currently running Turn's Task, if any.

Record a canonical Event: stamp seq, append to the Log, then publish. Returns the stamped Event, or a structured error if it was not canonical / failed to persist.

Hysteresis gate for context-pressure warnings (ADR 0020): returns {:ok, :warn} the first time a (latest checkpoint to_seq, tier) pair is seen and {:ok, :already_warned} afterwards — no warning spam on consecutive turns for the same pair. A new compaction checkpoint (different to_seq) or a different tier re-arms the gate. State is ephemeral process state by design: it is never logged, and re-warning after a Session restart is acceptable.

Start a Turn: run turn_fun.(ctx) in a supervised Task. Returns {:ok, ref} or {:error, :busy} if a Turn is already running. If the previous Turn left orphan tool calls in the Log, Pixir first records fallback tool_result events so Provider replay stays valid.

Whether a Turn is currently running.

{:via, Registry, …} name for a Session id.

Types

ctx()

@type ctx() :: %{
  session_id: String.t(),
  workspace: String.t(),
  role: role(),
  fork_root_session_id: String.t()
}

role()

@type role() :: atom()

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

emit(session_id, event)

@spec emit(String.t(), Pixir.Event.t()) :: :ok

Publish an ephemeral Event (live display only; never persisted).

gen_id()

@spec gen_id() :: String.t()

Generate a sortable, filename-safe Session id.

history(session_id)

@spec history(String.t()) :: {:ok, Pixir.Log.history()} | {:error, map()}

Reconstruct History by folding the Log (the source of truth).

info(session_id)

@spec info(String.t()) :: map()

Snapshot of Session metadata (id, workspace, role, next seq, turn state).

interrupt(session_id)

@spec interrupt(String.t()) :: :ok | {:error, :no_turn} | {:error, map()}

Kill the currently running Turn's Task, if any.

record(session_id, event)

@spec record(String.t(), Pixir.Event.t()) :: {:ok, Pixir.Event.t()} | {:error, map()}

Record a canonical Event: stamp seq, append to the Log, then publish. Returns the stamped Event, or a structured error if it was not canonical / failed to persist.

register_pressure_warning(session_id, checkpoint_to_seq, tier)

@spec register_pressure_warning(String.t(), integer() | nil, String.t()) ::
  {:ok, :warn | :already_warned}

Hysteresis gate for context-pressure warnings (ADR 0020): returns {:ok, :warn} the first time a (latest checkpoint to_seq, tier) pair is seen and {:ok, :already_warned} afterwards — no warning spam on consecutive turns for the same pair. A new compaction checkpoint (different to_seq) or a different tier re-arms the gate. State is ephemeral process state by design: it is never logged, and re-warning after a Session restart is acceptable.

start_link(opts)

start_turn(session_id, turn_fun)

@spec start_turn(String.t(), (ctx() -> any())) ::
  {:ok, reference()} | {:error, :busy} | {:error, map()}

Start a Turn: run turn_fun.(ctx) in a supervised Task. Returns {:ok, ref} or {:error, :busy} if a Turn is already running. If the previous Turn left orphan tool calls in the Log, Pixir first records fallback tool_result events so Provider replay stays valid.

turn_running?(session_id)

@spec turn_running?(String.t()) :: boolean()

Whether a Turn is currently running.

via(session_id)

{:via, Registry, …} name for a Session id.