Refs and atoms shouldn't have side effects, but agents are great for performing side effects. From page 214 of Clojure Programming:
Unlike refs and atoms, it is perfectly safe to use agents to coordinate
I/O and perform other blocking operations. This makes them a vital
piece of any complete application that use refs and Clojure’s STM
to maintain program state over time. Further, thanks to their semantics,
agents are often an ideal construct for simplifying asynchronous
processing involving I/O even if refs are not involved at all.
Further:
Agents are integrated into Clojure’s STM implementation such that
actions dispatched using send and send-off from within the scope
of a transaction will be held in reserve until that transaction
is successfully committed. This means that, even if a transaction
retries 100 times, a sent action is only dispatched once, and that
all of the actions sent during the course of a transaction’s
runtime will be queued at once after the transaction commits.