SwarmWright
documentation · v0.3 · build the swarmQuick 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
- Docker Engine 24+ and Docker Compose v2
- An Anthropic or OpenAI API key
- A mounted volume for
data/(handled by docker-compose.yml)
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
LLM_PROVIDER | Yes | anthropic | anthropic or openai |
LLM_MODEL | Yes | claude-opus-4-6 | Model identifier string |
ANTHROPIC_API_KEY | If anthropic | — | Anthropic API key |
OPENAI_API_KEY | If openai | — | OpenAI API key |
SWARM_ENCRYPTION_KEY | Optional | auto-generated | 32-byte base64 Fernet key. Auto-generated on first boot if not set. |
DATABASE_URL | No | sqlite:////data/swarm.db | SQLAlchemy database URL |
DATA_DIR | No | /data | Path to the data volume mount |
LOG_LEVEL | No | INFO | DEBUG / INFO / WARNING / ERROR |
SCHEDULER_TIMEZONE | No | Europe/Amsterdam | APScheduler 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:
SWARM_ENCRYPTION_KEYenvironment variable — wins if set<DATA_DIR>/.encryption_keyfile — auto-managed by the container- 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:
- meta.yaml — display metadata (name, description, icon). Folder names are stable identifiers; display names live here.
- hierarchy.json — the swarm's topology: which agents exist, what edges connect them, what human nodes are wired in, and the purpose of each connection. This is enforced at runtime.
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.
| Layer | Color | Role | Typical 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:
- escalate — upward authority, toward Policy
- delegate — downward authority, from Policy or Orchestrator
- report — returning results upward
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:
- High-stakes approval gates (invoice above threshold, contract signature, system change)
- Ambiguous decisions that require human judgment
- Compliance checkpoints that must be acknowledged
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:
- Place a Caller or Informer from the palette onto the canvas
- Select an agent node to open the inspector
- Click Connect to… to enter connect mode
- Click the human node you want to connect to
- In the modal that appears, enter the purpose of the connection
- 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:
- Awaiting — pending Caller requests. Select one to see full context, then approve (
yes), reject (no), or amend (provide an alternative instruction). The workflow resumes from the exact point it paused. - Notifications — unread Informer messages. These are informational and do not block a run. Mark them read individually or all at once.
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 constitution | Resolves to |
|---|---|
approval-thresholds | Most-local match: swarm → workspace → company |
workspace/finance-procedures | Workspace scope only |
company/glossary | Company 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:
| Field | Type | Description |
|---|---|---|
name | string | Display name shown in the UI |
layer | string | policy / orchestrator / executioner / perceptionist |
model | string | Model identifier. Overrides the swarm default if set. |
knowledge | list | List 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:
skill-name.py— must expose a top-levelrun(input: dict, context: dict) -> dictskill-name.yaml— declaresinput_schema,output_schema,timeout_seconds,allowed_packages
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:
| Kind | Fires when | Config |
|---|---|---|
| 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:
- Agent layers (Policy, Orchestrator, Executioner, Perceptionist) — adds an agent and opens the constitution editor
- Skill — adds a skill reference node; connect it to an Executioner to declare usage
- Trigger — opens the trigger creation modal
- Caller — places a human Caller node (✋) on the canvas unconnected
- Informer — places a human Informer node (📢) on the canvas unconnected
Drawing connections
- Click a node to open its inspector
- Click Connect to… in the inspector
- A banner appears: "click a target node to connect"
- Click any compatible target (agent, human node)
- Enter the
purposein the modal — this is mandatory - 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
| Tab | Contents |
|---|---|
| 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:
- Approve — decision
yes, workflow resumes - Reject — decision
no, workflow continues with rejection noted - Amend — provide an alternative instruction; the agent receives this as amended context
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:
- A green dot (●) indicates the swarm is active and can receive events
- A grey dot (●) indicates the swarm is paused
- The active / paused toggle button activates or pauses the swarm in place
- The ▶ Fire button opens a modal to send a raw event directly into the 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:
- Status: running, completed, failed, pending, awaiting human
- Workspace: filters to all swarms in that workspace
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
| Method | Path | Description |
|---|---|---|
| GET | /health | Health check — returns {"status":"ok"} |
Workspaces & Swarms
| Method | Path | Description |
|---|---|---|
| GET | /workspaces | List all workspaces |
| POST | /workspaces | Create 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>/swarms | List swarms in a workspace |
| POST | /workspaces/<id>/swarms | Create 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
| Method | Path | Description |
|---|---|---|
| GET | /swarms/<id>/agents | List agents in a swarm |
| GET | /agents/<id> | Get one agent with constitution text |
| PUT | /agents/<id>/constitution | Update agent constitution text |
Topology
| Method | Path | Description |
|---|---|---|
| GET | /swarms/<id>/topology | Get the full hierarchy.json content |
| PATCH | /swarms/<id>/topology | Apply a named operation to the topology. See hierarchy.json. |
Topology patch operations:
| op | params | Effect |
|---|---|---|
add_agent | id, layer | Creates agent, adds to topology |
remove_agent | id | Removes agent and all its edges |
add_edge | from, to, kind, purpose | Adds an agent-to-agent edge |
remove_edge | from, to | Removes an edge |
add_call | agent, caller, purpose | Connects an agent to a Caller node |
remove_call | agent, caller | Removes agent→Caller connection |
add_inform | agent, informer, purpose | Connects an agent to an Informer node |
remove_inform | agent, informer | Removes agent→Informer connection |
add_canvas_caller | caller | Places a Caller node on the canvas (unconnected) |
remove_canvas_caller | caller | Removes Caller node and all its connections |
add_canvas_informer | informer | Places an Informer node on the canvas (unconnected) |
remove_canvas_informer | informer | Removes Informer node and all its connections |
Events & Triggers
| Method | Path | Description |
|---|---|---|
| POST | /swarms/<id>/events | Fire an event into a swarm. Body is the event payload (arbitrary JSON). |
| GET | /events | List recent events. Params: swarm_id, limit |
| GET | /swarms/<id>/triggers | List triggers for a swarm |
| POST | /swarms/<id>/triggers | Create 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
| Method | Path | Description |
|---|---|---|
| GET | /runs | List runs. Params: status, swarm_id, workspace_id, limit, offset |
| GET | /runs/<id> | Get one run with full step trace |
| POST | /runs/<id>/replay | Re-fire the original event — creates a new run |
Human actions (Caller inbox)
| Method | Path | Description |
|---|---|---|
| GET | /human_actions | List Caller requests. Params: status (pending / yes / no), swarm_id |
| GET | /human_actions/<id> | Get one action with full context |
| POST | /human_actions/<id>/decide | Respond to a Caller request. Body: decision (yes/no), reason, amend |
Human informs (Informer inbox)
| Method | Path | Description |
|---|---|---|
| GET | /informs | List Informer notifications. Params: status (unread / read), swarm_id |
| POST | /informs/<id>/ack | Mark a notification as read |
| POST | /informs/ack-all | Mark all unread notifications as read |
Knowledge & Skills
| Method | Path | Description |
|---|---|---|
| GET | /knowledge | List knowledge documents. Params: scope, workspace_id, swarm_id |
| POST | /knowledge | Upload knowledge document |
| DELETE | /knowledge/<id> | Delete knowledge document |
| GET | /skills | List skills. Params: scope, workspace_id, swarm_id |
| POST | /skills | Register skill (upload .py + .yaml) |
| DELETE | /skills/<id> | Delete skill |
Settings
| Method | Path | Description |
|---|---|---|
| GET | /settings | List all settings key-value pairs |
| GET | /settings/<key> | Get one setting by key |
| PUT | /settings/<key> | Set one setting |
| PUT | /settings | Bulk-set multiple settings |
| POST | /settings/llm/test | Test LLM connectivity with current credentials |
hierarchy.json
The full topology for a swarm. Written by the canvas; read and enforced by the runtime.
| Key | Type | Description |
|---|---|---|
swarm | string | Swarm slug (folder name) |
entry_point | string | Agent ID that receives incoming events |
agents | array | All agent nodes: id, layer |
edges | array | Agent-to-agent connections: from, to, kind, purpose |
skills | array | Skill attachments: agent, skill |
calls | array | Agent→Caller connections: agent, caller, purpose |
informs | array | Agent→Informer connections: agent, informer, purpose |
canvas_callers | array | Names of Caller nodes present on the canvas (may be unconnected) |
canvas_informers | array | Names of Informer nodes present on the canvas (may be unconnected) |
consultations | array | Reserved 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
| Term | Definition |
|---|---|
| Agent | LLM-powered component with a constitution and a layer |
| Caller | A blocking human-in-the-loop node — pauses the run for a decision |
| Constitution | Markdown file defining an agent's identity, role, and knowledge references |
| Control Room | The operational dashboard — organogram + run log + fire controls |
| Edge | Declared connection in hierarchy.json with kind (escalate/delegate/report) and purpose |
| Entry point | The agent that receives incoming events; the first node in execution |
| Human Action | A pending Caller request waiting for a human decision |
| Human Inform | A non-blocking notification from an Informer node |
| Informer | A non-blocking human-in-the-loop node — sends a notification and continues |
| Layer | One of four agent roles: Policy / Orchestrator / Executioner / Perceptionist |
| Perceptionist | Read-only grounding agent — maps external reality to internal data structures |
| Purpose | A required plain-English string explaining why a connection exists |
| Run | One execution of a swarm in response to a single event |
| Run step | One recorded action within a run (agent call, skill call, human interaction, violation) |
| Scope | company / workspace / swarm — the three levels for reusable resources |
| Skill | Sandboxed Python script callable by an Executioner agent |
| Swarm | A coherent set of agents handling one class of work, with a declared topology |
| Topology | The declared graph in hierarchy.json — enforced at runtime with no exceptions |
| Topology violation | A blocked call to an undeclared target — logged as a run step, surfaced in the UI |
| Trigger | Deterministic script that produces events (heartbeat, listener, or invocation) |
| Workspace | Department-like container for swarms; provides a resource scope |