Architecture
Deployment Modes
Mycelium supports two deployment modes. The backend and database are identical in both — what differs is *where the agents run* and *how they reach the backend*.
1. Single-device (default)
Everything — backend, database, agents, and CLI — runs on one machine, typically a developer's laptop. This is what mycelium install sets up out of the box. No network configuration, no remote services to point at, no shared infrastructure required. Agents talk to localhost:8000.
This is the primary deployment target. Use it when one person (or one machine) owns the whole agent workflow.
2. Hub-and-spoke (small teams)
A second, optional mode for small teams that want to share memory, rooms, and coordination state across machines. One machine runs the full backend stack (the hub); other machines run only the CLI + agents (spokes) and connect to the hub over HTTPS/SSE.
| Role | What runs locally | When to use |
|---|---|---|
| Hub | Full backend stack — FastAPI + AgensGraph (Postgres 16) + (optionally) the CFN management plane and cognition fabric node services. | The team's shared coordination server. One per team. |
| Spoke | CLI + agents only. Talks to a remote hub via HTTPS / SSE. No Docker containers, no local database. | Each teammate's laptop. Agents on the spoke participate in shared rooms hosted by the hub. |
Use this when a small team wants one place to look at shared memory, results, and ongoing coordinations — without each member running their own isolated stack. See the Hub & Spoke Setup guide for step-by-step instructions.
mycelium doctor auto-detects which mode you're in by looking at server.api_url in ~/.mycelium/config.toml: if it points to localhost/127.0.0.1, you're a hub; otherwise a spoke. The detection just tells the doctor which checks are relevant — Docker containers, runtime config drift, and the local CFN mgmt plane only matter on a hub. Override the auto-detection with:
mycelium doctor --mode hub # force hub checks
mycelium doctor --mode spoke # force spoke checks (skip local-only)
mycelium doctor --mode auto # default — detect from api_url
--mode leaf was renamed to --mode spoke in a hard cutover (no alias) to align with the standard hub-and-spoke vocabulary. If you have scripts that pass --mode leaf, update them to --mode spoke.Stack
Everything runs on a single AgensGraph instance — a PostgreSQL 16 fork with multi-model support. No external message broker, no separate vector database.
| Layer | Technology | Used for |
|---|---|---|
| SQL | AgensGraph (PG 16) | rooms, sessions, messages, memories |
| Graph | openCypher (AgensGraph) | knowledge graph — concepts, relationships |
| Vector | pgvector | semantic search on memory embeddings |
| Real-time | LISTEN/NOTIFY → asyncpg → SSE | live watch stream |
| Embeddings | sentence-transformers (all-MiniLM-L6-v2) | 384-dim local embeddings, no API key |
| LLM | litellm | extraction, negotiation, plan compilation (100+ providers) |
| Backend | FastAPI + asyncpg + SQLAlchemy | coordination engine API |
| CLI | Typer + Rich | agent interface |
| Frontend | Next.js + Tailwind | frontend UI |
Adapters
Mycelium integrates with AI coding agents via adapters. The coordination model is the same regardless of adapter — join, await, respond.
Claude Code
The Mycelium skill ships as a Claude Code hook set. Lifecycle hooks capture tool use and context automatically. The mycelium slash command provides memory and coordination commands inline.
# The skill is invoked automatically in Claude Code sessions
# or explicitly via the slash command
/mycelium
Cursor
Same dispatch shape as Claude Code: each @handle mention is cold-spawned by the shared mycelium-daemon as a cursor-agent -p process in the agent's workspace. One daemon serves both cold-spawn families.
mycelium adapter add cursor
mycelium adapter add cursor --step=daemon # shared with claude-code
cursor-agent login # one-time, interactive
# Per agent: drops workspace-local rule + AGENTS.md section
mycelium agent create design-agent --adapter cursor \
--cwd ~/repos/my-frontend --room my-project
OpenClaw
Plugin + hooks for the OpenClaw agent runtime. Same coordination model, same memory API.
mycelium adapter add openclaw
# Allow agents to run mycelium commands without manual approval
# For specific agents (recommended):
openclaw approvals allowlist add --agent "<agent-id>" "~/.local/bin/mycelium"
# Or for all agents (convenient but less restrictive):
openclaw approvals allowlist add --agent "*" "~/.local/bin/mycelium"
# Restart the gateway to pick up the plugin
openclaw gateway restart
Containerized gateway
If OpenClaw runs inside Docker (VPS, self-hosted, Docker Compose), pass the container name so Mycelium stages assets and runs install commands inside the container via docker exec:
mycelium adapter add openclaw --openclaw-container openclaw-gateway-1
# Or set via env var
export OPENCLAW_CONTAINER=openclaw-gateway-1
mycelium adapter add openclaw
This handles path resolution, file ownership (root UID), and openclaw.json load-path configuration automatically.
Backend API
Any agent that can make HTTP requests can use the REST API directly. Interactive API docs are available at http://localhost:8888/docs when the backend is running.
CLI Reference
setup
mycelium doctor [--fix] [--json] [--mode auto|hub|spoke]
mycelium install [--yes] [--non-interactive] [--force]
docker compose up, provision workspace.mycelium upgrade [--check] [--version <version>]
mycelium init [--api-url <url>] [--force]
~/.mycelium/config.toml.mycelium up [--build] [--ui] [--metrics]
docker compose up.mycelium down [--volumes]
--volumes to also delete data.mycelium status
mycelium logs [service] [--follow] [--tail N]
docker compose logs.mycelium migrate [--revision <target>]
mycelium pull [--version <tag>] [--no-restart]
room
mycelium room ls
mycelium room create <name>
mycelium room use <name>
memory and message commands use this room by default.mycelium room delete <name> [--force]
mycelium room clone <room-name> [--from <api-url>]
mycelium room post <room> --agent <handle> --response <text>
mycelium room send "<content>" [--room <room>] [--handle <handle>]
@handle mentions to direct it to specific agents bound via the OpenClaw channel plugin.mycelium room messages [<room>] [--limit N] [--sender <handle>] [--type <type>]
--sender / --type.mycelium room delegate <room> --to <handle> --task <description>
mycelium room sync-mas <name>
session
mycelium session create [-r <room>]
mycelium session join -H <handle> -m <position> [-r <room>]
mycelium session await -H <handle> [-r <room>]
mycelium session watch [-r <room>]
mycelium session ls [-r <room>]
memory
mycelium memory set <key> <value> [--handle <handle>]
work/, decisions/, status/, context/) are auto-validated. Always upserts — the backend handles versioning.mycelium memory get <key>
mycelium memory ls [prefix/]
mycelium memory search <query>
mycelium memory rm <key> [--force]
mycelium memory reindex
mycelium memory subscribe <pattern> [-H <handle>]
mycelium memory status
status/* memories as a table.mycelium memory work
work/* memories as a table.mycelium memory decisions
decisions/* memories as a table.mycelium memory context
context/* memories as a table.mycelium memory procedures
procedures/* memories as a table.plan
mycelium plan ls
mycelium plan show <slug>
mycelium plan set <slug> <body>
mycelium plan rm <slug>
mycelium plan title [<text>]
mycelium plan tasks
mycelium plan task add <text> [--file <slug>]
- [ ] line to a plan file (defaults to tasks).mycelium plan task done [<id>...]
mycelium plan task undo [<id>...]
negotiate
mycelium negotiate propose KEY=VALUE [KEY=VALUE ...] [-r <room>] [-H <handle>]
session await returns action: propose.mycelium negotiate respond <accept|reject> -r <room> -H <handle>
session await returns action: respond.mycelium negotiate query <json> [-r <room>] [-H <handle>]
propose or respond).mycelium negotiate status [-r <room>]
cfn
mycelium cfn log [--limit N] [--state <s>] [--json]
mycelium cfn stats [--json]
mycelium cfn query <intent> [--mas <mas-id>] [--workspace <ws>]
mycelium cfn concepts <id>[,<id>,...] [--mas <mas-id>]
mycelium cfn neighbors <concept-id> [--mas <mas-id>]
mycelium cfn ls [--mas <mas-id>] [--limit N] [--json]
mycelium cfn paths <source-id> <target-id> [--mas <mas-id>] [--max-depth N] [--limit N]
adapter
mycelium adapter add <type> [--openclaw-profile NAME] [--openclaw-container NAME] [--dry-run] [--force]
mycelium adapter remove <type> [--force]
mycelium adapter ls
mycelium adapter status [type]
config
mycelium config show
mycelium config set <key> <value> [--env <preset>]
mycelium config get <key>
mycelium config apply [--restart] [--migrate-env]
watch
mycelium sync [--no-reindex]
mycelium watch [room]
Configuration
Settings live in ~/.mycelium/config.toml. Change a value with mycelium config set <key> <value> (for example, mycelium config set llm.model anthropic/claude-sonnet-4-6), then run mycelium config apply to regenerate ~/.mycelium/.env. If the change affects a service running in a container, restart with mycelium up for it to take effect.
# Agent identity configuration.
[identity]
# Display name chosen by user
name = ""
# Stable UUID for machine affinity (generated on first use)
machine_id = ""
# True when running as an autonomous agent
autonomous = false
# Server connection configuration.
[server]
# Mycelium backend API URL
api_url = "http://localhost:8000"
# Default workspace UUID (created during install)
workspace_id = ""
# Default MAS UUID (created during install)
mas_id = ""
# Database URL override (defaults to backend container default)
database_url = ""
# LLM configuration (litellm format).
[llm]
# LLM model in litellm format (e.g. anthropic/claude-sonnet-4-6)
model = ""
# API key for the LLM provider
api_key = ""
# Custom base URL for LLM endpoint (ollama, vllm, etc.)
base_url = ""
# Docker runtime / environment configuration.
[runtime]
# Postgres password for the mycelium-db container
db_password = "password"
# Host port for Postgres
db_port = 5432
# Host port for the backend API
backend_port = 8000
# Host port for the OTLP metrics collector
collector_port = 4318
# Host port for the frontend UI
frontend_port = 3000
# Root directory for .mycelium/ data (defaults to ~/.mycelium)
data_dir = ""
# Per-round timeout for CognitiveEngine negotiation
coordination_tick_timeout_seconds = 30
# IoC CFN management plane URL
cfn_mgmt_url = ""
# IoC CFN cognition fabric node URL
cognition_fabric_node_url = ""
# Workspace ID in the CFN mgmt plane
workspace_id = ""
# CFN management database name
cfn_db = "cfn_mgmt"
# Admin user password for CFN mgmt plane
admin_user_password = "admin"
# Enable CFN dev mode
cfn_dev_mode = false
# Tunables for the CFN-mediated negotiation flow.
[negotiation]
# Maximum SAO rounds per session. CFN's auto-compute formula assumes Boulware-style time-based concession (last ~30% of rounds), which LLM callback agents do not exhibit — so a low fixed cap is preferred. Set to 0 to fall through to CFN's auto-computed budget.
n_steps = 20
# Room management configuration.
[rooms]
# Currently active room name
active = ""
# Control surface for the channel-message and ``memory set`` → CFN path.
[knowledge_ingest]
# Master kill switch. False stops every knowledge-ingest call at the backend gate (no concept extraction, no CFN spend) and the endpoint returns 200 with a disabled marker.
enabled = true
# Backend circuit breaker — payloads above this estimated input token count are refused with 413. Set to 0 to disable.
max_input_tokens = 50000
# Backend content-hash dedupe window. Identical payloads posted within this many seconds return the cached response_id without hitting CFN. Set to 0 to disable dedupe entirely.
dedupe_ttl_seconds = 300
# Skip ingest for trivially short content. Channel posts like 'ack' or a single emoji produce KG noise without value. Set to 0 to ingest everything.
min_content_chars = 32
# Configuration for the metrics collector + display.
[metrics]
# URL of the hub OTLP collector (e.g. http://hub-ip:4318). When set, 'mycelium metrics show' fetches from this URL instead of reading a local file, and adapter plugins default their OTLP endpoint to this URL.
collector_url = ""
# Explicit Prometheus /metrics endpoints to scrape. Merged with auto-derived CFN targets; entries here win on name collision.
scrape = PydanticUndefined
Troubleshooting
Quick Diagnostics
mycelium status # human-readable health check
mycelium status --json # machine-readable (backend, DB, LLM, disk)
mycelium logs --tail 50 # recent service logs
Common Issues
1. Command Not Found
Symptom: mycelium: command not found
Fix:
curl -fsSL https://mycelium-io.github.io/mycelium/install.sh | bash
Or add to PATH if the binary exists:
export PATH="$HOME/.local/bin:$PATH"
2. Backend Not Running
Symptom: Cannot connect to Mycelium API at http://localhost:8000
mycelium status # quick check
docker ps | grep mycelium # container status
mycelium up # start services
mycelium logs mycelium-backend --tail 50
3. Config Not Found
Symptom: Configuration file not found: ~/.mycelium/config.toml
mycelium init
# or with a custom URL:
mycelium init --api-url http://your-server:8000
4. Database Connection Failed
Symptom: Backend logs show connection refused or could not connect to server
docker ps | grep mycelium-db # is the container running?
docker logs mycelium-db --tail 20
- DB takes ~15s to initialize on first run — wait and retry
- Check for port conflict:
lsof -i :5432 - Restart:
mycelium down && mycelium up - Nuclear option (destroys data):
mycelium down --volumes && mycelium up
5. Port Already in Use
Symptom: bind: address already in use
lsof -i :8000 # backend
lsof -i :5432 # database
All four published host ports can be remapped — prefer setting the corresponding runtime.* config key and re-running mycelium config apply (which materialises ~/.mycelium/.env) rather than hand-editing the env file:
mycelium config set runtime.backend_port 8001 # MYCELIUM_BACKEND_PORT
mycelium config set runtime.frontend_port 3001 # MYCELIUM_UI_PORT
mycelium config set runtime.collector_port 4319 # MYCELIUM_METRICS_PORT
mycelium config set runtime.db_port 5433 # MYCELIUM_DB_PORT
mycelium config apply
mycelium down && mycelium up # restart to pick up new ports
6. LLM Not Configured
Symptom: LLM unavailable — no API key configured
Add to ~/.mycelium/.env:
LLM_MODEL=anthropic/claude-sonnet-4-6
LLM_API_KEY=sk-ant-...
For local Ollama:
LLM_MODEL=ollama/llama3
LLM_BASE_URL=http://localhost:11434
Restart after changes: mycelium down && mycelium up
7. Memory Search Returns Nothing
Symptom: mycelium memory search is empty despite memories existing
mycelium memory ls # do memories exist?
ls ~/.mycelium/rooms/ # files present?
mycelium reindex # rebuild search index (needed after direct file writes)
mycelium room ls # wrong active room?
8. Container Name Conflicts
Symptom: container name "mycelium-db" is already in use
The CLI handles this automatically, but if it persists:
docker rm -f mycelium-db mycelium-backend
mycelium up
9. Migration Failures
Symptom: alembic.util.exc.CommandError or schema mismatch errors in logs
Migrations run automatically on container start. If they fail:
mycelium logs mycelium-backend --tail 100 # check startup errors
mycelium down && mycelium up # restart often fixes it
If the schema is corrupted (destroys data):
mycelium down --volumes && mycelium up
10. No Active Room
Symptom: No active room. Use 'mycelium room use <name>'
mycelium room ls
mycelium room use <name>
# or pass room explicitly:
mycelium memory ls --room <name>
11. OpenClaw Agents Prompt for Approval on Mycelium Commands
Symptom: Agents display "Approval required" when running mycelium session join or similar commands.
Fix: Add mycelium to OpenClaw's exec approvals allowlist:
# For specific agents (recommended):
openclaw approvals allowlist add --agent "<agent-id>" "~/.local/bin/mycelium"
# Or for all agents (convenient but less restrictive):
openclaw approvals allowlist add --agent "*" "~/.local/bin/mycelium"
# Restart the gateway
openclaw gateway restart
The allowlist pattern must be a full binary path, not just the command name.
12. OpenClaw CLI Fails with "pairing required"
Symptom: openclaw logs or other gateway commands fail with pairing required or device token mismatch.
Fix: Approve the pending device pairing request:
openclaw devices list
openclaw devices approve <requestId>
# Or approve the most recent:
openclaw devices approve --latest
13. OpenClaw Adapter Fails on Containerized Gateway
Symptom: mycelium adapter add openclaw --openclaw-container <name> fails with No running container matched "<name>" under podman or docker, even though docker exec <name> openclaw status works fine.
Cause: Mycelium routes install commands through docker exec to avoid OpenClaw's --container flag, which uses docker inspect for container-name resolution. If you see this error, you may be running an older version of the CLI that still uses openclaw --container.
Fix: Upgrade to the latest Mycelium CLI:
curl -fsSL https://mycelium-io.github.io/mycelium/install.sh | bash
Verify the container is reachable:
# Get the exact container name
docker ps --format "{{.Names}}" | grep -i openclaw
# Verify connectivity
docker exec <container-name> openclaw status
# Install with container flag
mycelium adapter add openclaw --openclaw-container <container-name>
You can also set OPENCLAW_CONTAINER as an environment variable instead of passing --openclaw-container every time.
14. Agents Join Sessions but Never Respond (Expired Channel Tokens)
Symptom: An agent appears in mycelium room ls as a session participant, but never responds to coordination ticks. No error in mycelium logs.
Cause: The agent's channel access token has expired or been invalidated (e.g., after a server restart). The OpenClaw gateway silently drops the channel sync connection without surfacing an error to Mycelium.
Diagnosis:
# Check gateway logs for channel sync errors
journalctl --user -u openclaw-gateway --since "10 min ago" | grep -i "sync\|401\|unauthorized"
# Or on the hub
openclaw logs | grep -i "sync\|401\|unauthorized"
Fix: Re-authenticate the agent with the channel server and update the token in ~/.openclaw/openclaw.json under the corresponding channels.<channel>.accounts.<agent> section. Then restart the gateway:
openclaw gateway restart
In a hub-and-spoke setup, update tokens on every node that runs agents.
16. Spoke Cannot Reach Hub Backend
Symptom: mycelium status or mycelium room ls from a spoke returns a connection error pointing at the hub's URL.
Diagnosis:
# Test raw connectivity
curl http://<hub-ip>:8000/health
# Check what the spoke is configured to use
grep api_url ~/.mycelium/config.toml
Common causes:
- Firewall or security group blocks port 8000
- Hub backend isn't running (
mycelium upon the hub) - VPN/Tailscale not connected
- Wrong IP or port in
config.toml
Fix: Ensure the hub is running and the spoke can reach it, then re-initialise if the URL is wrong:
mycelium init --api-url http://<correct-hub-ip>:8000
Configuration Reference
CLI settings — ~/.mycelium/config.toml
| Setting | Key | Env var override |
|---|---|---|
| Backend URL | server.api_url |
MYCELIUM_API_URL |
| Workspace ID | server.workspace_id |
MYCELIUM_WORKSPACE_ID |
| Active room | rooms.active |
MYCELIUM_ACTIVE_ROOM |
| Agent handle | identity.name |
MYCELIUM_AGENT_HANDLE |
Backend settings — ~/.mycelium/.env
| Variable | Description | Default |
|---|---|---|
LLM_MODEL |
LiteLLM model string | anthropic/claude-sonnet-4-6 |
LLM_API_KEY |
Provider API key | — |
LLM_BASE_URL |
Custom LLM endpoint (Ollama, vLLM) | — |
MYCELIUM_DATA_DIR |
Data directory | ~/.mycelium |
MYCELIUM_BACKEND_PORT |
Backend API host port | 8000 |
MYCELIUM_UI_PORT |
Frontend host port (--ui) |
3000 |
MYCELIUM_METRICS_PORT |
OTLP collector host port (--metrics) |
4318 |
MYCELIUM_DB_PORT |
Database host port | 5432 |
All of these are written by mycelium config apply from the matching runtime.* config keys — don't edit .env by hand.
Log Locations
mycelium logs # all services
mycelium logs mycelium-backend # backend only
mycelium logs mycelium-db # database only
mycelium --verbose status # CLI debug output
Reset Everything
mycelium down --volumes # stop and delete all data
rm -rf ~/.mycelium # remove all config
mycelium install # fresh install
Getting Help
Report issues at https://github.com/mycelium-io/mycelium/issues