# Mozart

An AI agent orchestrator that runs isolated, persistent agents defined in declarative `.soul` files. Each agent runs inside its own Podman container with resource limits, filesystem isolation, and a full toolkit for memory, scheduling, inter-agent messaging, skill discovery, and web browsing.

## Quick Start

```bash
# Install dependencies
bun install

# Install Podman, initialize the VM (macOS/Windows), and build the agent image
mozart setup

# Start agents from .soul files
export OPENROUTER_API_KEY=sk-or-...
mozart up agents/

# Open the chat UI
open http://localhost:4141
```

## Architecture

```mermaid
graph TB
    subgraph cli [CLI]
        MozartCLI["mozart up / ps / send / ..."]
    end

    subgraph daemon [Daemon Process]
        HTTP["HTTP Server :4141"]
        Supervisor
        Scheduler["Cron Scheduler"]
    end

    subgraph containers [Podman Containers]
        subgraph c1 ["Container: lead"]
            W1["Bun + Agent"]
            T1["16 Tools"]
            M1["SQLite Memory"]
        end
        subgraph c2 ["Container: coder"]
            W2["Bun + Agent"]
            T2["16 Tools"]
            M2["SQLite Memory"]
        end
        subgraph c3 ["Container: reviewer"]
            W3["Bun + Agent"]
            T3["16 Tools"]
            M3["SQLite Memory"]
        end
    end

    subgraph external [External Services]
        OpenRouter["OpenRouter API"]
        SkillsSH["skills.sh Registry"]
        GitHub["GitHub Raw Content"]
    end

    subgraph ui [Chat UI]
        SvelteApp["Svelte 5 SPA"]
    end

    MozartCLI -->|HTTP| HTTP
    SvelteApp -->|SSE + REST| HTTP
    HTTP --> Supervisor
    Supervisor -->|"stdin/stdout JSON"| c1
    Supervisor -->|"stdin/stdout JSON"| c2
    Supervisor -->|"stdin/stdout JSON"| c3
    Scheduler -->|"triggers via stdin"| c1
    Scheduler -->|"triggers via stdin"| c2
    W1 --> OpenRouter
    W2 --> OpenRouter
    W3 --> OpenRouter
    W1 --> SkillsSH
    W1 --> GitHub
    Supervisor --> containers
```

## How It Works

### Agent Lifecycle

```mermaid
sequenceDiagram
    participant User
    participant CLI as mozart CLI
    participant Daemon as Daemon / Supervisor
    participant Podman
    participant Container as Agent Container

    User->>CLI: mozart up agents/
    CLI->>Daemon: POST /api/agents {soulfilePath}
    Daemon->>Daemon: Parse .soul file
    Daemon->>Podman: podman run --rm -i mozart-agent:latest
    Podman->>Container: Start worker.ts
    Container->>Container: Initialize Agent + tools
    Container-->>Daemon: {"type":"ready"}
    Daemon-->>CLI: 201 {id: "lead", state: "running"}
    CLI-->>User: lead (openai/gpt-4.1-mini)
```

### Chat Message Flow

```mermaid
sequenceDiagram
    participant User
    participant UI as Chat UI
    participant Server as HTTP Server
    participant Sup as Supervisor
    participant Container as Agent Container
    participant LLM as OpenRouter

    User->>UI: Types message
    UI->>Server: POST /api/agents/lead/messages
    Server->>Sup: streamChat("lead", "user", message)
    Sup->>Container: stdin: {"type":"chat", message}
    Container->>LLM: Stream completion request
    LLM-->>Container: Delta chunks
    Container-->>Sup: stdout: {"type":"event", event}
    Sup-->>Server: yield StreamEvent
    Server-->>UI: SSE: data: {"type":"text", "text":"..."}
    UI-->>User: Renders streaming response
    Container-->>Sup: stdout: {"type":"chat_done"}
```

### Inter-Agent Routing

Agents communicate using the `send` tool. The Supervisor mediates all messages between containers.

```mermaid
sequenceDiagram
    participant Lead as lead container
    participant Sup as Supervisor
    participant Coder as coder container

    Note over Lead: User asks for a new feature
    Note over Lead: IF rule triggers: route to coder
    Lead->>Sup: stdout: {"type":"route_request", toId:"coder", message:"Implement..."}
    Sup->>Coder: stdin: {"type":"route", fromId:"lead", message:"Implement..."}
    Coder->>Coder: Writes code via LLM
    Coder-->>Sup: stdout: {"type":"route_done", response:"Implementation..."}
    Sup-->>Lead: stdin: {"type":"route_response", response:"Implementation..."}
    Note over Lead: send tool returns the response
```

### Event Delivery (Mailbox Architecture)

