Status: accepted design contract, implemented incrementally. This document fixes the vocabulary, ownership, and interoperability boundaries for the implementation work; a staged API described below may not exist yet.
Dvergr should support teams of communicating reasoning agents that solve complex tasks together without turning a room into a linear job queue or flooding its conversation with tool chatter.
The target combines four properties:
This is not a proposal to model every conversational turn as a heavyweight workflow run. The room's chronological log is a synchronization order; its semantic structure is a graph.
Room remains Dvergr's primary social and work substrate. Thread is its main
conversation-topology extension: a lightweight projection over messages in the
same room, store, policy environment, and chronological log. It does not create a
database, participant registry, execution context, or workspace.
Tasks and agent executions are orthogonal projections over Room facts rather than extensions of Room itself. A Workroom is different: it is an actual child Room, created only when work needs scoped participants, policy, budget, resources, or a forked substrate.
Use the following concepts distinctly:
| Concept | Meaning |
|---|---|
| Room | Long-lived social and work environment with participants, messages, resources, and policy. |
| Thread | Lightweight topical conversation rooted at a message. It shares the room substrate. |
| Task | Durable intention or delegated responsibility to produce an outcome. |
| Workroom | Scoped child room for complex collaborative work, optionally with a forked substrate. |
| Agent execution | One bounded invocation/attempt by one agent in response to a trigger. |
| Workflow run | One invocation of an explicit reusable workflow. |
| Simulation run | One bounded inference or what-if experiment. |
| Activity | An observation about execution: tool action, edit, test, approval, error, or progress. |
| Process | A live execution mechanism and control handle. It is transient unless separately persisted. |
A thread answers “what are we discussing?” A task answers “what outcome is owed?” An execution answers “what did this agent do this time?” A run is reserved for an explicit program or simulation whose invocation has its own lifecycle.
Several projections coexist over the same durable facts:
chronological room log
M1 pricing question
M2 deployment question
A1 pricing research activity
B1 deployment diagnostic activity
B2 deployment response
A2 pricing response
thread projection
pricing: M1 -> A1 -> A2
deployment: M2 -> B1 -> B2
work projection
pricing task -> researcher execution -> evidence -> A2
deploy task -> operator execution -> diagnosis -> B2
These projections are deliberately many-to-many:
Do not make a run or task own a private linear copy of the room transcript. Correlate facts using identities and edges.
Message already carries :in-reply-to. Add a durable thread-root identity so
clients can query a thread without recursively loading every parent:
{:id message-id
:in-reply-to parent-message-id
:thread-root-id root-message-id}
Rules:
Dvergr's current behavior has a useful foundation:
[room, agent] has a long-lived working ChatContext;Make the target semantics explicit. These are defaults, classified by an attention policy rather than permanently equating thread membership with interruption. The policy receives the active and incoming envelopes plus Room and Participant, so deployments can use authoritative sender identity and addressing:
| Incoming fact | Active execution behavior |
|---|---|
| Same-thread direct message | Steer at the next safe model boundary. |
| Different-thread direct message | Queue a new execution; do not mix topics or cancel by default. |
| Explicit urgent interrupt | Interrupt or suspend according to policy. |
| Passive message in same thread | Observe; integrate at a declared attention boundary. |
| Passive message in another thread | Retain as room awareness for later context assembly. |
| Tool activity, telemetry, reaction | Never wake an LLM agent by default. |
| Explicit delegation | Create or resume a task and wake its assignee. |
Start with one active execution per [room, agent]; agents remain concurrent with
one another. Later, one agent identity may run concurrent task-scoped executions
using forked contexts.
Every execution should eventually record a context frontier rather than relying on an implicit moving signal:
{:execution/context
{:room-basis basis-or-tx
:thread-root root-message-id
:trigger trigger-message-id
:observations [explicit-message-ids]}}
This makes replay, diagnosis, simulation, and “why did it miss that message?” answerable. It does not require copying the room database.
When a thread becomes long-running, multi-agent, branchable, or approval-gated, promote its work into a child workroom:
product room
└── thread: Implement forecasting
└── forecasting workroom
├── lead
├── data specialist
├── implementer
└── reviewer
A workroom may:
A child agent may recursively create another workroom. Existing
fork-room {:isolation :ctx}, merge-room, and discard provide the substrate
semantics; orchestration should compose them rather than introduce a second
branch manager.
Workspace policy must be explicit:
:workspace/shared-read ; safe concurrent investigation
:workspace/shared-write ; only when conflict semantics are accepted
:workspace/branch ; isolated edits, later merge/review
The first orchestration surface should be small and message-oriented:
(delegate! context
{:objective "Review the access-control changes"
:parent-task parent-task-id
:thread-root thread-root-id
:agent :agent/reviewer
:context-policy :thread-and-resources
:workspace-policy :workspace/branch})
;; => task-id
(message! task-id content) ; deliver information, no turn required
(follow-up! task-id content) ; deliver and start/resume a turn
(interrupt! task-id reason)
(await! [task-id ...])
(snapshot task-id)
The public names are provisional. Preserve these behavioral distinctions even if the final API is expressed as tagged room messages:
Task identity is separate from agent identity. Persistent room agents and task-scoped ephemeral workers should both implement the same protocol.
Delegation must say what the child sees:
:context/none
:context/task-brief
:context/thread
:context/selected-messages
:context/full-snapshot
Record the selected basis/frontier. Avoid passing opaque mutable parent context.
Delegated permissions narrow by default. A child receives only the tools, resources, network domains, credentials, and write scopes necessary for its task. An expansion requires an explicit policy decision or approval.
External effects retain their approval requirements regardless of delegation depth. A parent cannot launder authority through a child.
Agents communicate conclusions and evidence, not required private chain-of-thought. Useful typed speech acts include:
:work/brief
:work/finding
:work/question
:work/answer
:work/proposal
:work/review
:work/result
The body may remain natural language. Types support routing, summarization, and presentation without constraining reasoning style.
Provider adapters should emit one typed vocabulary:
:work/delegated
:work/started
:work/message-sent
:work/followed-up
:work/activity
:work/waiting
:work/completed
:work/failed
:work/interrupted
:work/result-produced
A minimal envelope:
{:work-event/id event-id
:work-event/type :work/activity
:work-event/task task-id
:work-event/root root-task-id
:work-event/parent parent-task-id
:work-event/room room-id
:work-event/thread-root thread-root-id
:work-event/actor actor-id
:work-event/execution execution-id
:work-event/at instant}
Activity facts add structured semantics where known:
{:activity/verb :test
:activity/object {:artifact/ref file-or-command-ref}
:activity/status :failed
:activity/outcome "1 failure in access-control tests"
:activity/critical? true}
Do not add an opaque EDN or JSON payload column to Datahike. Extend typed attributes as durable concepts stabilize; put large output behind store refs.
The event log is authoritative. Live process handles, cancellation functions, provider streams, and mailbox registrations are transient projections.
Codex Multi-Agent V2 is a recursive tree of agent threads with canonical paths. Its operations map as follows:
| Codex | Dvergr |
|---|---|
| Root agent thread | Lead execution in a workroom |
Agent path /root/reviewer | Task/delegation lineage, not global actor identity |
spawn_agent | delegate! plus child execution/workroom |
fork_turns | Explicit context policy and frontier |
| Shared working directory | Selected workspace policy |
send_message | message! without wake |
followup_task | follow-up! with wake |
wait_agent | Internal await! |
interrupt_agent | Interrupt the live execution, retain task history |
| Child final response | :work/result-produced, delivered to parent |
| Collaboration activity item | Normalized work/activity event |
Codex normally shares one filesystem among workers. Dvergr should prefer branched workspaces for independent writers and retain shared-write as an explicit policy.
OpenCode's Task tool creates a child session with parentID, derives restricted
permissions, runs in the foreground or background, and can resume the same child
session using task_id.
| OpenCode | Dvergr |
|---|---|
| Child session | Child task execution or private workroom |
parentID | Parent task/workroom edge |
subagent_type | Agent role/specification |
| Foreground task | Parent awaits child result |
| Background task | Parent continues and receives completion event |
task_id reuse | Follow-up/resumption on the existing task |
| Derived permission rules | Narrowed delegated capability policy |
| Synthetic task result | Result delivery to the parent execution |
Unlike OpenCode's default UI contract, child work must remain user-expandable in Simmis when authorization permits. Parent synthesis is the default view, not the only surviving record.
Buzz's “verb, object, outcome” activity cards solve immediate supervision. Simmis also needs recursive temporal and topical summaries:
sentence
-> paragraph
-> work episodes
-> actions/messages
-> raw arguments, output, diffs, and traces
A summary is a derived, versioned lens over evidence:
{:summary/id summary-id
:summary/level :sentence
:summary/covers #{event-id ...}
:summary/source-frontier basis-or-tx
:summary/content "Three agents fixed token refresh..."
:summary/critical #{critical-event-id ...}
:summary/generated-by {:model model-id :prompt-version version}
:summary/created-at instant}
Requirements:
:summary/covers links.Critical state propagates structurally through every collapsed level, independent of generated prose:
The UI may say “three agents implemented token refresh” while still displaying “one unresolved test failure” without expansion.
Follow doc/state-model.md:
Do not use signals as agent mailboxes. Signals integrate observable external state; mailboxes, deferreds, pub/sub, and semaphores coordinate agents.
:task/* models before adding delegated work.
Authoritative project tasks live in forkable room state; a global human inbox
is a derived delivery projection, not the semantic owner.:message/thread-root-id persistence and round-trip tests.:message/in-reply-to separately.This is the immediate dependency for Simmis's room UI.
delegate!, message!, follow-up!, interrupt!,
await!, and snapshot behavior using Spindel coordination primitives.Keep the orchestration surface REPL-first and provider-independent. The initial contract should be demonstrable with scripted agents:
Use await inside spins and dereference only at the REPL/test boundary. Never use
:reload-all; see CLAUDE.md.
dvergr.chat.schema or dedicated
namespaces merged into full-schema.Resolve these from executable scenarios and query requirements, not by forcing all work into one abstraction upfront.
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 |