Use agents when one independent Clojure state value should receive serialized asynchronous updates, and choose `send` or `send-off` based on CPU-bound versus blocking work.
An agent is a reference to one state value that changes asynchronously. You dispatch an action function with send or send-off; later, Clojure applies that function to the agent’s current state, and the return value becomes the new state.
For Java engineers, an agent feels like a small serialized worker around one immutable state value. It is not an actor with a custom receive loop. You do not block waiting for a message in the agent; you submit functions that transform state.
| Operation | Meaning |
|---|---|
(agent initial) |
Create an agent with an initial value. |
@agent |
Read the current state immediately. |
(send agent f args...) |
Queue a CPU-oriented action. |
(send-off agent f args...) |
Queue a potentially blocking action. |
(await agent) |
Block until actions already sent from this thread complete. |
(agent-error agent) |
Inspect an error cached by a failed action. |
1(def audit-log
2 (agent []))
3
4(defn append-event [events event]
5 (conj events event))
6
7(defn record-event! [event]
8 (send audit-log append-event event))
9
10(record-event! {:type :user-created
11 :id "u-100"})
The caller gets control back immediately. The action runs later and serializes with other actions for the same agent.
| Fit | Why |
|---|---|
| Ordered updates to one in-memory state value | Actions for a single agent are applied one at a time. |
| Background accumulation of telemetry | The caller does not need the new value immediately. |
| Work triggered after an STM transaction commits | Agent dispatches inside STM are held until commit. |
| Coordinating several values together | Bad fit; consider refs or one aggregate value. |
| General task execution with no state | A future, executor, queue, or channel may be clearer. |
Agents are independent. If two agents must stay consistent with each other, you probably need a different design.
send vs send-off| Dispatch | Use for |
|---|---|
send |
CPU-bound work that should use the fixed agent thread pool. |
send-off |
Potentially blocking work such as I/O. |
Do not use agents to hide arbitrary blocking calls without thinking about thread pools and shutdown behavior. Agent infrastructure uses background threads; command-line programs that use agents may need shutdown-agents during shutdown.
If an agent action throws, the error is associated with the agent and later interactions can fail until you handle or restart it. Treat agent errors as operational state, not as console noise.
1(when-let [err (agent-error audit-log)]
2 (println "audit agent failed:" (.getMessage err)))
For production code, prefer explicit error handlers, metrics, and a restart policy that matches the business meaning of the state.