Sagents.AgentSupervisor (Sagents v0.8.0-rc.4)
Copy MarkdownCustom supervisor for managing an Agent and its supporting infrastructure.
AgentSupervisor coordinates the lifecycle of:
- AgentServer - Agent execution and state management
- SubAgentsDynamicSupervisor - Dynamic supervisor for spawning sub-agents
Supervision Strategy
Uses :rest_for_one strategy to ensure crash resilience:
- If AgentServer crashes, SubAgentsDynamicSupervisor restarts
- SubAgentsDynamicSupervisor crashes only affect itself
Filesystem Integration
Filesystems are now managed independently via FileSystemSupervisor.
Agents reference filesystems through the filesystem_scope field in the Agent struct.
See Sagents.FileSystem for filesystem lifecycle management.
Configuration
Accepts a keyword list with:
:agent- The Agent struct (required):initial_state- Initial State for AgentServer (optional):pubsub- PubSub configuration as{module(), atom()}tuple ornil(optional, default: nil):shutdown_delay- Delay in milliseconds to allow the supervisor to gracefully stop all children (optional, default: 5000):conversation_id- Optional conversation identifier for message persistence (optional, default: nil):agent_persistence- Module implementingSagents.AgentPersistence(optional, default: nil):display_message_persistence- Module implementingSagents.DisplayMessagePersistence(optional, default: nil)
Examples
# Minimal configuration
{:ok, agent} = Agent.new(
agent_id: "my-agent",
model: model,
base_system_prompt: "You are a helpful assistant."
)
{:ok, sup_pid} = AgentSupervisor.start_link(agent: agent)
# With initial state and PubSub
initial_state = State.new!(%{
messages: [Message.new_user!("Hello!")]
})
{:ok, sup_pid} = AgentSupervisor.start_link(
agent: agent,
initial_state: initial_state,
pubsub: {Phoenix.PubSub, :my_app_pubsub}
)
# With external filesystem
# Start filesystem separately
{:ok, config} = FileSystemConfig.new(%{
scope_key: {:user, 123},
base_directory: "Memories",
persistence_module: Sagents.FileSystem.Persistence.Disk,
storage_opts: [path: "/data/users/123"]
})
{:ok, _fs_pid} = FileSystem.ensure_filesystem({:user, 123}, [config])
# Create agent with filesystem reference
{:ok, agent} = Agent.new(%{
agent_id: "my-agent",
model: model,
filesystem_scope: {:user, 123}
})
{:ok, sup_pid} = AgentSupervisor.start_link(agent: agent)
Summary
Functions
Returns a specification to start this module under a supervisor.
Get the name of the AgentSupervisor process for a specific agent.
Get the PID of an AgentSupervisor by agent_id.
Start the AgentSupervisor.
Start the AgentSupervisor and wait for the AgentServer to be ready.
Stop the AgentSupervisor.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec get_name(String.t()) :: GenServer.name()
Get the name of the AgentSupervisor process for a specific agent.
Examples
name = AgentSupervisor.get_name("my-agent-1")
AgentSupervisor.stop(name)
Get the PID of an AgentSupervisor by agent_id.
Examples
{:ok, pid} = AgentSupervisor.get_pid("my-agent-1")
{:error, :not_found} = AgentSupervisor.get_pid("non-existent")
@spec start_link(keyword()) :: Supervisor.on_start()
Start the AgentSupervisor.
Options
:agent- The Agent struct (required):initial_state- Initial State for AgentServer (optional):pubsub- PubSub configuration as{module(), atom()}tuple ornil(optional, default: nil). Used only forPhoenix.Presencepresence_diffwiring; per-agent events are delivered directly to subscriber pids viaSagents.Publisher.:inactivity_timeout- Timeout in milliseconds for automatic shutdown (optional, default: 300_000 - 5 minutes) Set tonilor:infinityto disable automatic shutdown:name- Supervisor name registration (optional):shutdown_delay- Delay in milliseconds to allow the supervisor to gracefully stop all children (optional, default: 5000):conversation_id- Optional conversation identifier for message persistence (optional, default: nil):agent_persistence- Module implementingSagents.AgentPersistence(optional, default: nil):display_message_persistence- Module implementingSagents.DisplayMessagePersistence(optional, default: nil)
Examples
{:ok, sup_pid} = AgentSupervisor.start_link(
agent: agent,
pubsub: {Phoenix.PubSub, :my_app_pubsub}
)
{:ok, sup_pid} = AgentSupervisor.start_link(
agent: agent,
name: AgentSupervisor.get_name("agent-123")
)
# With custom inactivity timeout
{:ok, sup_pid} = AgentSupervisor.start_link(
agent: agent,
inactivity_timeout: 600_000 # 10 minutes
)
# Disable automatic shutdown
{:ok, sup_pid} = AgentSupervisor.start_link(
agent: agent,
inactivity_timeout: nil
)
Start the AgentSupervisor and wait for the AgentServer to be ready.
This is a synchronous version of start_link/1 that waits for the AgentServer
child process to be fully registered before returning. This prevents race
conditions where callers try to interact with the AgentServer (e.g., subscribe,
add_message) before it's ready.
Why This Is Needed
When start_link/1 returns successfully, the supervisor is running but its
children are still completing their initialization asynchronously. If you
immediately try to call AgentServer.get_pid/1 or AgentServer.subscribe/1,
the AgentServer might not be registered yet, causing failures.
This function solves that by:
- Starting the supervisor normally
- Polling for the AgentServer to be registered with exponential backoff
- Returning only when the AgentServer is confirmed ready
Use Cases
Use this function when you need immediate access to the AgentServer:
- Web request handlers that need to subscribe or send messages right away
- CLI tools that need synchronous agent startup
- Test setups that require the agent to be fully ready
Use regular start_link/1 when:
- Startup timing isn't critical
- You can handle eventual consistency
- You're starting many agents in parallel
Options
Same as start_link/1, plus:
:startup_timeout- Maximum time to wait for AgentServer readiness (default: 5000ms)
Examples
# Start and wait for agent to be ready
{:ok, sup_pid} = AgentSupervisor.start_link_sync(
agent: agent,
pubsub: {Phoenix.PubSub, :my_app_pubsub}
)
# Agent is guaranteed to be ready - safe to use immediately
:ok = AgentServer.subscribe(agent.agent_id)
# Custom startup timeout
{:ok, sup_pid} = AgentSupervisor.start_link_sync(
agent: agent,
startup_timeout: 10_000
)Returns
{:ok, supervisor_pid}- Supervisor started and AgentServer is ready{:error, {:agent_startup_timeout, agent_id}}- AgentServer failed to become ready{:error, reason}- Supervisor failed to start
Stop the AgentSupervisor.
Examples
AgentSupervisor.stop("my-agent-1")
AgentSupervisor.stop("my-agent-1", 10_000) # 10 second timeout