Metadata-Version: 2.4
Name: drasill
Version: 1.0.0
Summary: Agent mesh control plane — coordinate AI coding agents from a live dashboard
Project-URL: Homepage, https://github.com/nazq/Drasill
Project-URL: Repository, https://github.com/nazq/Drasill
Project-URL: Issues, https://github.com/nazq/Drasill/issues
Author-email: Naz Quadri <naz.quadri@gmail.com>
License: Apache-2.0
Keywords: agents,ai,claude,codex,dashboard,heimdall,mesh
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Requires-Python: >=3.13
Requires-Dist: aiosqlite>=0.20.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: jinja2>=3.1.0
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: python-multipart>=0.0.22
Requires-Dist: typer>=0.15.0
Requires-Dist: uvicorn[standard]>=0.34.0
Description-Content-Type: text/markdown

# Drasill

[![Python](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Coverage](https://codecov.io/gh/nazq/Drasill/graph/badge.svg)](https://codecov.io/gh/nazq/Drasill)
[![Ruff](https://img.shields.io/badge/linting-ruff-orange.svg)](https://docs.astral.sh/ruff/)
[![FastAPI](https://img.shields.io/badge/framework-FastAPI-009688.svg)](https://fastapi.tiangolo.com/)
[![HTMX](https://img.shields.io/badge/frontend-HTMX%20%2B%20Alpine.js-3366cc.svg)](https://htmx.org/)

An agent mesh control plane and dashboard server for coordinating local [Claude Code](https://docs.anthropic.com/en/docs/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/agents` joins the mesh with a name and optional session ID
- **Discover** — `GET /mesh/agents` lists all active agents
- **Message** — `POST /mesh/agents/{name}/inbox` sends a message (with optional heimdall wake)
- **Consume** — `GET /mesh/agents/{name}/inbox` returns 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](https://docs.astral.sh/uv/) | Yes | Python package management and virtualenv |
| [heimdall (hm)](https://github.com/nazq/heimdall) | Yes | Rust pty supervisor — agents run inside heimdall sessions |
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Yes | The AI coding agent that Drasill coordinates |
| [git](https://git-scm.com/) | Yes | Used for session naming (repo + branch) and general dev |
| [curl](https://curl.se/) | Recommended | Agents use curl to communicate with the mesh API |
| [Tailscale](https://tailscale.com/) | Optional | Access Drasill from other devices on your tailnet |

```bash
# Verify prerequisites
uv --version && hm --version && claude --version && git --version && curl --version
```

## Quick Start

```bash
# 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

```bash
./deploy/install.sh
```

### Join the mesh from Claude Code

Agents are mesh-aware at launch — no setup needed. Send a message from any terminal:

```bash
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):

```toml
[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

```bash
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
