Read an agent as a completed-state snapshot, use await only when blocking is intentional, and avoid designs that need immediate results from asynchronous actions.
Reading an agent is easy: use @agent or deref. Interpreting the read correctly is the important part.
An agent read returns the latest completed state. It does not mean every action you recently sent has already run unless you deliberately wait.
1(def totals (agent {:processed 0}))
2
3(send totals update :processed inc)
4
5@totals
6;; May be {:processed 0} or {:processed 1}
That behavior is correct. The caller queued asynchronous work and then immediately read a snapshot.
Use await when the current thread intentionally needs actions already sent to an agent to finish.
1(send totals update :processed inc)
2(await totals)
3@totals
4;; => {:processed 1}
Blocking should be explicit and rare in request-handling paths. If a caller needs the new value immediately, an atom, ref transaction, future, promise, or direct function call may be a better fit.
| Need | Pattern | Caution |
|---|---|---|
| Display current progress | @agent |
It is a snapshot, not a guarantee that all queued work is done. |
| Test an agent example | send, then await, then read |
Keep tests from racing asynchronous work. |
| Return immediate result to caller | Avoid agent or use a different coordination tool | Agents intentionally decouple caller and update. |
| Shutdown a command-line process | await and shutdown-agents when needed |
Do not leave background agent threads running accidentally. |
Java developers often expect a submitted task to produce a Future that can be joined for a result. Agent actions are different: they update the agent’s state. The action result becomes the next state, not a returned value to the sender.
If your design wants “submit work and get a result,” choose the tool that says that directly. If your design wants “serialize updates to this state value,” an agent fits.