Browse Learn Clojure Foundations as a Java Developer

Use Agents for Asynchronous State

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.

Agent Basics

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.

When Agents Fit

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.

Errors Matter

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.

Knowledge Check

### What makes an agent different from an atom? - [x] Agent updates are asynchronous and serialized for that agent. - [ ] Agents are the only Clojure tool that can hold maps. - [ ] Agents coordinate several refs in a transaction. - [ ] Agents are compile-time constants. > **Explanation:** Atoms update synchronously. Agents queue action functions that later produce the next state. ### When should `send-off` be considered instead of `send`? - [x] When the action may block on I/O. - [ ] When the action is pure CPU work. - [ ] When the state must update synchronously. - [ ] When the value is a keyword. > **Explanation:** `send` is intended for CPU-oriented work, while `send-off` is for potentially blocking actions. ### What should you verify before using an agent? - [x] The state is one independent identity and callers do not need the new value immediately. - [ ] The state must coordinate with several other refs. - [ ] The program never needs error handling. - [ ] The action must mutate Java fields directly. > **Explanation:** Agents are good for independent asynchronous state. Coordinated invariants and immediate return values need different tools.
Revised on Saturday, May 23, 2026