Sagents.AgentServer (Sagents v0.7.0)

Copy Markdown

GenServer that wraps a DeepAgent and its State, managing execution lifecycle and broadcasting events via PubSub.

The AgentServer provides:

  • Asynchronous agent execution
  • State management and tracking
  • Event broadcasting for UI updates
  • Human-in-the-loop interrupt handling

Understanding agent_id

The agent_id is a runtime identifier used for process management and inter-process communication. It serves several critical purposes:

Process Registration

The agent_id is used to construct a Registry key via get_name(agent_id), which returns a :via tuple for GenServer registration:

  • Format: {:via, Registry, {Sagents.Registry, {:agent_server, agent_id}}}
  • Ensures only one AgentServer process exists per agent_id
  • Enables process lookup without maintaining PIDs

PubSub Topics

The agent_id forms the basis for PubSub topic construction:

  • Topic format: "agent_server:#{agent_id}"
  • External clients subscribe using: AgentServer.subscribe(agent_id)
  • Events broadcast include: status changes, LLM deltas, todos updates

Middleware Context

The agent_id is passed to middleware during initialization, enabling:

  • Coordination with agent-specific services (FileSystemServer, SubAgentsDynamicSupervisor)
  • Parent-child relationship establishment in SubAgent hierarchies
  • Per-agent resource isolation (virtual filesystems, etc.)

Supervision Tree Coordination

The agent_id flows through the entire supervision tree via AgentSupervisor, ensuring all child processes (FileSystemServer, AgentServer, SubAgentsDynamicSupervisor) are coordinated under the same agent context.

What agent_id IS NOT

Not Part of Conversation State: The agent_id is NOT included in serialized state (via export_state/1). It's a runtime identifier, not conversation data. This separation provides important benefits:

  • Flexibility: Restore the same conversation state under a different agent_id
  • State Cloning: Clone conversations for testing or forking scenarios
  • Clean Architecture: Clear separation between runtime identity and data

When restoring state via start_link_from_state/2, you must provide the agent_id as a parameter. This enables use cases like:

# Restore with same agent_id
AgentServer.start_link_from_state(saved_state, agent_id: "conversation-123")

# Clone conversation with different agent_id
AgentServer.start_link_from_state(saved_state, agent_id: "conversation-123-fork")

The agent_id can be any value that makes sense for your application:

  • Database conversation IDs: "conv_a1b2c3d4"
  • User-scoped identifiers: "user-#{user_id}-session-#{session_id}"
  • Randomly generated GUIDs: UUID.uuid4()
  • Application-defined values: "demo-agent-001"

Events

The server broadcasts events on the topic "agent_server:#{agent_id}".

All events are wrapped in an {:agent, event} tuple to help consumers identify and route events from AgentServer. This is similar to how FileSystemServer wraps its events in {:file_system, event}.

Event Format

Events are received in the format {:agent, event} where event is one of:

Todo Events

  • {:agent, {:todos_updated, todos}} - Complete snapshot of current TODO list

Status Events

  • {:agent, {:status_changed, :idle, nil}} - Server ready for work (also broadcast after successful execution completion)
  • {:agent, {:status_changed, :running, nil}} - Agent executing
  • {:agent, {:status_changed, :interrupted, interrupt_data}} - Awaiting human decision
  • {:agent, {:status_changed, :cancelled, nil}} - Execution was cancelled by user
  • {:agent, {:status_changed, :error, reason}} - Execution failed

Node Transfer Events

  • {:agent, {:node_transferring, data}} - Agent is about to leave this node (broadcast during terminate/2)
    • data.from_node - The node the agent is leaving
  • {:agent, {:node_transferred, data}} - Agent has been restored on a new node (broadcast on startup after restore)
    • data.to_node - The node the agent has been restored to

Shutdown Events

  • {:agent, {:agent_shutdown, shutdown_data}} - Agent is shutting down
    • shutdown_data.agent_id - The agent identifier
    • shutdown_data.reason - Shutdown reason (:inactivity | :no_viewers)

    • shutdown_data.last_activity_at - DateTime of last activity
    • shutdown_data.shutdown_at - DateTime when shutdown was initiated

