SwarmWright

documentation · v0.3 · build the swarm

Quick Start

SwarmWright runs as a single Docker container. No external infrastructure required — no broker, no separate database server, no build pipeline. One command gets you running.

docker run -d \
  --network=host \
  --name swarmwright \
  -v ./data:/data \
  -e LLM_PROVIDER=anthropic \
  -e LLM_MODEL=claude-opus-4-7 \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  ralphbarendse/swarmwright:latest

Or with Docker Compose — grab docker/docker-compose.yml and .env.example from the repo:

cp .env.example .env
# Set LLM_PROVIDER, LLM_MODEL, and your API key

docker compose -f docker/docker-compose.yml up -d

The GUI is available at http://localhost:5001. The API is at http://localhost:5001/api/v1/health.

Note: A fresh docker compose up works without any manual key setup. An encryption key is generated on first boot and persisted to data/.encryption_key. Back this file up alongside data/swarm.db.

Requirements

Environment Variables

VariableRequiredDefaultDescription
LLM_PROVIDERYesanthropicanthropic or openai
LLM_MODELYesclaude-opus-4-6Model identifier string
ANTHROPIC_API_KEYIf anthropicAnthropic API key
OPENAI_API_KEYIf openaiOpenAI API key
SWARM_ENCRYPTION_KEYOptionalauto-generated32-byte base64 Fernet key. Auto-generated on first boot if not set.
DATABASE_URLNosqlite:////data/swarm.dbSQLAlchemy database URL
DATA_DIRNo/dataPath to the data volume mount
LOG_LEVELNoINFODEBUG / INFO / WARNING / ERROR
SCHEDULER_TIMEZONENoEurope/AmsterdamAPScheduler timezone for heartbeat triggers

Encryption Key

LLM credentials and secrets are encrypted at rest using Fernet symmetric encryption. The master key is resolved in this order on every boot:

  1. SWARM_ENCRYPTION_KEY environment variable — wins if set
  2. <DATA_DIR>/.encryption_key file — auto-managed by the container
  3. If neither exists, a new key is generated and written to the file

For higher-assurance deployments, generate a key out-of-band and pin it:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Core Concepts

SwarmWright organizes work in three nested scopes: Company, Workspace, and Swarm. A workspace is a department-like container (Finance, Operations, HR). A swarm is a coherent set of agents that collaborate to handle one class of work.

Two artifacts define every swarm:

The runtime enforces hierarchy.json strictly. If an agent attempts to call another agent, tool, or perceptionist that is not declared in the topology, the action is blocked and logged as a topology violation. There is no way to suppress this enforcement.

Key idea: The diagram you draw in the canvas is the configuration. There is no separate specification file to keep in sync. If the connection is on the canvas, it is in hierarchy.json, and it is enforceable at runtime.

Agent Layers

Four layers exist. No more, no fewer. Every agent has exactly one layer, declared in its constitution.

LayerColorRoleTypical responsibilities
Policy Navy #1e3a5f Strategic decisions, governance, final authority Sets rules, approves exceptions, handles escalations, the single point of accountability
Orchestrator Teal #2a6b6b Coordination, routing, workflow management Receives events, decides which executioner to call, aggregates results, routes to next step
Executioner Slate #3d4f7c Task execution, tool use, external actions Calls skills, writes to databases, calls APIs, processes files, produces artifacts
Perceptionist Amber #c97c2a Read-only grounding — maps reality to internal data Reads sensors, classifies inputs, extracts structured data, never writes or acts

The layer hierarchy is enforced by convention, not by the runtime. A Perceptionist is an agent like any other — the distinction is declared in its constitution and visible in the topology. The UI color-codes everything so the structure is legible at a glance.

Topology

The topology is the declared graph in hierarchy.json. It lists every agent, every edge, every skill, every human node, and every perceptionist the swarm can use. The runtime enforces this declaration — agents cannot call anything not listed.

Edge kinds

Every connection between two agents uses one of these kinds:

Purpose is mandatory. Every edge requires a non-empty purpose string written in plain English. This is enforced at validation time. The friction is intentional — if you can't state why a connection exists, you shouldn't add it.

Topology violations

When a running agent attempts to call another agent, tool, or perceptionist not listed in its declared connections, the runtime blocks the call and emits a topology_violation run step. The run continues if possible; the violation is surfaced in the Control Room step trace and in the live stream bar on the canvas.

Human-in-the-Loop

