A terminal-native AI orchestration system. Route messages to multiple specialized AI agents simultaneously, receive their responses in real time, and manage the entire interaction from a single keyboard-driven interface — without leaving the terminal.
███╗ ███╗ █████╗ ███╗ ██╗ █████╗
████╗ ████║ ██╔══██╗ ████╗ ██║ ██╔══██╗
██╔████╔██║ ███████║ ██╔██╗ ██║ ███████║
██║╚██╔╝██║ ██╔══██║ ██║╚██╗██║ ██╔══██║
██║ ╚═╝ ██║██╗██║ ██║██╗██║ ╚████║██╗██║ ██║
╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═╝
M.A.N.A is not a chatbot wrapper or a round-robin dispatcher. It is a structured orchestration layer that treats agents as peers with distinct identities and capabilities, routes messages to them with precision, and streams all of their responses concurrently into a purpose-built terminal UI. Watch the demo
Two codebases with a clean contract between them:
- Python proxy server (
mana-server/) — owns routing intelligence, fan-out, mention parsing, and upstream agent communication - Go TUI client (
mana-tui/) — owns the terminal experience: streaming display, input handling, session management, status bar
┌─────────────────────────────────────────┐
│ Go TUI Client │
│ Bubble Tea · Lip Gloss · Glamour │
└───────────────┬─────────────────────────┘
│ WebSocket ws://localhost:8080/ws/chat
┌───────────────▼─────────────────────────┐
│ Python Proxy Server │
│ FastAPI · asyncio · @mention parser │
│ Fan-out queue · Heartbeat monitor │
└──────┬──────────────┬───────────────────┘
│ │ │
ws://…:8000 ws://…:8004 ws://…:8003
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌────▼────┐
│ Agent1 │ │ Agent2 │ │ Agent3 │
└─────────┘ └──────────┘ └─────────┘
The agent registry is fully data-driven. All agents are declared in config.yaml. Adding a new agent requires no code changes — the proxy, TUI autocomplete, @mention parser, and /talk vocabulary all update automatically at startup.
- Python 3.11+ with
pip - Go 1.21+
- One or more agent backends exposing a WebSocket endpoint (see Agent Protocol)
cd mana-server
pip install -r requirements.txt
python server.pyThe proxy listens on ws://0.0.0.0:8080/ws/chat by default.
cd mana-tui
go mod tidy
go run .Connect to a different port with go run . --port 9090.
Edit mana-server/config.yaml — no code changes required anywhere else:
agents:
myagent:
display_name: MyAgent
ws_url: ws://localhost:8001/ws/chat
start_cmd: ".venv/bin/python server.py" # optional — enables /wake
work_dir: "~/Projects/MyAgent" # optional — working directoryWhen a message targets exactly one agent, the proxy connects directly to that backend and pipes the stream to the client with no labeling overhead.
/talk airi
What are the key findings in this document?
When a message targets multiple agents, the proxy spins up a concurrent AgentProxy task for each one. All tasks share a single asyncio.Queue. Chunks from every agent stream into the queue as they arrive and drain to the TUI in arrival order — fully interleaved, never blocked by the slowest agent.
/talk airi zephyr
Analyze this network configuration.
Inline @mentions split a single message into per-agent payloads before dispatch. Any text before the first mention becomes shared context, prepended to every agent's payload. Agent-specific text is delivered only to the intended recipient.
@Airi summarize this document and @Zephyr run a port scan on 192.168.1.1
@all broadcasts a chunk to every registered agent.
Mentions coexist with /talk state — a /talk airi session can still fire a one-off @zephyr query without changing the default routing target.
| Command | Description |
|---|---|
/talk <agent> |
Route all subsequent messages to one agent |
/talk <a1> <a2> |
Fan-out to multiple agents simultaneously |
/talk all |
Broadcast to every registered agent |
/online |
Show which agents are currently reachable |
/online <agent> |
Check a specific agent's status |
/wake <agent> |
Spawn an agent's server process and wait for it to come up |
/wake <a1> <a2> |
Wake multiple agents in parallel |
/wake all |
Wake every registered agent in parallel |
/attach <path> |
Stage a .txt or .pdf file to send with the next message |
/detach |
Clear all staged file attachments |
/save-session [name] |
Serialize current conversation to ~/.mana/sessions/ |
/resume-session <name> |
Restore a saved conversation |
/resume-session |
List all saved sessions |
/help |
Show the command reference overlay |
| Key | Action |
|---|---|
ctrl+f |
Open the interactive file picker |
ctrl+y |
Copy the last agent response to clipboard |
ctrl+d |
Clear all staged file attachments |
↑ / ↓ |
Navigate input history or autocomplete suggestions |
Tab |
Accept the highlighted autocomplete suggestion |
Space |
Start / stop voice recording (when input is empty) |
ctrl+c |
Quit |
Each streaming agent response renders in its own labeled, rounded-border box. Boxes appear as streams arrive and grow in real time. When a stream completes, the box is committed to the message history. Multiple agent boxes render in the order their streams began — multi-agent conversations are structured and readable, not interleaved noise.
A persistent bar at the bottom of the screen displays: connection status, active agent(s), voice state, message count, session ID, token count, generation time, and tokens per second. Transient notifications (file attached, session saved, agent switched) temporarily replace the bar for two seconds.
The input field provides context-aware autocomplete for slash commands (triggered by /) and agent mentions (triggered by @). The suggestion list is rebuilt on every keystroke and always reflects the live registry state.
ctrl+f opens a full directory browser overlay. Navigate with arrow keys, open directories with Enter, attach .txt or .pdf files directly. Staged files appear as badges above the input field.
Supported types: .txt, .md, .log, .csv (plain text), .pdf (base64-encoded binary).
Space initiates recording via arecord. A second Space stops recording, encodes the audio as base64, and sends it to the active agent(s). The agent returns a transcript chunk which the TUI renders as a user message before the response stream begins.
The proxy runs a background heartbeat that probes every registered agent's WebSocket URL every 10 seconds and updates AGENT_STATUS accordingly.
/wake acts on this state. The boot sequence per agent:
- Already online → reports status immediately, no spawn
- No
start_cmd→ emits a configuration error and stops - Process already running → reports "still starting up"
- Otherwise → spawns via
asyncio.create_subprocess_shell - Polls the WebSocket URL every 500ms for up to 15 seconds
- Reports live progress every 3 seconds while waiting
- On success → updates
AGENT_STATUS, reports boot time - On timeout → emits a timeout error; the process may still be starting
┌─ Mana ──────────────────────────────────────────────┐
│ Waking 3 agents in parallel... │
│ │
│ • Airi is already online 🟢 │
│ • Waking Zephyr... │
│ Still waiting... (3s) │
│ • Zephyr is online 🟢 (boot time: 4.2s) │
│ • ITAA is online 🟢 (boot time: 2.1s) │
└─────────────────────────────────────────────────────┘
Any backend that can speak WebSocket integrates with M.A.N.A. The required contract:
Incoming payload (JSON):
{
"message": "user message text",
"session_id": "terminal-abc123",
"files": [
{ "name": "doc.pdf", "content": "<base64>", "mime_type": "application/pdf" }
]
}Outgoing message types:
| Type | Fields | Description |
|---|---|---|
start |
— | Optional. Signals stream begin. |
chunk |
content: string |
One chunk of the streaming response. |
end |
token_count: int, generation_time: float |
Signals stream completion. |
error |
message: string |
An error occurred; stream will not continue. |
Any agent framework that can emit these message types over WebSocket — agno, LangChain, LlamaIndex, a raw FastAPI server, a custom model endpoint — integrates with M.A.N.A out of the box.
Conversations are serialized to ~/.mana/sessions/<name>.json and can be fully restored across terminal sessions. Session names are slugified on save. A failed /resume-session lookup performs fuzzy matching and suggests the closest existing name.
Agent failures are isolated. If a connection is refused, the proxy emits an Agent offline error chunk into the shared queue. If a connection drops mid-stream, the error is caught, surfaced inline, and the queue sentinel is pushed — the fan-out drain loop continues normally for all other agents. A dead agent never hangs the interface.
| Layer | Technology |
|---|---|
| Proxy / Orchestration | FastAPI, Python, asyncio |
| Agent Communication | WebSocket (websockets) |
| Routing & Fan-out | Custom async queue, @mention parser |
| TUI Frontend | Go, Bubble Tea, Lip Gloss, Glamour |
| Input / Viewport | Charmbracelet Bubbles |
| Agent Registry | config.yaml (data-driven) |
| Agent Lifecycle | asyncio.create_subprocess_shell, WebSocket polling |
| Session Persistence | JSON at ~/.mana/sessions/ |
| File Attachments | Base64-encoded, multi-format |
| Voice Input | arecord, base64 audio |
| Clipboard | atotto/clipboard |
Agents are peers, not tools. Every registered agent has a name, an identity, and a dedicated response surface in the UI. Multi-agent interaction is a conversation with a team, not a query dispatched to an abstraction.
Routing intelligence lives in the proxy. The TUI knows which agents the user has selected. The proxy knows how to split, label, and fan-out messages. Agent backends know nothing about each other.
Streaming is first-class. No response is buffered and delivered whole. Every chunk arrives at the terminal the moment the agent produces it — even in fan-out mode with multiple concurrent streams.
The registry is the source of truth. Agent identity, routing targets, autocomplete vocabulary, heartbeat monitoring, and /talk resolution all derive from config.yaml. There is no hardcoded agent list anywhere in the runtime path.
Failures are isolated, never cascading. An offline agent produces an inline error and a sentinel. It does not stall the queue, hang the drain loop, or affect the streams of reachable agents.
Terminal aesthetics are non-negotiable. The TUI is not a concession to the absence of a GUI. It is the intended interface — purpose-built with rounded borders, color-coded agent boxes, a live status bar, smooth streaming, and keyboard-first interaction.