Tool Execution Events (consolidated)

  • {:agent, {:tool_execution_update, status, tool_info}} - Tool execution lifecycle update

    • status is one of: :executing, :completed, :failed
    • :executingtool_info contains :call_id, :name, :display_text, :arguments
    • :completedtool_info contains :call_id, :name, :result
    • :failedtool_info contains :call_id, :name, :error
  • {:agent, {:display_message_updated, display_msg}} - Tool status updated in DB (only when display_message_persistence is configured)

LLM Streaming Events

  • {:agent, {:llm_deltas, [%MessageDelta{}]}} - Streaming tokens/deltas received (list of deltas)
  • {:agent, {:llm_message, %Message{}}} - Complete message received and processed
  • {:agent, {:llm_token_usage, %TokenUsage{}}} - Token usage information

Message Persistence Events

  • {:agent, {:display_message_saved, display_message}} - Broadcast after message is persisted via display_message_persistence behaviour. The {:llm_message, ...} event is also broadcast alongside this event

Note: File events are NOT broadcast by AgentServer. Files are managed by FileSystemServer which provides its own event handling mechanism.

Debug Events

When debug PubSub is configured, additional debug events are broadcast on the topic "agent_server:debug:#{agent_id}". These events provide deeper insight into agent execution for debugging and monitoring purposes.

Debug events are also wrapped in {:agent, {:debug, event}} for consistent routing with regular events.

Middleware Debug Events

  • {:agent, {:debug, {:agent_state_update, state}}} - Middleware state update with full state snapshot

Usage

# Start a server
{:ok, agent} = Agent.new(
  agent_id: "my-agent-1",
  model: model,
  base_system_prompt: "You are a helpful assistant."
)

initial_state = State.new!(%{
  messages: [Message.new_user!("Write a hello world program")]
})

{:ok, _pid} = AgentServer.start_link(
  agent: agent,
  initial_state: initial_state,
  name: AgentServer.get_name("my-agent-1")
)

# Subscribe to events
AgentServer.subscribe("my-agent-1")

# Execute the agent
:ok = AgentServer.execute("my-agent-1")

# Cancel execution if needed
:ok = AgentServer.cancel("my-agent-1")

# Listen for events
receive do
  {:todos_updated, todos} -> IO.inspect(todos, label: "Current TODOs")
  {:status_changed, :idle, nil} -> IO.puts("Done!")
end

Human-in-the-Loop Example

# Configure agent with interrupts
{:ok, agent} = Agent.new(
  agent_id: "my-agent-1",
  model: model,
  interrupt_on: %{"write_file" => true}
)

{:ok, _pid} = AgentServer.start_link(
  agent: agent,
  initial_state: state,
  name: AgentServer.get_name("my-agent-1")
)

AgentServer.subscribe("my-agent-1")

# Execute
AgentServer.execute("my-agent-1")

# Wait for interrupt
receive do
  {:status_changed, :interrupted, interrupt_data} ->
    # Display interrupt_data.action_requests to user
    decisions = get_user_decisions(interrupt_data)
    AgentServer.resume("my-agent-1", decisions)
end

# Wait for completion
receive do
  {:status_changed, :idle, nil} -> :ok
end

Summary

Types

Status of the agent server

Functions

Add a message to the agent's state and transition to idle if completed.

Gets count of currently running agents.

Gets detailed information about a running agent.

Cancel a running LLM task.

Execute the agent.

Export the current conversation state to a serializable format.

Gets the agent configuration for the given agent.

Get the current inactivity status of an agent.

Get server info including status, state, and any error or interrupt data.

Gets metadata about the agent server including status and last activity.

Get the name of the AgentServer process for a specific agent.

Get the pid of the AgentServer process for a specific agent.

Get the current status of the server.

Gets all running agents matching a glob pattern.

Lists all currently running agent processes.

Send a targeted message to a specific middleware in a running AgentServer.

Request the AgentServer to publish a specific debug PubSub message or event.

Request the AgentServer to publish an specific PubSub message or event.

Reset the agent's state and filesystem to start fresh.

Restore agent state from a previously exported state.

Resume agent execution after an interrupt.

Check if an agent is running.

Start an AgentServer.

Start a new AgentServer with restored state.

Stop the AgentServer.

Subscribe to events from this AgentServer.

Subscribe to debug events from this AgentServer.

Touch the agent to indicate activity and reset the inactivity timer.

Unsubscribe from events from this AgentServer.

