Workers

A worker is a long-lived agent slot pinned to a machine and a worktree — the unit the orchestrator dispatches tasks to.

Workers

A worker is the unit Shelbi dispatches tasks to. It is a slot, not a process — declared once in the project YAML, alive for the lifetime of the project, with the agent process inside it cycled per task.

Each worker has three pieces:

  • A machine — where it lives. The hub itself, or any host reachable by ssh.
  • A persistent worktree — its own git checkout at <machine.work_dir>/.shelbi/wt/<worker-name>. The path is fixed; not configurable.
  • A runner — the agent CLI (claude, codex, …) the worker will launch when it picks up a task.

A worker handles one task at a time. It does not spawn, it does not fan out, it does not multiplex. If the project declares five workers, you have five concurrent slots — no more, no less.

The pool model

The pool is declared up front in the project YAML and stays fixed:

workers:
  - { name: alpha,   machine: hub,    runner: claude }
  - { name: bravo,   machine: hub,    runner: claude }
  - { name: charlie, machine: devbox, runner: claude }
  - { name: delta,   machine: devbox, runner: claude }

This is deliberate. Workers are not allocated on demand — they are named slots the orchestrator routes work to. That gives you:

  • Stable identities in the sidebar, the events log, and the kanban card's assigned_to field. "bravo is on the palette task" means the same thing across sessions.
  • Pre-warmed worktrees — no git worktree add on the hot path. A new task on bravo just switches branches in the worktree bravo already owns.
  • A real ceiling on concurrency. The number of declared workers is the parallelism cap; the orchestrator can't accidentally outrun your RAM by spawning more.

The wizard sizes the pool from total RAM (~10 GB per local worker, ~12 GB when spread across machines, clamped to [1, 16]). Add or remove workers later by editing the YAML and running shelbi reload.

Worker states

The hub polls each worker's tmux pane title every few seconds (see worker_poll_interval_secs, default 5) and writes the observed state to ~/.shelbi/workers/<name>/status.yaml. The sidebar reads from there.

BadgePersisted stateMeaning
workingagent is mid-turn — actively typing, calling tools, running shells.
💬awaiting_inputagent finished a turn and is sitting at the prompt.
blockedagent paused on a permission dialog or other interactive gate.
awaiting_reviewworker wrote the review-ready marker; its task moved to the review column.
·(no in-flight task)the slot is idle and ready to be assigned.

awaiting_input is the right state for "agent done with this turn, waiting for the next prompt" — it is what fires when claude's Stop hook runs at end of turn. The agent has not finished the task; it just finished one round of work. The actual completion signal is the review-ready marker (see below).

State changes are also appended to ~/.shelbi/events.log:

2026-06-22T14:22:11+00:00 worker=bravo none -> working
2026-06-22T14:24:03+00:00 worker=bravo working -> awaiting_input

That feed is what the orchestrator tails to know when to dispatch more work. See the events log.

Switching tasks clears context

When a worker picks up a new task its pane is killed and re-created from scratch:

  1. The pane (window for local workers, session for remote workers) is torn down.
  2. The worktree is switched to the task's branch — creating the branch off default_branch if it doesn't exist, refusing to switch if there are uncommitted changes.
  3. A fresh .claude/settings.json is deployed under the worktree.
  4. A new pane is created and the agent CLI is launched in it.
  5. Once the agent's input box is ready (shift+tab to cycle footer detected), the initial prompt is typed.

This is intentional. The previous task's conversation history, scratchpad files, and any agent-local state are gone. Each task starts the agent with a clean context — no leakage between tasks on the same worker.

The worktree itself persists. Files committed on the previous task's branch are still there on disk; only the branch checkout changes. This keeps the on-machine cost of a task switch small (one branch checkout, not a whole clone).

See crates/shelbi-orchestrator/src/worker.rs (start_worker_on_task) for the full sequence.

How a task completes

A worker reports task completion by writing its task id into a marker file in the worktree:

<worktree>/.claude/shelbi-review-ready

The hub poller cats this file on each tick (locally or over SSH), and when it finds a non-empty value:

  1. Confirms the named task is in-progress and assigned to this worker.
  2. Moves the task to review.
  3. Clears the marker.
  4. Appends task=<id> in_progress -> review reason=worker:review-marker to the events log.

The worker never runs shelbi itself. The marker file is the entire on-worker protocol. This is what makes a remote worker possible with nothing installed but tmux, git, and the agent CLI — see crates/shelbi-tui/src/poller.rs (maybe_promote_to_review) for the read-side, and crates/shelbi-orchestrator/src/worker.rs (compose_prompt) for what the initial prompt tells the worker to do.

Local vs remote workers

A worker's pane lives in different places depending on its machine:

                 ┌─ shelbi-myapp ─────────────────────┐
local workers →  │  dashboard | alpha | bravo | …     │   one tmux session
                 └────────────────────────────────────┘   on the hub

                 ┌─ shelbi-w-charlie ─────────────────┐   one tmux session
remote workers → │  agent                             │   per worker, on
                 └────────────────────────────────────┘   the remote machine

Local workers share the project session (one window per worker). Remote workers each get their own session on their machine so they survive SSH drops — shelbi reattaches with ssh -t host tmux attach -t shelbi-w-<name> whenever you focus them. The session naming is hard-coded; you can tmux ls on the remote to inspect.

See also

  • Columns — what in_progress and review mean, and where workers fit in the task lifecycle.
  • The events log — the shape of every worker transition line.
  • The orchestrator — how it picks which worker to dispatch a task to.