SwarmWright has first-class support for human participation at any point in a workflow. Two node types represent humans in a swarm topology: Caller and Informer. Both are placed on the canvas just like agents and connected with the same drag-to-connect mechanism.

Caller nodes

A Caller is a blocking human gate. When a workflow reaches a Caller, execution pauses and a Human Action request is created in the Inbox. The run waits in awaiting_human status until a human responds with a decision (yes, no, or an amended instruction). Only then does the workflow continue.

Use Callers for:

On the canvas, Caller nodes display a raised-hand glyph (✋) and are styled in sage-green to distinguish them from agent nodes. Connect an agent to a Caller by dragging from the agent's edge handle to the Caller node — the connection modal asks for a purpose.

Informer nodes

An Informer is a non-blocking notification. When a workflow reaches an Informer, a message is sent to the Inbox and the workflow continues immediately without waiting. Use Informers for progress updates, alerts, and FYI notifications that a human should see but doesn't need to act on.

On the canvas, Informer nodes display a megaphone glyph (📢).

Connecting human nodes

Human nodes (Caller and Informer) are placed on the canvas first, then connected to agents using the same edge-draw mechanism used for agent-to-agent connections:

  1. Place a Caller or Informer from the palette onto the canvas
  2. Select an agent node to open the inspector
  3. Click Connect to… to enter connect mode
  4. Click the human node you want to connect to
  5. In the modal that appears, enter the purpose of the connection
  6. Save — the connection is written to hierarchy.json

Multiple agents can connect to the same Caller or Informer. The node remains independent on the canvas — it is not "owned" by any single agent.

Inbox interaction

The Inbox tab shows two categories:

Past decisions appear under Approved and Rejected tabs with full context for audit purposes.

Resource Scopes

Knowledge documents, skills, and perceptionists exist at one of three scopes. References resolve most-local-first: swarm → workspace → company.

swarm scope      →  data/workspaces/<wid>/swarms/<sid>/<type>/
workspace scope  →  data/workspaces/<wid>/<type>/
company scope    →  data/company/<type>/
Reference in constitutionResolves to
approval-thresholdsMost-local match: swarm → workspace → company
workspace/finance-proceduresWorkspace scope only
company/glossaryCompany scope only

This scoping system means company-wide policies attach to all agents, department-level procedures are available to all agents in that workspace, and swarm-specific context stays private to one workflow. There are exactly three scopes — no additional nesting.

Constitutions

A constitution is a .md file with YAML frontmatter that defines what an agent is — its identity, values, role, and the knowledge it has access to. It does not declare connections. Connections live in hierarchy.json.

The constitution is passed verbatim as part of the system prompt when the agent runs. Writing a good constitution is the single most impactful thing you can do for agent quality.

---
name: Finance Orchestrator
layer: orchestrator
model: claude-opus-4-6
knowledge:
  - approval-thresholds
  - workspace/finance-procedures
---

You coordinate invoice intake for the Finance workspace.
Route incoming invoices to the appropriate Executioner agent
based on value, vendor, and approval requirements.

Never approve invoices directly. Escalate ambiguous cases
to the Policy layer with full context.

When routing, always state which agent you are delegating to
and why, so the decision is traceable.

Allowed frontmatter fields:

FieldTypeDescription
namestringDisplay name shown in the UI
layerstringpolicy / orchestrator / executioner / perceptionist
modelstringModel identifier. Overrides the swarm default if set.
knowledgelistList of knowledge document references (scope-qualified or bare)

Connection fields (skills, edges) belong in hierarchy.json, not in the constitution. Keeping identity separate from topology means you can change what an agent knows without touching what it connects to, and vice versa.

Skills

Skills are sandboxed Python scripts invocable by Executioner agents. Each skill lives as two files with the same basename:

Skills run in a subprocess, never in the Flask process. A misbehaving skill cannot crash the host. Allowed packages are enforced at registration time — if a package isn't in allowed_packages, the import fails at skill boot.

# parse-invoice.yaml
input_schema:
  type: object
  properties:
    text: { type: string }
  required: [text]
output_schema:
  type: object
  properties:
    vendor: { type: string }
    amount: { type: number }
    currency: { type: string }
allowed_packages: [re, json]
timeout_seconds: 10
# parse-invoice.py
import re, json

def run(input: dict, context: dict) -> dict:
    text = input["text"]
    # ... extraction logic ...
    return {"vendor": vendor, "amount": amount, "currency": currency}

Skills can be scoped at company, workspace, or swarm level. A skill at company scope is available to all agents in all swarms. Skills are listed in the Library tab and attached to agents via the topology canvas.