Unsubscribe from debug events from this AgentServer.

Updates both the agent configuration and state.

Types

status()

@type status() :: :idle | :running | :interrupted | :paused | :cancelled | :error

Status of the agent server

Functions

add_message(agent_id, message)

@spec add_message(String.t(), LangChain.Message.t()) :: :ok

Add a message to the agent's state and transition to idle if completed.

This is useful for conversational interfaces where you want to add a new user message after the agent has completed a previous execution.

Returns :ok on success.

Examples

# After agent completes
:ok = AgentServer.add_message("my-agent-1", Message.new_user!("What's next?"))
:ok = AgentServer.execute("my-agent-1")

agent_count()

@spec agent_count() :: non_neg_integer()

Gets count of currently running agents.

Returns the total number of AgentServer processes registered in the Sagents.Registry.

Examples

AgentServer.agent_count()
# => 5

agent_info(agent_id)

@spec agent_info(String.t()) :: map() | nil

Gets detailed information about a running agent.

Returns a map with agent status and state information, or nil if the agent is not running.

Return Value

If the agent is running, returns a map containing:

  • :agent_id - The agent identifier
  • :pid - The process ID
  • :status - Current execution status (:idle, :running, :interrupted, etc.)
  • :state - Exported state snapshot
  • :message_count - Number of messages in the state
  • :has_interrupt - Boolean indicating if there's pending interrupt data

Examples

AgentServer.agent_info("conversation-1")
# => %{
#   agent_id: "conversation-1",
#   pid: #PID<0.1234.0>,
#   status: :idle,
#   state: %State{...},
#   message_count: 5,
#   has_interrupt: false
# }

AgentServer.agent_info("nonexistent")
# => nil

cancel(agent_id)

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

Cancel a running LLM task.

Stops the currently executing agent task and transitions the server to completed status. Returns {:error, reason} if the server is not running (no task to cancel).

Examples

:ok = AgentServer.cancel("my-agent-1")

execute(agent_id)

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

Execute the agent.

Starts agent execution asynchronously. The server will broadcast events as the agent runs. Returns :ok immediately.

Returns {:error, reason} if the server is not idle (already running, interrupted, etc.).

Examples

:ok = AgentServer.execute("my-agent-1")

export_state(agent_id)

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

Export the current conversation state to a serializable format.

This can be persisted to a database and later used to restore the conversation state. The exported state uses string keys (not atoms) for compatibility with JSON/JSONB storage.

Returns a map with string keys containing:

  • "version" - Serialization format version
  • "state" - The conversation state (messages, todos, metadata)
  • "serialized_at" - ISO8601 timestamp

What is NOT included:

  • Agent configuration (middleware, tools, model) - must come from application code
  • agent_id - runtime identifier provided when restoring

This design allows you to restore the same conversation state under a different agent_id, enabling use cases like state cloning and conversation forking.

Examples

state = AgentServer.export_state("my-agent-1")
# Save to database
MyApp.Conversations.save_agent_state(conversation_id, state)

get_agent(agent_id)

@spec get_agent(String.t()) :: {:ok, Sagents.Agent.t()} | {:error, :not_found}

Gets the agent configuration for the given agent.

Returns

  • {:ok, Agent.t()} - The agent struct
  • {:error, :not_found} - Agent not found

Examples

{:ok, agent} = AgentServer.get_agent("my-agent-1")
# => {:ok, %Agent{agent_id: "my-agent-1", model: %ChatModel{...}, ...}}

get_inactivity_status(agent_id)

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

Get the current inactivity status of an agent.

Returns a map with:

  • :inactivity_timeout - Configured timeout in milliseconds (or nil/:infinity)
  • :last_activity_at - DateTime of last activity
  • :timer_active - Boolean indicating if timer is currently running
  • :time_since_activity - Milliseconds since last activity (or nil if no activity yet)

Examples

status = AgentServer.get_inactivity_status("my-agent-1")
# => %{
#   inactivity_timeout: 300_000,
#   last_activity_at: ~U[2025-11-06 10:15:30.123Z],
#   timer_active: true,
#   time_since_activity: 45_000
# }

get_info(agent_id)

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

Get server info including status, state, and any error or interrupt data.

Returns a map with:

  • :status - Current status
  • :state - Current State
  • :interrupt_data - Interrupt data if status is :interrupted
  • :error - Error reason if status is :error

