A tiny control plane that puts a fleet of Claude Code agents to work — unattended.
chela runs as a small daemon over a single tmux session. It does two things:
- Schedules long-lived agents — poke an agent's pane on an interval or cron
(
every 1h,0 */8 * * *, a one-shot timestamp). - Dispatches work — turn a markdown
TODO.md(or GitHub issues) into one git worktree per task, spawn an agent in it, and let it open a PR.
Most tmux + Claude Code tools help you talk to and supervise agents; chela is
for putting them to work and walking away. You watch them however you
already watch tmux — tmux attach, Mosh, or the
live terminal-wall dashboard.
![]() Desktop — the live wall, Dispatch, Kanban & Schedules |
![]() Phone — single-pane, pill switcher, keybar |
The dashboard, live — see it at chela.pages.dev.
Status: early. Core (scheduler + dispatcher + messaging) is solid and tested. The dashboard + live terminal wall is a first-class feature, shipped as a separate install (
--extra dashboard) to keep the core lean for headless use. The wall streams live ttyd sessions and is on by default but loopback-guarded: it serves writable shells, so the dashboard only serves it on a127.0.0.1bind (the documented model — front it with a tailnet/SSH tunnel). A non-loopback bind refuses the wall unless you setCHELA_TERMINALS_EXPOSE=true.
- Schedule agents — poke any agent's tmux pane on an interval (
every 45m), a cron expression (0 */6 * * *), or a one-shot timestamp. No human in the loop. - Dispatch TODOs → PRs — turn each
- [ ] taskin aWORKFLOW.md/TODO.md(or a GitHub issue) into a git worktree on a fresh branch, spawn an agent to implement it, and let it open a PR. - Live terminal wall — a web dashboard that streams every agent's pane (ttyd) into one grid, so you can watch the whole fleet work in real time.
- Per-agent monitoring — context-window usage, session cost, liveness (alive / working / waiting), latest recap, and next scheduled run, per agent.
- Account-wide rate-limit pills — the fleet shares one Claude account; the dashboard tracks the 5h / 7d limits so you can see headroom at a glance.
- Needs-input alerts — when an agent blocks on a prompt, chela fires a one-shot push (ntfy / Telegram / webhook) so you're not babysitting.
- tmux-native discovery — windows are agents;
tmux list-windowsis the single source of truth. No external registry, no heartbeat daemon. - Lean core, optional dashboard — a two-dependency headless core; the Flask
dashboard + terminal wall is a separate
--extra dashboardinstall.
chela uses uv. The core has two small deps
(croniter, pyyaml); the dashboard + live terminal wall ships as a
separate install (adds Flask) — same feature, kept out of the core so a
headless/CLI-only setup stays lean.
git clone https://github.com/Devail1/chelamux && cd chelamux
uv sync # core only — no Flask
uv run chela status
# dashboard + live terminal wall (separate install — keeps the core lean):
uv sync --extra dashboardRequirements: Python ≥ 3.11, tmux, git, the claude CLI on PATH
(plus gh for the dispatcher's PR flow).
Authenticate Claude once. chela doesn't manage credentials — it drives the
claude CLI inside your tmux windows. Log in once on the machine (claude, then
/login — or claude setup-token for a headless/long-lived token); every agent
window reuses the cached ~/.claude credentials. The whole fleet therefore runs
as one Claude account and shares its 5h / 7d rate limits (which is exactly
what the dashboard's rate-limit pills track).
# 1. Make a tmux session whose windows are your agents (the window name is the
# agent's display name). Override the session name with CHELA_TMUX_SESSION.
tmux new-session -d -s chela -n researcher
# 2. See what chela can see:
uv run chela status
# 3. Schedule an agent:
uv run chela schedule add researcher --every 1h --prompt "Run your research cycle."
# 4. Start the daemon (scheduler + optional dispatcher + needs-input notify):
uv run chela run
# 5. Dispatch work items from a repo's WORKFLOW.md (see examples/):
uv run chela dispatch /path/to/repo/WORKFLOW.md --once # one pass
uv run chela dispatch /path/to/repo/WORKFLOW.md # pollThe dispatcher is the headline feature. Drop a WORKFLOW.md + TODO.md in
a repo (copy examples/), and each - [ ] task becomes: a worktree on a fresh
branch → an agent that implements it, strikes the line, and opens a PR → a run
that flips to done when you merge. See examples/WORKFLOW.md.
Prefer to have an agent set it up? Copy
skills/chela-setupinto~/.claude/skills/and a Claude Code agent can install chela and seed a starterWORKFLOW.md+TODO.mdfor the current repo for you.
chela never manages permissions itself — it just launches claude, so the
agent's autonomy is set by the claude --permission-mode <mode> it's started
with. The modes (claude --help):
| Mode | Behaviour | Good for |
|---|---|---|
default |
Asks before every non-trivial action | Watching closely / untrusted repo |
plan |
Read-only; proposes a plan, changes nothing | Scoping before you let it run |
acceptEdits |
Auto-accepts file edits, still gates the rest (commands, etc.) | "Accept edits on" — light supervision |
auto |
A classifier auto-approves safe ops and gates dangerous ones | chela's dispatcher default — rarely hangs, still gated |
dontAsk / bypassPermissions |
No gating at all | Zero-hang autonomy on a repo you fully trust |
Two launch paths set the mode independently — this is the part to know:
- Dispatcher agents read
agent.cmdfrom each repo'sWORKFLOW.md(version-controlled, per-repo). Default:claude --permission-mode auto. - Dashboard Start button / launcher use the
CHELA_AGENT_CMDenv (global). Default: plainclaude, i.e.defaultmode — it asks on every action, so a launched agent you're not watching will sit waiting on a prompt.
To change the launcher/Start default, set the full command, e.g.
export CHELA_AGENT_CMD="claude --permission-mode acceptEdits" (or auto), then
restart the daemon. Prefer auto or acceptEdits for agents on a wall you don't
babysit; reserve bypassPermissions for repos you fully trust.
| Command | What it does |
|---|---|
chela status |
List the agent windows chela sees in the tmux session |
chela run |
The daemon loop: scheduler tick + dispatcher + needs-input notify |
chela schedule add <agent> --every/--cron/--once --prompt ... |
Add a scheduled poke |
chela schedule list / remove <id> |
Manage scheduled tasks |
chela dispatch <WORKFLOW.md> [--once] [--interval N] [--dry-run] |
Run the work-item dispatcher |
chela dispatch-runs |
List dispatcher runs and their status |
chela task-finished <task_id> |
(agent uses this) mark a run awaiting-review + kill its window |
chela msg <agent> <text> [--from] [--priority] |
Message a live agent over tmux |
chela broadcast <text> |
Message every other live agent |
chela install-statusline [--write] |
Print/install the Claude Code statusLine hook for the context bar |
chela dashboard [--host] [--port] |
Launch the dashboard + live terminal wall (needs the [dashboard] install) |
| Variable | Default | Purpose |
|---|---|---|
CHELA_TMUX_SESSION |
chela |
tmux session chela orchestrates |
CHELA_DIR |
~/.chela |
State dir (scheduler.db, worktrees, context) |
CHELA_SCHEDULER_POLL_INTERVAL |
30 |
Daemon loop interval (s) |
CHELA_DISPATCH_WORKFLOWS |
— | Colon-separated WORKFLOW.md paths the daemon dispatches |
CHELA_DISPATCH_TICK_INTERVAL |
60 |
Dispatcher tick interval in the daemon (s) |
CHELA_AGENT_CMD |
claude |
Launch command for the dashboard Start button |
CHELA_PROJECTS_DIR |
~/projects |
Folder scanned for git repos to suggest in the Launch sidebar (also settable in dashboard Settings → Projects folder, which wins) |
CHELA_NOTIFY_URL |
— | Needs-input notification target (ntfy / Telegram / webhook) |
CHELA_NOTIFY_KIND |
auto | Force ntfy | telegram | webhook |
CHELA_NOTIFY_CHAT_ID |
— | Telegram chat id (if not in the URL) |
CHELA_NOTIFY_INTERVAL |
20 |
Pane-state scan interval (s) |
CHELA_DASH_HOST / CHELA_DASHBOARD_PORT |
127.0.0.1 / 5001 |
Dashboard bind |
CHELA_TERMINALS_ENABLED |
true |
Embedded ttyd terminal wall (streams live; loopback-guarded — see below) |
CHELA_TERMINALS_EXPOSE |
false |
Serve the writable wall on a non-loopback bind too (RCE risk — opt-in) |
CHELA_DEFAULT_CONTEXT_WINDOW |
200000 |
Window size assumed by the transcript-based context estimate (fallback only) |
When an agent's pane enters the waiting state (blocked on a permission prompt
or a question), chela fires a one-shot notification — so you don't have to babysit.
export CHELA_NOTIFY_URL=https://ntfy.sh/my-chela-topic # ntfy
export CHELA_NOTIFY_URL="https://api.telegram.org/bot<token>/sendMessage?chat_id=<id>" # Telegram
export CHELA_NOTIFY_URL=https://example.com/hook # generic JSON webhookTransport is auto-detected from the URL; it's edge-triggered (one ping per
entry into waiting, not per tick). Pair it with ntfy on your phone for a
push-to-pocket "your agent needs you" alert.
The dashboard shows each agent's context-window usage and the account-wide
5h / 7d rate-limit pills. Those numbers live only in Claude Code's statusLine
payload (they aren't in the transcript), so chela ships a tiny statusLine hook
that caches the payload to $CHELA_DIR/context/<window>.json:
chela install-statusline # prints the snippet to add to settings.json
chela install-statusline --write # writes it for you (won't clobber an existing one)It's optional. Without it, the context bar falls back to a coarser estimate
derived from the agent's transcript (no rate-limit pills, and the window size is
a guess — see CHELA_DEFAULT_CONTEXT_WINDOW). Install the hook for exact numbers.
chela ships with zero built-in auth, by design. The dashboard and the ttyd
terminals bind 127.0.0.1. The terminal wall is a writable shell — exposing
it on an untrusted network is remote code execution. The tailnet is the trust
boundary, not a password.
For remote access, put the loopback dashboard behind one of:
- Tailscale —
tailscale serve 5001gives you TLS- tailnet ACLs for free (recommended).
- An SSH tunnel —
ssh -L 5001:127.0.0.1:5001 host. - A reverse proxy with your own auth.
Watch from your phone without the web UI at all: SSH/Mosh into the box from
a mobile terminal — Blink (iOS), Termius,
or any Mosh-capable client — then tmux attach -t chela and you've got the live
panes. A QR of the connect string makes this one tap.
Pairs well with ccbot — a Telegram ↔ tmux bridge for Claude Code (1 topic = 1 window = 1 session). It shares chela's model — a tmux window per agent — so you can let chela put the fleet to work and drive or supervise any agent from a Telegram topic on your phone (text, images, and file attachments flow both ways). (chela's built-in needs-input notifications just ping you; ccbot is a full two-way bridge.) Independent project, not required by chela.
A first-class feature, shipped as a separate install to keep the core lean.
uv sync --extra dashboard && uv run chela dashboard serves a web UI on
127.0.0.1:5001 with tabs for agents (liveness from claude agents --json:
alive / waiting / offline), schedules, the dispatcher, and a Kanban
of runs. Liveness is derived live from the native session status — no heartbeat
daemon. An embedded ttyd terminal wall (a multi-pane view that streams the
live panes) is on by default, but loopback-guarded: because it serves
writable shells, the dashboard only serves it on a 127.0.0.1 bind. Bind to a
non-loopback interface (e.g. --host 0.0.0.0) and the wall is refused — its
routes 404 and its UI is hidden — unless you explicitly set
CHELA_TERMINALS_EXPOSE=true. Turn the wall off entirely with
CHELA_TERMINALS_ENABLED=false.
Agents view — context usage, cost, liveness, latest recap and next run, per agent.
Schedules — interval, cron, and one-shot pokes that type a prompt into an agent's window.
Keys not reaching the terminal? If Esc (or other keys) never reaches an
embedded terminal, a vim-style browser extension such as Vimium is almost
certainly capturing them at the page level — it injects into the terminal's
iframe too. Fix: exclude the dashboard's URL in the extension's settings (in
Vimium, Options → "Excluded URLs and keys" → add the dashboard URL and leave
the Keys field blank). Quick workaround: Ctrl+3 (or Ctrl+[) sends a
literal Escape.
| Route | Returns |
|---|---|
GET /api/agents |
Per-window liveness/health, session status, context, schedules |
GET /api/summary |
Header counts (agents online, schedules) |
GET /api/schedules · POST · DELETE/PATCH /<id> |
Scheduled tasks CRUD |
GET /api/dispatcher |
Open tasks + active/awaiting/recent runs per workflow |
POST /api/dispatcher/runs/<id>/merge · /merge-all |
Squash-merge PRs + clean up |
POST /api/agents/{start,stop,restart,msg,broadcast,trigger} |
Agent controls |
GET /api/events |
Server-Sent Events stream (reactive UI accelerator) |
- Discovery is tmux-native.
tmux list-windows+pane_current_pathare the single source of truth — no external state file, no daemon to coordinate with. tmux never lies about what's live right now. - The dispatcher keys each task by a stable SHA of its source line, creates
~/.chela/worktrees/<...>/per task on branch<project_key>-<n>, and tracks runs in~/.chela/scheduler.db. Dispatched agents default toclaude --permission-mode auto(a classifier auto-approves safe ops and gates dangerous ones); setagent.cmd: claude --permission-mode bypassPermissionsinWORKFLOW.mdfor zero-hang autonomy on a repo you trust.
The work-item dispatcher is an adaptation of OpenAI's Symphony pattern (task-list → isolated git worktree → autonomous agent → PR) — chela does not claim novelty for that shape.
MIT — see LICENSE.

