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
Release files for drasill 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| drasill-1.0.0.tar.gz | 301.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| drasill-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 525.0 kB
Release files / drasill-1.0.0.tar.gz
| Download URL | drasill-1.0.0.tar.gz |
|---|---|
| Size | 301.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
80eed5590a7587308a7c21f13a515e237765e80e7c7e0bfe8d755acbea0121e7
|
|
BLAKE2b-256 checksum How to use checksums |
38dfb8337e67027a50d069f0a8a2a4ae2c68ff4bcbbb2cb182f5d0602797a1d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 15, 2026.
Transparency logRelease files / drasill-1.0.0-py3-none-any.whl
| Download URL | drasill-1.0.0-py3-none-any.whl |
|---|---|
| Size | 223.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8aa9c4b61dbec14d436468cbe20bb5e5fbbf4a8d03fe5c73a8c20e8162049b20
|
|
BLAKE2b-256 checksum How to use checksums |
d9d6031a09b1476416853c198ef1bcea5486c396d65436f7616053fb5f031faf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 15, 2026.
Transparency log