Examples

info = AgentServer.get_info("my-agent-1")

get_metadata(agent_id)

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

Gets metadata about the agent server including status and last activity.

Returns

  • {:ok, metadata} - Map with agent metadata
  • {:error, :not_found} - Agent not found

Metadata Fields

  • :status - Current status atom (:idle, :running, :interrupted, :error, :cancelled)
  • :last_activity_at - DateTime of last activity (may be nil)
  • :conversation_id - Conversation ID (may be nil)
  • :node - The Erlang node where this agent is running

Examples

{:ok, metadata} = AgentServer.get_metadata("my-agent-1")
# => {:ok, %{status: :idle, last_activity_at: ~U[2024-01-01 12:00:00Z], conversation_id: "conv-123"}}

get_name(agent_id)

@spec get_name(String.t()) :: GenServer.name()

Get the name of the AgentServer process for a specific agent.

Examples

name = AgentServer.get_name("my-agent-1")
GenServer.call(name, :get_status)

get_pid(agent_id)

@spec get_pid(String.t()) :: pid() | {atom(), node()} | nil

Get the pid of the AgentServer process for a specific agent.

Examples

pid = AgentServer.get_pit("my-agent-1")
send(pid, message)

get_status(agent_id)

@spec get_status(String.t()) :: status() | :not_running

Get the current status of the server.

Returns one of:

  • :idle - Server ready for work
  • :running - Agent executing
  • :interrupted - Awaiting human decision
  • :cancelled - Execution was cancelled
  • :error - Execution failed
  • :not_running - Agent process does not exist

Examples

:idle = AgentServer.get_status("my-agent-1")
:not_running = AgentServer.get_status("non-existent-agent")

list_agents_matching(pattern)

@spec list_agents_matching(String.t()) :: [String.t()]

Gets all running agents matching a glob pattern.

Supports wildcard patterns using * which matches any sequence of characters.

Examples

# Get all conversation agents
AgentServer.list_agents_matching("conversation-*")
# => ["conversation-1", "conversation-2", "conversation-123"]

# Get all user agents
AgentServer.list_agents_matching("user-*")
# => ["user-42", "user-99"]

# Get specific prefix
AgentServer.list_agents_matching("demo-*")
# => ["demo-agent-001"]

list_running_agents()

@spec list_running_agents() :: [String.t()]

Lists all currently running agent processes.

Returns a list of agent_ids for all running AgentServer processes registered in the Sagents.Registry.

Examples

AgentServer.list_running_agents()
# => ["conversation-1", "conversation-2", "user-123"]

notify_middleware(agent_id, middleware_id, message)

@spec notify_middleware(String.t(), term(), term()) :: :ok

Send a targeted message to a specific middleware in a running AgentServer.

The message is routed through the middleware registry and delivered to the middleware's handle_message/3 callback. The middleware is identified by its ID (module name by default, or a custom string if configured via :id option).

This is a fire-and-forget operation — the caller does not wait for a response. If the AgentServer is not running, the message is silently dropped.

Use Cases

There are two primary use cases for this function:

1. External notifications (LiveViews, controllers, other processes)

Send context updates or configuration changes to a middleware from outside the agent system. The middleware decides how to handle the message — typically by updating state metadata that before_model/2 reads on the next LLM call.

# LiveView: user switched to editing a different blog post
AgentServer.notify_middleware(agent_id, MyApp.UserContext, {:post_changed, %{
  slug: "/blog/getting-started-with-elixir",
  title: "Getting Started with Elixir"
}})

# Controller: user changed a preference
AgentServer.notify_middleware(agent_id, MyApp.Preferences, {:preference_changed, :verbose, true})

2. Async task results (middleware sending messages to itself)

Middleware that spawns background tasks (e.g., title generation, embedding computation) uses this to send results back to the AgentServer for state updates.

# Inside an async task spawned by the middleware
AgentServer.notify_middleware(agent_id, middleware_id, {:title_generated, title})

Parameters

  • agent_id - The agent identifier (used to locate the AgentServer process)
  • middleware_id - The middleware ID to route the message to (module name or custom string)
  • message - Any term to be delivered to the middleware's handle_message/3 callback

Returns

  • :ok — always returns :ok, even if the AgentServer is not running

