SwarmWright

documentation · 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. Two commands get you running.

docker pull ralphbarendse/swarmwright:latest

docker run -d \
  --name swarmwright \
  --network host \
  --restart unless-stopped \
  -v ./data:/data \
  ralphbarendse/swarmwright:latest

Open http://localhost:5001. On first boot you will be prompted to create an admin account — set a username and password, then log in. From there, go to Settings → LLM Providers and enter your API key. No environment variables required to get started.

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

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

Note: An encryption key is generated on first boot and persisted to data/.encryption_key. Back this file up alongside data/swarm.db. All LLM credentials are encrypted at rest — they never leave the container.

Managed Hosting

Don't want to run servers? The same product is available as a managed instance at console.swarmwright.com — pick a plan, name your instance, and it's live at your-name.swarmwright.com in about a minute.

Note: everything else in these docs applies to hosted instances too — the cloud runs the same container you'd run yourself. See pricing for plans.

Requirements

Environment Variables

VariableRequiredDefaultDescription
LLM_PROVIDERNoanthropic, openai, or deepseek. Configurable in Settings UI; env var takes precedence if set.
LLM_MODELNoModel identifier string. Configurable in Settings UI; env var takes precedence if set.
ANTHROPIC_API_KEYIf anthropicAnthropic API key
OPENAI_API_KEYIf openaiOpenAI API key
DEEPSEEK_API_KEYIf deepseekDeepseek 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 and skills exist at one of four scopes. References resolve most-local-first: swarm → workspace → company → built-in.

swarm scope      →  data/workspaces/<wid>/swarms/<sid>/<type>/
workspace scope  →  data/workspaces/<wid>/<type>/
company scope    →  data/company/<type>/
built-in scope   →  app/builtin_skills/  (read-only, platform-provided)
Reference in constitutionResolves to
approval-thresholdsMost-local match: swarm → workspace → company
workspace/finance-proceduresWorkspace scope only
company/glossaryCompany scope only
write_swarm_fileResolves to built-in if not overridden at any higher scope

Company-wide policies attach to all agents; department-level procedures are available to all agents in that workspace; swarm-specific context stays private to one workflow. The built-in scope is the final fallback for skills — it is read-only and cannot be edited, but any built-in skill can be overridden by creating a skill with the same name at any other scope.

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-7
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 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. Packages listed in a skill's allowed_packages must first be registered in the global allowlist via Settings → System → Packages. Adding a package there installs it into the runtime and persists it across container restarts.

# 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.

Built-in skills

SwarmWright ships 20 platform-provided skills at the built-in scope. They are always available to any agent without setup and appear under Library → Skills → Built-in as read-only cards. No allowed_packages required — they use only the Python standard library.

To override a built-in, create a skill with the same name at swarm, workspace, or company scope. The resolver finds yours first.

File Store

The runtime injects context["files_root"] into every skill call — the path to the current swarm's files/ directory. Path traversal outside this root is rejected.

SkillDescriptionKey inputsReturns
write_swarm_file Write a file to files/. Creates parent directories automatically. path, content, encoding (utf-8 / base64) path, size_bytes, checksum (SHA-256)
read_swarm_file Read a file from files/. path, encoding (utf-8 / base64) content, encoding, size_bytes
list_swarm_files List files in files/, optionally filtered by path prefix. prefix (optional) files[] — path, filename, size_bytes, modified_at
delete_swarm_file Delete a file. Succeeds silently if the file does not exist. path deleted (bool), path

HTTP

SkillDescriptionKey inputsReturns
http_get Perform an HTTP GET request. Timeout 30 s. url, headers (optional), as_json (bool) status, headers, body, json (when as_json=true)
http_post Perform an HTTP POST with a JSON body. Sets Content-Type: application/json automatically. url, body (object), headers (optional), as_json (bool) status, headers, body, json (when as_json=true)

Key-Value Store