All events from a container's stdout flow through a single `EventBus` per agent. Consumers subscribe independently — each gets its own async-iterable mailbox. No callbacks, no polling, no single-slot stomping.

```mermaid
graph LR
    subgraph container [Agent Container]
        Worker["Worker stdout"]
    end

    subgraph supervisor [Supervisor]
        Parse["JSON line parser"]
        Bus["EventBus per agent"]
    end

    subgraph consumers [Subscribers]
        Chat1["streamChat #1"]
        Chat2["streamChat #2"]
        BGStream["Background SSE"]
    end

    Worker -->|"stdout JSON lines"| Parse
    Parse -->|"emit()"| Bus
    Bus -->|"subscribe()"| Chat1
    Bus -->|"subscribe()"| Chat2
    Bus -->|"subscribe()"| BGStream
```

Each subscriber filters for the event kinds it cares about:

```mermaid
flowchart TB
    Emit["EventBus.emit(event)"]

    Emit --> Sub1["Chat subscriber"]
    Emit --> Sub2["Background SSE subscriber"]

    Sub1 -->|"kind: stream"| Yield1["yield StreamEvent"]
    Sub1 -->|"kind: chat_done"| Done1["return"]
    Sub1 -->|"kind: bg*"| Skip1["ignore"]

    Sub2 -->|"kind: bg"| Yield2["enqueue SSE data"]
    Sub2 -->|"kind: bg_done"| Done2["enqueue SSE done"]
    Sub2 -->|"kind: stream"| Skip2["ignore"]
```

The primitives are two small classes (~100 LOC total):

| Primitive | Role | Pattern |
|---|---|---|
| `Mailbox<T>` | Async FIFO queue | Multi-producer, single-consumer. `send()` enqueues, `for await` dequeues |
| `EventBus<T>` | Fan-out | Multi-producer, multi-consumer. Each `subscribe()` returns an independent `Mailbox<T>` |

This replaces three ad-hoc patterns (callback slot, promise map, listener set) with one unified mechanism. Multiple concurrent chat streams, background SSE connections, and agent-to-agent routes all coexist on the same bus without interference.

### Container Isolation

```mermaid
graph LR
    subgraph host [Host Machine]
        Sup["Supervisor"]
        AgentDir["~/.mozart/agents/lead/"]
        Sanctum1["/Users/sean/projects/app"]
    end

    subgraph container ["Podman Container (per agent)"]
        Agent["Agent Process"]
        DataDir["/data/agents/lead/"]
        S1["/sanctum/app"]
    end

    AgentDir -.->|"--volume :rw"| DataDir
    Sanctum1 -.->|"--volume :rw or :ro"| S1
    Sup <-->|"stdin/stdout JSON"| Agent
```

Each container runs with:

| Flag | Purpose |
|---|---|
| `--memory 256m` | Hard RAM cap |
| `--cpus 0.5` | CPU limit |
| `--read-only` | Immutable root filesystem |
| `--tmpfs /tmp:rw,noexec,size=64m` | Writable temp without exec |
| `--network slirp4netns` | User-mode networking for API calls |
| `--volume` | Only explicitly declared directories |

## The `.soul` File Format

A `.soul` file is a declarative agent definition. The filename (minus `.soul`) becomes the agent ID.

```
MODEL openai/gpt-4.1-mini
SANCTUM ~/projects/my-app
SANCTUM ~/data/shared:ro
SKILL vercel-labs/agent-skills@code-review

SOUL <<BLOCK
You are a code review assistant for my-app.
You analyze pull requests and provide actionable feedback.
BLOCK

IF "the change affects the database schema" THEN "flag it as high-risk and notify the backend team via send"
SCHEDULE "weekdays at 9am" "Check for open PRs that need review"
```

### Instructions