Examples

# Notify by module name (default middleware ID)
AgentServer.notify_middleware("conv-123", MyApp.Middleware.UserContext, {:post_changed, post})

# Notify by custom ID (when middleware was configured with `id: "english_title"`)
AgentServer.notify_middleware("conv-123", "english_title", {:regenerate, %{}})

publish_debug_event_from(agent_id, event)

@spec publish_debug_event_from(String.t(), term()) :: :ok

Request the AgentServer to publish a specific debug PubSub message or event.

Designed to make it easier for middleware to publish debug messages to the Agent's debug PubSub. Debug events are useful for development and debugging but separate from user-facing events.

A debug PubSub message is only broadcast if the AgentServer is configured with debug_pubsub.

Standardized Middleware Action Pattern

Middleware should use the :middleware_action tuple pattern to avoid event proliferation:

{:middleware_action, middleware_module, action_data}

Where:

  • middleware_module - The middleware module (atom) that generated the event
  • action_data - Middleware-specific action tuple or data

This pattern allows the debug UI to handle all middleware events generically without needing to know about every possible middleware-specific event type.

Examples

# From ConversationTitle middleware
AgentServer.publish_debug_event_from(
  agent_id,
  {:middleware_action, Sagents.Middleware.ConversationTitle, {:title_generation_started, user_text}}
)

AgentServer.publish_debug_event_from(
  agent_id,
  {:middleware_action, Sagents.Middleware.ConversationTitle, {:title_generation_completed, title}}
)

# From custom middleware
AgentServer.publish_debug_event_from(
  agent_id,
  {:middleware_action, MyApp.CustomMiddleware, {:validation_started, params}}
)

publish_event_from(agent_id, event)

@spec publish_event_from(String.t(), term()) :: :ok

Request the AgentServer to publish an specific PubSub message or event.

Designed to make it easier for a middleware desiring to publish messages to the Agent's PubSub.

A PubSub message is only broadcast if the AgentServer is configured with PubSub.

reset(agent_id)

@spec reset(String.t()) :: :ok

Reset the agent's state and filesystem to start fresh.

This clears:

  • All messages
  • All TODOs
  • Middleware state
  • Memory-only files (completely removed)
  • In-memory modifications to persisted files (discarded)

This preserves:

  • Metadata (configuration)
  • Persisted files (reverted to pristine state from storage)

Status transitions:

  • :completed, :error, or :cancelled:idle (ready for new execution)
  • Other statuses remain unchanged

Returns :ok on success.

Examples

# After agent completes or encounters error
:ok = AgentServer.reset("my-agent-1")
# Now you can execute again with clean state
:ok = AgentServer.execute("my-agent-1")

restore_state(agent_id, persisted_state)

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

Restore agent state from a previously exported state.

This updates an already-running agent to restore its state from a previously serialized format. The state should be a map with string keys (as returned by export_state/1).

Returns :ok on success or {:error, reason} on failure.

Examples

# Load from database
{:ok, persisted_state} = MyApp.Conversations.load_agent_state(conversation_id)

# Restore into existing agent
:ok = AgentServer.restore_state("my-agent-1", persisted_state)

resume(agent_id, resume_data)

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

Resume agent execution after an interrupt.

Parameters

  • agent_id - The agent identifier
  • resume_data - Data to resume with (polymorphic per middleware). For HITL: list of decision maps. For AskUserQuestion: response map.

Examples

# HITL resume
decisions = [
  %{type: :approve},
  %{type: :edit, arguments: %{"path" => "safe.txt"}},
  %{type: :reject}
]
:ok = AgentServer.resume("my-agent-1", decisions)

# AskUserQuestion resume
:ok = AgentServer.resume("my-agent-1", %{type: :answer, selected: ["PostgreSQL"]})

running?(agent_id)

Check if an agent is running.

send_middleware_message(agent_id, middleware_id, message)

This function is deprecated. Use `Sagents.AgentServer.notify_middleware/3` instead..
@spec send_middleware_message(String.t(), term(), term()) :: :ok

Deprecated: Use notify_middleware/3 instead.

This is an alias for notify_middleware/3 retained for backwards compatibility.

start_link(opts)

Start an AgentServer.