Triggers

Triggers are deterministic scripts — not agents. They produce events that enter a swarm. No LLM calls happen inside a trigger. Use regex, JSON Schema, JSONPath, and standard Python libraries. Keep them dumb. Their job is to detect and route, not to think.

Three trigger types:

KindFires whenConfig
Heartbeat On a cron schedule via APScheduler cron expression, payload template
Listener External webhook / incoming message arrives at the listener endpoint path, optional filter JSONPath expression
Invocation Fired manually via POST /api/v1/triggers/invocations/<id> or the Control Room Fire button payload_schema for validation

Triggers are created and managed in the swarm canvas inspector. Each trigger can be individually enabled or disabled without deleting it. The Control Room's Fire button fires a raw event into a swarm without going through a trigger — useful for testing.

Org Design

The Org tab is your company organogram. It shows all workspaces (departments) and the swarms within them.

Workspaces

Create a workspace for each department or functional area: Finance, Operations, HR, Customer Success. A workspace is a container — it has no runtime behavior of its own. It provides a namespace for swarms and a scope for shared resources (knowledge, skills, perceptionists).

Swarms

Each swarm handles one class of work. Create swarms within workspaces. A swarm has an enabled flag — disabled swarms cannot receive events and will not start runs. Activate or pause swarms from the Org view or the Control Room without deleting them.

Deleting a swarm removes both the database record and the filesystem directory (data/workspaces/<ws>/swarms/<swarm>/) permanently.

Swarm Canvas

The swarm canvas is where you design a swarm's topology. It is a Cytoscape.js diagram that writes directly to hierarchy.json on every change — there is no separate save step.

Adding nodes

Drag items from the left palette onto the canvas:

Drawing connections

  1. Click a node to open its inspector
  2. Click Connect to… in the inspector
  3. A banner appears: "click a target node to connect"
  4. Click any compatible target (agent, human node)
  5. Enter the purpose in the modal — this is mandatory
  6. Save — the edge appears and is written to hierarchy.json

Deleting elements

Click any node or edge to open its inspector, then click Delete. For agents, this removes the agent from the topology and its constitution file. For human nodes, this disconnects all edges and removes the node from the canvas. For edges, only the connection is removed — both endpoints remain.

Inspector

The right inspector panel shows context-sensitive actions for the selected element. For agents: edit constitution, fire test event, view connections. For edges: view purpose, delete. For the canvas background: view swarm info, fire event, delete swarm.

Inbox

The Inbox is the human-facing side of the human-in-the-loop system. The notification pip in the top nav shows the count of items requiring attention.

Tabs

TabContents
Awaiting Pending Caller requests. Each shows the triggering run, the agent that raised the request, and full context. Respond with Approve, Reject, or Amend.
Notifications Unread Informer messages. These are FYI — the workflow already continued. Mark read to dismiss.
Approved Past approvals (decision: yes) with timestamp and reason.
Rejected Past rejections (decision: no) with timestamp and reason.

Approving a Caller request

Select an awaiting item. The detail panel shows the run context, the requesting agent's question or proposal, and the full event payload. Three actions are available:

You can optionally add a reason to any decision. This reason is attached to the run step for audit purposes.

Control Room

The Control Room (formerly Runs) is the operational dashboard for your swarm fleet. It combines a live org chart with a filtered run log.

Organogram panel

The left panel shows all workspaces and their swarms as a collapsible tree. For each swarm:

Clicking a workspace row filters the run log to that workspace's swarms. Clicking a swarm row filters to that swarm only. Click again to deselect and show all runs.

Run log

The right panel shows runs in reverse-chronological order. Filter by:

Live updates arrive via SSE — the run log refreshes automatically when a run starts, completes, or fails. No manual refresh needed.

Run detail

Click a run to see its step trace: every agent call, skill invocation, human interaction, and topology violation in sequence. Each step shows the agent name, edge purpose, input/output, duration, and any errors. A Replay button re-fires the original event payload.

Library

The Library tab manages reusable resources: Knowledge (documents) and Skills (Python scripts). Both can exist at company, workspace, or swarm scope.

Upload a knowledge document as a Markdown or plain-text file. Attach it to agents by referencing it by name in the constitution's knowledge array. Company-scoped documents are available to all agents without explicit reference.

Skills are registered by uploading the .py and .yaml pair. Once registered, a skill appears in the canvas palette and can be dragged onto a swarm and connected to Executioner agents.

API Endpoints

All endpoints are JSON. Base path: /api/v1.

Health

