### Implement Server Start Callback Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Shows how to execute logic when the AgentServer initializes or restarts, useful for broadcasting initial state to subscribers. ```elixir defmodule MyApp.Middleware.InitialBroadcast do @behaviour Sagents.Middleware alias Sagents.AgentServer @impl true def on_server_start(state, _config) do if state.todos != [] do AgentServer.publish_event_from(state.agent_id, {:todos_updated, state.todos}) end {:ok, state} end end ``` -------------------------------- ### Initialize Agent with Middleware Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Shows how to register and configure custom middleware when creating a new Agent. ```elixir {:ok, agent} = Agent.new( agent_id: "my-agent", model: chat_model, middleware: [ {MyApp.Middleware.CustomMiddleware, [api_key: "secret-key"]} ] ) ``` -------------------------------- ### Start Basic Agent Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md Initializes and starts an AgentServer with a basic agent configuration, including agent ID, model, and middleware. It also sets up the initial state with a user message. ```elixir alias Sagents.{Agent, AgentServer, State} # Create agent configuration {:ok, agent} = Agent.new(%{ agent_id: "my-agent", model: model, middleware: [TodoList, FileSystem] }) # Create initial state state = State.new!(%{ messages: [Message.new_user!("Hello")] }) # Start the AgentServer {:ok, pid} = AgentServer.start_link( agent: agent, initial_state: state, pubsub: {Phoenix.PubSub, :my_pubsub} ) ``` -------------------------------- ### Setup and Dependency Management Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Commands to initialize the project environment and configure API keys. It includes fetching dependencies and setting up local environment variables. ```bash mix deps.get cp .envrc_template .envrc source .envrc ``` -------------------------------- ### Start Agent with Options Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md Starts an AgentServer with advanced options such as inactivity timeout, presence tracking, and auto-save configurations. It also handles message persistence. ```elixir {:ok, pid} = AgentServer.start_link( agent: agent, initial_state: state, pubsub: {Phoenix.PubSub, :my_pubsub}, # Lifecycle options inactivity_timeout: 3_600_000, # 1 hour (default: 5 minutes) # Presence tracking presence_tracking: [ enabled: true, presence_module: MyApp.Presence, topic: "conversation:123" ], # Auto-save auto_save: [ callback: &MyApp.save_agent_state/2, interval: 30_000 # Every 30 seconds ], # Message persistence conversation_id: conversation_id, save_new_message_fn: fn conv_id, message -> MyApp.Conversations.save_message(conv_id, message) end ) ``` -------------------------------- ### Define Middleware Module and Initialization Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Shows the structure of a middleware module implementing the Sagents.Middleware behaviour and the required init/1 callback. ```elixir defmodule MyApp.Middleware.CustomMiddleware do @behaviour Sagents.Middleware require Logger alias Sagents.State @impl true def init(opts) do api_key = Keyword.get(opts, :api_key) unless api_key do {:error, "CustomMiddleware requires :api_key option"} else {:ok, %{api_key: api_key}} end end end ``` -------------------------------- ### Elixir ConversationTitle Middleware Usage Example Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md This Elixir code snippet demonstrates how to initialize and use the ConversationTitle middleware when creating a new agent. It shows how to pass necessary options like the chat model and fallback models. ```elixir {:ok, agent} = Sagents.new( model: main_model, middleware: [ {Sagents.Middleware.ConversationTitle, [ chat_model: ChatAnthropic.new!(%{model: "claude-3-5-haiku-latest"}), fallbacks: [backup_model] }]} ] ) ``` -------------------------------- ### Implement Lifecycle Hooks and Async Tasks Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Demonstrates implementing after_model/2 to trigger asynchronous processing tasks, including telemetry instrumentation and error handling. ```elixir @impl true def after_model(state, config) do if should_process?(state) do spawn_async_task(state, config) end {:ok, state} end defp spawn_async_task(state, config) do agent_id = state.agent_id middleware_id = Map.get(config, :id, __MODULE__) data = extract_data(state) Task.start(fn -> try do result = process_data(data, config) AgentServer.publish_event_from(agent_id, {:custom_event, result}) AgentServer.send_middleware_message(agent_id, middleware_id, {:success, result}) rescue error -> AgentServer.send_middleware_message(agent_id, middleware_id, {:error, error}) end end) end ``` -------------------------------- ### Handling Post-Initialization Work with handle_continue (OTP) Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Explains the usage of `handle_continue/2` within an OTP GenServer. This callback is intended for performing work after the initial setup of the GenServer, allowing for deferred or asynchronous initialization tasks. ```elixir def handle_continue(request, state) do # ... handle continuation work ... {:noreply, new_state} end ``` -------------------------------- ### Elixir Testing Process Supervision Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Demonstrates the recommended method for starting processes in Elixir tests using `start_supervised!/1`. This ensures proper cleanup between tests. ```elixir start_supervised!(process_definition) ``` -------------------------------- ### Use Descriptive Message Tags in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md This Elixir example illustrates the importance of using clear and descriptive message tags when communicating between middleware components via `AgentServer.send_middleware_message`. It contrasts good examples with clear intent against bad examples that are ambiguous, improving code readability and maintainability. ```elixir # ✅ GOOD - Clear message intent AgentServer.send_middleware_message(agent_id, id, {:title_generated, title}) AgentServer.send_middleware_message(agent_id, id, {:title_generation_failed, error}) # ❌ BAD - Ambiguous messages AgentServer.send_middleware_message(agent_id, id, title) AgentServer.send_middleware_message(agent_id, id, {:error, error}) ``` -------------------------------- ### Configure Middleware Instances Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Demonstrates how to define middleware instances in Sagents. It shows both single-instance configuration using the module name and multi-instance configuration using custom IDs. ```elixir # Single instance - uses module name as ID middleware = [ {ConversationTitle, [ chat_model: model ]} ] # Multiple instances - custom IDs required middleware = [ {ConversationTitle, [ id: "english_title", chat_model: model, prompt_template: "Generate English title..." ]}, {ConversationTitle, [ id: "spanish_title", chat_model: model, prompt_template: "Generate Spanish title..." ]} ] ``` -------------------------------- ### Handle State Updates and Events in Middleware Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Demonstrates how to update agent state via handle_message/3 and broadcast custom events using AgentServer.publish_event_from/2. ```elixir def handle_message({:result, data}, state, _config) do updated_state = State.put_metadata(state, "key", data) {:ok, updated_state} end # In async task after work completes AgentServer.publish_event_from(agent_id, {:custom_event, data}) AgentServer.send_middleware_message(agent_id, middleware_id, {:result, data}) ``` -------------------------------- ### Start AgentServer and Execute (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/README.md Starts the AgentServer, which manages the agent's lifecycle as a GenServer. It initializes the agent with a state containing user messages and subscribes to real-time events before executing the agent's task. ```elixir # Create initial state state = State.new!(%{ messages: [Message.new_user!("Create a hello world program")] }) # Start the AgentServer (runs as a supervised GenServer) {:ok, _pid} = AgentServer.start_link( agent: agent, initial_state: state, pubsub: {Phoenix.PubSub, :my_app_pubsub}, inactivity_timeout: 3_600_000 # 1 hour ) # Subscribe to real-time events AgentServer.subscribe("my-agent-1") # Execute the agent :ok = AgentServer.execute("my-agent-1") ``` -------------------------------- ### Capture Server PID and Spawn Async Tasks Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Illustrates how to capture the AgentServer context within an after_model callback and spawn an asynchronous task that reports results back to the server. ```elixir def after_model(state, config) do # Capture agent_id before spawning task agent_id = state.agent_id middleware_id = Map.get(config, :id, __MODULE__) Task.start(fn -> # This runs in a different process result = do_work() # Send back to the AgentServer using public API AgentServer.send_middleware_message(agent_id, middleware_id, {:result, result}) end) {:ok, state} end ``` -------------------------------- ### Start Agent with Persistence using Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/persistence.md This Elixir code defines how to start a new agent conversation session, checking if an agent already exists for the given conversation ID. If not, it loads or creates the initial state, creates the agent, and starts it with auto-save functionality configured. ```elixir defmodule MyApp.Agents.Coordinator do def start_conversation_session(conversation_id) do agent_id = "conversation-#{conversation_id}" case AgentServer.whereis(agent_id) do nil -> start_new_agent(agent_id, conversation_id) pid -> {:ok, %{agent_id: agent_id, pid: pid}} end end defp start_new_agent(agent_id, conversation_id) do # Load or create state initial_state = case Conversations.load_agent_state(conversation_id) do {:ok, state} -> state {:error, :not_found} -> State.new!() end # Create agent from code {:ok, agent} = AgentFactory.create_agent(agent_id: agent_id) # Start with auto-save {:ok, pid} = AgentServer.start_link( agent: agent, initial_state: initial_state, pubsub: {Phoenix.PubSub, MyApp.PubSub}, auto_save: [ callback: fn _id, state -> Conversations.save_agent_state(conversation_id, state) end, interval: 30_000, on_idle: true ], conversation_id: conversation_id, save_new_message_fn: fn conv_id, message -> Conversations.save_message(conv_id, message) end ) {:ok, %{agent_id: agent_id, pid: pid}} end end ``` -------------------------------- ### Sagents Usage Patterns (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/README.md Illustrates common usage patterns for sagents, including creating conversations, saving and restoring agent state, and starting an agent from code with restored state. These examples showcase the core functionalities for managing agent lifecycles and data persistence. ```elixir # Create conversation {:ok, conversation} = Conversations.create_conversation(scope, %{title: "My Chat"}) # Save state during execution state = AgentServer.export_state(agent_id) Conversations.save_agent_state(conversation.id, state) # Restore conversation later {:ok, persisted_state} = Conversations.load_agent_state(conversation.id) # Create agent from code (middleware/tools come from code, not database) {:ok, agent} = MyApp.AgentFactory.create_agent(agent_id: "conv-#{conversation.id}") # Start with restored state {:ok, pid} = AgentServer.start_link_from_state( persisted_state, agent: agent, agent_id: "conv-#{conversation.id}", pubsub: {Phoenix.PubSub, :my_pubsub} ) ``` -------------------------------- ### Initialize AgentServer on Start/Restart (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md The `on_server_start/2` callback is executed when the AgentServer begins or restarts. This is useful for performing initialization tasks that require the server to be active, such as broadcasting initial state to subscribers. It accepts the current state and configuration, returning either an updated state or an error. ```elixir @callback on_server_start(State.t(), config :: map()) :: {:ok, State.t()} | {:error, reason :: term()} ``` ```elixir @impl true def on_server_start(state, _config) do # Broadcast initial todos when AgentServer starts AgentServer.publish_event_from(state.agent_id, {:todos_updated, state.todos}) {:ok, state} end ``` -------------------------------- ### Configuring Agent with Middleware for Fan-Out Callbacks Source: https://github.com/sagents-ai/sagents/blob/main/docs/observability.md Illustrates how to initialize an agent with multiple middleware components, showcasing the fan-out behavior where all declared callbacks fire for each event. This setup allows for independent telemetry, logging, and metrics collection. ```elixir iex> {:ok, agent} = Sagents.Agent.new(%{ model: model, middleware: [ {MyApp.Middleware.Metrics, customer_id: customer.id}, {MyApp.Middleware.AuditLog, user_id: user.id}, # ... other middleware ] }) ``` -------------------------------- ### Manage tasks with timeouts using Task.Supervisor Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Implements a supervised task with timeout handling to prevent tasks from hanging indefinitely, ensuring system stability. ```elixir defp spawn_with_timeout(agent_id, middleware_id, work_fn) do Task.Supervisor.start_child(MyApp.TaskSupervisor, fn -> try do result = work_fn.() AgentServer.send_middleware_message(agent_id, middleware_id, {:success, result}) catch :exit, {:timeout, _} -> AgentServer.send_middleware_message(agent_id, middleware_id, {:timeout, "Task exceeded timeout"}) end end) end ``` -------------------------------- ### Phoenix Presence Module Setup in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/pubsub_presence.md Sets up a Phoenix.Presence module for tracking user presence within a Phoenix application. It requires specifying the OTP app and the PubSub server. ```elixir defmodule MyApp.Presence do use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub end ``` -------------------------------- ### Implement error handling in asynchronous tasks Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Demonstrates the use of try/rescue blocks within Task.start to ensure errors are captured and reported back to the AgentServer, preventing silent task failures. ```elixir Task.start(fn -> try do result = do_work() AgentServer.send_middleware_message(agent_id, id, {:success, result}) rescue error -> Logger.error("Task failed: #{inspect(error)}") AgentServer.send_middleware_message(agent_id, id, {:error, error}) end end) ``` -------------------------------- ### Setup Presence Tracking Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md Configures Phoenix.Presence for an agent to track connected clients. This involves defining a Presence module and enabling presence tracking in the AgentServer configuration. ```elixir defmodule MyApp.Presence do use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub end {:ok, pid} = AgentServer.start_link( agent: agent, presence_tracking: [ enabled: true, presence_module: MyApp.Presence, topic: "conversation:#{conversation_id}" ] ) ``` -------------------------------- ### Advanced Sagents Setup Configuration (Bash) Source: https://github.com/sagents-ai/sagents/blob/main/README.md Configures advanced settings for sagents, including scope, owner type and field, factory, coordinator, pubsub, presence, and table prefix. This command allows for deep customization of the agent system's integration with your application. ```bash mix sagents.setup MyApp.Conversations \ --scope MyApp.Accounts.Scope \ --owner-type user \ --owner-field user_id \ --factory MyApp.Agents.Factory \ --coordinator MyApp.Agents.Coordinator \ --pubsub MyApp.PubSub \ --presence MyAppWeb.Presence \ --table-prefix sagents_ ``` -------------------------------- ### Support multiple middleware instances with custom IDs Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Configures middleware to accept custom IDs during initialization, allowing multiple instances to operate independently without message routing conflicts. ```elixir @impl true def init(opts) do custom_id = Keyword.get(opts, :id) config = if custom_id, do: %{id: custom_id}, else: %{} {:ok, config} end defp spawn_task(state, config) do agent_id = state.agent_id middleware_id = Map.get(config, :id, __MODULE__) Task.start(fn -> result = do_work() AgentServer.send_middleware_message(agent_id, middleware_id, {:result, result}) end) end ``` -------------------------------- ### Implement Custom Middleware Behavior in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/README.md Provides an example of implementing a custom middleware by defining a module that adheres to the `Sagents.Middleware` behavior in Elixir. It includes implementations for `init`, `system_prompt`, `tools`, `before_model`, `after_model`, `handle_message`, and `on_server_start`. ```elixir defmodule MyApp.CustomMiddleware do @behaviour Sagents.Middleware @impl true def init(opts) do config = %{ enabled: Keyword.get(opts, :enabled, true) } {:ok, config} end @impl true def system_prompt(_config) do "You have access to custom capabilities." end @impl true def tools(config) do [my_custom_tool(config)] end @impl true def before_model(state, _config) do # Preprocess state before LLM call {:ok, state} end @impl true def after_model(state, _config) do # Postprocess state after LLM response # Return {:interrupt, state, interrupt_data} to pause for HITL {:ok, state} end @impl true def handle_message(message, state, _config) do # Handle async messages from spawned tasks {:ok, state} end @impl true def on_server_start(state, _config) do # Called when AgentServer starts - broadcast initial state {:ok, state} end end ``` -------------------------------- ### Set up Sagents Supervisor in Application.ex Source: https://github.com/sagents-ai/sagents/blob/main/README.md This code demonstrates how to integrate Sagents into your Elixir application's supervision tree by adding `Sagents.Supervisor` as a child process. This ensures that Sagents' core components, such as the process registry and supervisors, are started and managed correctly. ```elixir # lib/my_app/application.ex children = [ # ... your other children (Repo, PubSub, etc.) Sagents.Supervisor ] ``` -------------------------------- ### Publish Metadata via on_server_start Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Shows how to initialize and publish stable configuration data into the agent state using the on_server_start hook, making it available to all middleware. ```elixir defmodule MyApp.Middleware.InjectCurrentDate do @behaviour Sagents.Middleware alias Sagents.State @impl true def init(opts) do {:ok, %{timezone: Keyword.get(opts, :timezone, "UTC")}} end @impl true def on_server_start(state, config) do {:ok, State.put_metadata(state, "timezone", config.timezone)} end end ``` -------------------------------- ### Start Agent from Persisted State Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md Restores an agent's state from a previously saved state and starts the AgentServer. This is useful for resuming interrupted conversations or sessions. ```elixir # Load saved state {:ok, persisted_state} = MyApp.Conversations.load_agent_state(conversation_id) # Create agent from code (middleware/tools always come from code) {:ok, agent} = MyApp.AgentFactory.create_agent(agent_id: "conv-#{conversation_id}") # Start with restored state {:ok, pid} = AgentServer.start_link( agent: agent, initial_state: persisted_state, pubsub: {Phoenix.PubSub, :my_pubsub} ) ``` -------------------------------- ### Agent State Serialization Example in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/persistence.md This Elixir code provides an example of a serialized agent state. It shows the structure of messages, todos, and metadata, demonstrating how complex data can be represented in a serializable format. ```elixir # State.to_serialized/1 %{ "messages" => [ %{ "role" => "user", "content" => "Hello", "metadata" => %{} }, %{ "role" => "assistant", "content" => "Hi there!", "tool_calls" => [], "metadata" => %{} } ], "todos" => [ %{ "id" => "todo-1", "content" => "Task description", "status" => "completed" } ], "metadata" => %{ "conversation_title" => "Greeting", "custom_data" => %{...} } } ``` -------------------------------- ### Broadcast Standard and Debug Events Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Examples of broadcasting standard events to main topics and debug events to internal monitoring topics. ```elixir # Standard Event AgentServer.publish_event_from(state.agent_id, {:my_event, data}) # Debug Event AgentServer.publish_debug_event_from( state.agent_id, {:middleware_action, __MODULE__, {:action_name, details}} ) ``` -------------------------------- ### Handle Middleware Messages Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Implements the handle_message/3 callback to process results from asynchronous tasks and update the agent state accordingly. ```elixir @impl true def handle_message({:success, result}, state, _config) do updated_state = state |> State.put_metadata("processed", true) |> State.put_metadata("result", result) {:ok, updated_state} end def handle_message({:error, error}, state, _config) do Logger.error("Processing failed: #{inspect(error)}") {:ok, state} end ``` -------------------------------- ### Implement Static System Prompts with Middleware in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Demonstrates how to use the system_prompt/1 callback to inject session-scoped configuration values that are cached for the agent's lifetime. ```elixir defmodule MyApp.Middleware.InjectCurrentDate do @behaviour Sagents.Middleware @impl true def init(opts) do timezone = Keyword.get(opts, :timezone, "UTC") {:ok, %{timezone: timezone}} end @impl true def system_prompt(config) do date = DateTime.utc_now() |> DateTime.shift_zone!(config.timezone) |> Calendar.strftime("%a, %Y-%m-%d %Z") "Today's date is #{date}. The user's timezone is #{config.timezone}." end end ``` -------------------------------- ### Search Documentation with Multi-word Queries (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Demonstrates how to search documentation for multi-word queries using the `mix usage_rules.search_docs` command. This is useful for finding specific documentation entries that contain phrases. The `-p` flag specifies the query. ```elixir mix usage_rules.search_docs "making requests" -p req ``` -------------------------------- ### Configure Phoenix.PubSub for Sagents Source: https://github.com/sagents-ai/sagents/blob/main/docs/pubsub_presence.md Initializes the PubSub system in the application supervision tree and registers it with the AgentServer. ```elixir # application.ex children = [ {Phoenix.PubSub, name: MyApp.PubSub}, # ... other children ] AgentServer.start_link( agent: agent, pubsub: {Phoenix.PubSub, MyApp.PubSub} ) ``` -------------------------------- ### Running Project Tests Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Commands to execute the test suite, including options for running unit tests, specific live API tests, or individual test files/lines. Live tests require caution as they may incur costs. ```bash mix test mix test --include live_call mix test --include live_anthropic mix test test/path/to/test_file.exs mix test test/path/to/test_file.exs:42 ``` -------------------------------- ### Searching Documentation with usage_rules Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Use the usage_rules mix task to search for documentation across project dependencies and the Elixir standard library. ```bash # Search a whole module mix usage_rules.docs Enum # Search a specific function mix usage_rules.docs Enum.zip # Search a specific function & arity mix usage_rules.docs Enum.zip/1 # Search docs for all packages mix usage_rules.search_docs Enum.zip ``` -------------------------------- ### Best Practice: Clean Up Old Conversations (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/docs/persistence.md Provides an example of a periodic job to clean up old conversations. It deletes conversations that were updated more than 30 days ago. ```elixir # Periodic cleanup job def cleanup_old_conversations do cutoff = DateTime.add(DateTime.utc_now(), -30, :day) Conversation |> where([c], c.updated_at < ^cutoff) |> Repo.delete_all() end ``` -------------------------------- ### Mix Command for Dependency Cleanup Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Highlights the Mix command `mix deps.clean --all` and advises against its frequent use, suggesting it should only be used with good reason. ```bash mix deps.clean --all ``` -------------------------------- ### Emit telemetry events for task monitoring Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Uses :telemetry.execute to track the lifecycle of asynchronous tasks, enabling better observability, alerting, and debugging of middleware performance. ```elixir :telemetry.execute([:sagents, :middleware, :task, :spawned], %{count: 1}, metadata) # ... do work ... :telemetry.execute([:sagents, :middleware, :task, :completed], %{count: 1}, metadata) ``` -------------------------------- ### Implement Phoenix Layout and Components Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Standard patterns for wrapping LiveView templates and using core components like icons and inputs. ```elixir <.icon name="hero-x-mark" class="w-5 h-5"/> <.input name="example" label="Example" /> ``` -------------------------------- ### Handle Agent Shutdown Info Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md Example of handling `{:agent_shutdown, metadata}` messages in a client, such as a LiveView. It allows the client to react to different shutdown reasons like inactivity or no viewers. ```elixir def handle_info({:agent, {:agent_shutdown, metadata}}, socket) do case metadata.reason do :inactivity -> # Agent timed out, can restart on next user action {:noreply, assign(socket, agent_status: :inactive)} :no_viewers -> # All viewers left after completion {:noreply, assign(socket, agent_status: :inactive)} :manual -> # Explicitly stopped {:noreply, assign(socket, agent_status: :stopped)} end end ``` -------------------------------- ### Usage Rules Mix Task for Module Documentation Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Demonstrates how to use the `mix usage_rules.docs` task to search for documentation of modules and functions within the current Elixir project and its dependencies. ```bash mix usage_rules.docs Enum mix usage_rules.docs Enum.zip mix usage_rules.docs Enum.zip/1 ``` -------------------------------- ### Broadcast significant events from async tasks Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware_messaging.md Shows how to conditionally publish events to subscribers from within an asynchronous task while ensuring state updates are still sent to the middleware. ```elixir Task.start(fn -> result = do_work() if significant_change?(result) do AgentServer.publish_event_from(agent_id, {:important_update, result}) end AgentServer.send_middleware_message(agent_id, middleware_id, {:result, result}) end) ``` -------------------------------- ### Implement Async Middleware with handle_message Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Demonstrates how to perform non-blocking background tasks within middleware. It uses Task.start to offload work and AgentServer.send_middleware_message to route results back to the middleware's handle_message callback. ```elixir defmodule MyApp.Middleware.AsyncEnrichment do @behaviour Sagents.Middleware alias Sagents.AgentServer alias Sagents.State @impl true def after_model(state, _config) do spawn_enrichment_task(state) {:ok, state} end defp spawn_enrichment_task(state) do agent_id = state.agent_id middleware_id = __MODULE__ Task.start(fn -> result = fetch_enrichment_data() AgentServer.send_middleware_message(agent_id, middleware_id, {:enrichment_ready, result}) end) end @impl true def handle_message({:enrichment_ready, result}, state, _config) do updated_state = State.put_metadata(state, :enrichment, result) AgentServer.publish_event_from(state.agent_id, {:enrichment_updated, result}) {:ok, updated_state} end end ``` -------------------------------- ### Capture Middleware Configuration via Closures Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Demonstrates how to capture middleware configuration within a tool definition using a closure. This ensures the configuration is available during the tool's execution phase. ```elixir @impl true def tools(config) do [ Function.new!(%{ name: "my_tool", description: "Does something", parameters_schema: %{type: "object", properties: %{}}, function: fn args, context -> execute(args, context, config) end }) ] end defp execute(_args, context, config) do {:ok, "result"} end ``` -------------------------------- ### Manage LiveView Lifecycle and Subscriptions Source: https://github.com/sagents-ai/sagents/blob/main/docs/pubsub_presence.md Provides best practices for subscribing to agents only when connected, handling agent startup errors, and explicitly unsubscribing from events. ```elixir def mount(_params, _session, socket) do if connected?(socket) do AgentServer.subscribe(agent_id) end {:ok, socket} end def mount(%{"id" => id}, _session, socket) do case Coordinator.start_conversation_session(id) do {:ok, session} -> AgentServer.subscribe(session.agent_id) {:ok, assign(socket, agent_id: session.agent_id)} {:error, :not_found} -> {:ok, redirect(socket, to: ~p"/conversations")} end end @impl true def handle_event("close_conversation", _params, socket) do AgentServer.unsubscribe(socket.assigns.agent_id) {:noreply, socket} end ``` -------------------------------- ### Create Minimal Elixir Middleware for Sagents Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md This Elixir code demonstrates a minimal custom middleware for Sagents. It implements the `system_prompt` callback to add a greeting instruction to the agent's system prompt. ```elixir defmodule MyApp.Middleware.Greeting do @behaviour Sagents.Middleware @impl true def system_prompt(_config) do "Always greet the user warmly before responding." end end ``` -------------------------------- ### Handling Task Failures (OTP) Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Provides examples of how to handle task failures in OTP using `Task.yield/2` or `Task.shutdown/2`. This ensures that your application can gracefully manage errors in asynchronous tasks. ```erlang Task.yield(task_ref, timeout) Task.shutdown(task_ref, strategy) ``` -------------------------------- ### Agent Registry and Discovery (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/docs/architecture.md Demonstrates how agents register themselves with a named Registry and how other parts of the system can discover running agents. This includes registering an agent upon startup and listing or finding agents by their ID. ```elixir # Registration happens in AgentServer.start_link Registry.register(Sagents.Registry, agent_id, %{}) # Discovery AgentServer.list_running_agents() # => ["conversation-1", "conversation-2"] AgentServer.whereis("conversation-1") # => #PID<0.1234.0> ``` -------------------------------- ### Testing Expected Exceptions with assert_raise (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Provides an example of how to test for expected exceptions in Elixir using `assert_raise`. This assertion is used to verify that a specific function call or code block raises a particular error type, such as `ArgumentError`. ```elixir assert_raise ArgumentError, fn -> invalid_function() end ``` -------------------------------- ### Running Specific Tests with Tags (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Demonstrates how to run specific tests in Elixir based on tags using the `mix test --only tag` command. This is useful for isolating and executing a subset of tests marked with a particular tag, improving test execution efficiency. ```elixir mix test --only tag_name ``` -------------------------------- ### Attach Telemetry Handlers in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/lifecycle.md This snippet demonstrates how to attach a telemetry handler to specific Sagents events using the :telemetry.attach_many function. It registers a callback function to process agent start, stop, and execution events. ```elixir :telemetry.attach_many( "my-handler", [ [:sagents, :agent, :start], [:sagents, :agent, :stop], [:sagents, :execution, :stop] ], &MyApp.Telemetry.handle_event/4, nil ) ``` -------------------------------- ### Configure Auto-Save for Agent Server in Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/persistence.md This Elixir snippet demonstrates how to configure the auto-save feature when starting an Agent Server. It includes settings for the save callback function, the save interval, and options to save on idle or shutdown. ```elixir AgentServer.start_link( agent: agent, auto_save: [ # Function called to save state callback: fn agent_id, state -> conversation_id = extract_conversation_id(agent_id) Conversations.save_agent_state(conversation_id, state) end, # Save interval (only if changed) interval: 30_000, # 30 seconds # Save when execution completes on_idle: true, # Save before shutdown on_shutdown: true # default: true ] ) ``` -------------------------------- ### Usage Rules Mix Task for Searching Documentation Source: https://github.com/sagents-ai/sagents/blob/main/AGENTS.md Shows how to use the `mix usage_rules.search_docs` task to search documentation across packages in the current Elixir application or for specific packages. ```bash mix usage_rules.search_docs Enum.zip mix usage_rules.search_docs Req.get -p req ``` -------------------------------- ### Filter Response Content After LLM Call with Elixir Source: https://github.com/sagents-ai/sagents/blob/main/docs/middleware.md Provides middleware to process LLM responses after the model has generated them. This example demonstrates filtering out forbidden content from the last assistant message and replacing it with filtered content if necessary. ```elixir defmodule MyApp.Middleware.ResponseFilter do @behaviour Sagents.Middleware @impl true def after_model(state, config) do # Get the last assistant message case find_last_assistant_message(state.messages) do nil -> {:ok, state} message -> if contains_forbidden_content?(message.content) do # Replace with filtered content filtered = filter_content(message.content) updated_messages = replace_last_assistant(state.messages, filtered) {:ok, %{state | messages: updated_messages}} else {:ok, state} end end end end ``` -------------------------------- ### Configure AgentServer Lifecycle and Persistence Source: https://github.com/sagents-ai/sagents/blob/main/README.md Demonstrates how to initialize an AgentServer with inactivity timeouts, Phoenix Presence tracking for automatic shutdown, and custom persistence implementations for state and messages. ```elixir AgentServer.start_link( agent: agent, inactivity_timeout: 3_600_000, presence_tracking: [ enabled: true, presence_module: MyApp.Presence, topic: "conversation:#{conversation_id}" ], initial_state: state, pubsub: {Phoenix.PubSub, :my_app_pubsub}, agent_persistence: MyApp.AgentPersistenceImpl, display_message_persistence: MyApp.DisplayMessagePersistenceImpl ) ``` -------------------------------- ### Running Tests in a Specific File or Line (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/CLAUDE.md Explains how to execute tests within a specific file or even at a particular line number using the `mix test` command. This allows for focused testing during development and debugging. ```elixir mix test test/my_test.exs mix test path/to/test.exs:123 ``` -------------------------------- ### Agent Crash State Preservation (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/docs/architecture.md Provides an Elixir code example for enabling automatic state saving in AgentServer to preserve agent state across crashes. It configures a callback function and an interval for periodic state persistence. ```elixir AgentServer.start_link( agent: agent, auto_save: [ callback: &MyApp.save_state/2, interval: 30_000 # Save every 30 seconds ] ) ``` -------------------------------- ### Usage of Scope in Data Retrieval (Elixir) Source: https://github.com/sagents-ai/sagents/blob/main/docs/persistence.md Provides examples of how to use the Scope struct to retrieve data, specifically listing conversations, based on whether the scope is for a user or an organization. This ensures data is accessed within the correct context. ```elixir def list_conversations(%Scope{type: :user, id: user_id}, opts) do Conversation |> where([c], c.user_id == ^user_id) |> order_by([c], desc: c.updated_at) |> Repo.all() end def list_conversations(%Scope{type: :organization, id: org_id}, opts) do Conversation |> join(:inner, [c], u in User, on: c.user_id == u.id) |> where([c, u], u.organization_id == ^org_id) |> Repo.all() end ```