Skip to main content

Workers

A worker is a separate agent that one agent or conversation starts for a single brief. pan worker run starts it for an issue, waits, and prints its report. The worker is a real Overdeck agent with the role worker:
  • It has its own ~/.overdeck/agents/<id>/state.json, transcript, cost, and model routing (roles.worker.model in config.yaml; the default is the same workhorse as work).
  • It runs in a persistent pane on the host’s terminal backend (Herdr by default, tmux when terminal.backend: tmux). It is never a one-shot claude -p or codex exec run.
  • It appears in the Agents Directory under the agent or conversation that started it and under its issue, and in the issue’s session tree as Worker <n>.
  • pan tell, pan kill, and the other lifecycle verbs work on it.
A worker never runs the issue’s lifecycle: it does not run pan done, pan review, or pan task done, and it does not push unless the brief says to. Pipeline patrols that act on the work role never act on it. --issue is required. Issue-less workers are not supported yet.

Commands

Worker ids are agent-<issue>-worker-<n>, for example agent-pan-123-worker-2.

Exit codes

run and wait print the report body on stdout and every status line on stderr. A report’s status line names its sequence number and the next wait, for example worker agent-pan-123-worker-1 report 2: done; next: pan worker wait agent-pan-123-worker-1 --after 2. The wait polls every two seconds and never messages the worker. A worker that was never seen alive is not called dead during its first minute, while the harness is still starting.

The report contract

The worker writes its report as Markdown to a file and runs pan worker report <id> --file <path>. The command writes ~/.overdeck/agents/<id>/reports/<seq>.json:
seq is 1-based and zero-padded to four digits in the file name. Each report is written to a temporary file and renamed into place, so a reader never sees half a report. Overdeck never rewrites or deletes reports. The body limit is 1 MiB. Reporting does not signal the pipeline. The directory derives a worker’s done state from its newest report: an idle worker whose newest report was written in the turn that just ended is done.

Working directory and read-only mode

--read-only puts a git shim first on the worker’s PATH. The shim prevents accidental git writes to the issue’s repository: the primary checkout, every worktree of it (the issue workspace included), and their subdirectories, however the call reaches them (cd .., -C, --git-dir, GIT_DIR, --work-tree). Inside that repository it lets only reads through:
  • status, diff, log, show, rev-parse, rev-list, ls-files, ls-tree, blame, cat-file, merge-base, describe, grep, shortlog, for-each-ref, show-ref;
  • config only when its first argument is --get, --get-all, --get-regexp, --list/-l (or get/list), and every later option is a read option such as --show-origin or --global;
  • remote bare or -v, remote show [-n] and remote get-url [--push|--all];
  • branch --show-current, branch --list/-l with patterns, a bare branch listing, and exactly the listing options -a, -r, -v, -vv, --contains, --merged, --no-merged, --points-at, --sort= and --format=.
Options are matched as exact tokens: git accepts unique prefixes of long options, so an abbreviation such as --unset or --set-upstream-t= is refused. Everything else is refused there, including fetch, which writes refs. The shim is not a sandbox. Calling git by its absolute path (/usr/bin/git) bypasses it, so does setting OVERDECK_PAN_GIT_OP=1 (the switch Overdeck’s own pan git operations use), and it does not restrict file writes or network access at all. It is there to stop a reviewing worker from changing the branch by mistake, not to contain a worker that tries to.

Parent and pan tell

The parent is --parent, else $OVERDECK_AGENT_ID, else $OVERDECK_CONVERSATION. Every conversation launcher exports OVERDECK_CONVERSATION. When an agent (a caller whose stdin is not a terminal) has none of these, run fails and asks for --parent. An interactive operator shell may start a worker without a parent. The parent is recorded in state.json (parentId) and stamped on the pane as the parent token. The prompt guard lets a worker pane accept messages from:
  • the issue’s work agent,
  • its parent,
  • an operator conversation.
Everyone else is refused. A worker stays running after it reports; to give it more work, run pan tell <worker-id> "<message>" and then pan worker wait <worker-id> --after <n>, where <n> is the last report you read. Without --after you would get report <n> again.

Waiting from each harness

  • Claude Code: run pan worker run … with the Bash tool’s run_in_background: true. The foreground limit is 10 minutes; a background command notifies the conversation when it exits, and its stdout is the report.
  • Codex and other harnesses: run pan worker run … --detach, then wait in a loop until the exit code is not 3. A timed-out wait loses nothing: a report written between two waits is returned by the next one.

When the worktree goes away

Stopping a worker (pan kill, or --stop-after-report) keeps its .swarm/worker-<n> worktree and its <feature-branch>-worker-<n> branch: the parent may still need the work. When the issue is torn down (close-out, close, approve) the worker worktrees are removed only if the workspace itself is deleted (git worktree remove --force, then git worktree prune); a kept workspace keeps them, uncommitted changes included. The closed-issue residue reaper removes them with the workspace. A worker branch exists only on this machine, so every cleanup path deletes it only when its tip is already in the default branch (main, origin/main, or whatever origin/HEAD names) or in the feature branch while that still exists. Any other worker branch is kept, and the cleanup logs its name and its unmerged commit count. A branch still checked out in a kept worktree is kept. .swarm/ is in the repository’s .gitignore, so git add -A in a workspace never commits an embedded worktree.

Where the files are

See also: Harnesses — Use Overdeck workers instead of harness plugins.