MethodPathDescription
GET/healthHealth check — returns {"status":"ok"}

Workspaces & Swarms

MethodPathDescription
GET/workspacesList all workspaces
POST/workspacesCreate a workspace. Body: display_name, description
GET/workspaces/<id>Get workspace with nested swarm list
PUT/workspaces/<id>Update workspace display name / description
DELETE/workspaces/<id>Delete workspace (must have no swarms)
GET/workspaces/<id>/swarmsList swarms in a workspace
POST/workspaces/<id>/swarmsCreate swarm. Body: display_name, description
GET/swarms/<id>Get swarm with full metadata
PUT/swarms/<id>Update swarm. Supports enabled (boolean) to activate/pause
DELETE/swarms/<id>Delete swarm — removes DB row AND filesystem directory

Agents

MethodPathDescription
GET/swarms/<id>/agentsList agents in a swarm
GET/agents/<id>Get one agent with constitution text
PUT/agents/<id>/constitutionUpdate agent constitution text

Topology

MethodPathDescription
GET/swarms/<id>/topologyGet the full hierarchy.json content
PATCH/swarms/<id>/topologyApply a named operation to the topology. See hierarchy.json.

Topology patch operations:

opparamsEffect
add_agentid, layerCreates agent, adds to topology
remove_agentidRemoves agent and all its edges
add_edgefrom, to, kind, purposeAdds an agent-to-agent edge
remove_edgefrom, toRemoves an edge
add_callagent, caller, purposeConnects an agent to a Caller node
remove_callagent, callerRemoves agent→Caller connection
add_informagent, informer, purposeConnects an agent to an Informer node
remove_informagent, informerRemoves agent→Informer connection
add_canvas_callercallerPlaces a Caller node on the canvas (unconnected)
remove_canvas_callercallerRemoves Caller node and all its connections
add_canvas_informerinformerPlaces an Informer node on the canvas (unconnected)
remove_canvas_informerinformerRemoves Informer node and all its connections

Events & Triggers

MethodPathDescription
POST/swarms/<id>/eventsFire an event into a swarm. Body is the event payload (arbitrary JSON).
GET/eventsList recent events. Params: swarm_id, limit
GET/swarms/<id>/triggersList triggers for a swarm
POST/swarms/<id>/triggersCreate trigger. Body: name, kind, config
PUT/triggers/<id>Update trigger (config, enabled flag)
DELETE/triggers/<id>Delete trigger
POST/triggers/invocations/<id>Manually invoke a trigger

Runs

MethodPathDescription
GET/runsList runs. Params: status, swarm_id, workspace_id, limit, offset
GET/runs/<id>Get one run with full step trace
POST/runs/<id>/replayRe-fire the original event — creates a new run

Human actions (Caller inbox)

MethodPathDescription
GET/human_actionsList Caller requests. Params: status (pending / yes / no), swarm_id
GET/human_actions/<id>Get one action with full context
POST/human_actions/<id>/decideRespond to a Caller request. Body: decision (yes/no), reason, amend

Human informs (Informer inbox)

MethodPathDescription
GET/informsList Informer notifications. Params: status (unread / read), swarm_id
POST/informs/<id>/ackMark a notification as read
POST/informs/ack-allMark all unread notifications as read

Knowledge & Skills

MethodPathDescription
GET/knowledgeList knowledge documents. Params: scope, workspace_id, swarm_id
POST/knowledgeUpload knowledge document
DELETE/knowledge/<id>Delete knowledge document
GET/skillsList skills. Params: scope, workspace_id, swarm_id
POST/skillsRegister skill (upload .py + .yaml)
DELETE/skills/<id>Delete skill

Settings

MethodPathDescription
GET/settingsList all settings key-value pairs
GET/settings/<key>Get one setting by key
PUT/settings/<key>Set one setting
PUT/settingsBulk-set multiple settings
POST/settings/llm/testTest LLM connectivity with current credentials

hierarchy.json

The full topology for a swarm. Written by the canvas; read and enforced by the runtime.