A per-swarm persistent store backed by _kv_store.json in the swarm's files/ directory. Values survive across runs and can hold any JSON-serializable type.

SkillDescriptionKey inputsReturns
kv_get Retrieve a value by key. key, default (optional fallback) key, value, found (bool)
kv_set Store a value by key. Atomic write — safe under concurrent runs. key, value (any JSON type) key, value

Utilities

SkillDescriptionKey inputsReturns
get_datetime Return the current UTC date and time. Useful for scheduling logic or timestamping output. none utc_iso, unix_timestamp, date, time, weekday, year, month, day, hour, minute

Notifications

SkillDescriptionKey inputsReturns
send_webhook POST a JSON payload to any webhook URL. Works with Slack, Discord, Teams, or custom endpoints. url, payload (object), headers (optional) status, body, ok (true if 2xx)

Chat — Platform Skills

Used by the built-in Operator and Concierge swarms. Available at company scope so operators can also attach them to custom swarms. Like all built-ins, each can be overridden by a same-named skill at any higher scope.

SkillUsed byDescription
create_workspaceOperatorProvision a new workspace folder and meta.yaml.
create_swarmOperatorProvision a new swarm shell inside a workspace.
setup_swarmOperatorCreate a swarm and its first agent in a single step.
add_agent_to_swarmOperatorAdd an agent to an existing swarm, set its entry point, and attach skills.
draft_constitutionOperatorGenerate a starter constitution markdown for a new agent.
patch_topologyOperatorApply a named structural edit to a swarm's hierarchy.json.
patch_swarmOperatorEnable or disable a swarm, or update its metadata.
read_skillOperatorRead an existing skill's source before editing or recreating it.
create_skillOperatorCreate a custom skill from Python + YAML content.
trigger_runOperatorInvoke an existing swarm with a given input payload.
list_runsOperatorFetch recent runs for a swarm with status and summary.
read_runBothFetch a run's status and full step trail by run ID.
list_swarmsOperatorList swarms across workspaces with their enabled status.
list_workspacesOperatorList all workspaces on the platform.
list_swarm_artifactsOperatorList the files a swarm has written to its files/ directory, with size and modified time.
read_swarm_artifactOperatorRead the contents of a file from any swarm's files/ directory (200 KB cap).
search_knowledgeOperatorFull-text search across all knowledge files on the platform.
web-searchOperatorSearch the web via DuckDuckGo for current information.
list_unmet_needsOperatorRead the queue of Concierge requests that could not be routed.
list_workspace_swarmsConciergeEnumerate sibling swarms in the current workspace.
flag_unmet_needConciergeRecord a request the Concierge could not fulfil into unmet_needs.

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.

Four 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
File Watcher A file matching a glob pattern is created or modified in the swarm's files/ directory glob pattern (e.g. reports/*.csv), optional events list (created, modified)

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.

Swarm Files

Every swarm has a persistent files/ directory on disk. Agents and humans can both read and write to it. Files are indexed in the database with full provenance — who created each file (agent, human, or unknown), which run and which step produced it, and when it was last modified.

This shared filesystem acts as a durable handoff layer between runs: an agent can write a CSV to files/reports/ in one run, and a human can download it from the canvas while another run is processing the next batch.

How agents write files

Agents use the built-in write_swarm_file skill. The runtime injects a files_root path into every skill's context dict so skills know where to write without needing database access. Subdirectory nesting is unlimited — agents can organise files into any folder structure they need.

# write_swarm_file input
{
  "path": "reports/2024-06/summary.md",
  "content": "# June Summary\n...",
  "encoding": "utf-8"
}

After each skill call, the runtime reconciles the file index: new files are inserted with origin=agent; deleted files are removed from the index.

How humans manage files

The swarm canvas has a Files panel below the inspector. From there you can:

Human-uploaded files are indexed with origin=human. A color-coded dot on each row indicates origin: accent color for agent-written, grey for human-uploaded.

