Liking cljdoc? Tell your friends :D

Dvergr Architecture Map

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.

Overview

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:

  • L0 — CoW substrate / persistence: config loading (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).
  • L1 — reactive runtime primitives: spindel ExecutionContext/FRP (external), the pub/sub 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).
  • L2 — per-chat / per-room state: 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.
  • L3 — discourse + rooms: 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.
  • L4 — tools + sandbox: the 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.
  • L5 — agent execution: the turn loop (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).
  • L6 — orchestration: the 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.
  • L7 — clients: the 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:

  • residual :status :online presence assumptions in skills/stats that pre-date the resolved "presence = room membership" model.

Diagrams

Layer Stack L0–L7

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

Subsystem Dependency Graph

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

Inbound Message Flow (medium → model → tools)

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

Per-namespace-family layout

familylayerrole
substrate/{config,geschichte,git,log,paths,load}L0EDN 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/L2the 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/indexL0/L2Clojure source parse/diff + in-memory katzen ACSet code index
kb/schemaL0Knowledge-base schema (katzen canonical → Datahike idents)
scheduler/{schema,cron,core,tools}L0–L6persistent-schedule schema; pure cron math; spindel-native scheduler; agent schedule tools
runtime/{bus,peer_bus,clock,ctx}L1pub/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/L5per-chat signals+Datahike state; the unified schema; one durability policy for message persistence; budgets; compaction; the core turn loop; tool→schema gen
participant/contextL2uniform participant context (LLM/human/hybrid)
agent/room_contextL2per-[room,agent] long-lived ChatContext folded from the room bus
system/{db,home,rooms,mail}L2/L3the 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/L3slug↔Room registry; PRoomStore protocol + Datahike/in-memory impls
discourse (core)L3Room/Participant/Message, fork/merge, algebra combinators
discourse/{background,enrichment,human,commands,personas,definitions,attention}L3/L6background 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/L5LLM participant factory; GenerationHandle bridge
rooms + rooms/{forks,messages,tree,stats,theme,scheduler}L3room 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/L6one 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/coreL3transport-agnostic medium adapter: inbound posting + egress Participant
tools + tools/{structural,llm_call,approval}L4tool registry/executor (role-scoped); structural edits; one-shot LLM tool; approval workflow
effects, authority, resource + resource/authorityL4the 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}L4the 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}L4frozen 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/*L4SCI 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_testL4babashka's clojure.test, vendored and adapted for the SCI sandbox
intake/{bash,jail,mail}L4the 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/L6channel 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}L5provider 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/L6shared turn mechanics; system-prompt assembler; checkpoint/resume process; tool commands; persona resolution; agent-management ops + field spec
agent/{program,run,world,roster,workflow}L5/L6hire! 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/L6the 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/L6bounded, 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}L6catalog 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, activityL6long operations as durable Runs; content-addressed portable values; durable observations on room messages
orchestration/{daemon,skills,tasks,stats}L6the runtime daemon (boot/shutdown, agent lifecycle, channel/MCP/web wiring, GC loop); skill registry; task ledger; stats cache
actors + actors/transportL6durable actor identity table; PActorTransport impls
opsL6central operations spec — one data map; web/MCP/Telegram surfaces derived (datahike-spec pattern)
security/allowlistL6Telegram user allowlist access control
coreL7public API facade (re-exports discourse/llm/personas/bus)
mcp/{server,json_rpc,http,surface,repl}L7TCP/stdio MCP server + JSON-RPC/MCP dispatch; Streamable HTTP; profiles, toolsets and result encoding; the REPL's dvergr.ops (doc/mcp.md)
clients/clientL7nREPL client — inspect/interact + fork-task workflow over a daemon
cli/main + tui/app (src-clients)L7CLI entry point; the TUI app (a rich medium adapter onto rooms)
web/{server,guard,dashboard,api,ops,agents,apps}L7http-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

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