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
Prerequisites: a JDK (22 or newer, as CI uses), the Clojure CLI,
git, and babashka (bb, for the MCP relay bin/dvergr-mcp). For
subscription models, the claude CLI (Claude Code) and/or the codex CLI, logged in.
Where state goes. Everything dvergr writes lives under one state root, DVERGR_HOME
(default <checkout>/.dvergr): the stores, logs, the MCP relay's tool cache. rm -rf it to
reset.
Claude Code models without your email in their prompts. With the interactive login the
claude CLI adds the signed-in account's email to every prompt. Run claude setup-token once
and save the token to ~/.dvergr/secrets/claude-oauth-token (per user, not under
DVERGR_HOME; DVERGR_CLAUDE_TOKEN_FILE names another file): claude-code-* candidates then
run on that token with an empty config directory and no account details
(details).
From the shell, a CSV of past cases (an id, the inputs, the outcome a person recorded)
becomes a case pack, which room-run certifies, benchmarks and reports on:
clojure -M -m dvergr.catalog.casepack-cli cases.csv \
--id id --inputs description,supplier_country,net_amount \
--outcome vat_code:exact --outcome vat_amount:number:0.01 --out packs/vat-codes
clojure -M -m dvergr.catalog.room-run packs/vat-codes \
--models claude-code-haiku,codex-subscription-luna --cases 10
certification.edn in the pack says which cases can grade an answer and why the others
cannot. room-run keeps the experiment in its own home, --home (default
$DVERGR_HOME/workflow-runs/<pack>, or workflow-runs/<pack> without DVERGR_HOME);
running it again resumes. stdout has progress and the report; the log is <home>/dvergr.log.
The report gives each candidate's pass rate with a 95 % interval, cost, tokens and time, and
compares the candidates paired: on the cases both answered, how many each passed where the
other failed, the exact McNemar p and a verdict ("a better", or "no detectable difference at
these cases"); see doc/running-benchmarks.md.
From an MCP client (Claude Code, Codex, …), the bench profile has the same path as
tools (room_write the table, catalog_cases, catalog_benchmark, job_status,
experiment_report):
{"mcpServers": {"dvergr": {"type": "stdio", "command": "/path/to/dvergr/bin/dvergr-mcp",
"args": ["--profile", "bench"]}}}
The relay starts the daemon on first use. The first boot takes about two minutes (dependencies, then the stores); the tools appear only then. See doc/mcp.md, doc/room-workflows.md and doc/running-benchmarks.md.
(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 |