The Files browser

Beyond the per-swarm panel, the Files page (#files in the top navigation) is an org-wide browser over every swarm's file store — one place to find, preview and manage everything your swarms have produced. A tree on the left groups files by workspace → swarm → folder; selecting a node scopes the list on the right.

Cross-swarm links

A single file can appear in more than one swarm through a link — a logical reference, not a copy. The bytes live exactly once with the original (canonical) file; every other swarm holds a lightweight pointer to it. Use the row action in the Files browser to link a file into another swarm.

File watcher triggers

A File Watcher trigger watches the swarm's files/ directory using a glob pattern. When a matching file is created or modified — by an agent, by a human upload, or by an external process writing to the mounted volume — the trigger fires an invocation event into the swarm. This enables file-arrival workflows: drop a PDF into files/inbox/ and the swarm starts processing it automatically. Cross-swarm links are passive and do not fire the watcher — only a real file written to disk does.

Index reconciliation

On each swarm load, SwarmWright reconciles the disk contents of files/ against the database index. Files present on disk but absent from the index are inserted with origin=unknown. Index rows pointing to files that no longer exist on disk are removed. This means the index stays accurate even after manual filesystem operations (backup restores, external writes, direct volume mounts).

Cross-swarm Delegation

Any agent can invoke another swarm as a synchronous, blocking sub-call — like calling a function in a different department. The calling agent sends an input payload, the target swarm runs to completion, and the result is returned inline. Execution in the parent swarm pauses until the child finishes.

This is different from firing an event into another swarm. A swarm call is:

Setting up a swarm call

  1. Open the canvas of the calling swarm
  2. Click External swarm in the left palette — a modal opens listing all swarms across all workspaces
  3. Select the target swarm and click Add to canvas — a teal ⬡ node appears
  4. Select an agent node and click Connect to…, then click the ⬡ swarm node
  5. In the modal: enter an alias (a short slug the agent uses in its action) and a purpose
  6. Save — the edge is written to hierarchy.json as a swarm_calls entry

Alias naming: Use short, meaningful slugs — hr_lookup, credit_check, compliance_scan. The alias is what the agent writes in its invoke_swarm action. It cannot contain spaces.

How agents invoke a swarm

When an agent has declared swarm calls, the runtime adds them to the system prompt automatically. The agent invokes a target using the invoke_swarm action:

{
  "action": "invoke_swarm",
  "alias": "hr_lookup",
  "input": {
    "employee_id": "EMP-4821",
    "query": "Is this employee currently active?"
  }
}

The runtime resolves the alias to the target swarm's entry point, creates a child execution context sharing the parent run's ID and step sequence, runs the target swarm, and returns the final output back to the calling agent as the action result. The child swarm's steps appear in the same run trace in the Control Room.

Cross-workspace delegation

Swarm calls can target swarms in any workspace, not just the current one. This enables cross-departmental workflows — a Finance swarm can query an HR swarm, an Operations swarm can trigger a Compliance swarm, and so on. The target swarm does not need to be aware it is being called from another workspace.

On the canvas, cross-workspace swarm nodes are styled in amber (instead of teal) and carry an ↗ cross-workspace badge. This is a deliberate visual warning: cross-workspace calls introduce a dependency between departments. If the target swarm is disabled, renamed, or deleted, the calling swarm will fail at runtime.

Design guidance: Cross-workspace calls are powerful but should be used sparingly. Each one creates an inter-department coupling. If a call is only needed in one direction, prefer making the data available through shared knowledge or a shared skill rather than a live swarm call.

Removing swarm nodes

Click a ⬡ swarm node to open its inspector. The inspector shows the swarm name, workspace, and all agents connected to it. Buttons:

Click a swarm-call edge to inspect it: see the alias, connecting agent, and purpose. Click Remove to delete just that edge without removing the swarm node.

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.

Files panel

Below the inspector is the swarm's Files panel. It shows every file in the swarm's files/ directory, with a color dot indicating whether each was written by an agent or uploaded by a human. From the panel header you can upload files, create folders, download individual files, and delete files. Drag and drop onto the panel is also supported. See Swarm Files for the full architecture.

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. Use the scope sidebar on the left to switch between scopes. Both tabs have a live filter bar to search by name or description.

The Skills scope sidebar also includes a Built-in entry at the top. Built-in skills are platform-provided, read-only, and always available. They are displayed as informational cards — you can click through to read the source but cannot edit or delete them. Override any built-in by creating a skill with the same name at any other scope.

Knowledge documents

Knowledge documents are Markdown files injected into an agent's context at runtime. Attach them to agents by referencing the document name in the constitution's knowledge array. Company-scoped documents are available to all agents without explicit reference.

Clicking + New document or any existing card opens an inline full-pane editor with:

Document cards show a content preview snippet and a relative last-modified timestamp.

Skills

Skills are written directly in the browser using a split-pane code editor: the left pane is skill.py (Python, CodeMirror), the right pane is config.yaml. Both are saved atomically.

The Draft with AI button generates a working .py + .yaml pair from a plain-English description. The collapsible Available Python libraries panel shows what's installed and which packages need an allowed_packages entry in the YAML.

Once saved, a skill appears in the canvas palette and can be connected to Executioner agents. Skill cards show the description, timeout, package chips, and a last-modified timestamp.

Settings

The Settings tab is the central control panel for your SwarmWright instance. It is organized into six tabs. The Users tab is only visible to administrators.

LLM Providers

Configure credentials for Anthropic, OpenAI, and DeepSeek. Each provider has a Default provider radio button — this determines which provider is used when no model is specified in an agent's constitution. Credentials are encrypted with Fernet before being written to the database.

Use the Test connection button to verify a provider's key is valid before saving.

Models

Manage the list of model identifiers available to agents. The default list includes Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5, GPT-4o, GPT-4o mini, DeepSeek Chat, and DeepSeek Reasoner. You can add custom model identifiers (useful for self-hosted models or new releases) and set a default model that applies when no model is declared in an agent's constitution.

Branding

Customize your instance: set a company name (shown in the nav) and upload a logo. Logos are served from /api/v1/settings/branding/logo. The Reset button removes the custom logo and reverts to the default SwarmWright mark.

System

Configure runtime defaults:

Package installer

The Packages panel is a package manager for the skill runtime. Each package in the global allowlist is available to any skill that declares it in allowed_packages. Packages not yet installed show an Install button — clicking it runs pip install inside the container and registers the package. Installed packages are shown with a green indicator.

Packages are also auto-installed on container start from the allowlist, so new deployments pick up all registered packages without manual steps.

Security

View the settings-change audit log (who changed what, and when) and rotate the master encryption key. Key rotation re-encrypts all stored secrets with a new Fernet key. Session-based login is active once users are configured — see the Users tab.

Users & Permissions

SwarmWright has two account types: Admin and User. Admins have unrestricted access to everything. Regular users get a per-account set of 17 permission flags that gate both the UI (buttons are hidden) and the API (returns 403 when a restricted endpoint is called).

First boot

On first boot, navigating to http://localhost:5001 redirects to /setup. Fill in a username, optional display name, and password to create the first admin account. Once created, all subsequent visits redirect to /login until a valid session exists.

Managing users

Admins can create, edit, and delete accounts from Settings → Users. The Users tab is not visible to non-admin accounts. When creating or editing a user, the Administrator toggle grants full access and hides the permissions checklist. For regular users, each of the 15 flags can be toggled independently.

Default permissions for new users (unless overridden at creation):

Permission flags

FlagWhat it controls
can_create_workspaceCreate new workspaces
can_edit_workspaceRename / update workspace metadata
can_delete_workspaceDelete a workspace (must be empty)
can_create_swarmCreate new swarms inside a workspace
can_edit_swarmRename, enable/disable, transfer swarms
can_delete_swarmDelete a swarm and its contents
can_start_runFire events, invoke triggers, replay runs
can_stop_runStop a running swarm execution
can_edit_constitutionAdd / remove agents and edit their constitutions; manage callers and informers on the canvas
can_manage_triggersCreate, edit, and delete triggers
can_manage_skillsCreate, edit, and delete skills in the Library
can_manage_knowledgeCreate, edit, and delete knowledge documents
can_decide_inboxApprove or reject human-in-the-loop inbox items
can_view_settingsAccess the Settings tab (read-only for non-admins)
can_manage_usersReserved — admin-only user management is enforced server-side regardless of this flag
can_chat_workspaceAccess the Concierge chat inside any workspace the user can already see
can_chat_operatorAccess the Operator chat at the org level. Implies can_chat_workspace. This chat can design topology, create swarms, and trigger runs.

Session & security

Authentication uses server-side signed sessions (Flask secure cookies). Sessions are permanent and last 30 days of inactivity. Passwords are hashed with werkzeug.security (PBKDF2-HMAC-SHA256). There is no token-based or OAuth flow — this is intentional for the target use case of small, trusted teams on a private network.

Operator Chat

The Operator is a built-in swarm that ships with the platform at data/workspaces/platform/swarms/operator-chat/. It gives users with can_chat_operator a conversational surface for designing and operating the platform: creating workspaces, drafting swarms, patching topology, triggering runs, and reviewing the unmet-needs queue.

A round chat icon appears in the top-right of the workspace canvas on the Org screen. Clicking it opens a side panel (40 % screen width, resizable) with the Operator conversation. The panel header reads Operator alongside the currently active default model name and a hint: "This chat can edit your platform."

No special runtime path. The Operator executes as a normal swarm run. Every action it takes — creating a workspace, patching a topology, firing a trigger — goes through the same API surface a human would use through the GUI, producing the same run steps and audit trail.

What the Operator can do

The Operator's topology gives it access to a set of built-in skills with declared purposes (listed in full under Built-in skills → Chat). It cannot perform actions outside these skills — attempts are logged as topology violations.

Unmet-needs signals

When a new Operator session opens, if there are unaddressed unmet needs older than 24 hours, the Operator surfaces them as an opening message. A Signals tab next to the conversation transcript lists all open unmet-need rows, grouped by workspace, with a Draft a swarm for this button that pre-fills the Operator's input with the user's original request phrasing.

Model

The Operator constitution omits the model: frontmatter field. It inherits whatever is selected under Settings → Models → Default model. Changing the platform default immediately affects the next Operator message — no constitution edit needed.

Concierge Chat

Each workspace has a built-in Concierge swarm provisioned automatically at creation and on every boot after a Phase 8 upgrade. It lives at data/workspaces/<wid>/swarms/concierge/.

A round chat icon appears in the workspace topbar next to the existing tabs. Clicking it opens a side panel whose header reads Concierge — <workspace name>. Users with can_chat_workspace see the button; users without it do not.

The Concierge routes natural-language requests to the right swarm in the workspace. It never uses the words "swarm" or "agent" in default responses — it speaks in domain terms. Each response includes a collapsed "How did I get this?" link that expands the run-step trail for transparency.

What the Concierge can do

The Concierge has no topology edges to topology-mutation skills. It cannot create swarms, patch constitutions, or edit hierarchy files. That boundary is structural, not a permission check — the skills simply do not exist in its hierarchy.json.

Concierge topology auto-update

The Concierge's swarm_calls block is regenerated to reflect the current set of sibling swarms in the workspace every time the registry loads. Adding or removing a swarm from a workspace is immediately reflected in what the Concierge can route to.

Chat sessions

Both chat surfaces persist sessions across browser refreshes. One session exists per (user, scope) — org for the Operator or a specific workspace ID for the Concierge. Sessions are created lazily on first message.

A Wipe conversation button (with confirmation) deletes the visible chat_messages rows. It does not delete the underlying runs or run steps — the audit trail is unchanged. The chat layer forgets; the ledger remembers.

When the accumulated conversation context would exceed the configured token budget (default 32 000 tokens), the chat layer drops the oldest messages first and prepends a summary line so the agent retains situational context.

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
add_canvas_swarmswarm_idPlaces an external swarm ⬡ node on the canvas (unconnected)
remove_canvas_swarmswarm_idRemoves the swarm node and all its swarm_call edges from the topology
add_swarm_callagent, alias, swarm_id, purposeConnects an agent to an external swarm with a named alias; swarm must already be on the canvas
remove_swarm_callagent, aliasRemoves a single agent→swarm delegation edge by agent and alias

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/inboxList Caller requests. Params: status (pending / yes / no), swarm_id
GET/inbox/<id>Get one action with full context
POST/inbox/<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
GET/informs/<id>Get one notification with full context
POST/informs/<id>/readMark a notification as read
POST/informs/<id>/dismissDismiss a notification

Knowledge

MethodPathDescription
GET/knowledgeList knowledge documents. Params: scope, workspace_id, swarm_id
POST/knowledgeCreate knowledge document. Body: scope, name, title, content
GET/knowledge/<id>Get one document with full content
PUT/knowledge/<id>Update document. Body: title, content
DELETE/knowledge/<id>Delete document and its .md file
POST/knowledge/<id>/draftAI-generate document content. Body: prompt (optional description)

Skills

MethodPathDescription
GET/skillsList skills. Params: scope, workspace_id, swarm_id
POST/skillsCreate skill. Body: scope, name, py_content, yaml_content
GET/skills/<name>Get one skill with full .py and .yaml content. Params: scope
PUT/skills/<name>Update skill files. Body: py_content, yaml_content
DELETE/skills/<name>Delete skill — removes .py and .yaml files. Params: scope
POST/skills/_meta/draftAI-generate skill files. Body: name, prompt
GET/skills/_meta/runtimeReturns Python version, stdlib highlights, and installed third-party packages available to skills

Swarm Files

MethodPathDescription
GET/swarms/<id>/filesList files in the swarm's files/ directory. Param: prefix to filter by path prefix
POST/swarms/<id>/filesUpload a file. Multipart form with file field and optional path field. Add overwrite=true to replace an existing file. Max 50 MB.
GET/swarms/<id>/files/downloadDownload a file. Param: path (relative to files/). Returns raw bytes with correct MIME type.
DELETE/swarms/<id>/filesDelete a file. Param: path. Also removes the index row and prunes empty parent directories.

Auth

MethodPathDescription
POST/auth/setupCreate the first admin account (only succeeds when no users exist). Body: username, password, display_name (optional)
POST/auth/loginLog in. Body: username, password. Sets a signed session cookie on success.
POST/auth/logoutClear the current session.
GET/auth/meReturn the currently logged-in user including all 15 permission flags. Returns 401 if not authenticated.

Users (admin only)

MethodPathDescription
GET/usersList all users with their permission flags.
GET/users/<id>Get a single user.
POST/usersCreate a user. Body: username, password, display_name, is_admin, permissions (map of flag → bool).
PUT/users/<id>Update a user. Body: any subset of display_name, password, is_admin, is_active, permissions. Cannot remove own admin status or deactivate own account.
DELETE/users/<id>Delete a user. Cannot delete own account.

Chat

All chat endpoints live under /api/chat/ (not /api/v1/). Concierge endpoints require can_chat_workspace; Operator endpoints require can_chat_operator.

MethodPathDescription
POST/api/chat/sessionsGet or create a session. Body: scope ("org" or "workspace"), workspace_id (required when scope = "workspace"). Idempotent — returns the existing session if one already exists for this (user, scope, workspace_id).
GET/api/chat/sessionsList sessions. Params: scope, workspace_id.
GET/api/chat/sessions/<id>/messagesPaginated message history. Params: before_id, limit (default 50).
POST/api/chat/sessions/<id>/messagesSend a message. Body: content. Saves the user message, starts the underlying swarm run, and streams response events over SSE. Returns {message_id, run_id} synchronously.
DELETE/api/chat/sessions/<id>/messagesWipe message history for this session. Underlying runs are preserved. Returns {deleted: count}.
DELETE/api/chat/sessions/<id>Delete the session and cascade its messages. Underlying runs are preserved.

Unmet Needs

MethodPathDescription
GET/api/unmet-needsList unmet needs. Params: workspace_id, status (default "open"). Requires can_chat_operator.
PATCH/api/unmet-needs/<id>Update status. Body: status ("dismissed" or "addressed"), addressed_by_run_id (optional). Requires can_chat_operator.

Settings

MethodPathDescription
GET/settingsList all settings key-value pairs (secrets redacted)
GET/settings/<key>Get one setting by key
PUT/settings/<key>Set one setting. Body: value, is_secret, value_type
PUT/settingsBulk-set multiple settings in one transaction. Body: array of {key, value, is_secret, value_type}
POST/settings/llm/testTest LLM connectivity. Body: provider, model, api_key (optional — uses stored key if omitted)
GET/settings/auditSettings change audit log. Params: limit, offset
POST/settings/security/rotate-keyRotate the master encryption key — re-encrypts all stored secrets
GET/settings/branding/logoGet the current logo. Returns the image file (PNG or SVG) with correct MIME type
POST/settings/branding/logoUpload a custom logo. Multipart form with a file field (PNG or SVG, max 2 MB)
DELETE/settings/branding/logoRemove the custom logo and revert to the default mark
GET/settings/system/packages/checkCheck if a package is installed. Param: name (PyPI distribution name, e.g. beautifulsoup4)
POST/settings/system/packages/installInstall a package and add it to the global allowlist. Body: name. Runs pip install inside the container.

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)
canvas_swarmsarrayIDs of external swarm nodes present on the canvas (may be unconnected)
swarm_callsarrayDeclared cross-swarm delegations: agent, alias, swarm_id, purpose
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"],
  "canvas_swarms": ["hr-onboarding"],
  "swarm_calls": [
    {
      "agent": "finance-orchestrator",
      "alias": "hr_lookup",
      "swarm_id": "hr-onboarding",
      "purpose": "look up employee onboarding status for invoice vendor verification"
    }
  ],
  "consultations": []
}