| Instruction | Required | Description |
|---|---|---|
| `MODEL <provider/model>` | Yes | OpenRouter model ID (350+ models supported) |
| `SOUL <text>` | Yes | Agent persona / system prompt. Use `<<DELIM ... DELIM` for multi-line |
| `SANCTUM <path>[:ro\|:rw]` | No | Mount a host directory into the container. Defaults to read-write. Supports `~/` and relative paths. Every agent also gets a default sanctum at `~/.mozart/agents/<id>/sanctum/` mounted at `/sanctum` |
| `SKILL <owner/repo@name>` | No | Install a skill from [skills.sh](https://skills.sh) |
| `IF "<condition>" THEN "<action>"` | No | Conditional behavioral rule |
| `SCHEDULE "<timing>" "<task>"` | No | Recurring task with natural-language timing |

Lines starting with `#` are comments. Blank lines are ignored.

## Agent Tools

Every agent has access to 16 built-in tools:

| Tool | Description |
|---|---|
| `search_memory` | Full-text search over long-term memory (SQLite FTS5) |
| `save_to_memory` | Persist facts, preferences, or decisions |
| `send` | Send a message to another agent and receive the response |
| `schedule` | Create a recurring task with natural-language timing |
| `list_schedules` | List active cron schedules |
| `delete_schedule` | Remove a schedule |
| `list_agents` | See all running agents |
| `stop_agent` | Stop another agent |
| `start_agent` | Restart a stopped agent |
| `remove_agent` | Permanently remove another agent |
| `spawn_agent` | Create a new agent from `.soul` content at runtime |
| `search_skills` | Search the skills.sh registry |
| `install_skill` | Install a skill and add it to the system prompt |
| `list_skills` | List installed skills |
| `read_skill` | Read the full content of an active skill |
| `web_fetch` | Fetch a URL and return clean markdown content |

## Skills

Skills are reusable instruction sets from the [skills.sh](https://skills.sh) open registry. They're fetched from GitHub and injected into the agent's system prompt.

```mermaid
sequenceDiagram
    participant Agent
    participant SkillsMgr as SkillsManager
    participant Cache as ~/.mozart/agents/id/skills/
    participant SkillsSH as skills.sh API
    participant GH as GitHub Raw

    Agent->>SkillsMgr: search_skills("code review")
    SkillsMgr->>SkillsSH: GET /api/search?q=code+review
    SkillsSH-->>SkillsMgr: [{source, skillId, installs}]
    SkillsMgr->>GH: Fetch SKILL.md descriptions
    GH-->>SkillsMgr: SKILL.md content
    SkillsMgr-->>Agent: Ranked results with descriptions

    Agent->>SkillsMgr: install_skill("vercel-labs/agent-skills@code-review")
    SkillsMgr->>Cache: Check local cache
    alt Not cached
        SkillsMgr->>GH: Fetch SKILL.md
        GH-->>SkillsMgr: Content
        SkillsMgr->>Cache: Save to disk
    end
    SkillsMgr-->>Agent: Skill injected into system prompt
```

Skills are managed per-agent via the `SKILL` instruction in `.soul` files, or dynamically using the `install_skill` / `search_skills` tools at runtime.

## Memory and Persistence

Each agent has its own SQLite database at `~/.mozart/agents/<id>/memory.db` with WAL mode for concurrent access.

```mermaid
graph TB
    subgraph db ["SQLite Database (per agent)"]
        Messages["messages — full conversation history"]
        Memories["memories — long-term facts via FTS5"]
        Schedules["schedules — persistent cron jobs"]
    end

    subgraph tools [Agent Tools]
        SearchMem["search_memory → FTS5 MATCH query"]
        SaveMem["save_to_memory → INSERT into memories"]
        Sched["schedule → INSERT into schedules"]
    end

    subgraph host [Host APIs]
        HistoryAPI["GET /api/agents/:id/history"]
        SchedAPI["GET /api/agents/:id/schedules"]
    end

    SearchMem --> Memories
    SaveMem --> Memories
    Sched --> Schedules
    HistoryAPI --> Messages
    SchedAPI --> Schedules
```

Agent registrations are discovered from the filesystem — any directory under `~/.mozart/agents/` containing an `agent.soul` file is restored on daemon restart.

## Scheduling

Two-layer scheduling system combining static declarations with runtime flexibility.

```mermaid
sequenceDiagram
    participant Soul as .soul file
    participant Sup as Supervisor
    participant LLM as OpenRouter
    participant Cron as CronScheduler
    participant Container as Agent Container

    Note over Soul: SCHEDULE "weekdays at 9am" "Send briefing"
    Soul->>Sup: Parse SCHEDULE instruction
    Sup->>LLM: "Convert 'weekdays at 9am' to cron"
    LLM-->>Sup: "0 9 * * 1-5"
    Sup->>Cron: Register cron job

    Note over Cron: 9:00 AM Monday
    Cron->>Sup: Job fires
    Sup->>Container: stdin: {"type":"chat", fromId:"scheduler", message:"Send briefing"}
    Container->>LLM: Process the task
    LLM-->>Container: Response
```

Agents can also create schedules dynamically using the `schedule` tool during conversation.

## CLI Reference

```bash
# Setup
mozart setup              # Install Podman + build agent image
mozart setup --rebuild    # Force rebuild the container image

# Agent management
mozart up [path]          # Start agents from .soul files (default: current dir)
mozart up --foreground    # Run the daemon in foreground mode
mozart stop <id>          # Stop an agent (keeps registered)
mozart start <id>         # Restart a stopped agent
mozart rm <id>            # Permanently remove an agent
mozart rm --all           # Remove everything and kill the daemon
mozart ps                 # List agents with state, model, uptime
mozart logs [id]          # Tail agent logs (daemon logs if no id)
```

## HTTP API

The daemon runs on `http://localhost:4141`.

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/health` | Health check |
| `GET` | `/api/agents` | List all agents |
| `POST` | `/api/agents` | Register a new agent |
| `DELETE` | `/api/agents` | Remove all agents |
| `DELETE` | `/api/agents/:id` | Remove an agent |
| `POST` | `/api/agents/:id/stop` | Stop an agent |
| `POST` | `/api/agents/:id/start` | Restart a stopped agent |
| `POST` | `/api/agents/:id/messages` | Send a message (SSE stream response) |
| `POST` | `/api/agents/:id/talk` | Agent-to-agent message (JSON response) |
| `POST` | `/api/agents/:id/intro` | Trigger agent self-introduction |
| `GET` | `/api/agents/:id/history` | Get conversation history |
| `GET` | `/api/agents/:id/schedules` | Get active schedules |
| `DELETE` | `/api/agents/:id/schedules/:sid` | Delete a schedule |

## The Built-in Mozart Agent

Mozart ships with a meta-agent called `mozart` that creates other agents. When you ask it to build an agent, it:

1. Identifies the domain and purpose
2. Searches skills.sh for relevant skills
3. Picks well-adopted skills that fit the use case
4. Chooses an appropriate model tier
5. Outputs a complete `.soul` file
6. Can spawn the agent directly using the `spawn_agent` tool

## Project Structure

```
mozart/
├── src/
│   ├── index.ts          # CLI entry point (Commander)
│   ├── server.ts         # HTTP daemon on :4141, serves UI + API
│   ├── supervisor.ts     # Agent lifecycle: spawn, stop, restart, route
│   ├── agent/
│   │   ├── agent.ts      # Agent class: LLM loop, tools, skills
│   │   ├── tools.ts      # 16 built-in agent tools
│   │   ├── skills.ts     # skills.sh registry client
│   │   ├── memory.ts     # SQLite persistence (messages, memories, schedules)
│   │   ├── parser.ts     # .soul file parser + Zod schema
│   │   └── paths.ts      # ~/.mozart/ directory path helpers
│   ├── worker.ts         # Runs inside Podman — stdin/stdout JSON protocol
│   ├── mailbox.ts        # Mailbox<T> + EventBus<T> async channel primitives
│   ├── podman.ts         # Container spawning, volume flags, image management
│   └── setup.ts          # mozart setup: install Podman, build image
├── agents/               # Default .soul files (mozart, lead, coder, reviewer, scout)
├── planck-claw/          # Embedded LLM provider library (OpenRouter)
├── ui/                   # Svelte 5 + Tailwind 4 + shadcn-svelte chat UI
├── Containerfile         # Agent container image definition
└── package.json
```

### Runtime Data

All state lives under `~/.mozart/`:

```
~/.mozart/
├── mozart.pid            # Daemon PID file
├── daemon.log            # Daemon-level log
└── agents/
    ├── mozart/
    │   ├── agent.soul    # Agent definition (copied on registration)
    │   ├── memory.db     # SQLite (messages, memories, schedules)
    │   ├── agent.log     # Agent-level log
    │   ├── skills/       # Cached skills from GitHub
    │   └── sanctum/      # Default read-write sanctum
    ├── lead/
    │   ├── agent.soul
    │   ├── memory.db
    │   ├── agent.log
    │   ├── skills/
    │   └── sanctum/
    └── ...
```

## Example: Dev Team

Mozart ships with a four-agent dev team that showcases inter-agent routing, scheduling, multi-model orchestration, skills, memory, and agent spawning.

| Agent | Model | Role |
|---|---|---|
| `lead` | `openai/gpt-4.1-mini` | Triages requests and routes to specialists |
| `coder` | `anthropic/claude-sonnet-4` | Writes and iterates on code |
| `reviewer` | `google/gemini-2.5-pro` | Reviews code and triages bugs |
| `scout` | `google/gemini-2.5-flash` | Researches topics and monitors dependencies |

The agents form a collaborative loop: **lead** routes work to **coder** or **reviewer**, **coder** sends code to **reviewer** for feedback, and any agent can ask **scout** for research. Schedules keep things running autonomously — lead sends daily standups, scout monitors dependencies every 6 hours.

```bash
mozart up agents/
# Registers lead, coder, reviewer, scout + built-in mozart agent
# Open http://localhost:4141 and talk to lead to kick things off
```

## Requirements

- [Bun](https://bun.sh) runtime
- [Podman](https://podman.io) (installed automatically via `mozart setup`)
- An [OpenRouter](https://openrouter.ai) API key

## License

[MIT](LICENSE)
