A map of the codebase: the layer model, the subsystem dependency graph, the inbound message flow, and a per-namespace-family table (the namespaces under
src/+src-clients/). The exhaustive per-file table was retired in favour of the family table below — it stays accurate across refactors.
Dvergr is a Clojure AI-agent harness organized as an 8-layer stack (L0–L7) over a
copy-on-write substrate and a reactive (FRP) runtime. The architecture has
converged on Rooms + Participants on a Bus as the single discourse model, with
medium adapters (dvergr.adapters.core) bridging external chat media (Telegram)
into rooms as remote-user actors. A room is a self-contained project (its own Geschichte
workspace, a git repository kept as a Datahike branch with a muschel virtual
filesystem, + Datahike message/knowledge stores); a thin global system-db
(dvergr.system.db) is only the registry that keeps the set of rooms/agents together.
Layer summary:
substrate/config), the
Geschichte room workspaces (substrate/geschichte; forking a room branches
them), state-root paths (substrate/paths), logging
(substrate/log), the katzen-bound knowledge schema (kb/schema), exact-text +
semantic code parsing (code), and per-domain Datahike schema (scheduler/schema).runtime/bus + runtime/peer-bus, the single reactive runtime/clock, the
CoW execution-context memory model (runtime/ctx), and the pure cron math
(scheduler/cron).chat.context (signals + Datahike + SCI),
chat.schema (the unified Datahike schema), chat.accounting (microdollar budgets),
participant.context, the per-[room,agent] folded ChatContext (agent/room-context),
Room store impls (room/store/*), the system.db registry and the in-memory code
index.discourse core (Room/Participant/Message, fork/merge,
algebra: ask/fan-out/race/quorum/pipeline), the room.registry, rooms (CRUD +
Datahike unification), rooms-as-projects provisioning (system.rooms, system.mail),
reactive room views (rooms/messages, rooms/tree, rooms/stats, rooms/forks), the
per-room reactive scheduler (rooms/scheduler), and the transport-agnostic adapters/core.tools registry/executor, the SCI sandbox (sandbox +
sandbox/ns/* injectors + sandbox/workspace load-root + gated sandbox/deps),
structural/analyzer code-edit utilities, the channels framework, and the room
workspace's file operations. The read-only intake data sources are no longer in dvergr's source —
they live as agent-editable Clojure in the
dvergr-sandbox stdlib repo, cloned into
each room's workspace; only intake/bash (the muschel shell) and intake/mail remain
in-tree.chat/agent), context compaction, model
abstraction (model/* + provider impls for Anthropic/OpenAI, native Codex
subscription, and CLI compatibility paths), the
LLM-backed participant factory (discourse/llm), the shared turn mechanics
(agent/turn), the system-prompt assembler (agent/prompt), and process
checkpoint/resume (agent/process).daemon (boot and shutdown, agent lifecycle and
room joining, the Telegram, MCP and web wiring, the store GC loop), the scheduler, actors + transport,
skills + the unified definitions loader, tasks, stats, personas, the
central ops spec, and the Telegram channel.core facade, MCP server/json-rpc, the nREPL client, the CLI
entry point, the TUI app, and the web dashboard/API.Key cross-cutting threads. Spindel's ExecutionContext is the spine: the
peer-bus ([:dvergr/peer-bus]) and the stats cache ([:dvergr/stats]) live as
ctx-local state. Schedules do not: they are :schedule/* rows in each room's own
messages store, so they fork and merge with the room (rooms/scheduler). Canonical
Spindel world forks over registered Yggdrasil systems underpin the
fork/review/merge lifecycle (rooms/forks and the room_fork, room_diff,
room_review, room_merge and room_discard ops; hire! forks for subagents,
and the spawn_agent/propose_change tools are adapters over it).
The daemon (orchestration/daemon, about 1300 lines) holds no turn loop and no
evaluator: turns run in chat/agent and discourse/llm, Telegram traffic goes
through adapters/core (the daemon only builds the adapter's capability map), and
evaluation lives in agent/evaluation and agent/experiment.
The largest remaining structural debt:
:status :online presence assumptions in skills/stats that pre-date the
resolved "presence = room membership" model.graph TD
L7["L7 Clients: core / mcp / client / cli / tui / web"]
L6["L6 Orchestration: daemon / scheduler / actors / skills / definitions / tasks / personas / ops"]
L5["L5 Agent Execution: chat.agent / compaction / model.* / discourse.llm / agent.turn / agent.process"]
L4["L4 Tools and Sandbox: tools / sandbox.* / workspace / channels (intakes live in the sandbox repo)"]
L3["L3 Discourse + Rooms: discourse / rooms / registry / adapters / system.rooms"]
L2["L2 Per-room State: chat.context / chat.schema / accounting / room.store / system.db"]
L1["L1 Reactive Runtime: spindel FRP / bus / clock / ctx / cron"]
L0["L0 CoW Substrate: datahike / geschichte / config / paths / code / kb.schema"]
L7 --> L6 --> L5 --> L4 --> L3 --> L2 --> L1 --> L0
graph TD
daemon[daemon]
core[core-facade]
discourse[discourse]
discLLM[discourse.llm]
adapters[adapters.core]
rooms[rooms]
sysrooms[system.rooms]
sysdb[system.db]
bus[bus]
clock[runtime.clock]
registry[room.registry]
roomstore[room.store]
chatCtx[chat.context]
chatAgent[chat.agent]
compaction[chat.compaction]
schema[chat.schema]
model[model.*]
tools[tools]
sandbox[sandbox]
actors[actors]
definitions[definitions]
skills[skills]
tasks[tasks]
scheduler[scheduler.core]
roomsched[rooms.scheduler]
process[agent.process]
turn[agent.turn]
roomctx[agent.room-context]
partctx[participant.context]
channels[channels.core]
telegram[channels.telegram]
core --> discourse
core --> discLLM
core --> bus
core --> partctx
daemon --> discourse
daemon --> adapters
daemon --> roomctx
daemon --> rooms
daemon --> sysrooms
daemon --> scheduler
daemon --> actors
daemon --> skills
daemon --> tools
daemon --> channels
daemon --> telegram
daemon --> clock
discLLM --> discourse
discLLM --> chatCtx
discLLM --> chatAgent
discLLM --> compaction
discLLM --> turn
discLLM --> process
discLLM --> tools
adapters --> discourse
rooms --> discourse
rooms --> roomstore
rooms --> registry
rooms --> schema
sysrooms --> sysdb
sysrooms --> rooms
roomsched --> clock
roomsched --> discourse
roomctx --> discourse
roomctx --> chatCtx
roomctx --> registry
roomctx --> bus
discourse --> bus
discourse --> registry
discourse --> roomstore
chatAgent --> chatCtx
chatAgent --> compaction
chatAgent --> model
chatAgent --> tools
compaction --> chatCtx
compaction --> model
chatCtx --> schema
chatCtx --> sandbox
tools --> sandbox
tools --> process
skills --> actors
definitions --> skills
scheduler --> actors
scheduler --> discourse
process --> chatCtx
channels --> tools
telegram --> channels
graph TD
ext["External medium (Telegram update)"]
chan["channels.telegram polling"]
adapter["adapters.core inbound!"]
actorUp["actors: upsert external-user actor"]
room["rooms / discourse Room post! as user"]
busN["bus publish (room-scoped)"]
roomctx["agent.room-context fold into ChatContext signal"]
part["discourse.llm Participant on-message"]
turn["chat.agent run-agent-turn!"]
modelc["model.chat -> provider (anthropic/openai/claude-code) SSE"]
toolexec["tools/execute (role-scoped, sandbox SCI)"]
reply["agent reply post! to Room"]
egress["adapters.core egress-participant send-fn"]
out["External medium outbound"]
ext --> chan --> adapter --> actorUp --> room --> busN --> roomctx --> part --> turn --> modelc
turn --> toolexec --> turn
turn --> reply --> busN
reply --> egress --> out
| family | layer | role |
|---|---|---|
substrate/{config,geschichte,git,log,paths,load} | L0 | EDN config load/cache; Geschichte room workspaces (a Datahike branch + a muschel filesystem); native git helpers (the sandbox stdlib clone, the safe fallback workspace); structured logging; .dvergr/ state-root resolution; thread-safe runtime require |
substrate/{datahike,store_ids,kontor_book} | L0/L2 | the one helper that provisions a dvergr Datahike store and registers it as a forkable system; each file store's logical id kept beside it (<store>.id, so a moved home opens its stores); a room's Kontor business book, settled by replay |
code + code/index | L0/L2 | Clojure source parse/diff + in-memory katzen ACSet code index |
kb/schema | L0 | Knowledge-base schema (katzen canonical → Datahike idents) |
scheduler/{schema,cron,core,tools} | L0–L6 | persistent-schedule schema; pure cron math; spindel-native scheduler; agent schedule tools |
runtime/{bus,peer_bus,clock,ctx} | L1 | pub/sub substrate; control-plane peer-bus; the single reactive clock; CoW execution-context memory model |
chat/{context,schema,persist,accounting,compaction,agent,tool_schema} | L2/L5 | per-chat signals+Datahike state; the unified schema; one durability policy for message persistence; budgets; compaction; the core turn loop; tool→schema gen |
participant/context | L2 | uniform participant context (LLM/human/hybrid) |
agent/room_context | L2 | per-[room,agent] long-lived ChatContext folded from the room bus |
system/{db,home,rooms,mail} | L2/L3 | the global system-db registry (identity backbone); home.edn and moved-vs-copied homes (doc/homes.md); rooms-as-projects provisioning/resolvers; attach a briefkasten mailbox as a ygg system |
room/{registry,store,store/datahike,store/memory} | L2/L3 | slug↔Room registry; PRoomStore protocol + Datahike/in-memory impls |
discourse (core) | L3 | Room/Participant/Message, fork/merge, algebra combinators |
discourse/{background,enrichment,human,commands,personas,definitions,attention} | L3/L6 | background spawner; on-message decorators; human participant; slash-command registry; pre-built personas; the unified skill+agent-identity loader; pure attention decisions |
discourse/{llm,generation} | L4/L5 | LLM participant factory; GenerationHandle bridge |
rooms + rooms/{forks,messages,tree,stats,theme,scheduler} | L3 | room CRUD + Datahike unification; fork describe/review/merge/discard; signal-backed transcript/tree/stats views; per-speaker theme; per-room reactive scheduler |
rooms/{facts,files,repo,subsystems} | L3/L6 | one change feed per room (a signal bumped on each durable change); workspace files for outside clients (room_files/room_read/room_write); import a Git repository into a room workspace and export its changes as a patch; the one call that starts every per-room subsystem |
adapters/core | L3 | transport-agnostic medium adapter: inbound posting + egress Participant |
tools + tools/{structural,llm_call,approval} | L4 | tool registry/executor (role-scoped); structural edits; one-shot LLM tool; approval workflow |
effects, authority, resource + resource/authority | L4 | the effect boundary between sandbox code and the world (doc/effects.md); the can? authorization seam; conserved budgets and spindel's resource authority over the ledger (doc/worlds-and-forks.md) |
drive/{core,fs,blobs,integration} | L4 | the built-in room drive mounted at /drive: file nodes, the muschel FS, content-addressed blobs, host wiring |
io/{frozen_web,acquisition}, search/secondary, media/{doc,vision}, audio/{stt,record} | L4 | frozen web for discovery experiments; HTTP acquisition records; fulltext over Datahike's scriptum secondary index; document text and vision; speech-to-text |
sandbox + sandbox/{deps,workspace,work,extensions} + sandbox/ns/* | L4 | SCI runtime (ctx/eval/limits); gated add-libs; the workspace load-root; room-owned handles for spindel work-admission controllers; the extensions an AgentDef's :sandbox set names (:foerster/inference); the injected namespaces (io/data/datahike/agent/kb/room/mail/codec/dev/intake/malli), with doc attaching their docstrings |
sci/clojure_test + sci/impl/clojure_test | L4 | babashka's clojure.test, vendored and adapted for the SCI sandbox |
intake/{bash,jail,mail} | L4 | the only in-tree intakes — muschel-jailed shell (jail: listed programs such as python3 under bubblewrap, over a disk mirror of the worktree) + briefkasten mail (all other data sources live in the dvergr-sandbox stdlib repo) |
channels/{core,telegram,telegram_commands,telegram_send} | L4/L6 | channel framework; Telegram Bot API (polling/old dispatch); dvergr.ops slash-command binding; outbound Markdown→HTML rendering + chunking |
model/{provider,providers,registry,chat,quirks,gateway,subscription} + model/api/{anthropic,openai,claude_code,codex_subscription,codex_auth} | L5 | provider protocol + registry + metadata; origin-confined credential injection; HTTP/SSE, native Codex subscription, and CLI compatibility transports; provider quirks; the subscription usage meter and governor |
agent/{turn,prompt,process,tool_commands,persona,ops,fields} | L5/L6 | shared turn mechanics; system-prompt assembler; checkpoint/resume process; tool commands; persona resolution; agent-management ops + field spec |
agent/{program,run,world,roster,workflow} | L5/L6 | hire! and agent programs; durable Runs; a Run's forked work world; immutable rosters; one task run N times on forks |
agent/{evaluation,environment,experiment,experiment/{runner,preflight,select,stats},verifiers,evaluators,attempt,attempt/governance,grounds,spend,trajectory,conversation,episode} | L5/L6 | the evaluation path (doc/evaluation-model.md): Attempts, EnvironmentDefs, experiments and their runner, preflight, case selection and statistics, verifier trust tiers, the writer predicate that keeps certified rows immutable, what an Attempt's result rests on (its grounds), spend, trajectory export, certified conversations |
agent/{observation,arenas/renewal} | L5/L6 | bounded, scoped projections over one agent execution tree; a state-backed renewal-intervention arena |
catalog + catalog/{wiki,wiki_gen,room,room_run,casepack,casepack_cli,report,workspace} | L6 | catalog workflows (the wiki family) and room workflows (doc/room-workflows.md): bundles, case packs (and casepack-cli, a table of cases into a bundle from the shell), room-run (a client of the room path), the report |
jobs, artifact, activity | L6 | long operations as durable Runs; content-addressed portable values; durable observations on room messages |
orchestration/{daemon,skills,tasks,stats} | L6 | the runtime daemon (boot/shutdown, agent lifecycle, channel/MCP/web wiring, GC loop); skill registry; task ledger; stats cache |
actors + actors/transport | L6 | durable actor identity table; PActorTransport impls |
ops | L6 | central operations spec — one data map; web/MCP/Telegram surfaces derived (datahike-spec pattern) |
security/allowlist | L6 | Telegram user allowlist access control |
core | L7 | public API facade (re-exports discourse/llm/personas/bus) |
mcp/{server,json_rpc,http,surface,repl} | L7 | TCP/stdio MCP server + JSON-RPC/MCP dispatch; Streamable HTTP; profiles, toolsets and result encoding; the REPL's dvergr.ops (doc/mcp.md) |
clients/client | L7 | nREPL client — inspect/interact + fork-task workflow over a daemon |
cli/main + tui/app (src-clients) | L7 | CLI entry point; the TUI app (a rich medium adapter onto rooms) |
web/{server,guard,dashboard,api,ops,agents,apps} | L7 | http-kit server and its browser boundary (Host/Origin checks); dashboard (hiccup+HTMX); spec-derived JSON API; web ops binding; agent config UI; a room's app/ served as a static app (doc/web.md) |
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 |