Options

  • :agent - The Agent struct (required)
  • :initial_state - Initial State (default: empty state)
  • :pubsub - PubSub configuration as {module(), atom()} tuple or nil to disable (default: nil) Example: {Phoenix.PubSub, :my_app_pubsub}
  • :debug_pubsub - Optional separate PubSub for debug events as {module(), atom()} or nil (default: nil) Example: {Phoenix.PubSub, :my_debug_pubsub}
  • :name - Server name registration (optional, defaults to get_name(agent.agent_id))
  • :inactivity_timeout - Timeout in milliseconds for automatic shutdown due to inactivity (default: 300_000 - 5 minutes) Set to nil or :infinity to disable automatic shutdown
  • :shutdown_delay - Delay in milliseconds to allow the supervisor to gracefully stop all children (default: 5000)
  • :conversation_id - Optional conversation identifier for message persistence (default: nil)
  • :agent_persistence - Module implementing Sagents.AgentPersistence for state snapshots (default: nil)
  • :display_message_persistence - Module implementing Sagents.DisplayMessagePersistence for display messages (default: nil)

Examples

# Start with automatic name (recommended)
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  initial_state: state
)

# With PubSub enabled
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  initial_state: state,
  pubsub: {Phoenix.PubSub, :my_app_pubsub}
)

# Start with explicit name (advanced use cases)
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  initial_state: state,
  name: :my_custom_name
)

# With custom inactivity timeout
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  inactivity_timeout: 600_000  # 10 minutes
)

# Disable automatic shutdown
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  inactivity_timeout: nil
)

# Enable debug pubsub
{:ok, pid} = AgentServer.start_link(
  agent: agent,
  pubsub: {Phoenix.PubSub, :my_app_pubsub},
  debug_pubsub: {Phoenix.PubSub, :my_debug_pubsub}
)

stop(agent_id)

@spec stop(String.t()) :: :ok

Stop the AgentServer.

Examples

:ok = AgentServer.stop("my-agent-1")

subscribe(agent_id)

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

Subscribe to events from this AgentServer.

The calling process will receive messages for all events broadcast by this server.

Returns :ok on success or {:error, reason} if PubSub is not configured.

Examples

AgentServer.subscribe("my-agent-1")

subscribe_debug(agent_id)

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

Subscribe to debug events from this AgentServer.

The calling process will receive messages for all debug events broadcast by this server. Debug events provide additional debugging insight into what the agent is doing, such as middleware state updates.

Returns :ok on success or {:error, reason} if debug PubSub is not configured.

Examples

AgentServer.subscribe_debug("my-agent-1")

touch(agent_id)

@spec touch(String.t()) :: :ok

Touch the agent to indicate activity and reset the inactivity timer.

This is useful when external events indicate activity (e.g., user viewing the agent in the UI, clicking tabs, etc.) to keep the agent alive and prevent automatic shutdown due to inactivity.

Returns :ok immediately (non-blocking).

Examples

:ok = AgentServer.touch("my-agent-1")

unsubscribe(agent_id)

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

Unsubscribe from events from this AgentServer.

The calling process will stop receiving messages broadcast by this server.

Returns :ok on success or {:error, reason} if PubSub is not configured.

Examples

AgentServer.unsubscribe("my-agent-1")

unsubscribe_debug(agent_id)

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

Unsubscribe from debug events from this AgentServer.

The calling process will stop receiving debug messages broadcast by this server.

Returns :ok on success or {:error, reason} if debug PubSub is not configured.

Examples

AgentServer.unsubscribe_debug("my-agent-1")

update_agent_and_state(agent_id, agent, state)

Updates both the agent configuration and state.

This is the recommended way to restore a conversation:

  1. Create agent from current code using your agent factory
  2. Load state from database
  3. Call this function to update the running AgentServer

Parameters

  • agent_id - The agent server's identifier
  • agent - The new agent configuration (from current code)
  • state - The restored state (from database)

Examples

# Restore conversation
{:ok, agent} = MyApp.Agents.create_demo_agent(agent_id: "demo-123")
{:ok, state_data} = MyApp.Conversations.load_state(conversation_id)
{:ok, state} = Sagents.State.from_serialized(state_data["state"])

:ok = AgentServer.update_agent_and_state("demo-123", agent, state)

Returns

  • :ok - Agent and state updated successfully
  • {:error, reason} - If agent server is not running or update fails