A modern tmux session manager for LLM development tools with health monitoring, auto-restart, and WebSocket API.
LLM coding tools like Claude Code and Cursor struggle with background tasks. Taskmux provides an LLM-friendly CLI for managing multiple background processes — restarting, checking status, reading logs — all from within your AI coding environment.
# Recommended (global install)
uv tool install taskmux
# From source
git clone https://github.com/nc9/taskmux
cd taskmux
uv tool install .# Initialize in your project (creates taskmux.toml, injects agent context)
taskmux init
# Add tasks
taskmux add server "npm run dev"
taskmux add build "npm run build:watch"
taskmux add db "docker compose up postgres"
# Start all auto_start tasks
taskmux start
# Check status
taskmux statusOr create a taskmux.toml manually:
name = "myproject"
[hooks]
before_start = "echo starting stack"
after_stop = "echo stack stopped"
[tasks.server]
command = "npm run dev"
[tasks.server.hooks]
before_start = "npm run build"
[tasks.build]
command = "npm run build:watch"
[tasks.test]
command = "npm run test:watch"
[tasks.db]
command = "docker compose up postgres"
auto_start = falseA full-stack app with a database, API server, and frontend — using health checks to ensure each service is ready before starting its dependents:
name = "fullstack-app"
[tasks.db]
command = "docker compose up postgres redis"
health_check = "pg_isready -h localhost -p 5432"
health_interval = 3
[tasks.migrate]
command = "python manage.py migrate && echo 'done' && sleep infinity"
cwd = "apps/api"
depends_on = ["db"]
health_check = "test -f .migrate-complete"
[tasks.api]
command = "python manage.py runserver 0.0.0.0:8000"
cwd = "apps/api"
depends_on = ["migrate"]
health_check = "curl -sf http://localhost:8000/health"
[tasks.worker]
command = "celery -A myapp worker -l info"
cwd = "apps/api"
depends_on = ["db"]
[tasks.web]
command = "bun dev"
cwd = "apps/web"
depends_on = ["api"]
health_check = "curl -sf http://localhost:3000"
[tasks.storybook]
command = "bun storybook"
cwd = "apps/web"
auto_start = falseWhat happens on taskmux start:
- db starts first (no dependencies)
- migrate and worker wait for db's health check (
pg_isready) to pass - api waits for migrate's health check
- web waits for api's health check (
curl localhost:8000/health) - storybook is skipped (
auto_start = false) — start it manually withtaskmux start storybook
taskmux start # Starts everything in dependency order
taskmux logs # Interleaved logs from all tasks
taskmux logs -g "ERROR" # Grep all tasks for errors
taskmux logs api # Logs from just the API
taskmux logs -f api # Follow API logs live
taskmux health # Health check table
taskmux inspect api # JSON state for a single task
taskmux restart worker # Restart just the worker
taskmux start storybook # Start a manual task# Session
taskmux start # Start all auto_start tasks
taskmux start <task> # Start a single task
taskmux stop # Stop all tasks (graceful C-c)
taskmux stop <task> # Stop a single task (graceful C-c)
taskmux restart # Restart all tasks
taskmux restart <task> # Restart a single task
taskmux status # Show session status
taskmux list # List tasks with health indicators
# Tasks
taskmux kill <task> # Hard-kill a task (destroys window)
taskmux add <task> "<command>" # Add task to config
taskmux remove <task> # Remove task from config
taskmux inspect <task> # JSON task state (pid, command, health)
# Logs
taskmux logs # Interleaved logs from all tasks
taskmux logs <task> # Show recent logs for a task
taskmux logs -f # Attach to session (switch windows with tmux keybinds)
taskmux logs -f <task> # Follow a task's logs live
taskmux logs -n 200 <task> # Last N lines
taskmux logs -g "error" # Search all tasks
taskmux logs <task> -g "error" # Search one task
taskmux logs <task> -g "error" -C 5 # Grep with context lines
# Init
taskmux init # Interactive project setup
taskmux init --defaults # Non-interactive, use defaults
# Monitoring
taskmux health # Health check table
taskmux watch # Watch config for changes, reload on edit
taskmux daemon --port 8765 # Run with WebSocket API + auto-restartstopsends C-c (graceful). Window stays alive so you can see exit output.killdestroys the window immediately.
Config file is taskmux.toml in the current directory:
name = "session-name"
auto_start = true # global toggle, default true
[hooks]
before_start = "echo starting"
after_stop = "echo done"
[tasks.server]
command = "python manage.py runserver"
cwd = "apps/api"
health_check = "curl -sf http://localhost:8000/health"
depends_on = ["db"]
[tasks.server.hooks]
before_start = "python manage.py migrate"
[tasks.db]
command = "docker compose up postgres"
health_check = "pg_isready -h localhost"
[tasks.worker]
command = "celery worker -A myapp"
depends_on = ["db"]
[tasks.tailwind]
command = "npx tailwindcss -w"
auto_start = false| Field | Default | Description |
|---|---|---|
name |
"taskmux" |
tmux session name |
auto_start |
true |
Global toggle — if false, start creates session but launches nothing |
hooks.before_start |
— | Run before starting tasks |
hooks.after_start |
— | Run after starting tasks |
hooks.before_stop |
— | Run before stopping tasks |
hooks.after_stop |
— | Run after stopping tasks |
tasks.<name>.command |
— | Shell command to run |
tasks.<name>.auto_start |
true |
Start with taskmux start |
tasks.<name>.cwd |
— | Working directory for the task |
tasks.<name>.health_check |
— | Shell command to check health (exit 0 = healthy) |
tasks.<name>.health_interval |
10 |
Seconds between health checks |
tasks.<name>.health_timeout |
5 |
Seconds before health check times out |
tasks.<name>.health_retries |
3 |
Consecutive failures before "unhealthy" |
tasks.<name>.depends_on |
[] |
Task names that must be healthy before this task starts |
tasks.<name>.hooks.* |
— | Per-task lifecycle hooks (same fields as global) |
Tasks with depends_on are started in topological order. Before starting a task, taskmux waits for each dependency's health check to pass (up to health_retries * health_interval seconds). If a dependency never becomes healthy, the dependent task is skipped with a warning.
Circular dependencies and references to nonexistent tasks are rejected at config load time.
When starting a single task with taskmux start <task>, dependencies are not auto-started — you get a warning if they aren't running.
If health_check is set, taskmux runs it as a shell command. Exit code 0 means healthy. If not set, taskmux falls back to checking if the tmux pane has a running process (not just a shell prompt).
Health checks are used by:
taskmux health— shows a table of all task healthtaskmux start— waits for dependencies to be healthy before starting dependentstaskmux daemon— continuously monitors and auto-restarts unhealthy tasks
Hooks fire in this order:
- Start: global
before_start→ taskbefore_start→ run command → taskafter_start→ globalafter_start - Stop: global
before_stop→ taskbefore_stop→ send C-c → taskafter_stop→ globalafter_stop
If a before_* hook fails (non-zero exit), the action is aborted.
taskmux init bootstraps your project:
- Creates
taskmux.tomlwith session name (defaults to directory name) - Detects installed AI coding agents (Claude, Codex, OpenCode)
- Injects taskmux usage instructions into agent context files:
- Claude:
.claude/rules/taskmux.md - Codex/OpenCode:
AGENTS.md
- Claude:
Use --defaults to skip prompts (CI/automation).
taskmux inspect <task> returns JSON with task state:
{
"name": "api",
"command": "python manage.py runserver 0.0.0.0:8000",
"auto_start": true,
"cwd": "apps/api",
"health_check": "curl -sf http://localhost:8000/health",
"depends_on": ["db"],
"running": true,
"healthy": true,
"pid": "12345",
"pane_current_command": "python",
"pane_current_path": "/home/user/project/apps/api",
"window_id": "@1",
"pane_id": "%1"
}Run as a background daemon with WebSocket API and auto-restart:
taskmux daemon # Default port 8765
taskmux daemon --port 9000 # Custom portWebSocket API:
const ws = new WebSocket('ws://localhost:8765');
ws.send(JSON.stringify({ command: "status" }));
ws.send(JSON.stringify({ command: "restart", params: { task: "server" } }));
ws.send(JSON.stringify({ command: "logs", params: { task: "server", lines: 50 } }));Taskmux creates standard tmux sessions — all tmux commands work:
tmux attach-session -t myproject # Attach to session
tmux list-sessions # List all sessions
# Ctrl+b 1/2/3 to switch windows, Ctrl+b d to detachMIT