Quickstart
Five minutes from clone to a scheduled agent.
1. Install
chela uses uv. The core has two small deps; the
dashboard + live terminal wall ship as a separate install (keeps the core lean). You also need tmux, git, the
claude CLI on PATH (and gh for the dispatcher's PR flow).
# core git clone https://github.com/Devail1/chelamux && cd chelamux uv sync uv run chela status # dashboard + live terminal wall (separate install — keeps the core lean) uv sync --extra dashboard uv run chela dashboard
Authenticate Claude once. chela never touches 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 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.
2. Make a session, schedule an agent
A tmux session is your fleet; each window is an agent (the window name is its display name).
# one tmux session whose windows are your agents tmux new-session -d -s chela -n researcher # see what chela can see uv run chela status # poke the agent every hour, then run the daemon uv run chela schedule add researcher --every 1h --prompt "Run your research cycle." uv run chela run
3. Dispatch a TODO list into PRs
Drop a WORKFLOW.md + TODO.md into a repo (copy examples/),
then point chela at it. Each open - [ ] becomes a worktree → an agent → a PR.
uv run chela dispatch /path/to/repo/WORKFLOW.md --once # one pass uv run chela dispatch /path/to/repo/WORKFLOW.md # poll forever
Concepts
tmux is the source of truth
chela holds no separate registry of agents. Discovery is tmux list-windows +
pane_current_path, read live every tick — so what chela sees is exactly what's
running right now. A window rename keeps the same agent; nothing to re-register.
One window per agent
Each window of your session (CHELA_TMUX_SESSION, default chela) is an
agent. The window name is how you target it — schedules, messages, and the dashboard all key off it.
Two ways to put agents to work
- Schedule — for long-lived agents with a standing role. chela types
a prompt into the agent's pane on an interval, a cron expression, or once at a set time. The
agent persists between pokes; give it a
CLAUDE.mdfor stable context. - Dispatch — for ephemeral, one-shot work. Each
- [ ]in aTODO.mdspawns a throwaway agent in its own git worktree that implements the item, opens a PR, and tears its window down. Many tasks run in parallel.
The wall
The dashboard streams every agent's live terminal in one grid (drag, lock, maximize),
with a context-window bar per tile and account-wide rate-limit pills — a first-class feature,
just a separate install. The wall itself is opt-in
(CHELA_TERMINALS_ENABLED=true) because it exposes writable shells — keep it on loopback
or a tailnet. See Remote access & security.
Scheduling agents
A schedule pokes an agent's pane with a prompt on a cadence — the agent does the rest.
# interval: 30s / 5m / 1h / 1d chela schedule add researcher --every 1h --prompt "Run your research cycle." # cron expression chela schedule add reporter --cron "0 */8 * * *" --prompt "Post the 8-hourly summary." # one-shot at an ISO timestamp chela schedule add deployer --once "2026-06-01T09:00" --prompt "Cut the release." chela schedule list # every task + its id chela schedule remove 3 # delete by id
Standing context: the agent CLAUDE.md
A scheduled agent wakes into whatever its working directory contains. Drop a
CLAUDE.md at the root to give it a stable role — what it is, what to do each cycle,
and its boundaries (see examples/agent-template.md). The schedule supplies the
recurring nudge; CLAUDE.md supplies the identity.
Dispatching work (the headline feature)
Turn a markdown checklist into a stream of pull requests, each built by its own isolated agent.
TODO.md — the work list
Every unchecked - [ ] bullet is a work item. Append
<!-- blocked: reason --> to make the dispatcher skip a line.
## Open - [ ] Add a --version flag to the CLI - [ ] Write a docstring for the public API entry point - [ ] Add a unit test for the config loader <!-- blocked: waiting on fixtures -->
WORKFLOW.md — the config + agent brief
A YAML front-matter block configures the dispatcher; the markdown body below it is the prompt
template handed to each agent (with {{placeholders}} like {{task_title}},
{{branch_name}}, {{workspace_path}}). Put both files in the repo root.
project_key: PROJ # branches/windows are <key>-<n>, e.g. proj-1 tracker: kind: markdown # markdown TODO.md — also: gh_issues path: TODO.md # relative to this file workspace: root: ~/.chela/worktrees/proj # where per-task worktrees go base_branch: main # branch worktrees fork from + PRs target concurrency: max: 1 # tasks in flight at once agent: cmd: claude --permission-mode auto # or: bypassPermissions on a trusted repo startup_delay_seconds: 4 ready_timeout_seconds: 60 hooks: # all optional, run in the worktree # after_create: seed .claude/settings.local.json (least-privilege perms) # before_run: uv sync --quiet || true (lockfile sync / codegen) # after_done: runs in the repo when the PR merges (e.g. a deploy)
The lifecycle
Each task is keyed by a stable SHA of its source line (idempotent — a task is never picked up twice). For each open item the dispatcher:
- creates a git worktree on branch
<project_key>-<n>, forked frombase_branch; - runs the optional
after_create/before_runhooks, then spawns an agent in that worktree with your prompt body; - the agent implements the task, strikes its
- [ ]→- [x]on its own branch, pushes, and opens a PR; - the agent's last step is
chela task-finished <task_id>, which marks the runawaiting_review, records the PR URL, and kills its window; - when you merge, the struck line lands on
base_branch, the item disappears, and the run flips todoneon the next tick.
The dispatcher shape (task-list → isolated worktree → autonomous agent → PR) is an adaptation of OpenAI's Symphony pattern.
Dashboard & the wall
uv sync --extra dashboard
uv run chela dashboard # http://127.0.0.1:5001
The web UI has tabs for agents (live liveness — alive / waiting / offline), schedules, the dispatcher, and a Kanban of runs. Liveness is derived from the native session status — no heartbeat daemon.
The embedded terminal wall streams the live ttyd panes in a grid. It's off by default because it serves writable shells; enable it explicitly:
CHELA_TERMINALS_ENABLED=true uv run chela dashboard
Context & rate-limit pills
Exact context-window usage and the 5h / 7d rate-limit pills come from Claude Code's statusLine payload, which chela caches via a tiny hook. Install it once for precise numbers (without it, the context bar falls back to a coarse transcript estimate):
chela install-statusline # prints the snippet chela install-statusline --write # writes it (won't clobber an existing one)
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 and swallows the keypress.
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 to disable it on that site. Quick workaround: Ctrl+3
(or Ctrl+[) sends a literal Escape.
Command reference
One CLI, chela. Every command targets agents by their tmux window name.
Core
chela statusList the agent windows discovered in your tmux session — the source of truth.
chela runRun the daemon: scheduler tick + dispatcher + needs-input notify. Leave it running.
chela dashboard [--host] [--port]Launch the dashboard + live terminal wall (needs the dashboard install). Binds 127.0.0.1:5001.
Scheduling
chela schedule add <agent> --prompt … (--every|--cron|--once)Schedule a prompt — an interval (30s/5m/1h/1d), a cron expression, or a one-shot ISO timestamp.
chela schedule listList every scheduled task with its id.
chela schedule remove <id>Delete a task by id.
Dispatch
chela dispatch <WORKFLOW.md> [--once] [--interval N] [--dry-run]Turn each open - [ ] item into a worktree, an agent, and a PR. Polls every 60s; --once runs one tick.
chela dispatch-runsList dispatcher runs and their status.
chela task-finished <task_id>(agents call this) mark a run awaiting-review, record the PR, and kill its window.
Messaging
chela msg <agent> <message> [--from] [--priority]Drop a message into one agent's pane. Priority: critical|high|normal|low.
chela broadcast <message> [--from] [--priority]Send the same message to every other live agent at once.
Setup
chela install-statusline [--write] [--force] [--settings]Print (or --write) the statusLine snippet for ~/.claude/settings.json so panes report live context + rate limits.
Configuration
All configuration is environment variables, with sensible defaults.
| 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_NOTIFY_URL | — | Needs-input notification target (ntfy / Telegram / webhook) |
| CHELA_DASH_HOST / CHELA_DASHBOARD_PORT | 127.0.0.1 / 5001 | Dashboard bind address |
| CHELA_TERMINALS_ENABLED | false | Embedded ttyd terminal wall (opt-in; streams live) |
| CHELA_DEFAULT_CONTEXT_WINDOW | 200000 | Window size assumed by the transcript-based context estimate (fallback) |
Needs-input notifications
When an agent's pane enters the waiting state (a permission prompt or a question),
chela fires one edge-triggered notification — so you don't have to babysit. Transport is
auto-detected from the URL:
CHELA_NOTIFY_URL=https://ntfy.sh/my-chela-topic # ntfy CHELA_NOTIFY_URL="https://api.telegram.org/bot<token>/sendMessage?chat_id=<id>" # Telegram CHELA_NOTIFY_URL=https://example.com/hook # generic webhook
Remote access & security
chela ships with zero built-in auth, by design. The dashboard and
the ttyd terminals bind 127.0.0.1. The 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.
Or skip the web UI entirely: SSH/Mosh in from a mobile terminal and tmux attach -t chela
for the live panes straight from your phone.