Multi-agent collaboration engine — AI agents that work as one.
Ensemble orchestrates AI agents into collaborative teams. Out of the box it pairs Codex (lead) + Claude Code (worker). They communicate, share findings, and solve problems together in real time. Teams of three (adding Grok) work too. The live TUI monitor opens where you are already looking: a herdr pane inside a herdr workspace, a native iTerm split pane on macOS + iTerm2 (no tmux needed), or a tmux session elsewhere. Agents themselves are orchestrated via the ensemble bridge. The monitor is just a viewer.
Status: Experimental developer tool. macOS and Linux only.
- Team orchestration: spawn multi-agent teams with a single command
- Real-time messaging: agents communicate via a structured message bus
- TUI monitor: live viewer that opens in a herdr pane, a native iTerm2 split pane on macOS, or tmux elsewhere
- Auto-disband: completion detection ends teams when every agent has signalled done
- Multi-host support: run agents across local and remote machines
- CLI & HTTP API: full control via command line or REST endpoints
- Node.js 18+, Python 3.6+, curl
- tmux — required on Linux; optional on macOS (only used as a fallback if iTerm2 is not available)
- Claude Code and Codex CLIs installed
git clone https://github.com/michelhelsdingen/ensemble.git
cd ensemble
npm install
# Start the server (keep this running)
npm run dev
# ...or on macOS, once, to keep it running across reboots:
./scripts/install-launchd.shcurl http://localhost:23000/api/v1/health
# → {"status":"healthy","version":"1.0.0"}# Via CLI
npx ensemble status
# Via API — create a team of two agents
curl -X POST http://localhost:23000/api/ensemble/teams \
-H "Content-Type: application/json" \
-d '{
"name": "review-team",
"description": "Review the authentication module",
"agents": [
{ "program": "claude", "role": "lead" },
{ "program": "codex", "role": "worker" }
],
"workingDirectory": "'$(pwd)'"
}'
# Watch the collaboration live
npx ensemble monitor --latest
# Steer the team
npx ensemble steer <team-id> "focus on the auth module"Or use the all-in-one collab script:
./scripts/collab-launch.sh "$(pwd)" "Review the authentication module"Ensemble ships with a skill for Claude Code. Once installed, just type:
/collab "Review the auth module for security issues"
Claude spawns a Codex + Claude team, shows their conversation live in your terminal, and presents a summary when done. One-command setup:
./scripts/setup-claude-code.shThis installs the skill, configures permissions, and verifies prerequisites. See the full setup guide for details.
The default team is Codex (lead) + Claude Code (worker). This is the tested, production-ready combination.
| Agent | Status | How to use |
|---|---|---|
| Codex + Claude Code | Fully tested | Default, just run /collab or collab-launch.sh |
| Grok CLI | Tested in three-agent teams | Add explicitly (see below) |
| GLM | Tested in four-agent teams | Add explicitly (see below) |
Antigravity CLI (agy) |
Configured | Add explicitly (see below) |
| Gemini CLI | Legacy | Configuration retained |
| MiMo Code CLI | Configured | Add explicitly as mimo |
| opencode | Untested | Add explicitly (see below) |
| Any CLI tool | Via agents.json |
Add a custom agent |
Four ways to change which agents are on your team:
1. Name them in your /collab prompt:
/collab "Review the auth module with agy and claude"
2. Pass them as the third argument to collab-launch.sh:
# Comma-separated. First agent = lead, rest = workers.
./scripts/collab-launch.sh "$(pwd)" "Security audit" codex,claude,grok3. Set COLLAB_AGENTS once in your shell, for a line-up you do not want to retype:
export COLLAB_AGENTS="codex,claude,grok"
./scripts/collab-launch.sh "$(pwd)" "Security audit" # runs all threePrecedence is: third argument > COLLAB_AGENTS > the default pair. Naming your agents as an
argument turns off the auto-fallback: a dead agent then fails preflight loudly (exit 7 with the
healthy alternatives) instead of being swapped for a working one. COLLAB_AGENTS is a standing
preference, so it keeps the auto-fallback; every swap is printed as a warning
(Auto-fallback: codex -> glm), so you still hear that an agent was unavailable.
4. Specify agents in the API call:
curl -X POST http://localhost:23000/api/ensemble/teams \
-H "Content-Type: application/json" \
-d '{
"name": "my-team",
"description": "Security audit",
"agents": [
{ "program": "codex", "role": "lead" },
{ "program": "claude", "role": "worker" },
{ "program": "agy", "role": "worker" }
],
"workingDirectory": "'$(pwd)'"
}'Antigravity CLI (
agy): Starts with--dangerously-skip-permissionsand receives prompts throughsendKeys. Ensemble unsetsGEMINI_API_KEYandGOOGLE_API_KEYafter all environment exports so agy uses subscription authentication rather than paid API tokens. In an untrusted directory, the existing startup gate confirms the preselected "Yes, I trust this folder" option. Gemini CLI configuration remains available for existing setups.
- Create a team: define agents and their task via API or CLI
- Agents spawn: each agent is started by the ensemble bridge with the task prompt
- Communication: agents use
team-say/team-readscripts to exchange messages - Monitor: watch the collaboration unfold in real time via the TUI monitor (herdr pane, iTerm split pane on macOS, or tmux)
- Auto-disband: when every agent signals completion, results are summarized and persisted
collab-launch picks the best viewer automatically:
| Situation | Monitor |
|---|---|
| Inside a herdr workspace | herdr pane (checked first) |
| Already inside tmux | tmux split pane (right) |
| macOS + iTerm2, no tmux | native iTerm2 split pane |
| Linux, or no iTerm2 | detached tmux session (tmux attach -t ensemble-<id>) |
herdr is checked before iTerm2 on purpose: herdr draws its own panes inside a host iTerm
session but passes TERM_PROGRAM=iTerm.app through unchanged. Trusting that would open a real
iTerm split outside the layout you are looking at, so the monitor would never be seen.
HERDR_ENV=1 is the reliable signal. The pane is labelled after your project directory and
closes itself when the team disbands.
Override with env vars:
COLLAB_MONITOR=herdr\|tmux\|iterm\|none: force a specific mode (or disable the monitor)COLLAB_ITERM_MODE=split\|tab\|window: iTerm layout (defaultsplit)COLLAB_HERDR_MODE=split\|tab: herdr layout (defaultsplit)
On macOS, you never need tmux attach for the monitor.
All team settings are optional. The service and preflight share
~/.config/ensemble/config.json; set ENSEMBLE_CONFIG to use another file.
Environment variables override file values. Missing or invalid settings use defaults.
| JSON key | Environment override | Default |
|---|---|---|
alertHubUrl |
ENSEMBLE_ALERT_HUB_URL |
Empty (hub alerts disabled) |
healthCommand |
ENSEMBLE_HEALTH_CMD |
No hook |
fallbackOrder |
ENSEMBLE_FALLBACK_ORDER (comma-separated) |
["codex","claude","glm","grok","agy","gemini"] |
maxTeamMinutes |
ENSEMBLE_MAX_TEAM_MINUTES |
No time limit |
graceMinutes |
ENSEMBLE_GRACE_MINUTES |
3 |
agentEnv |
None | No additional variables |
taskPreamble |
ENSEMBLE_TASK_PREAMBLE |
No preamble |
agentEnv adds environment variables to each locally spawned agent, including
when the tmux server is already running. For example,
{"agentEnv":{"MY_TOOL_NESTED":"1"}} tells a tool it is running inside a team.
Configured values override forwarded process variables. Names must match
^[A-Z_][A-Z0-9_]*$; values must be strings without carriage returns or newlines.
Invalid entries are ignored with one warning listing names only.
Set alertHubUrl and ALERT_HUB_SECRET to enable summary notifications through
an alert hub. An explicitly empty ENSEMBLE_ALERT_HUB_URL disables the hub, even
when a URL exists in the file. Without a hub, direct Telegram notifications remain
opt-in through ENSEMBLE_TELEGRAM_BOT_TOKEN and ENSEMBLE_TELEGRAM_CHAT_ID.
healthCommand is a trusted shell command. Preflight appends requested agent keys
and fallback candidates as separate arguments. It must return a JSON object on stdout:
{"codex":{"status":"ok","detail":"Ready"},"claude":{"status":"limit","detail":"Quota exhausted"}}Each status is ok, down, limit, slow, or unknown; detail is optional text.
Missing agents count as unknown. A command failure, invalid JSON, or a 60-second
timeout warns and continues with the existing checks. Explicitly selected down or
limit agents abort with exit code 7, listing healthy alternatives. For the default
pair and agents selected through COLLAB_AGENTS, preflight replaces unavailable agents in fallbackOrder, without duplicates;
no healthy replacement also exits 7. slow and unknown only warn.
maxTeamMinutes must be positive. At the limit the service asks all agents for a
final conclusion and the exact <<COLLAB_DONE>> sentinel within two minutes.
It stops the team after graceMinutes (nonnegative, measured from the warning),
preserving the summary and time-limit reason. taskPreamble is inserted before
each team's task in agent startup prompts, including solo and staged teams.
Copy .env.example to .env and adjust as needed. Key server variables:
| Variable | Default | Description |
|---|---|---|
ENSEMBLE_PORT |
23000 |
Server port |
ENSEMBLE_URL |
http://localhost:23000 |
CLI target URL |
ENSEMBLE_DATA_DIR |
~/.ensemble |
Data directory |
ENSEMBLE_CORS_ORIGIN |
localhost only | Allowed CORS origins |
See full configuration docs for all options including Telegram notifications, multi-host setup, and agent customization.
- Getting Started — Prerequisites, install, first team
- Configuration — Environment variables, agents, hosts
- API Reference — All HTTP endpoints
- CLI Reference — Commands and monitor keybindings
- Collab Scripts — Shell scripts for automation
- Architecture — How it all fits together
Optional trusted shell commands in ~/.config/ensemble/config.json (or ENSEMBLE_CONFIG):
{
"rosterCommand": "my-brain roster",
"planCommand": "my-brain plan",
"eventsCommand": "my-brain events",
"replaceStalledAgents": true,
"maxReplacementsPerTeam": 2
}Environment overrides: ENSEMBLE_ROSTER_CMD, ENSEMBLE_PLAN_CMD, ENSEMBLE_EVENTS_CMD,
ENSEMBLE_REPLACE_STALLED (true/1 enables, false/0 disables).
Replacement defaults to false, with a budget of two attempts per team.
Missing commands preserve existing behavior. Commands run as
/bin/bash -c '<cmd> "$@"' ensemble-<name> <args...> in their own process group,
which is killed on timeout. Output is limited to 1 MiB.
roster: no arguments, 5 seconds, cached JSON object keyed by agent key. Each entry is{ "status": "ok|down|limit|slow|unknown", "detail": "optional" }, using the same schema ashealthCommand.plan: task text in$1, 20 seconds, returns{ "agents": ["codex", "mimo"], "template": "implement", "reason": "Complementary roles" }. The first agent leads. Extra fields are ignored. Used only with missing or empty request agents; an explicit template wins. Invalid plans fall back to the default pair. The plan and reason appear in the team feed.events: no arguments, 5 seconds, one JSON line on stdin followed by EOF. Delivery runs in the background; failures are nonfatal and warn at most once per team. Every event hasevent,teamId,team, and an ISO-8601ts. Types and additional fields:team_started(agents,cwd,branch),agent_ready/agent_done(agent),agent_stalled/agent_failed(agent,detail),agent_replaced(agent,replacement,detail),team_finished(status:ok|failed|stopped,durationS,branch,cwd,detail). Fields without a value are omitted.
Set API worktree: "team" or COLLAB_WORKTREE=team when launching to create a
shared local worktree from HEAD on ensemble/<short-team-id>. A git repository
is required. Every local agent uses it, and the worktree and branch remain after
completion for review and manual merging. The summary and finish event include
both path and branch. Existing useWorktrees still creates individual worktrees;
worktree: "team" takes precedence if both are supplied.
With replacement enabled, the watchdog checks local sessions every 20 seconds.
A missing session or stalled agent is replaced with the first unused configured
agent in fallbackOrder whose roster status is ok. An unavailable roster falls
back to the health hook, then to no health check. The replacement keeps the role
and directory and receives the original task plus at most 20 recent messages
(8 kB). Exhausted budgets or unavailable replacements mark the agent failed.
Remote replacements remain on the original host.
MiMo Code CLI (mimo) is included with --trust --dangerously-skip-permissions,
Type your message readiness, and file-based prompt delivery.