KeyTypeDescription
swarmstringSwarm slug (folder name)
entry_pointstringAgent ID that receives incoming events
agentsarrayAll agent nodes: id, layer
edgesarrayAgent-to-agent connections: from, to, kind, purpose
skillsarraySkill attachments: agent, skill
callsarrayAgent→Caller connections: agent, caller, purpose
informsarrayAgent→Informer connections: agent, informer, purpose
canvas_callersarrayNames of Caller nodes present on the canvas (may be unconnected)
canvas_informersarrayNames of Informer nodes present on the canvas (may be unconnected)
consultationsarrayReserved for future lateral peer consultation edges
{
  "swarm": "invoice-intake",
  "entry_point": "policy-agent",
  "agents": [
    { "id": "policy-agent",          "layer": "policy"       },
    { "id": "finance-orchestrator",  "layer": "orchestrator" },
    { "id": "invoice-executor",      "layer": "executioner"  }
  ],
  "edges": [
    {
      "from": "policy-agent",
      "to": "finance-orchestrator",
      "kind": "delegate",
      "purpose": "route invoice work to finance orchestrator"
    },
    {
      "from": "finance-orchestrator",
      "to": "invoice-executor",
      "kind": "delegate",
      "purpose": "execute invoice parsing and classification"
    }
  ],
  "skills": [
    { "agent": "invoice-executor", "skill": "parse-invoice" }
  ],
  "calls": [
    {
      "agent": "policy-agent",
      "caller": "finance-approval",
      "purpose": "request human approval for invoices above 10000"
    }
  ],
  "informs": [
    {
      "agent": "invoice-executor",
      "informer": "ops-notifications",
      "purpose": "notify ops team when an invoice is successfully processed"
    }
  ],
  "canvas_callers":  ["finance-approval"],
  "canvas_informers": ["ops-notifications"],
  "consultations": []
}

Folder Structure

swarmwright/
├── app/
│   ├── models/        — SQLAlchemy ORM (8 tables)
│   ├── api/           — Flask blueprints under /api/v1
│   │   ├── workspaces.py
│   │   ├── swarms.py
│   │   ├── agents.py
│   │   ├── topology.py
│   │   ├── events.py
│   │   ├── triggers.py
│   │   ├── runs.py
│   │   ├── human_actions.py
│   │   ├── informs.py
│   │   ├── knowledge.py
│   │   ├── skills.py
│   │   └── settings.py
│   ├── core/          — Business logic (no Flask imports)
│   │   ├── registry.py     — filesystem scanner, hierarchy cache
│   │   ├── runtime.py      — topology-enforced agent execution
│   │   └── executor.py     — skill sandboxing
│   └── static/        — Vanilla JS frontend (no build step)
│       ├── index.html
│       ├── js/
│       │   ├── app.js          — router
│       │   ├── api.js          — fetch wrapper
│       │   ├── sse.js          — SSE client
│       │   └── views/          — one file per tab
│       └── css/
├── docker/            — Dockerfile, docker-compose.yml, entrypoint.sh
├── migrations/        — Alembic schema migrations
├── tests/             — pytest suite
└── data/              — Mounted volume (never committed to git)
    ├── swarm.db
    ├── .encryption_key
    ├── company/
    │   ├── knowledge/
    │   └── skills/
    └── workspaces/
        └── <workspace-slug>/
            ├── meta.yaml
            ├── knowledge/
            ├── skills/
            └── swarms/
                └── <swarm-slug>/
                    ├── meta.yaml
                    ├── hierarchy.json
                    └── agents/
                        └── <agent-id>.md

Glossary

TermDefinition
AgentLLM-powered component with a constitution and a layer
CallerA blocking human-in-the-loop node — pauses the run for a decision
ConstitutionMarkdown file defining an agent's identity, role, and knowledge references
Control RoomThe operational dashboard — organogram + run log + fire controls
EdgeDeclared connection in hierarchy.json with kind (escalate/delegate/report) and purpose
Entry pointThe agent that receives incoming events; the first node in execution
Human ActionA pending Caller request waiting for a human decision
Human InformA non-blocking notification from an Informer node
InformerA non-blocking human-in-the-loop node — sends a notification and continues
LayerOne of four agent roles: Policy / Orchestrator / Executioner / Perceptionist
PerceptionistRead-only grounding agent — maps external reality to internal data structures
PurposeA required plain-English string explaining why a connection exists
RunOne execution of a swarm in response to a single event
Run stepOne recorded action within a run (agent call, skill call, human interaction, violation)
Scopecompany / workspace / swarm — the three levels for reusable resources
SkillSandboxed Python script callable by an Executioner agent
SwarmA coherent set of agents handling one class of work, with a declared topology
TopologyThe declared graph in hierarchy.json — enforced at runtime with no exceptions
Topology violationA blocked call to an undeclared target — logged as a run step, surfaced in the UI
TriggerDeterministic script that produces events (heartbeat, listener, or invocation)
WorkspaceDepartment-like container for swarms; provides a resource scope