Manages the lifecycle of one AI agent conversation.
The session holds conversation history, tracks agent status, and
coordinates between the provider (pi RPC, etc.) and the editor UI.
It runs as a supervised GenServer under Agent.Supervisor, so a
crash here never affects buffers or the editor.
Status lifecycle
:idle → :thinking → :tool_executing → :thinking → ... → :idle
↓ ↓
:error :errorSubscribing to events
Call subscribe/2 with a pid to receive {:agent_event, session_pid, event}
messages. The editor uses this to update the modeline and chat panel.
Summary
Types
Snapshot of session state needed by the editor for rendering.
File touch record.
Deprecated: use MingaAgent.SessionMetadata.t() directly.
Pending tool approval data.
Internal session state.
Agent session status.
Functions
Aborts the current agent operation.
Activates a skill by name.
Appends a system message to the conversation and notifies subscribers.
Returns the edit boundary for the given file path, or nil if unbounded.
Branches the conversation at the given turn index.
Returns a specification to start this module under a supervisor.
Clears all edit boundaries for this session.
Clears the edit boundary for the given file path, restoring full-buffer access.
Clears both queues without returning their contents.
Converts a list of queue entries (strings or ContentPart lists) into a single string suitable for display or restoring to the prompt input.
Manually triggers context compaction on the provider.
Continues from an interrupted stream response.
Cycles to the next model in the configured rotation.
Cycles to the next thinking level.
Deactivates a skill by name.
Pops and returns all pending steering messages, clearing the steering queue.
Returns a snapshot of session state for the editor to rebuild AgentState.
Fetches available models from the provider.
Fetches available commands from the provider.
Returns the provider pid for direct provider-specific calls.
Returns both queues without modifying them (for pending message display).
Lists all conversation branches.
Lists all discovered skills and which are active.
Loads a previously saved session, replacing the current conversation history.
Returns the conversation messages.
Returns the conversation messages paired with their stable BEAM-assigned IDs.
Returns lightweight metadata about this session (for the picker).
Starts a fresh conversation.
Queues a message as a follow-up (sent automatically once the current agent run finishes).
Queues a message as a steering prompt (injected between tool calls on the next turn).
Returns both queues and clears them. Used by abort (Ctrl-C) and dequeue (Alt+Up) so pending messages can be restored to the prompt input.
Responds to a pending tool approval.
Sends a user prompt to the agent.
Returns the session ID.
Sets an edit boundary for the agent on the given file path.
Sets the model without resetting conversation context.
Sets the thinking level on the provider.
Starts a new agent session.
Returns the current session status.
Subscribes the calling process to session events.
Generates a context artifact summarizing the current session.
Switches to a named branch, replacing the current messages.
Toggles all tool call messages between collapsed and expanded.
Toggles the collapsed state of a tool call message.
Returns files touched by this agent session, ordered by most recent first.
Unsubscribes the calling process from session events.
Returns accumulated token usage.
Types
@type editor_snapshot() :: %{ status: status(), pending_approval: map() | nil, error: String.t() | nil }
Snapshot of session state needed by the editor for rendering.
@type file_touch() :: %{ path: String.t(), action: :created | :modified | :deleted, timestamp: integer() }
File touch record.
@type metadata() :: MingaAgent.SessionMetadata.t()
Deprecated: use MingaAgent.SessionMetadata.t() directly.
@type pending_approval() :: MingaAgent.ToolApproval.t()
Pending tool approval data.
@type state() :: %{ session_id: String.t(), provider: pid() | nil, provider_module: module(), provider_opts: keyword(), status: status(), messages: [MingaAgent.Message.t()], message_ids: [pos_integer()], next_message_id: pos_integer(), subscribers: MapSet.t(pid()), total_usage: MingaAgent.Event.token_usage(), error_message: String.t() | nil, pending_thinking_level: String.t() | nil, pending_approval: pending_approval() | nil, model_name: String.t(), provider_name: String.t(), save_timer: reference() | nil, branches: [MingaAgent.Branch.t()], steering_queue: [String.t() | [ReqLLM.Message.ContentPart.t()]], follow_up_queue: [String.t() | [ReqLLM.Message.ContentPart.t()]], touched_files: %{required(String.t()) => file_touch()}, boundaries: %{required(String.t()) => MingaAgent.EditBoundary.t()} }
Internal session state.
@type status() :: :idle | :thinking | :tool_executing | :error
Agent session status.
Functions
@spec abort(GenServer.server()) :: :ok
Aborts the current agent operation.
@spec activate_skill(GenServer.server(), String.t()) :: {:ok, term()} | {:error, term()}
Activates a skill by name.
@spec add_system_message( GenServer.server(), String.t(), MingaAgent.Message.system_level() ) :: :ok
Appends a system message to the conversation and notifies subscribers.
@spec boundary_for(GenServer.server(), String.t()) :: {non_neg_integer(), non_neg_integer()} | nil
Returns the edit boundary for the given file path, or nil if unbounded.
@spec branch_at(GenServer.server(), non_neg_integer()) :: {:ok, String.t()} | {:error, String.t()}
Branches the conversation at the given turn index.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear_all_boundaries(GenServer.server()) :: :ok
Clears all edit boundaries for this session.
@spec clear_boundary(GenServer.server(), String.t()) :: :ok
Clears the edit boundary for the given file path, restoring full-buffer access.
@spec clear_queues(GenServer.server()) :: :ok
Clears both queues without returning their contents.
@spec combine_queue_entries_to_text([String.t() | [ReqLLM.Message.ContentPart.t()]]) :: String.t()
Converts a list of queue entries (strings or ContentPart lists) into a single string suitable for display or restoring to the prompt input.
@spec compact(GenServer.server()) :: {:ok, String.t()} | {:error, String.t()}
Manually triggers context compaction on the provider.
@spec continue(GenServer.server()) :: :ok | {:error, term()}
Continues from an interrupted stream response.
@spec cycle_model(GenServer.server()) :: {:ok, map()} | {:error, term()}
Cycles to the next model in the configured rotation.
@spec cycle_thinking_level(GenServer.server()) :: {:ok, term()} | {:error, term()}
Cycles to the next thinking level.
@spec deactivate_skill(GenServer.server(), String.t()) :: :ok | {:error, term()}
Deactivates a skill by name.
@spec dequeue_steering(GenServer.server()) :: [ String.t() | [ReqLLM.Message.ContentPart.t()] ]
Pops and returns all pending steering messages, clearing the steering queue.
@spec editor_snapshot(GenServer.server()) :: editor_snapshot()
Returns a snapshot of session state for the editor to rebuild AgentState.
@spec get_available_models(GenServer.server()) :: {:ok, term()} | {:error, term()}
Fetches available models from the provider.
@spec get_commands(GenServer.server()) :: {:ok, [map()]} | {:error, term()}
Fetches available commands from the provider.
@spec get_provider(GenServer.server()) :: pid() | nil
Returns the provider pid for direct provider-specific calls.
@spec get_queued_messages(GenServer.server()) :: {[String.t() | [ReqLLM.Message.ContentPart.t()]], [String.t() | [ReqLLM.Message.ContentPart.t()]]}
Returns both queues without modifying them (for pending message display).
@spec list_branches(GenServer.server()) :: {:ok, String.t()}
Lists all conversation branches.
@spec list_skills(GenServer.server()) :: {:ok, [map()], [String.t()]} | {:error, term()}
Lists all discovered skills and which are active.
@spec load_session(GenServer.server(), String.t()) :: :ok | {:error, term()}
Loads a previously saved session, replacing the current conversation history.
The provider's conversation context is not synced; the loaded messages are for display only until the user sends a new prompt, which re-establishes the provider context.
@spec messages(GenServer.server()) :: [MingaAgent.Message.t()]
Returns the conversation messages.
@spec messages_with_ids(GenServer.server()) :: [ {pos_integer(), MingaAgent.Message.t()} ]
Returns the conversation messages paired with their stable BEAM-assigned IDs.
@spec metadata(GenServer.server()) :: MingaAgent.SessionMetadata.t()
Returns lightweight metadata about this session (for the picker).
@spec new_session(GenServer.server()) :: :ok | {:error, term()}
Starts a fresh conversation.
@spec queue_follow_up( GenServer.server(), String.t() | [ReqLLM.Message.ContentPart.t()] ) :: :ok | {:queued, :follow_up} | {:error, term()}
Queues a message as a follow-up (sent automatically once the current agent run finishes).
When the agent is idle, behaves identically to send_prompt/2.
Returns {:queued, :follow_up} when the message was queued.
@spec queue_steering( GenServer.server(), String.t() | [ReqLLM.Message.ContentPart.t()] ) :: :ok | {:queued, :steering} | {:error, term()}
Queues a message as a steering prompt (injected between tool calls on the next turn).
When the agent is idle, behaves identically to send_prompt/2.
Returns {:queued, :steering} when the message was queued.
@spec recall_queues(GenServer.server()) :: {[String.t() | [ReqLLM.Message.ContentPart.t()]], [String.t() | [ReqLLM.Message.ContentPart.t()]]}
Returns both queues and clears them. Used by abort (Ctrl-C) and dequeue (Alt+Up) so pending messages can be restored to the prompt input.
@spec respond_to_approval(GenServer.server(), :approve | :reject | :approve_all) :: :ok
Responds to a pending tool approval.
Sends the decision directly to the Task process that is blocking
on receive, then clears the pending approval and broadcasts
the resolution to subscribers.
@spec send_prompt(GenServer.server(), String.t() | [ReqLLM.Message.ContentPart.t()]) :: :ok | {:queued, :steering} | {:error, term()}
Sends a user prompt to the agent.
Accepts either a plain text string or a list of ContentPart structs (for multi-modal messages with images).
@spec session_id(GenServer.server()) :: String.t()
Returns the session ID.
@spec set_boundary( GenServer.server(), String.t(), non_neg_integer(), non_neg_integer() ) :: :ok | {:error, String.t()}
Sets an edit boundary for the agent on the given file path.
The agent will be restricted to editing within the specified line range (0-indexed, both inclusive). Edits outside the boundary are rejected with a descriptive error message.
@spec set_model(GenServer.server(), String.t()) :: :ok | {:error, term()}
Sets the model without resetting conversation context.
@spec set_thinking_level(GenServer.server(), String.t()) :: :ok | {:error, term()}
Sets the thinking level on the provider.
@spec start_link(keyword()) :: GenServer.on_start()
Starts a new agent session.
@spec status(GenServer.server()) :: status()
Returns the current session status.
@spec subscribe(GenServer.server()) :: :ok
Subscribes the calling process to session events.
@spec summarize(GenServer.server()) :: {:ok, String.t(), String.t()} | {:error, term()}
Generates a context artifact summarizing the current session.
@spec switch_branch(GenServer.server(), non_neg_integer()) :: :ok | {:error, String.t()}
Switches to a named branch, replacing the current messages.
@spec toggle_all_tool_collapses(GenServer.server()) :: :ok
Toggles all tool call messages between collapsed and expanded.
@spec toggle_tool_collapse(GenServer.server(), non_neg_integer()) :: :ok
Toggles the collapsed state of a tool call message.
@spec touched_files(GenServer.server()) :: [file_touch()]
Returns files touched by this agent session, ordered by most recent first.
Each entry contains:
path: relative file pathaction::created,:modified, or:deletedtimestamp: monotonic timestamp of the last touch
Derived from tool call history (file_write, file_edit, multi_edit_file).
@spec unsubscribe(GenServer.server()) :: :ok
Unsubscribes the calling process from session events.
@spec usage(GenServer.server()) :: MingaAgent.Event.token_usage()
Returns accumulated token usage.