Liking cljdoc? Tell your friends :D

dvergr-cli

The dvergr command-line entry point. clojure -M:cli starts the daemon (the system), an nREPL server on :7888, and — unless --no-tui — a terminal chat UI over your rooms, built on spindel-tui

  • the dvergr.runtime.bus + dvergr.discourse.llm substrate. The TUI is a frontend: the same daemon also drives the web dashboard, Telegram, and nREPL clients.

Run

# From a dvergr checkout — daemon + nREPL(:7888) + TUI
clojure -M:cli

# Headless: daemon + nREPL only (a server box, or attach a client later)
clojure -M:cli --no-tui

# Server box: daemon + nREPL + web dashboard, no TUI
clojure -M:cli --no-tui --web

Flags

FlagDefaultMeaning
-p, --port PORT7888nREPL port (written to .nrepl-port for editor/clj-nrepl-eval auto-discovery)
--nrepl-port-file PATH.nrepl-portwhere to write the nREPL port
--no-tui—run without the TUI (daemon + nREPL only)
--web—also start the web dashboard
--web-port PORT17880web dashboard port (with --web)
--web-bind IP127.0.0.1web bind address — the UI/API are unauthenticated, so default is loopback. Loopback does not keep out the pages your browser shows, so the server also checks the Host header (DNS rebinding), the Origin of every state-changing request and a per-session CSRF token; a reverse proxy's name and origin go in :http {:allowed-hosts […] :allowed-origins […]}
--mcp—also serve MCP on loopback TCP (clients connect through bin/dvergr-mcp; mcp.md)
--mcp-port PORT17888MCP port (with --mcp)
-h, --help—show usage

Provider, model, and agents are not CLI flags — they come from config.local.edn (with an env fallback). The shipped default leaves the provider unpinned and auto-selects the best one you have a key or login for (Anthropic → Fireworks → OpenAI → native Codex subscription → isolated Codex CLI → the local claude CLI). See provider-setup.md and configuration.md.

Layout

┌─ rooms ─────────┬─ dvergr · room: scratch · $0.12 / $1.00 · last 8.3s 245tok $0.0012 · ● generating ─┐
│ rooms           │                                                                                    │
│ ────────────    │  you  > Refactor src/app.clj                                                       │
│ ▸ scratch       │  coder> I'll start by reading the file.                                            │
│   plan          │         [tool: read_file src/app.clj]                                              │
│   triage        │         ...                                                                        │
│                 │                                                                                    │
│ Ctrl-N: new     │ > _                                                                                │
└─────────────────┴────────────────────────────────────────────────────────────────────────────────────┘
                      Enter: send · Ctrl-N: new room · Tab: cycle · Esc: cancel · Ctrl-C/q: quit
  • Sidebar (left): rooms list, active room marked ▸
  • Header: room name, live budget, last-turn elapsed/tokens/cost, in-flight tool, generation status
  • Body: scrolling messages — your turns prefixed you >, the agent's coder>; an in-progress reply streams into a draft line until the turn finishes
  • Footer: input prompt + key hints

Keys

KeyAction
EnterSend the input line to the active room's agent
Ctrl-NCreate a new room and switch to it
TabCycle the active room
EscCancel the current turn (interrupts the agent's generation)
Ctrl-RVoice input — press to start recording, again to stop; transcribes and posts as a 🎤 message (setup)
Ctrl-T / Ctrl-EToggle the trace view / per-agent context bars
Ctrl-C / q (input empty)Quit
Backspace / printable charsEdit input

Persistence

Rooms, their message history, knowledge, and per-agent budget persist automatically in Datahike under ./.dvergr/ (the daemon owns the stores). There is no save/resume step — restart clojure -M:cli and your rooms rehydrate from the registry + stores, the agent continuing with full conversational state. Forks made with :isolation :ctx carry their own branched stores until merged or discarded.

(Long conversations are kept in bounds by context compaction — summarize + prune before a turn as the model's window fills; see tools-and-sandbox.md.)

Agents & tools

What the TUI talks to are the agents joined to each room, configured in config.local.edn / resources/agents/*.md — not a CLI flag. Each agent's tool set is its capability boundary (a :tools allowlist or a role tag like #{:coding}); the default secretary (var) routes and chats. See rooms-and-agents.md and tools-and-sandbox.md.

Telemetry

The TUI renders each room from the live :room-messages signal (a fold of the room bus) plus per-chat stats. The header reflects:

  • Live budget / cost — from the chat-ctx :budget-signal + dvergr.rooms.stats
  • Generation status — ● generating / ○ idle / ✕ error

(:partial/token streaming and :telemetry/* turn events are bus primitives — see examples/scenario_streaming_partial.clj — not currently wired into the header.) External consumers (the web dashboard, a billing system, a logging agent) subscribe to the same bus channels.

Troubleshooting

Agent doesn't reply — make sure a provider is configured (a key in env or config.local.edn). Reply errors print to stderr; the daemon log is .dvergr/dvergr.log. List registered models with (dvergr.model.registry/list-models) over the nREPL.

Garbled output / Unicode issues — the terminal needs UTF-8 + 256-color. The TUI uses JLine; in an unusual terminal set TERM=xterm-256color.

Tab doesn't cycle rooms — some terminals map Tab to focus-change; Ctrl-Tab is also wired.

See also

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