# Run with: mix run examples/stateful_agent.exs # # Demonstrates ExAgent.Server (Roadmap Phase 1): a supervised, long-lived agent # that keeps conversation history across many turns, preserves a stateful model # between runs, and emits ExAgent.Event envelopes over the Local PubSub. # # Uses the in-process TestModel — no API key needed. alias ExAgent.{Event, Models.Test, PubSub, Server} # A scripted model whose replies advance turn by turn. Because ExAgent.Server # threads the (updated) model struct from one run to the next, the script index # keeps advancing — proving stateful model preservation across chats. model = %Test{ script: [ "Greetings, traveller. The tavern is warm tonight.", "You roll a 14 — barely enough to pick the lock.", "The guard didn't see you. For now.", "Dawn breaks. Your adventure continues..." ] } # Start a supervised agent under the app's ExAgent.AgentSupervisor and enable the # Local PubSub so we can observe events. {:ok, pid} = ExAgent.AgentSupervisor.start_agent( agent: ExAgent.new(model: model, instructions: "You are a DM."), agent_id: "dm", pubsub: :local ) # Subscribe to the agent's event topic. Every run publishes versioned envelopes # with a monotonic `seq`. :ok = PubSub.subscribe({ExAgent.PubSub.Local, []}, Event.agent_topic("dm")) # A small collector process that prints each event as it arrives. collector = spawn(fn -> Enum.each(Stream.repeatedly(fn -> receive(do: ({:exagent_event, e} -> e)) end), fn e -> IO.puts("[event seq=#{e.seq}] #{e.type}") end) end) # Bind the collector to the topic by also subscribing it. PubSub.subscribe({ExAgent.PubSub.Local, []}, Event.agent_topic("dm")) IO.puts("=== chat 1 ===") {:ok, %{output: out1}} = Server.chat(pid, "I enter the tavern.") IO.puts("DM: #{out1}") IO.puts("\n=== chat 2 (history is preserved) ===") {:ok, %{output: out2}} = Server.chat(pid, "I try to pick the lock on the back door.") IO.puts("DM: #{out2}") IO.puts("\n=== chat 3 ===") {:ok, %{output: out3}} = Server.chat(pid, "Does the guard notice?") IO.puts("DM: #{out3}") IO.puts("\n=== accumulated usage ===") IO.inspect(Server.usage(pid), label: "usage") IO.puts("\n=== history length (req/resp per turn) ===") IO.inspect(length(Server.history(pid)), label: "messages") # Async dispatch: returns immediately, result arrives as a :run_finished event. IO.puts("\n=== send_message (async) ===") {:ok, request_id} = Server.send_message(pid, "anything") IO.puts("queued request #{request_id}, waiting for the event...") receive do {:exagent_event, %Event{type: :run_finished, request_id: ^request_id}} -> IO.puts("got :run_finished for #{request_id}") after 1_000 -> IO.puts("(no event received)") end Process.exit(collector, :shutdown) :ok = ExAgent.AgentSupervisor.stop_agent(pid)