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:
- π¬ Discourse rooms β humans + LLM agents exchange messages as equal participants
- π·οΈ Tagged routing β agents escalate
:escalation/budget, policy-bots subscribe by capability tag; neither hardcodes the other - πΏ Substrate forks β a "what would the coder write?" probe runs on a branched Geschichte workspace + datahike, then settles through one affine world handle
- π§© Compositional kernel β five primitives (tagged message, capability sub, dynamic subscribe, fork-room, GenerationHandle) cover the whole programming surface
β¦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 gate - π¦ A real workspace it owns β each room clones the
dvergr-sandbox stdlib into its own
git repo; the agent
requires, edits, andgit commits its own code there, and the workspace forks/merges with the room - π οΈ A curated toolset around it β structured read/write/edit, structural
Clojure edit, a test runner, and a jailed
muschelshell (parsed + filesystem-sandboxed, not rawbash -c) - π‘ ~25 intake sources β read-only data feeds (Hacker News, Reddit, RSS, web / YouTube / tweet fetch, mail, SEC-EDGAR, GitHub, β¦) ship as editable source in the workspace β the agent reads, copies, and extends them β pulling into a Datahike knowledge graph
- π Boundary credential handling β intakes that need an API key get a placeholder in the sandbox; the gated HTTP egress swaps in the real key only at the bound domain and scrubs it from the response, so the agent uses keys it never sees (design)
- π Multiple LLM providers β Anthropic API/Claude Code, a native Codex subscription transport, OpenAI API, Fireworks, or any OpenAI-compatible endpoint β swappable per agent
- πͺ Budgets & accounting β every turn is metered in microdollars against a
per-agent budget; an agent hits a checkpoint and can escalate
:directive/raise-budgetfor more, with all spend recorded in a ledger β so cost stays bounded - π§ Context management β long runs auto-prune tool outputs and summarize older turns as the model's window fills, so an agent holds a long conversation without overflowing context
- πΎ Persistent + multi-frontend β rooms, history, and knowledge persist in Datahike; drive the same daemon from the TUI, the web dashboard, Telegram, or nREPL
- ποΈ Voice & vision β speak to any frontend (web mic, Telegram voice notes,
REPL, TUI
^R) through one speech-to-text path; hand agents images and PDFs, with a defensivevision/extractthat pulls schema-shaped JSON from invoices/receipts (media) - π Self-programming substrate β agents operate in the same SCI/FRP world they're
built from, so an agent can spawn agents and wire workflows in a fork, gated by your
mergeand 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 chatPick 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 loginThe 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 itPrerequisites: 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. doc/homes.md has the layout, experiment homes, and moved vs copied homes.
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 10certification.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.
- Getting Started β first-room tutorial, REPL + CLI paths
- Programming Model β bus, tagged routing, GenerationHandle, the distributive law Ξ»
- Runs β durable agent-turn identity, activity/output correlation, lifecycle observation, and targeted cancellation
- Agent programs β immutable specialized-agent rosters, Run-backed hiring, Spindel composition, and fork-safe state placement
- CLI Reference β
dvergr-clikeys, persistence, provider config - Architecture β the formal model: rooms, the pub/sub bus, agents as reactive processes, ToM via substrate fork
- Tools & the SCI sandbox β the toolset, the workspace agents run code in, and its safety boundaries (incl. credential injection)
- Media β voice, vision & files β speech-to-text across
frontends, image describe/extract, documents, the
/drivemount - Channels β Telegram Β· Scheduling
β bridging a chat surface into Rooms; per-room recurring /
:codetasks - Doc index β full table of contents
Literate, 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.