Folder Structure

swarmwright/
├── app/
│   ├── models/           — SQLAlchemy ORM (12 tables, incl. chat_sessions, chat_messages, unmet_needs)
│   ├── api/              — Flask blueprints under /api/v1 (chat under /api/chat/)
│   │   ├── workspaces.py
│   │   ├── swarms.py
│   │   ├── agents.py
│   │   ├── topology.py
│   │   ├── events.py
│   │   ├── triggers.py
│   │   ├── runs.py
│   │   ├── callers.py     — Caller/Informer nodes + inbox + informs
│   │   ├── knowledge.py
│   │   ├── skills_api.py
│   │   ├── files.py       — Swarm file store endpoints
│   │   ├── stream.py      — SSE event stream
│   │   ├── chat.py        — Chat sessions, messages, unmet-needs endpoints
│   │   └── settings.py
│   ├── core/             — Business logic (no Flask imports)
│   │   ├── registry.py         — filesystem scanner, hierarchy cache
│   │   ├── runtime.py          — topology-enforced agent execution
│   │   ├── file_store.py       — swarm file index management
│   │   ├── executor.py         — skill subprocess runner
│   │   └── builtin_swarms.py   — reconciliation pass for built-in swarm templates
│   ├── builtin_skills/   — Platform-provided skills (read-only)
│   │   ├── write_swarm_file.py / .yaml
│   │   ├── read_swarm_file.py / .yaml
│   │   ├── list_swarm_files.py / .yaml
│   │   ├── delete_swarm_file.py / .yaml
│   │   ├── http_get.py / .yaml
│   │   ├── http_post.py / .yaml
│   │   ├── kv_get.py / .yaml
│   │   ├── kv_set.py / .yaml
│   │   ├── get_datetime.py / .yaml
│   │   ├── send_webhook.py / .yaml
│   │   ├── create_workspace.py / .yaml   — Operator chat
│   │   ├── create_swarm.py / .yaml       — Operator chat
│   │   ├── draft_constitution.py / .yaml — Operator chat
│   │   ├── patch_topology.py / .yaml     — Operator chat
│   │   ├── trigger_run.py / .yaml        — Operator chat
│   │   ├── list_runs.py / .yaml          — Operator chat
│   │   ├── read_run.py / .yaml           — Operator + Concierge
│   │   ├── list_unmet_needs.py / .yaml   — Operator chat
│   │   ├── list_workspace_swarms.py / .yaml — Concierge
│   │   └── flag_unmet_need.py / .yaml    — Concierge
│   ├── builtin_swarms/   — Platform-shipped swarm templates
│   │   ├── operator-chat/     — platform-scope (materialised into platform workspace)
│   │   │   ├── meta.yaml
│   │   │   ├── hierarchy.json
│   │   │   └── agents/operator.md
│   │   └── concierge/         — workspace-scope (one per workspace)
│   │       ├── meta.yaml
│   │       ├── hierarchy.json
│   │       └── agents/concierge.md
│   └── 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
│       │   └── components/
│       │       └── chat-panel.js — shared chat panel for Operator and Concierge
│       └── 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
                    ├── skills/
                    ├── knowledge/
                    ├── triggers/
                    └── files/           — swarm file store (unlimited nesting)

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
SkillPython script callable by an Executioner agent, run as a subprocess with a declared timeout
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
Built-in skillA platform-provided skill shipped with SwarmWright, visible in Library → Skills → Built-in; read-only but overridable at any scope
File WatcherA trigger that fires when a file matching a glob pattern appears or changes in the swarm's files/ directory
Swarm FilesThe per-swarm files/ directory — a shared, persistent filesystem accessible to both agents and humans, indexed in the database
TriggerDeterministic script that produces events (heartbeat, listener, invocation, or file watcher)
WorkspaceDepartment-like container for swarms; provides a resource scope
Alias (swarm call)A short slug declared on a swarm-call edge that the calling agent uses in its invoke_swarm action to address the target swarm
Swarm callA topology-declared, synchronous invocation of another swarm's entry point — the caller blocks until the target returns a result
Swarm nodeA ⬡ canvas element representing an external swarm that can be delegated to; teal for same-workspace targets, amber for cross-workspace
Cross-workspace delegationA swarm call whose target swarm lives in a different workspace; shown in amber with an ↗ badge as a deliberate inter-department coupling warning
OperatorA built-in platform-scope swarm that gives authorised users a conversational interface for designing and operating the platform — creating swarms, patching topology, triggering runs
ConciergeA built-in workspace-scope swarm, one per workspace, that routes natural-language user requests to the right swarm and records requests it cannot fulfil as unmet needs
Chat sessionA persisted conversation thread between a user and a chat swarm (Operator or Concierge). One session exists per (user, scope). Wiping a session deletes message rows but not the underlying runs.
Unmet needA request the Concierge could not route to any swarm, recorded in the unmet_needs table and surfaced to the Operator as an organisational feedback signal
Built-in swarmA swarm template shipped with the platform in app/builtin_swarms/, materialised automatically at boot. Operator overrides are preserved; a "Restore to default" action re-materialises from the bundle.