Minimal background workers for Clojure services.
Noun: (Finnish): agent, operative
agentti provides a small, explicit framework for running periodic background tasks inside a long-running JVM process.
It is designed for internal services and data pipelines that need a handful of reliable background jobs, but which do not require heavyweight infrastructure.
Many Clojure and JVM services need a small number of background tasks but do not want the complexity of a full job system (Quartz, distributed queues) or the cognitive overhead of building everything on top of core.async.
agentti takes a deliberately simple approach: each task runs on its own single-thread executor, is scheduled explicitly, cannot overlap, and can be started, inspected, and stopped as part of the normal service lifecycle.
While agentti is built on top of chime for time-based scheduling, it adds explicit execution semantics—dedicated executors, timeouts, lifecycle management, and introspection.
Such features are often reimplemented ad hoc in production services.
Add to deps.edn:
{:deps {com.sturdystats/agentti {:mvn/version "VERSION"}}}
Typical examples:
If you need persistence, distribution, or external coordination, this is not the right tool.
chimeThe API favors clarity and predictability over abstractions.
When jitter is enabled, agentti intentionally applies it cumulatively between runs.
Each execution time is computed relative to the previous execution, not to a fixed wall clock.
This introduces a bounded random walk, so scheduled times gradually drift.
This behavior helps avoid synchronized “thundering herd” effects across workers and across processes, and favors de-correlation over strict calendar alignment.
If you require a predictable, non-drifting schedule, you can bypass this behavior by using chime/periodic-seq directly and wrapping it with agentti.schedules/attach-next-eta! to retain introspection support.
(This is also what agentti does with jitter disabled.)
(require '[agentti.lifecycle :as agentti])
(agentti/add-worker!
{:worker-name :example
:interval-ms 10_000
:timeout-ms 2_000
:body-fn (fn []
(println "hello from background worker"))})
;; later…
(agentti/stop-worker! :example)
(require '[agentti.admin :as admin])
(admin/list-workers)
;; =>
;; [{:worker-name "example"
;; :status :idle
;; :num-runs 12
;; :last-duration 87
;; :next-run-eta "..."}]
This is intended for internal admin endpoints or dashboards.
Worker callbacks submitted by agentti are non-blocking with respect to the scheduler.
Each tick submits work to a dedicated executor and returns immediately, allowing subsequent ticks to arrive on time.
If a tick occurs while a previous run is still in flight, it is intentionally dropped and recorded in the worker’s metrics. This provides accurate visibility into overload or misconfigured intervals, without allowing overlapping executions.
Apache License 2.0
Copyright © Sturdy Statistics
A note to Finnish speakers: We chose the name agentti in homage to metosin and out of admiration for the expressiveness of the Finnish language. We’re not Finnish speakers, so if we’ve misused the term, we apologize.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |