mozartsh
v0.1.0
Published
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 messagi
Readme
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
# 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:4141Architecture
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 --> containersHow It Works
Agent Lifecycle
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
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.
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 responseEvent 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.
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()"| BGStreamEach subscriber filters for the event kinds it cares about:
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
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"| AgentEach 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 |
| 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 open registry. They're fetched from GitHub and injected into the agent's system prompt.
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 promptSkills 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.
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 --> SchedulesAgent 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.
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: ResponseAgents can also create schedules dynamically using the schedule tool during conversation.
CLI Reference
# 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:
- Identifies the domain and purpose
- Searches skills.sh for relevant skills
- Picks well-adopted skills that fit the use case
- Chooses an appropriate model tier
- Outputs a complete
.soulfile - Can spawn the agent directly using the
spawn_agenttool
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.jsonRuntime 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.
mozart up agents/
# Registers lead, coder, reviewer, scout + built-in mozart agent
# Open http://localhost:4141 and talk to lead to kick things offRequirements
- Bun runtime
- Podman (installed automatically via
mozart setup) - An OpenRouter API key
