Agent mesh control plane — coordinate AI coding agents from a live dashboard
Project description
Drasill
An agent mesh control plane and dashboard server for coordinating local Claude Code sessions. Agents register, discover each other, and exchange messages through Drasill — using heimdall stdin injection for message delivery.
Why "Drasill"?
In Norse mythology, Yggdrasill is the world tree — an immense ash that connects the nine realms, carrying messages between gods, humans, and the dead through its branches and roots. The squirrel Ratatoskr runs up and down its trunk delivering messages (and insults) between realms.
Drasill is your local world tree. It connects Claude Code agents running in separate terminals, letting them discover each other, exchange tasks, and coordinate work — all through a single trunk. Each agent is a Ratatoskr, checking in at the tree for new messages whenever it finishes its current task.
The name drops the "Ygg-" prefix (meaning "terrible" or "dreadful") because there's nothing terrible about well-coordinated agents.
What It Does
Agent Mesh
The mesh turns Drasill into a control plane for local Claude Code sessions:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Claude Code │ │ Claude Code │ │ Claude Code │
│ (alpha) │ │ (beta) │ │ (gamma) │
└──────┬────────┘ └──────┬────────┘ └──────┬────────┘
│ │ │
│ register │ send msg │ inbox
│ send msg │ check inbox │ consume
│ check inbox │ │ send msg
│ │ │
└────────────────────┼────────────────────┘
│
┌───────┴────────┐
│ Drasill │
│ :8400 │
│ │
│ /mesh/* │
│ SQLite │
│ SSE + async │
└────────────────┘
- Register —
POST /mesh/agentsjoins the mesh with a name and optional session ID - Discover —
GET /mesh/agentslists all active agents - Message —
POST /mesh/agents/{name}/inboxsends a message (with optional heimdall wake) - Consume —
GET /mesh/agents/{name}/inboxreturns the oldest unread message - Pruning — Stale agents auto-transition: active → idle → dead → removed
All /mesh/* endpoints are localhost-only (middleware rejects non-loopback requests), so exposure via Cloudflare Tunnel or similar is safe.
Web UI
A live-updating control panel showing agent status, recent messages, and a send-message form. SSE events trigger HTMX partial reloads, Alpine.js handles client-side interactivity. Web terminal gives browser access to agent pty sessions.
Agent Integration
No plugins or hooks needed. Drasill injects mesh awareness into each agent's system prompt at launch and delivers messages via heimdall stdin injection when agents go idle. Agents communicate with peers using curl to the mesh API.
Prerequisites
| Tool | Required | Purpose |
|---|---|---|
| uv | Yes | Python package management and virtualenv |
| heimdall (hm) | Yes | Rust pty supervisor — agents run inside heimdall sessions |
| Claude Code | Yes | The AI coding agent that Drasill coordinates |
| git | Yes | Used for session naming (repo + branch) and general dev |
| curl | Recommended | Agents use curl to communicate with the mesh API |
| Tailscale | Optional | Access Drasill from other devices on your tailnet |
# Verify prerequisites
uv --version && hm --version && claude --version && git --version && curl --version
Quick Start
# Install dependencies
uv sync
# Start the server
uv run uvicorn drasill.app:create_app --factory --reload --port 8400
# Open the dashboard
open http://localhost:8400
Deploy with systemd
./deploy/install.sh
Join the mesh from Claude Code
Agents are mesh-aware at launch — no setup needed. Send a message from any terminal:
curl -s -X POST "http://localhost:8400/mesh/agents/alpha/inbox" \
-H 'Content-Type: application/json' \
-d '{"from_agent": "human", "body": "Run the tests and report results"}'
Configuration
config.toml at the project root (or set DRASILL_CONFIG env var):
[server]
host = "0.0.0.0"
port = 8400
[db]
path = "~/.local/share/drasill/dash.db"
# Per-dashboard overrides
[dashboards.agent_mesh]
enabled = true
schedule = "5s"
Development
uv sync # install deps
uv run pytest # run tests (95% coverage gate)
uv run ruff check py_src py_tests # lint
uv run ruff format py_src py_tests # format
uv run nox -s all_checks # lint + format + typecheck + test
150 tests, 96% coverage, zero lint warnings.
Stack
| Layer | Tech |
|---|---|
| Language | Python 3.13+ |
| Framework | FastAPI + uvicorn |
| Database | SQLite via aiosqlite |
| Realtime | SSE event bus + asyncio background tasks |
| Frontend | Jinja2 + HTMX + Alpine.js + Tailwind CDN |
| Validation | Pydantic v2 |
| Linting | Ruff (ALL rules) |
| Type checking | ty (strict) |
| Testing | pytest + pytest-asyncio, 95% coverage gate |
| Build | hatchling, managed by uv |
Project Structure
py_src/drasill/
├── app.py # FastAPI app factory + lifespan
├── config.py # TOML → Pydantic config loader
├── db.py # SQLite schema + init
├── claude.py # Async claude CLI wrapper
├── doctor.py # Dependency health checks
├── supervisor_client.py # Async heimdall socket client
├── mesh/
│ ├── router.py # /mesh/* API endpoints
│ ├── models.py # Pydantic request/response models
│ ├── events.py # SSE event bus (asyncio.Queue fanout)
│ ├── middleware.py # Localhost-only security middleware
│ ├── uploads.py # File attachment storage
│ └── pruning.py # Agent lifecycle management
├── terminal/
│ └── router.py # WebSocket heimdall proxy + session creation
├── templates/ # Jinja2 + HTMX templates
└── static/ # PWA assets + CSS
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drasill-1.0.0.tar.gz.
File metadata
- Download URL: drasill-1.0.0.tar.gz
- Upload date:
- Size: 301.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80eed5590a7587308a7c21f13a515e237765e80e7c7e0bfe8d755acbea0121e7
|
|
| MD5 |
86628f6045a4e9be01143874b0543f75
|
|
| BLAKE2b-256 |
38dfb8337e67027a50d069f0a8a2a4ae2c68ff4bcbbb2cb182f5d0602797a1d3
|
Provenance
The following attestation bundles were made for drasill-1.0.0.tar.gz:
Publisher:
release.yml on nazq/Drasill
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drasill-1.0.0.tar.gz -
Subject digest:
80eed5590a7587308a7c21f13a515e237765e80e7c7e0bfe8d755acbea0121e7 - Sigstore transparency entry: 1108325387
- Sigstore integration time:
-
Permalink:
nazq/Drasill@eb0e25b84f8fe56bc5a305918dda972288f148b8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nazq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb0e25b84f8fe56bc5a305918dda972288f148b8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file drasill-1.0.0-py3-none-any.whl.
File metadata
- Download URL: drasill-1.0.0-py3-none-any.whl
- Upload date:
- Size: 223.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8aa9c4b61dbec14d436468cbe20bb5e5fbbf4a8d03fe5c73a8c20e8162049b20
|
|
| MD5 |
120fcbcfc119cb383d590cf9d555f530
|
|
| BLAKE2b-256 |
d9d6031a09b1476416853c198ef1bcea5486c396d65436f7616053fb5f031faf
|
Provenance
The following attestation bundles were made for drasill-1.0.0-py3-none-any.whl:
Publisher:
release.yml on nazq/Drasill
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drasill-1.0.0-py3-none-any.whl -
Subject digest:
8aa9c4b61dbec14d436468cbe20bb5e5fbbf4a8d03fe5c73a8c20e8162049b20 - Sigstore transparency entry: 1108325391
- Sigstore integration time:
-
Permalink:
nazq/Drasill@eb0e25b84f8fe56bc5a305918dda972288f148b8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nazq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@eb0e25b84f8fe56bc5a305918dda972288f148b8 -
Trigger Event:
push
-
Statement type: