An FRP programming model for agents, in a discourse framework. Agents are reactive processes in continuous-time rooms; humans, LLMs, and scripts are participants in the same shape, composing through tagged messages on a small pub/sub kernel.
You program agents and compose them into multi-agent workflows. And because agents run in the same live medium they're built from, an agent can even begin to compose workflows itself, in a forked world you review — early and exploratory, not the default mode.
Built on Spindel (functional reactive runtime), Datahike (immutable Datalog), and Yggdrasil (copy-on-write branching across git + database).
A programming model for multi-agent systems:
:escalation/budget, policy-bots
subscribe by capability tag; neither hardcodes the other…and a batteries-included substrate to run agents on:
clojure_eval — a safe code sandbox — the agent's main programming surface:
Clojure eval in an isolated SCI context wired to the dvergr world (rooms,
knowledge, intake), with new dependencies loaded through a human-approval gaterequires, edits, and git commits its own code there, and the
workspace forks/merges with the roommuschel shell (parsed +
filesystem-sandboxed, not raw bash -c):directive/raise-budget
for more, with all spend recorded in a ledger — so cost stays bounded^R) through one speech-to-text path; hand agents images and PDFs, with
a defensive vision/extract that pulls schema-shaped JSON from invoices/receipts
(media)merge and bounded by budgets. (An early capability we're exploring — not the default mode.)| Web dashboard | Terminal UI |
|---|---|
![]() | ![]() |
A room with its agents, live token cost, and collapsible thinking/tool activity — the same rooms, in the browser or the terminal.
git clone https://github.com/replikativ/dvergr.git
cd dvergr
clojure -M:cli # daemon + nREPL(:7888) + TUI chat
Pick a provider by exporting its API key (or set it in config.local.edn):
# Anthropic — or zero-key via the local `claude` CLI (auto-detected on PATH,
# no key needed with a Claude Code subscription)
export ANTHROPIC_API_KEY=sk-ant-...
# OpenAI API (usage-based; distinct from a ChatGPT/Codex subscription)
export OPENAI_API_KEY=sk-...
# export OPENAI_BASE_URL=https://api.openai.com/v1 # optional override
# Fireworks (OpenAI-compatible; the bundled model registry is Fireworks)
export FIREWORKS_API_KEY=fw-...
# export FIREWORKS_BASE_URL=https://api.fireworks.ai/inference/v1
# OpenAI Codex subscription — no API key; choose Sign in with ChatGPT
codex login
The shipped default config leaves the agent's provider unpinned, so it auto-selects the best provider available (preference: Anthropic → Fireworks → OpenAI → native Codex subscription → isolated Codex CLI → the local
claudeCLI) and its default model. Set one of the keys above or log into a supported CLI and it works — you don't have to match a specific provider. Pin:provider/:modelinconfig.local.ednto force one. If no provider key is set at all, the daemon still boots but logs a warning and agent turns fail at call time.
Copy config.example.edn → config.local.edn (gitignored)
to set the default model, agents, and a Telegram bot token — see
doc/configuration.md and
provider setup.
Type a message, press Enter; Ctrl-C quits. Rooms + history persist automatically (Datahike). Frontends are interchangeable over the one daemon:
clojure -M:cli # daemon + nREPL(:7888) + TUI
clojure -M:cli --no-tui --web # server box: daemon + nREPL + web dashboard (127.0.0.1:17880)
Standalone uberjar — one file with daemon + nREPL + TUI + web + Telegram.
CircleCI builds it on every push to main and attaches it to a GitHub release,
so you can grab the prebuilt jar from the
latest release — or build
it yourself:
clojure -T:build harness # → target/dvergr-<ver>-harness.jar
java -jar dvergr-<ver>-harness.jar # run it
(require '[dvergr.core :as d])
;; Build a room and join a coder agent
(def room (d/room :scratch))
(binding [org.replikativ.spindel.engine.core/*execution-context* (:ctx room)]
(d/join room (d/coder {:id :coder})))
;; Send a user message; the agent replies asynchronously
(d/post! room (d/message :you :coder "Add input validation to src/app.clj"))
;; Read the message log
(d/log room)
;; => [{:from :you :to :coder :content "..." :type :user/message}
;; {:from :coder :to :you :content "..." :type :user/message}
;; ...]
For tagged routing, per-consumer buffer policies, persistence, and the fork-and-merge proposal pattern, see doc/getting-started.md.
dvergr-cli keys, persistence,
provider config/drive mount:code tasksLiterate, live-running Clay notebooks — each
builds and runs a real room inline. Browse them rendered at
replikativ.github.io/dvergr, or open the
source in your editor. Build locally with clj -M:clay -m notebooks.render
(needs the quarto CLI).
| Notebook | What it shows |
|---|---|
| Getting started | rooms, participants, post!, tagged routing — from zero |
| Programming model | the compositional kernel: capability subscriptions, escalation, per-consumer buffer/SLA policy |
| Humans & agents | humans as participants, background tasks, propose → accept/reject (fork & merge) |
| Agents & tools | a real LLM agent, clojure_eval as the SCI sandbox, budgets + context compaction |
The standalone, runnable scenarios these import live in examples/
(clj -M:examples -m scenario-auditor).
| Namespace | What it provides |
|---|---|
dvergr.core | Public facade — re-exports the most-used vars |
dvergr.discourse | Room, participant, post!, ask, fork-room, merge-room |
dvergr.runtime.bus | Pub/sub routing kernel + opinionated buffer policy table |
dvergr.discourse.llm | llm-agent — directive-aware participant |
dvergr.discourse.generation | GenerationHandle + sync/future/external/streaming adapters |
dvergr.agent.roster | immutable, versioned AgentDefs and Rosters |
dvergr.agent.program | Run-backed interpreters and native Spindel result composition |
dvergr.participant.context | ParticipantContext — uniform memory+budget across LLM/human/hybrid |
dvergr.discourse.personas | researcher, coder, reviewer — pre-built agents |
dvergr.rooms.forks | review and settlement of canonical Run worlds retained by propose_change |
dvergr.cli.main | -main of the TUI chat client |
(require '[dvergr.model.providers :as providers])
;; Auto-registers API keys, Claude Code, and a logged-in Codex subscription.
(providers/ensure-initialized!)
;; Verify what this process can use and what an unpinned agent will select.
(vec (providers/list-providers))
(providers/default-spec)
;; Anthropic via the local `claude` CLI (no API key needed)
;; Auto-detected if `claude` is on PATH.
;; OpenAI via a Codex/ChatGPT subscription (no API key needed).
;; Run `codex login` once, then select :codex-subscription / "codex-subscription".
;; Use that subscription from a bounded, Dvergr-native AgentDef program.
(require '[dvergr.agent.roster :as roster]
'[dvergr.agent.program :as program])
(def team
(roster/make-agent
(roster/make-roster)
{:id :researcher
:prompt "Investigate carefully and report evidence."
:tools #{:clojure_eval}
:model-policy {:provider :codex-subscription
:model "codex-subscription-sol"}
:program {:kind :llm :budget-dollars 0.25}}))
;; Inside a Room execution context:
;; @(program/hire! room team :researcher {:task "test this hypothesis"})
;; Any OpenAI-compatible provider
(providers/register-openai-compatible!
:groq
{:base-url "https://api.groq.com/openai/v1"
:api-key (System/getenv "GROQ_API_KEY")})
;; Local model via Ollama
(providers/register-openai-compatible!
:ollama
{:base-url "http://localhost:11434/v1"
:api-key "ollama"})
| Library | Role |
|---|---|
| Spindel | FRP reactive runtime, CoW context forking, pub/sub |
| Datahike | Immutable Datalog database (conversation + knowledge) |
| Yggdrasil | Copy-on-write branching across git + Datahike |
| SCI | Sandbox for clojure_eval and agent code |
| spindel-tui | Terminal UI built on JLine + Spindel signals |
| hato | HTTP client for provider APIs |
| Telemere | Structured logging + observability |
SCI fork note. dvergr directly depends on the published replikativ SCI build for resource interruption and forkable interpreter worlds while the corresponding changes are reviewed upstream. Maven and git consumers resolve the same implementation:
org.replikativ/sci {:mvn/version "0.15.59-replikativ.1"}This returns to
org.babashka/scionce the fork lands upstream.
Copyright © 2026 Christian Weilbach. Apache License 2.0 — see LICENSE.
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 |