MingaAgent.SessionManager (Minga v0.1.0)

Copy Markdown View Source

Owns agent session lifecycle independently of any UI.

Maps stable session IDs to session PIDs. Local scratch sessions still use human-readable generated IDs (e.g., "session-1"), while remote attach sessions pass a deterministic :session_id derived from their server-side working directory. Sessions are started via a configurable DynamicSupervisor, which defaults to MingaAgent.Supervisor, and monitored here. When a session dies or restarts, the manager broadcasts lifecycle events so the Editor (or any subscriber) can react without monitoring PIDs directly.

Summary

Types

Restart bookkeeping for a managed session that is being recovered.

An entry in the sessions map.

Internal state of the SessionManager.

Functions

Aborts the current operation on a session by ID.

Aborts the current operation on a session by ID through the given manager.

Releases the calling Session's exact reservation without changing its ID.

Returns a specification to start this module under a supervisor.

Commits the calling Session's reserved ID and transfers its persisted token.

Looks up the PID for a session ID.

Looks up the PID for a session ID through the given manager.

Lists background sub-agents for a parent session pid, or all background sub-agents when parent is nil.

Lists background sub-agents through the given manager.

Lists every active registration with available metadata or a safe unavailable reason.

Lists every active registration through the given manager without treating metadata failure as session death.

Registers the provider and external effect workers owned by a managed Session.

Reserves an unused durable ID for the managed Session that calls this API.

Sends a user prompt to a session by ID.

Sends a user prompt to a session by ID through the given manager.

Looks up the session ID for a PID.

Looks up the session ID for a PID through the given manager.

Returns the broker token for a live session. Used by the remote API bootstrap path.

Returns the broker token for a live session through the given manager.

Builds the deterministic session ID used for a server-side working directory.

Starts a background sub-agent, sends it the task asynchronously, and returns a stable handle.

Starts a background sub-agent through the given manager.

Starts the SessionManager.

Starts or returns the stable session with the given ID.

Starts or returns the stable session with the given ID through the given manager.

Starts a new agent session with a generated human-readable ID.

Starts a new agent session through the given manager.

Stops a session by its human-readable ID.

Stops a session by its human-readable ID through the given manager.

Stops a session by its PID (looks up the ID internally).

Stops a session by its PID through the given manager.

Types

identity_reservation()

@type identity_reservation() :: {String.t(), reference()}

restart_state()

@type restart_state() :: %{
  attempts: pos_integer(),
  window_started_at_ms: integer(),
  timer_ref: reference() | nil,
  timer_token: reference() | nil,
  old_pid: pid(),
  reason: term()
}

Restart bookkeeping for a managed session that is being recovered.

session_entry()

@type session_entry() :: %{
  pid: pid() | nil,
  monitor_ref: reference() | nil,
  provider_pid: pid() | nil,
  provider_monitor_ref: reference() | nil,
  effect_worker_refs: %{required(pid()) => reference()},
  token: String.t(),
  restart_opts: keyword(),
  restart_state: restart_state() | nil,
  stop_pending?: boolean(),
  startup_delivery: startup_delivery() | nil
}

An entry in the sessions map.

state()

@type state() :: %{
  sessions: %{required(String.t()) => session_entry()},
  background_subagents: %{
    required(String.t()) => MingaAgent.Subagent.Handle.t()
  },
  identity_reservations: %{required(String.t()) => identity_reservation_entry()},
  identity_reservations_by_owner: %{required(pid()) => String.t()},
  next_id: pos_integer(),
  session_supervisor: GenServer.server(),
  startup_task_supervisor: GenServer.server()
}

Internal state of the SessionManager.

Functions

abort(session_id)

@spec abort(String.t()) :: :ok | {:error, :not_found}

Aborts the current operation on a session by ID.

abort(manager, session_id)

@spec abort(GenServer.server(), String.t()) ::
  :ok | {:error, :not_found | :session_id_changed}

Aborts the current operation on a session by ID through the given manager.

abort_session_identity(manager, reservation)

@spec abort_session_identity(GenServer.server(), identity_reservation()) :: :ok

Releases the calling Session's exact reservation without changing its ID.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

commit_session_identity(manager, reservation)

@spec commit_session_identity(GenServer.server(), identity_reservation()) ::
  :ok | {:error, :stale_identity_reservation}

Commits the calling Session's reserved ID and transfers its persisted token.

get_session(session_id)

@spec get_session(String.t()) :: {:ok, pid()} | {:error, :not_found}

Looks up the PID for a session ID.

get_session(manager, session_id)

@spec get_session(GenServer.server(), String.t()) ::
  {:ok, pid()} | {:error, :not_found}

Looks up the PID for a session ID through the given manager.

list_background_subagents(parent_session_pid \\ nil)

