Makro is a multi-surface AI coding-agent orchestrator. It manages many coding agents (Claude Code, GitHub Copilot, Codex, …) running in parallel tmux sessions, and gives you three ways to drive them:
- a terminal TUI (split-pane, the original),
- a desktop app (Electron + Go backend, with chat / terminals / kanban / artifacts / cost dashboard),
- an iOS app (voice calls, sessions, kanban, artifacts — talk to your agents from your phone).
All three talk to the same Go backend over a TLS-secured, authenticated HTTP/WebSocket API.
- Split-pane workspace — chat with the orchestrator on one side, live tmux terminals on the other.
- Terminal tabs — one tab per session;
Ctrl+Tab/Shift+Ctrl+Tabcycle tabs (browser-style);Ctrl+1..9jumps. - Kanban — a human-facing task board (todo / in-progress / review / done). Send a card to a session to dispatch it.
- Artifacts — auto-discovered HTML/video that agents generate, previewable in-app.
- Cost dashboard — per-model / per-session prompt-usage tracking with a 5h quota view.
@mention/&monitor— target a specific session, or watch one until idle and auto-handle confirmations.- Views —
Ctrl+Tterminals,Ctrl+Ddashboard,Ctrl+Ucost,Ctrl+Btoggle chat,Ctrl+Jfocus chat↔terminal.
- Voice calls — phone-call-style continuous STT/TTS. Discuss an idea out loud, have the assistant propose a plan, confirm, and it dispatches the work to a session. See Voice calls & modes.
- Sessions — view and drive any tmux terminal from the phone (xterm over WebSocket).
- Kanban — the same task board, on the go.
- Artifacts — browse and preview agent-generated HTML/video.
- Push — APNs + Bark notifications when an agent stops or needs attention.
- Split-pane chat + tmux viewer, phone layout for narrow terminals,
@mention, slash commands, session guardian, cross-agent relay.
- Multi-provider LLM — Anthropic Claude, OpenAI, and OpenAI-compatible endpoints (e.g. BigModel/GLM, auto-detected).
- Tool-calling orchestrator with a hook system (before/after tool call, agent start/stop, permission).
- Send-to-session reliability — atomic send + first-Enter-loss recovery (
SendConfirmed), so the first message to a freshly-rendered agent actually submits. - Brain / memory — optional proactive memory layer (
~/.makro/, memory-cli integration). - TLS + password auth — self-signed cert (TOFU pinning on iOS), constant-time password compare, path-traversal-safe serving.
Placeholder — capture and drop into
docs/:
Shot Suggested file Shows Desktop app docs/shot-desktop.pngChat + terminal tabs + kanban iOS voice call docs/shot-ios-call.pngCall screen with the mode dropdown + (ideally) a pending-plan card iOS artifacts docs/shot-ios-artifacts.pngArtifacts list for a session Reference them here as
once added.
A voice call runs a discuss → propose → confirm → dispatch flow, and the assistant can never dispatch without your confirmation (the send_to_session tool is gated during a call; dispatch is performed deterministically by the backend only after you confirm).
Pick a mode from the dropdown at the top of the call screen:
| Mode | Behavior |
|---|---|
| 闲聊 / Chat | Pure conversation — no tools, no dispatch. "Say it, get a reply." |
| 落实 / Plan (default) | Discuss, then the assistant proposes a structured plan (who/what/how), you confirm, it dispatches to a session. |
| 查询 / Query | Read-only tools (list_sessions, read_session_output) to answer "what's dev doing?" — no dispatch. |
Modes are switched manually (voice-switching is intentionally deferred). The selected mode is remembered across calls.
┌─────────────────────────┌──────────────────────────┐
│ Desktop app │ iOS app │
│ Electron + Vite UI │ SwiftUI │
│ (cmd/gui/frontend) │ (ios/Makro) │
├─────────────────────────┴──────────────────────────┤
│ HTTPS / WebSocket (TLS, password-auth) │
├─────────────────────────────────────────────────────┤
│ makro-serve (cmd/gui, Go) │
│ chat · terminals(xterm) · kanban · artifacts · │
│ cost · brain · push(APNs/Bark) │
├─────────────────────────────────────────────────────┤
│ Agent Orchestrator (internal/agent) │
│ tool-calling loop · hooks · send-gate · modes │
├─────────────────────────────────────────────────────┤
│ Tmux Manager (internal/tmux) · LLM (internal/llm) │
└─────────────────────────────────────────────────────┘
▲
│ (the TUI at repo root is a standalone client
│ of the same orchestrator/tmux/llm packages)
makro/
├── main.go # Terminal TUI (Bubbletea, split-pane)
├── cmd/gui/ # Desktop app
│ ├── *.go # makro-serve backend (HTTP/WS server)
│ ├── electron/ # Electron shell (main/preload/afterPack)
│ ├── frontend/ # Vite UI (vanilla JS)
│ └── package.json
├── ios/Makro/ # iOS app (SwiftUI, xcodegen + CocoaPods)
├── internal/
│ ├── agent/ # orchestrator, tools, hooks, notifier
│ ├── brain/ # proactive memory layer
│ ├── llm/ # multi-provider streaming + tool use
│ ├── tmux/ # tmux CLI client + parser
│ ├── apns/ # Apple push notifications
│ ├── notify/ # hook → running-instance forwarding
│ ├── usage/ # prompt-usage tracking
│ ├── config/ # config loading, .claude fallback
│ ├── util/ # shared helpers
│ └── tui/ # Bubbletea TUI (root app)
└── docs/ # diagrams (kanban-architecture.svg, …)
# 1. Go backend binary
go build -o cmd/gui/bin/makro-serve ./cmd/gui/
# 2. Frontend bundle
cd cmd/gui/frontend && npm install && npm run build && cd ..
# 3. Electron deps + package the .app (--dir skips the DMG/notarize step;
# DMG isn't needed for a local install)
cd cmd/gui && npm install && rm -rf release/ && ./node_modules/.bin/electron-builder --dir
# → release/mac-arm64/Makro.app
# Dev mode (hot frontend, spawns makro-serve)
cd cmd/gui && npm run devcd ios/Makro
xcodegen generate # regenerates Makro.xcodeproj from project.yml
pod install # Azure Speech SDK via CocoaPods
xcodebuild -workspace Makro.xcworkspace -scheme Makro \
-sdk iphoneos -configuration Debug -derivedDataPath buildOpen Makro.xcworkspace (not the .xcodeproj) in Xcode. Configure the Azure Speech key/region and the Makro server URL/password in Settings. Device builds require an Apple Developer signing identity (Xcode → Signing & Capabilities).
go build -o makro .
./makro # default split-pane
./makro --phone # vertical layout
./makro --chat # headless chat (for testing)Makro reads ~/.makro/config.json and falls back to .claude/settings.json for API key/model:
{
"llm_provider": "openai",
"llm_model": "glm-4.7",
"tmux_mode": "auto"
}| Variable | Description |
|---|---|
MAKRO_LLM_PROVIDER |
anthropic or openai |
MAKRO_LLM_API_KEY |
API key (overrides config) |
MAKRO_LLM_MODEL |
Model name |
MAKRO_TMUX_MODE |
auto, dedicated, or shared |
MAKRO_PASSWORD |
Fixed backend password (desktop app; random otherwise) |
ANTHROPIC_API_KEY |
Anthropic API key (fallback) |
OPENAI_API_KEY |
OpenAI(-compatible) API key (fallback) |
~/.makro/ also holds chat.jsonl (history), tasks.json (kanban), and prompt_usage.db (cost tracking).
Declare named roles that tell the orchestrator which tmux session handles what. Roles are injected into the orchestrator's system prompt as context — the main conversation reads them and uses the existing send_to_session tool to route matching tasks to the right session, instead of spawning yet another anonymous one. There is no separate router: the orchestrator LLM does the routing as part of its normal conversation. Missing or empty roles.toml means the orchestrator gets no role context (it behaves as before).
# ~/.makro/roles.toml — user-global. A project-local ./.makro/roles.toml overrides (see note below).
[[role]]
name = "makro"
description = "Makro 项目本身的开发、编排、修复"
session = "makro" # tmux session that already has a coding agent running
[[role]]
name = "juli"
description = "juli 项目的开发与维护"
session = "juli"
[[role]]
name = "default"
description = "兜底:无法路由时的默认角色"| Field | Required | Notes |
|---|---|---|
name |
yes | Identifier-safe (letters, digits, _, -); unique within a file. |
description |
yes | What this role owns. Written verbatim into the routing prompt — be specific. |
session |
no | Bound tmux session name. At dispatch time, an empty session resolves to the role's name. The session must already exist and have a coding agent running — Phase 1 does not auto-create sessions (auto-launch is planned for a later phase). |
clear_after |
no | manual (default), marker, guardian. Reserved for the reviewer-role auto-clear (Phase 3). |
state_file |
no | Per-project state file template, e.g. reviewer/{project}.md. Reserved for the reviewer role (Phase 3). |
How routing works: roles are context, not a separate router. The orchestrator's main conversation reads the role list from its system prompt and decides — per task — whether to send_to_session to a matching role's session or handle the task inline. Slash commands and @mention always bypass this (they work exactly as before). A task with no clear role match is handled in the main conversation.
Config override: ~/.makro/roles.toml (user-global) is loaded first, then ./.makro/roles.toml (project-local) overrides it. A role name repeated across the two files is silently last-wins (the project entry replaces the user entry); duplicates within one file are an error.
| Key | Action | Key | Action | |
|---|---|---|---|---|
Ctrl+O |
Focus chat ↔ viewer | [ / ] |
Switch sessions (viewer) | |
Ctrl+D |
Quit | Up/Down |
Input history (chat) | |
Ctrl+R |
Reflow layout | Enter |
Send | |
Ctrl+C |
Clear sticky target (×2 = quit) | Tab |
Accept autocomplete |
@session text Send text to a tmux session
/create <name> Create a session
/kill <name> Kill a session
/list List sessions
/switch <name> Switch viewer
/watch <name> Start guardian for a session
/layout phone Vertical layout
/help Show commands
go test ./... # unit tests
go test ./... -tags=integration -v # integration tests (needs tmux)
go vet ./... && go fmt ./... # vet + formatThe pre-commit hook runs go vet + gofmt + go test over the entire tree (not just staged files) — format stray Go files before committing.
- Go 1.26+, tmux
- (Desktop app) Node.js, Electron
- (iOS app) Xcode, CocoaPods, an Azure Speech resource (for voice)
- Anthropic or OpenAI(-compatible) API key
Apache-2.0