Liking cljdoc? Tell your friends :D

⚒️ dvergr

Clojars Project CircleCI Slack Docs

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).

What it gives you

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 git worktree + 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, and git 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 muschel shell (parsed + filesystem-sandboxed, not raw bash -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-budget for 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 defensive vision/extract that 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 merge and bounded by budgets. (An early capability we're exploring — not the default mode.)

See it

Web dashboardTerminal UI
web dashboardterminal UI

A room with its agents, live token cost, and collapsible thinking/tool activity — the same rooms, in the browser or the terminal.

60-second quickstart

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 claude CLI) 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/:model in config.local.edn to 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.ednconfig.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

REPL quickstart

(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.

Documentation

  • 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 Referencedvergr-cli keys, 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 /drive mount
  • Channels — Telegram · Scheduling — bridging a chat surface into Rooms; per-room recurring / :code tasks
  • Doc index — full table of contents

Example notebooks

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).

NotebookWhat it shows
Getting startedrooms, participants, post!, tagged routing — from zero
Programming modelthe compositional kernel: capability subscriptions, escalation, per-consumer buffer/SLA policy
Humans & agentshumans as participants, background tasks, propose → accept/reject (fork & merge)
Agents & toolsa 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).

Building blocks

NamespaceWhat it provides
dvergr.corePublic facade — re-exports the most-used vars
dvergr.discourseRoom, participant, post!, ask, fork-room, merge-room
dvergr.runtime.busPub/sub routing kernel + opinionated buffer policy table
dvergr.discourse.llmllm-agent — directive-aware participant
dvergr.discourse.generationGenerationHandle + sync/future/external/streaming adapters
dvergr.agent.rosterimmutable, versioned AgentDefs and Rosters
dvergr.agent.programRun-backed interpreters and native Spindel result composition
dvergr.participant.contextParticipantContext — uniform memory+budget across LLM/human/hybrid
dvergr.discourse.personasresearcher, coder, reviewer — pre-built agents
dvergr.rooms.forksreview and settlement of canonical Run worlds retained by propose_change
dvergr.cli.main-main of the TUI chat client

Provider setup

(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"})

Dependencies

LibraryRole
SpindelFRP reactive runtime, CoW context forking, pub/sub
DatahikeImmutable Datalog database (conversation + knowledge)
YggdrasilCopy-on-write branching across git + Datahike
SCISandbox for clojure_eval and agent code
spindel-tuiTerminal UI built on JLine + Spindel signals
hatoHTTP client for provider APIs
TelemereStructured 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/sci once the fork lands upstream.

License

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

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close