liveBook · Manning

Agent

As explained in Elixir in Action, Third Edition

Agent is an Elixir module and OTP abstraction for managing state through a process and exposing synchronous or asynchronous state operations.

Agent is an Elixir and OTP abstraction for managing state inside a process. It reduces GenServer boilerplate when the process only needs initialization, state reads, and state updates, while remaining suitable for supervision trees. section 10.0 section 10.2

Use the Agent API to expose state operations:

  • Start an agent with Agent.start_link/1; the initialization function runs in the new process, and its return value becomes the initial state. section 10.2.1
  • Read state with Agent.get/2, which evaluates a function in the agent process and returns that function’s result to the caller. section 10.2.1
  • Change state synchronously with Agent.update/2 or asynchronously with Agent.cast/2. section 10.2.1
  • Rely on process serialization when multiple clients access the same agent; operations execute one at a time and later clients observe completed changes. section 10.2.2

A shared counter

Start an agent with state 0, update it from another process using Agent.update(counter, fn count -> count + 1 end), then read the resulting value with Agent.get/2. The shell observes 1 because the state belongs to the agent process rather than any client. section 10.2.2

Listing 10.4 Agent-powered to-do server (todo_agent/lib/todo/server.ex)
defmodule Todo.Server do
 use Agent, restart: :temporary

 def start_link(name) do
   Agent.start_link(
     fn ->
       IO.puts("Starting to-do server for #{name}")
       {name, Todo.Database.get(name) || Todo.List.new()}
     end,
     name: via_tuple(name)
   )
 end

 def add_entry(todo_server, new_entry) do
   Agent.cast(todo_server, fn {name, todo_list} ->
     new_list = Todo.List.add_entry(todo_list, new_entry)
     Todo.Database.store(name, new_list)
     {name, new_list}
   end)
 end

 def entries(todo_server, date) do
   Agent.get(
     todo_server,
     fn {_name, todo_list} -> Todo.List.entries(todo_list, date) end
   )
 end

 defp via_tuple(name) do
   Todo.ProcessRegistry.via_tuple({__MODULE__, name})
 end
end

A production-shaped to-do server wraps Agent behind a dedicated module, uses Agent.cast/2 for asynchronous additions, Agent.get/2 for queries, and remains usable through the existing server interface. section 10.2.3

Wrap agent code in a dedicated module. Agent functions accept arbitrary lambdas, leaving the process state more exposed to accidental corruption than GenServer state. Choose GenServer when the process must handle plain messages, termination logic, or idle timeouts; agents cannot implement those behaviors. section 10.2.3 section 10.2.4

How Elixir in Action, Third Edition covers it

How deep it goes
Explains initialization, reads, updates, serialization, implementation over GenServer, module boundaries, and practical limitations.
Examples
Demonstrates a shared counter, a Bob state map, and a production-oriented to-do server backed by an Agent.
What it assumes
Assumes familiarity with Elixir processes, functions, and OTP workers while contrasting Agent behavior with GenServer.
What you can do after
Build stateful OTP workers with Agent.start_link/1, Agent.get/2, Agent.update/2, and Agent.cast/2 behind a module interface.
How it connects
Connects Agent to GenServer, OTP supervision trees, Task, ETS tables, and concurrent client processes.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Elixir in Action, Third Edition. The section links point to the parts of the book it draws on.