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.modelinconfig.yaml; the default is the same workhorse aswork). - 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-shotclaude -porcodex execrun. - 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.
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 runspan 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;configonly when its first argument is--get,--get-all,--get-regexp,--list/-l(orget/list), and every later option is a read option such as--show-originor--global;remotebare or-v,remote show [-n]andremote get-url [--push|--all];branch --show-current,branch --list/-lwith patterns, a barebranchlisting, and exactly the listing options-a,-r,-v,-vv,--contains,--merged,--no-merged,--points-at,--sort=and--format=.
--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
workagent, - its parent,
- an operator conversation.
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’srun_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 not3. 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.