@spec list_background_subagents(pid() | nil) :: [MingaAgent.Subagent.Handle.t()]

Lists background sub-agents for a parent session pid, or all background sub-agents when parent is nil.

list_background_subagents(manager, parent_session_pid)

@spec list_background_subagents(GenServer.server(), pid() | nil) :: [
  MingaAgent.Subagent.Handle.t()
]

Lists background sub-agents through the given manager.

list_sessions()

@spec list_sessions() :: [MingaAgent.SessionListing.t()]

Lists every active registration with available metadata or a safe unavailable reason.

list_sessions(manager)

@spec list_sessions(GenServer.server()) :: [MingaAgent.SessionListing.t()]

Lists every active registration through the given manager without treating metadata failure as session death.

register_effect_workers(manager, session_pid, provider_pid, worker_pids)

@spec register_effect_workers(GenServer.server(), pid(), pid(), [pid()]) ::
  :ok | {:error, :session_not_found | :provider_mismatch}

Registers the provider and external effect workers owned by a managed Session.

reserve_session_identity(manager, target_id)

@spec reserve_session_identity(GenServer.server(), String.t()) ::
  {:ok, :unchanged | identity_reservation()}
  | {:error,
     :session_not_managed
     | :session_id_in_use
     | {:remote_token_persistence_failed, term()}}

Reserves an unused durable ID for the managed Session that calls this API.

send_prompt(session_id, prompt)

@spec send_prompt(String.t(), String.t()) :: :ok | {:error, term()}

Sends a user prompt to a session by ID.

send_prompt(manager, session_id, prompt)

@spec send_prompt(GenServer.server(), String.t(), String.t()) ::
  :ok | {:queued, :steering} | {:error, term()}

Sends a user prompt to a session by ID through the given manager.

session_id_for_pid(pid)

@spec session_id_for_pid(pid()) :: {:ok, String.t()} | {:error, :not_found}

Looks up the session ID for a PID.

session_id_for_pid(manager, pid)

@spec session_id_for_pid(GenServer.server(), pid()) ::
  {:ok, String.t()} | {:error, :not_found}

Looks up the session ID for a PID through the given manager.

session_token(session_id)

@spec session_token(String.t()) :: {:ok, String.t()} | {:error, :not_found}

Returns the broker token for a live session. Used by the remote API bootstrap path.

session_token(manager, session_id)

@spec session_token(GenServer.server(), String.t()) ::
  {:ok, String.t()} | {:error, :not_found}

Returns the broker token for a live session through the given manager.

stable_session_id_for_workdir(path)

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

Builds the deterministic session ID used for a server-side working directory.

start_background_subagent(parent_session_pid, task, opts \\ [])

@spec start_background_subagent(pid() | nil, String.t(), keyword()) ::
  {:ok, MingaAgent.Subagent.Handle.t()} | {:error, term()}

Starts a background sub-agent, sends it the task asynchronously, and returns a stable handle.

start_background_subagent(manager, parent_session_pid, task, opts)

@spec start_background_subagent(
  GenServer.server(),
  pid() | nil,
  String.t(),
  keyword()
) ::
  {:ok, MingaAgent.Subagent.Handle.t()} | {:error, term()}

Starts a background sub-agent through the given manager.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the SessionManager.

start_or_get_session(session_id, opts \\ [])

@spec start_or_get_session(String.t(), keyword()) ::
  {:ok, String.t(), pid()} | {:error, term()}

Starts or returns the stable session with the given ID.

start_or_get_session(manager, session_id, opts)

@spec start_or_get_session(GenServer.server(), String.t(), keyword()) ::
  {:ok, String.t(), pid()} | {:error, term()}

Starts or returns the stable session with the given ID through the given manager.

start_session(opts \\ [])

@spec start_session(keyword()) :: {:ok, String.t(), pid()} | {:error, term()}

Starts a new agent session with a generated human-readable ID.

Returns {:ok, session_id, pid} on success.

start_session(manager, opts)

@spec start_session(GenServer.server(), keyword()) ::
  {:ok, String.t(), pid()} | {:error, term()}

Starts a new agent session through the given manager.

stop_session(session_id)

@spec stop_session(String.t()) :: :ok | {:error, :not_found}

Stops a session by its human-readable ID.

stop_session(manager, session_id)

@spec stop_session(GenServer.server(), String.t()) :: :ok | {:error, :not_found}

Stops a session by its human-readable ID through the given manager.

stop_session_by_pid(pid)

@spec stop_session_by_pid(pid()) :: :ok | {:error, :not_found}

Stops a session by its PID (looks up the ID internally).

stop_session_by_pid(manager, pid)

@spec stop_session_by_pid(GenServer.server(), pid()) :: :ok | {:error, :not_found}

Stops a session by its PID through the given manager.