Run mechanics
This document states the exact mechanics of one worker run and the requirements they serve. The mechanics cover these parts of the run:
- files
- command lines
- environment
- audit
- rounds
- records
The following hold for both backends:
- inputs
- placement
- the run directory
- the runtime directory
- the progress file
- the audit
- rounds
- the run record
- errors
The launch below is the Claude Code backend's. The pi run mechanics state what the pi
backend does instead. The Harness generates the following, which the run places in the runtime
directory's control/:
- the worker settings
- the write hook
- the tool sets
The Harness's Claude Code mechanics state these. The entry explains why the run is shaped this way. The worker result contract fixes the worker's answer.
Inputs
A run is requested with:
| Input | Meaning |
|---|---|
| backend | claude or pi: the worker backend the worker configuration chooses for the worker, pi when nothing chooses one |
| worktree | Absolute path of the Git worktree the worker works in, lying directly in .claude/worktrees/ of its repository's primary worktree (Placement): in Concorde a bound workspace's worktree, or for an unbound run its unbound checkout, which the caller passes like any worktree |
| parent | The trace node folder the caller gives, in Concorde the node of the run that asks for the worker, below which the worker run's node is created |
| task type | One of the eight Protocol task types; it selects the tool set, the read-only one for a task type that writes nothing, such as review-architecture |
| grant | The frozen grant as data, in the worker harness's grant input: every path with its level rw, ro or names, relative to the worktree, and its context identity; a grant with no rw path makes a worker that changes nothing, which is how a caller runs a reading worker |
| instructions | The caller's task-specific part of the brief: in Concorde, an Operation's prompt with the definitions of the glossary terms its grant carries and the rules for a Spec gap, which Method composes |
| round validation | Optionally the caller's callback, called after every round whose worker ended ok with a clean audit (Round validation) |
| runtime paths | Extra absolute paths, outside the grant, that every tool of the worker may read and none may write, exactly as the caller lists them: the worker configuration's runtime paths that exist, such as .venv or node_modules, the directories the project interpreter needs, and the host material the caller admits for this run alone, such as the folder of the check logs its own run recorded (Reading beside the grant) |
| project interpreter | Optionally the absolute path of the project's own Python interpreter, which the caller resolved from the project's configuration as its checks run it; its directory comes first on the worker's PATH and the brief names it |
| limits | Timeout per round, the turn limit, the budget limit when the configuration sets one, the number of resume rounds (default 3), and the number of retry rounds (default 5) with the delay before the first (default 15 seconds) |
| model | The project model name the run worktree's worker configuration chooses for the worker's id, which every Operation gives, recorded only |
| local model, model map | The model's local id on the backend, which the model map gives it and which is passed with --model, and the path of that map, recorded only |
| operation, worker, modules | The Operation and the worker id the model was chosen for and the Modules the job is about, labels the caller gives, recorded only |
| reasoning | Optionally the reasoning level from the same configuration, passed with --effort on the Claude Code backend and --thinking on the pi backend |
Placement
Every worktree a worker runs in lies directly in .claude/worktrees/ of its repository's primary
worktree. The primary worktree is the first worktree git worktree list names. In Concorde, the
worker runs in a task worktree .claude/worktrees/<task> or an
unbound checkout .claude/worktrees/unbound-<run-id>.
Before it generates anything, the host checks this with read-only Git. With worktree_misplaced,
the host refuses any other placement, the primary worktree itself included. Once the placement
holds, the host collects the repository's Git administrative paths, as real paths:
| Path | Found by |
|---|---|
the common Git directory, with its worktrees/ and modules/ | git rev-parse --git-common-dir in the worktree |
| the worktree's own Git directory | git rev-parse --git-dir in the worktree |
every .git entry of the worktree, file or directory: its own and each submodule's or nested repository's | a walk of the worktree that enters no .git, no symbolic link and no runtime path |
for each .git file, the Git directory its gitdir: line names and that directory's commondir | reading the file |
The host hands the Harness the primary worktree and these paths with the grant. Wherever the repository lies, the Harness hides them from every tool (Claude Code mechanics, pi mechanics). Every worktree a worker runs in lies in the primary worktree. Therefore, as it hides the user's home, the Harness also hides the primary worktree as a whole, except for the following:
- the way to the worktree
- the runtime paths
- what the grant makes readable
Reading beside the grant
The grant is the whole of what a worker may read of the project. The runtime paths are the only other material it may read. Its caller alone decides the runtime paths. Workers hands them to the Harness exactly as given, adding none and widening none. On both backends, the Harness makes each readable, and none writable, to every tool (Claude Code mechanics, pi mechanics). The runtime paths change neither the grant nor its context identity. They reach the generated worker settings or permission extension. The run record keeps the digest of those settings or extension.
When a caller lets a worker read host material, it lists the folder that holds only that material.
The full logs of checks the caller's own run recorded are one example. The caller never lists
the trace node or task folder around the material. Since the brief lists only the grant, the
caller names the files its worker should read in its task instructions. The worker reads them at
their absolute paths, like any ro file.
The project interpreter is the one other thing a caller may give. On both backends, Workers puts
its directory first on every round's PATH, ahead of the host's own PATH. The brief also names
the project interpreter. Both make python the project's interpreter. The host's own environment is
never changed. Workers never resolves the interpreter itself. Running the interpreter needs its
environment and the installation it links to readable. The caller therefore lists them among the
runtime paths.
Round validation
Whether a round left something to repair is the caller's judgement, not the worker harness's. The worker harness does none of the following:
- run a check
- read a Spec
- know a glossary
A caller that has such a judgement passes a round validation, a callback. After every round
whose worker ended ok with a clean audit, the host calls the callback once with the following:
- the worktree
- the folder of the round's trace node
- the round's worker result, valid against its schema
The caller may therefore judge what the worker returned as well as what it changed. The callback answers with:
| Field | Meaning |
|---|---|
| evidence | The values to keep with the round as its evidence, in the caller's own shape, possibly none; nodes of its own, such as check nodes, it places below the round's folder, and a top-level string of an evidence value that is an absolute path below that folder is an artifact path, which the round's node keeps relative to its folder and the returned record absolute |
| repair | Nothing, or the text naming what the worker must repair, which becomes the next resume round's prompt |
| failure | With a repair, whether the run must end failed when no rounds are left for it, and then the code and the causes of Workers' link; without, a repair left over at the last round leaves the round's result for the caller to judge |
| violation | Instead of a repair, when the round did something its caller does not allow at all: the code and the causes of Workers' link, which ends the run failed at once, with no further round, as an audit violation does |
| unavailable | Instead of the fields above, when it could not validate at all: the code and the causes of Workers' link, which ends the run failed with no further round |
In Concorde, Method's round validation runs the configured checks through Check execution. These are the checks of the bound Modules and of every Module that uses one of them. Method's round validation keeps their check results as evidence. For a failing check, Method's round validation names the following as the repair:
- its identity
- its status
- its exit code
- the last 20,000 bytes of its log
For a run whose checks still fail, it asks the run to end with checks_failed and one link per
failing check. When the checks cannot run, it answers checks_unavailable with Check execution's
link. Once the checks pass, a step's own validation adds what it finds to the repair. Such
validation includes the structural validation of a Spec-writing step.
Where the grant makes the project glossary writable, Method's round validation also audits the glossary by entry. Every Module's concepts share that one file. The audit covers these entries:
- entries the round added
- entries the round changed
- entries the round removed
When a Module outside the grant owns such an entry before or after the round, the round validation
answers a violation audit_violation. The violation names
<glossary>#<concept> (owner before: <Module>, after: <Module>) for every such entry
(How an Operation runs its workers).
Run directory and runtime directory
A worker run keeps what analysis needs and what it only needs while it runs apart. Its run
directory is its trace node workers/<run-id>/ inside
the trace node folder its caller gave. Thus, a worker run lies inside the run that launched it.
<run-id> is w-<YYYYMMDD>T<HHMMSS>-<6 hex digits>. The host chooses this unique identity.
| Path | Content | Worker access |
|---|---|---|
trace.json | The node's record, whose content is the run record (worker run trace) | none |
status.json | The progress file | none |
grant.json | The frozen grant and its context identity | none |
brief.md | The brief as sent | none |
transcript.jsonl | The latest session's transcript, of the session the worker's output named last, even in a round interrupted before it returned, moved here from config/ when the run ended | none |
rounds/<n>/ | Each round's node: trace.json (worker round trace), the round's standard error stderr.log and the nodes its round validation placed, such as Concorde's check nodes checks/<check-id>/ | none |
Its runtime directory is a private directory
created for the run. Where /tmp is writable, the directory is /tmp/concorde-<suffix>-<random>/.
Where /tmp is not writable, as inside a check boundary, the directory is under the system
temporary directory. When the run ends, however it ends, the host removes the runtime directory
after moving the transcript into the run directory. The host removes the credential copies in
config/ first, then the rest. The host opens up any directory the worker's own tools left
unwritable or unreadable. The host checks that the directory is gone.
If the host could not keep a transcript or remove a runtime directory, the run ends failed with
cleanup_failed. The error names what remains and why. Unless the host process itself is killed
outside its control, by SIGKILL, credentials therefore never outlive the run unnoticed. Such a
kill leaves the directory to the system's temporary-file cleaning, as
the entry explains. The runtime directory holds:
| Path | Content | Worker access |
|---|---|---|
control/settings.json | The worker settings (Claude Code backend) | none |
control/write_hook.py | The write hook with the task worktree and the grant's rw, ro and names lists embedded (Claude Code backend) | none |
control/result.schema.json | The worker result schema | none |
config/ | The agent's own configuration directory: on the Claude Code backend CLAUDE_CONFIG_DIR with the credential copy, sessions and transcripts; on the pi backend see pi | none |
home/ | HOME | Bash read and write |
tmp/ | TMPDIR | Bash read and write |
work/ | The working directory | Bash read and write |
Claude Code's Bash sandbox creates Unix sockets below TMPDIR. A socket path must stay under the
operating system's 108-byte limit. A short directory directly under /tmp keeps the socket paths under that limit.
When any deny rule the host generated covers any of these directories, the host refuses to launch:
work/home/tmp/
Progress file
status.json tells an observer what the run is doing while it runs. The run that launched it is
one such observer. The host rewrites it atomically at every phase change and at most once a second
for worker activity:
| Field | Content |
|---|---|
run_id, task_type, backend, worktree | the run's identity, task type, backend and worktree |
operation_run_id | the identity of the run that launched it, which its caller gives, by which an observer pairs the two; null when the caller gives none |
phase | preparing, worker, audit, validation, waiting (before a retry round) or finished |
round | the current round, from 1 |
last_action | the worker's latest tool call as tool and target (a path, pattern or the first line of a command, at most 200 characters) with its time, or null |
status | null while running; the final status once phase is finished |
host_pid | the process identifier, in its own PID namespace, of the process running the run: its caller's process, in Concorde the Execution runner of the run that launched it |
started_at, updated_at | UTC times |
The phases follow the rounds. Every round runs the worker and then audits it. Only a
round whose worker ended ok with a clean audit goes on to the round validation or to a resume
round. Only a round that a transient model-service error ended goes on to waiting and a retry
round. When something outside ends the run and the host can handle it, any phase moves to
finished with interrupted.
Illustrative, non-normative. This diagram explains; it declares no relationship.
It is an observation aid only: the run record, not the progress file, is the run's evidence.
Launch
On the Claude Code backend, the first round runs the following command. The runtime directory's
work/ is the working directory. The brief is on standard input:
claude -p --settings <runtime>/control/settings.json --tools <tool set>
--json-schema <worker result schema> --output-format stream-json --verbose
--permission-mode bypassPermissions --allow-dangerously-skip-permissions
--strict-mcp-config --max-turns <n> [--max-budget-usd <x>] [--model <model>]
[--effort <level>]
Only when the worker configuration sets max_budget_usd, the host passes --max-budget-usd.
Without --max-budget-usd, the run has no budget limit.
A resume round runs the same command with --resume <latest session id> and the round validation's
repair text as the prompt. A retry round runs the same command with
--resume <latest session id> and the retry prompt.
The environment is cleared and then set to exactly:
| Variable | Value |
|---|---|
PATH | the host's value, preceded by the project interpreter's directory when the request names one (Reading beside the grant) |
LANG | the host's value |
HOME | <runtime>/home |
TMPDIR | <runtime>/tmp |
CLAUDE_CONFIG_DIR | <runtime>/config |
CLAUDE_CODE_DISABLE_CLAUDE_MDS | 1 |
CLAUDE_CODE_DISABLE_AUTO_MEMORY | 1 |
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC | 1 |
ANTHROPIC_API_KEY | the host's value, only when the host has one |
| the proxy variables | as that section derives them from the host's |
Before the first round the host copies the user's Claude Code credentials file into config/.
The process starts in a new process group. When the round ends for any reason, including a timeout,
the host kills the whole group. The host reads standard output as it arrives. Each tool use in an
assistant message updates the progress file. The final result record gives the following for the
round:
- the session identifier
- the exit status
- the tokens
- the cost
- the turns
- the schema-validated structured output
The host keeps the round's standard error, up to its last 80,000 bytes, as stderr.log of the
round's node. Thus, no round overwrites another's.
Proxy
If the host uses a proxy, a worker's model calls leave through it. On both backends, the host passes on each of these variables that its own environment sets to a non-empty value:
HTTP_PROXYHTTPS_PROXYhttp_proxyhttps_proxy
Only when it passes at least one of them, the host also passes each of NO_PROXY and no_proxy
that its own environment sets. The host applies the following rules to those no-proxy lists:
- When every passed proxy names a loopback host, the host removes the loopback entries listed below.
- Otherwise, the host keeps the lists as they are, so a developer's own proxy elsewhere keeps sending loopback direct.
- When nothing is left in a list, the host does not pass it at all.
A loopback host is localhost or a loopback address, with or without a scheme. The host removes
these loopback entries under the first rule:
localhost127.0.0.1::1[::1]
Other proxy variables, such as ALL_PROXY, never pass. The reason is where a worker runs: in the
network of whatever started it. That network's only way out may be a proxy on loopback. Examples
include a developer's own local model proxy or the sandbox proxy of a
main agent whose own session is sandboxed. Those
variables name the proxy on localhost while the no-proxy lists name loopback.
A worker that dropped that proxy could reach no model endpoint behind it. A worker that kept
loopback in its no-proxy lists could not reach an endpoint on localhost reachable only through
that proxy. When a worker starts with no proxy in its environment, it gets no proxy variable. The
worker runs exactly as without this rule. The proxy serves the worker's own process only. Its
tools run in their own sandbox without network
(req.workers.bash-sandbox,
req.workers.pi-sandbox). The sandbox's namespace cannot reach the
proxy.
Audit
The audit attributes every change since its snapshot to the worker. Therefore, the caller keeps every other writer away from the following:
- the worktree's files
- its
HEAD - its index
- its branch
This isolation lasts from before the snapshot until the run ends, its round validations and the host's deletions included. Readers may go on (The caller isolates the worktree). In Concorde, the workspace lock the launching run holds provides this isolation. The host takes no lock of its own for this.
Before the first round, the host records a snapshot of the worktree. The snapshot holds the following:
HEAD, as the branch it names and its commit- the index digest
- the digest of every tracked change and untracked file that already exists, including its content and, as Git sees it, its executable bit
After each round, the host runs read-only Git
(git status --porcelain=v2 -z --untracked-files=all and the digests of the listed files) and
compares with the snapshot. When Git no longer lists a file of the snapshot, the host measures the
file where it lies. If the file is equal to HEAD again, it changed. If the file is gone, as an
untracked file deleted is, it was deleted.
| Observation since the snapshot | Verdict |
|---|---|
a changed or new file whose level is rw | allowed |
| a changed or new file of any other level, or of none | violation |
| a deleted file | violation |
a changed HEAD, index or branch | violation |
| a change under a path Git ignores | not observed |
As the Harness reads it, a file's level is that of the grant's most specific entry for it. The
exact entry takes precedence. If there is no exact entry, the longest directory entry above the
file takes precedence. Therefore, a file the grant lists ro below a rw directory is not
writable. Proposed deletions read the level the same way.
Each round records its audit as an object with:
| Field | Content |
|---|---|
verdict | clean when nothing is a violation, otherwise violation |
changed | every path, relative to the worktree and in path order, whose file was created, changed or deleted since the snapshot |
violations | each violation as one string, in path order after any Git state: HEAD for a HEAD that names another branch or commit and index for a changed index; the path of a file created or changed outside rw; and the path followed by (deleted) for a deleted file |
A round whose audit did not run, such as one whose command could not be started, has audit null
in its node. The returned run record's example shows an
audit with violations.
Proposed deletions
After the last round, when that round's audit was clean and its worker returned a valid
worker result, the host performs the worker's
proposed_deletions. The host does this whatever the run's outcome otherwise, and alike for each
of these outcomes:
- an
okrun - a
blockedorfailedworker - a round that timed out or whose agent process failed after the result
- a round validation that failed the run or could not validate
When the round validation answered a violation, the host performs none, since the caller does not allow the round at all. When the run was interrupted, the host performs none. Interruption leaves the worktree as the interruption found it.
As the brief asks of every path in its result, the worker writes each path relative to the
worktree. The host also accepts an absolute path. Once normalized, the host reads it as the same
path in the worktree. If a path normalizes to a path outside the worktree, the host refuses it.
The host judges each path with its directories' symbolic links resolved. Thus, a directory link
below a rw directory never lets the host delete a file elsewhere. The host removes a final
symbolic link itself, never its target. If an earlier entry already named a path, the host drops
an entry naming that path. The host then takes each remaining entry in order:
| The proposed path | Outcome | Recorded in |
|---|---|---|
outside the worktree, outside the rw list, or an existing entry that is not a file, such as a directory | refused, nothing changes | deletions_refused, as the worker gave it |
in the rw list and absent from the worktree | nothing to do | deletions_absent, relative to the worktree |
an existing file in the rw list | deleted | deleted, relative to the worktree |
an existing file in the rw list whose deletion fails | left in place; the host goes on with the next | deletions_failed, relative to the worktree |
When a deletion failed, the run ends failed with deletion_failed whatever status it would
otherwise have had. Its error lists what was deleted and what was not. The link the run would
otherwise have ended with is the error's cause. The deletions that succeeded stay done. This
happens after the last round's validation. Therefore, the evidence that validation recorded
describes the worktree before these deletions.
Rounds
| Worker result and audit | Round validation | Next |
|---|---|---|
| the agent process ended with a transient model-service error, audit clean, no valid result, retries left | not called | retry round |
| the round timed out, or the agent process failed or reached a limit | not called | end failed with worker_timeout, worker_limit_reached or the backend's process failure code, naming any audit violation in its detail |
| audit violation, whatever the result | not called | end failed with audit_violation |
| invalid result, audit clean | not called | end failed with worker_result_invalid |
blocked or failed, audit clean | not called | end with the worker's status and worker_blocked or worker_failed |
ok, audit clean, no round validation given | — | end ok |
ok, audit clean | raises instead of answering | end failed with validation_unavailable |
ok, audit clean | answers unavailable | end failed with the code and causes it names, reason environment |
ok, audit clean | answers a violation | end failed with the code and causes it names, reason permission |
ok, audit clean | nothing to repair | end ok |
ok, audit clean | a repair, rounds left | resume round |
ok, audit clean | a repair that fails the run, no rounds left | end failed with the code and causes it names, reason exhausted |
ok, audit clean | a repair that does not fail the run, no rounds left | end ok; the caller judges the result |
The rows are tried in this order. The resume prompt is the round validation's repair text.
Whatever ends its round, a valid worker result is kept as the run's worker_result. This includes
a timeout or a failed agent process. The worker's claim is evidence even when the run fails.
Retries
A model service may refuse a call for a while or lose it on its way. Many workers that share one provider meet its concurrency limit. The same call can succeed a little later. A transient model-service error is an error that the agent program reports for its model call and that names one of these causes:
- a concurrency or rate limit, too many requests or the HTTP status 429
- an overloaded service, a service under high demand, the HTTP status 500, 502, 503, 504 or 529, or a service that is unavailable for now
- an explicit request to retry or try again later
- a lost call: an upstream or stream failure, a connection that was reset, refused or lost, or a call that timed out
An error that names an exhausted quota, billing or a usage limit is never transient, since waiting
does not lift it. On pi, the host reads the error message of the last assistant message whose stop
reason is error, in a round that named its session. On Claude Code, the host reads the final text
of a failed result envelope when it starts with API Error, and the errors the envelope lists.
The round's own timeout is no such error: worker_timeout is never retried.
The host starts a retry round when all of these conditions hold:
- The agent process ended the round with a transient model-service error.
- The round's audit is clean.
- The worker returned no valid result.
- The round named a session to continue, or the worktree has no change since the snapshot.
- The run has started fewer retry rounds than its
retrieslimit.
The retry rounds of a run are counted apart from its resume rounds. Before the k-th retry round of
the run, the host waits retry_delay_seconds × 2^(k−1) seconds, multiplied by a factor drawn
uniformly between 0.5 and 1. The random factor spreads the retries of workers that failed together.
With the defaults, the host waits at most 15, 30, 60, 120 and 240 seconds. While it waits, the
progress file's phase is waiting.
A retry round continues the round's session, as a resume round does. Its prompt names the error and asks the worker to go on from where it stopped. The worker's earlier work and its session's history are kept. When no round named a session, the worktree has no change since the snapshot, so the host starts the retry round with the brief. The audit after a retry round compares the worktree with the run's one snapshot, as after every round. Every change of the failed round and of its retries is therefore audited against the grant. No round is replayed over changes it made.
Each round's node records the transient error, whether the host retried it and the delay it waited.
The run's error lists every retry in its attempts. Every other ending of a round is handled as
Rounds says. In particular, a round whose audit found a violation is never retried. When
the retries are used up, the run ends failed with the backend's process failure code and the
reason exhausted.
Run record
The run record is the worker run's trace.json. The host
writes it at these times:
- when the run directory is created, with status
running - again after every round, once that round's node is written
- finally when the run ends, whatever ends it
Its uniform fields are Tracing's. They hold the following:
- the times
- the status
- the outcome
- Workers' error link
- the files of the run directory with their digests
- the metadata the node kinds table lists
The status is one of these values:
okblockedfailed
When the configuration named one, and only then, the fields hold the project model name and
reasoning level. The outcome is the status, or interrupted. The record's content is the
worker run trace.
That content also holds the model's local id and the model map it came from.
Each round's node is written when the round's worker launches and again after the round's audit and validation. Its usage holds the following:
- the tokens the agent program reported for the round
- the cache reads and writes the agent program reported for the round
- the cost the agent program reported for the round
- the turns the agent program reported for the round
- the round's duration
Its content is the worker round trace. The
trace's agent keeps what the agent program reported beyond those fields. On the Claude Code
backend, this is the following from the result envelope:
- its subtype
- its error flag
- its turn count
- its cost
- its
permission_denials - its
modelUsage - its
duration_api_ms
For each field, when the envelope gave none, its value is null. Otherwise, its value is as the
envelope gave it. The permission_denials are the tool calls Claude Code refused under the worker
settings. They are evidence for telling a refused worker's
boundary case. The modelUsage gives the tokens and
cost of each model the round used. The duration_api_ms gives the time spent waiting for the
model. The nodes a round's validation placed, such as Concorde's check nodes, lie
below the round's node. Since its rounds hold what it consumed, a worker run's own usage records
nothing.
Tracing is best-effort for the work and never silent
(req.tracing.written-at-start).
When the operating system refuses a write of the run's or a round's trace.json, neither the run
nor its status changes. The host lists every such failure as the run's trace_failures. Each
failure names the following:
- the file
- the moment
- the error
The returned record completes trace_failures with any failure of the final write itself. When
the run does not end ok, the host also lists every such failure as trace-write evidence of its
error.
Besides the files, the host returns the run record to its caller as the returned run record. This is the run node's content, with the ordered list of every round's content in place of the number of rounds. Thus, the caller reads the audits and the round validation's evidence without reading the files. That value is never stored. The trace nodes are the record that is kept.
Errors
Every run that does not end ok has an error. Its top link holds the following:
- the level
workers - the actor
Workers run <run-id> (<task type> worker) - one of the codes below
- a detail that names the items listed below
- the reason Workers cannot handle the error
The detail names the following:
- the round
- the paths concerned
- the commands concerned
- the messages concerned
Its evidence names the worker run's node as trace evidence. The evidence uses the node's
identity with its folder in the detail. Once a session exists, the evidence also names the
transcript. Every code whose round had a write outside rw reports that write, even when the
round also timed out or failed otherwise.
| Code | Detail | Reason | Causes |
|---|---|---|---|
grant_unavailable | which of the task type, grant, context identity or entries is missing | input | none |
grant_malformed | the first grant entry that is not an object with a worktree-relative path and a level of rw, ro or names, and what is wrong with it | input | none |
worktree_misplaced | the worktree, the primary worktree's .claude/worktrees/ where it must lie directly, or what Git said when it found no primary worktree | environment | none |
run_directory_denied | the deny rule that would cover the worker's own directories of the runtime directory | environment | none |
snapshot_failed | the Git command that failed and its output | environment | none |
launch_failed | the command that could not be started and the operating system's error | environment | none |
worker_timeout | the round and the timeout | exhausted | none |
worker_limit_reached | the round and the limit the agent program reported: on Claude Code its turn or budget subtype, on pi the limit and the value the permission extension names | exhausted | the agent process's link: the Claude Code process's link, or the pi process's link with that limit and value |
claude_failed | the round and the error Claude Code reported, or that it printed no envelope (Claude Code backend); attempts lists every retry | environment, or exhausted when the retries for a transient model-service error are used up | the Claude Code process's link |
pi_runtime_missing, pi_failed | see the pi run mechanics | environment, or for pi_failed exhausted when the retries for a transient model-service error are used up | the pi process's link for pi_failed |
worker_result_invalid | the schema violation, or the worker's final text when it gave no structured result | capability | none |
audit_violation | every violating path and the worker's own reported status, or that its result was invalid | permission | the worker's link, when its result was valid and carries an error; none for a valid ok result, whose error is null, or an invalid one |
worker_blocked, worker_failed | the worker's code and detail | capability | the worker's link |
the code the round validation names for a violation, in Concorde audit_violation for a glossary entry another Module owns | every violating entry the round validation names | permission | the links the round validation names, none in Concorde |
the code the round validation names when it cannot validate, in Concorde checks_unavailable | the round and what the round validation reported | environment | the links the round validation names, in Concorde Check execution's |
validation_unavailable | the round and the error the round validation raised instead of answering | capability | none |
the code the round validation names for a repair that fails the run, in Concorde checks_failed | what still needs repair and the rounds used; attempts lists each round's repair | exhausted | the links the round validation names, in Concorde one per failing check, from Check execution |
deletion_failed | every proposed deletion the host performed, every one that failed with the operating system's error, and those it refused or found already absent | environment | the link the run would otherwise have ended with, if any |
cleanup_failed | the transcript the host could not keep and the runtime directory it could not remove, each with the operating system's error | environment | the link the run would otherwise have ended with, if any |
interrupted | what ended the run from outside before it finished, such as a signal or the cancellation of the run that launched it | environment | none |
The Claude Code process's link has the level component. The link states the following:
- the envelope's subtype
- the error flag
- the turn count
- the cost
- the exit status
- the final text
- the reported errors
- the tail of standard error
For the error_max_turns and error_max_budget_usd subtypes, its reason is exhausted.
Otherwise, its reason is environment. The worker's link is the worker result's error with
the level worker. Its actor names the run and the latest session. The worker's link has no
causes. Workers copies it unchanged.
Refusals before a run
When the caller asks, before it calls the host, the configuration reader settles a worker's backend, model and level. At the same time, the reader checks the worker's program and the model map. The caller declares the Operations and worker ids it may launch. Against those declarations, the reader checks every name of the configuration. The reader's refusals therefore come before any worker run exists. None of the following is made for those refusals:
- a run directory
- a progress file
- a run record
Workers writes no error link of its own. Each refusal carries one of these codes and a message. The message names the file concerned, the worker configuration or the model map. The message says what is wrong and how to repair it:
| Code | What the message names | Reason |
|---|---|---|
config_missing | the missing .concorde/workers.json, what it must hold and that it must be committed | input |
config_invalid | the file and its first problem, or the retired file and how to move it | input |
model_not_enabled | the entry, the model, the enabled models and how to repair the entry | input |
model_unresolved | the worker, every entry its model may come from and how to set one | input |
backend_missing | the worker, its program, the source of its backend and how to choose the other program | environment |
model_map_missing | the map's path and what it holds | environment |
model_map_invalid | the map's path, or the relative path CONCORDE_MODEL_MAP gives, and what is wrong | environment |
model_unmapped | the map, each worker with its backend and model and where each came from, and the exact entry to add | environment |
The caller that asked turns a refusal into a component link with the following:
- the actor
Workers (worker configuration) - the code
- the message as its detail
- the reason above
- no causes
The caller makes that link the cause of its own link. In Concorde, Method's steps name the
caller's link worker_model_unavailable.
Requirements
One grant for the whole run
The host SHALL generate the following from one frozen grant for a run:
- settings
- write hook
- tool set
- brief
The grant comes from the request alone
The host SHALL take a worker's grant only from its request, in the shape of the grant input, never computing, widening or completing a grant from any other source.
The round validation follows every clean ok round
When a request gives a round validation, the host SHALL call it once after every round whose worker ended ok with a clean audit, and after no other round.
The round validation's answer is recorded
The host SHALL keep the evidence and outcome the round validation returned in the node of the round it validated.
A worker of every Protocol task type launches
The host SHALL launch a worker of each of the eight Protocol task types.
A task type the Protocol does not define is refused
Before launch, the host SHALL refuse a request naming a task type that is not one of the eight Protocol task types.
Nothing is generated from a malformed grant
Before it generates any settings, write hook or permission extension, the host SHALL refuse to launch a worker whose grant has an entry that is not an object with a non-empty path relative to the task worktree and a level of rw, ro or names.
A malformed grant's refusal names the entry
The refusal of a malformed grant SHALL name the first malformed entry and what is wrong with it.
The run's configuration never changes between rounds
For every round of the run, the host SHALL keep the following unchanged:
- the run's settings
- its write hook
- its tool set
- its brief
Only rw paths are writable by file tools
On the Claude Code backend, the write hook SHALL deny every Edit or Write whose target, with every symbolic link resolved, the final one included, is not in the grant's rw list.
A write is judged by the file it would change. Only when its target is rw too does a symbolic
link at a rw path let a write through. The judgement is tighter than a link's own name. The
pi write table makes the same judgement.
File tools cannot read what the grant withheld when the rules were generated
On the Claude Code backend, the deny rules SHALL forbid Read, Glob and Grep on every worktree path that meets all of these conditions:
- The path exists when the host generates the deny rules.
- Its level is neither
ronorrw. - It lies below none of the run's runtime paths.
Concorde's callers admit nothing else of the worktree beside the grant. A runtime path inside the
worktree, such as .venv or node_modules, is therefore one the tracked worker configuration
lists. Claude Code applies a Read denial to Bash too, and Bash must run the toolchain below the
runtime path. The Harness therefore leaves the runtime path readable
(Claude Code mechanics). The worker configuration's
runtime list therefore decides what a worker may read beside its grant. A change of that list is
a change of the worker configuration. It is reviewed and merged like any other change of the
project.
Bash runs sandboxed without network
On the Claude Code backend, every Bash command of a worker SHALL run in Claude Code's sandbox with no allowed network domain.
An unlisted host is denied, never approved
On the Claude Code backend, the Bash sandbox SHALL use a strict network allowlist, so that a request to a host it does not list is denied rather than approved by the permission mode.
No Bash command runs outside the sandbox
On the Claude Code backend, the worker settings SHALL disable unsandboxed commands, so that a request to run a command outside the sandbox still runs it sandboxed.
The worker never works in the worktree
A worker's working directory SHALL be its runtime directory's work/ directory.
Since the runtime directory is a private directory of its own under the system's temporary
directory, work/ is never the worktree and lies outside the run directory.
A run the deny rules would disable is refused
When a deny rule the host generated covers the runtime directory's work/, home/ or tmp/, the host SHALL refuse to launch a worker.
Tools take absolute paths
The host SHALL tell every worker in its brief to give its tools absolute paths.
The working directory is not the worktree, so a tool needs the absolute path.
Results name paths relative to the worktree
The host SHALL tell every worker in its brief to write every path in the task worktree that its result names relative to the worktree.
Every Operation's output names project paths relative to the worktree, so a result must not copy the tools' form.
Nothing ambient reaches the worker
The host SHALL start every worker round with only the environment variables listed in Launch, or on the pi backend in the pi launch.
The project's interpreter comes first on the worker's PATH
When the request names a project interpreter, the host SHALL start every worker round, on both backends, with that interpreter's directory first on PATH, followed by the host's own PATH.
The brief names the project's interpreter
When the request names a project interpreter, the host SHALL name it in the brief as the interpreter to run the project's code and tests with.
A worker reads beside its grant only what its caller lists
The host SHALL hand the Harness the request's runtime paths exactly as given, adding none, beside a grant and context identity left as the request gave them.
A worker's model comes from its worker configuration alone
The configuration reader SHALL resolve every worker's model and reasoning level from the worktree's worker configuration alone, never from the developer's own agent settings.
A worker without a settled model is refused
When any of these conditions holds, the configuration reader SHALL refuse the worker before launch:
- its worktree has no worker configuration
- its configuration names no model for it
- its configuration names a model outside
enabled_models
A worker's local model id comes from the model map alone
The configuration reader SHALL pass a worker's program the local id that the model map gives the worker's project model name on that program, never the project model name itself.
A worker the model map cannot place is refused
When any of these conditions holds, the configuration reader SHALL refuse the worker before launch:
- its model map is missing
- its model map is malformed
- its model map gives its model no id for its backend
A refusal of the model map names the map
Every refusal the model map causes SHALL name the map's file.
An unmapped model's refusal names the entry to add
The refusal of a worker whose model the map gives no id for its backend SHALL name the exact entry to add to the map.
An Operation's workers are checked against the map at once
When asked to check one Operation's workers against the model map, the configuration reader SHALL refuse with one model_unmapped that names all of these:
- every model and backend the map lacks for those workers
- the workers that would take each
Every refusal before a run has its fixed reason
Every refusal of the configuration reader SHALL carry the code and reason that Refusals before a run list for it and a message naming the file it concerns.
A worker's model calls use the host's proxy
On both backends, the host SHALL pass on to every worker round exactly the proxy variables that Proxy derives from its own environment.
A worker runs only inside .claude/worktrees/
Before it generates any settings, write hook or permission extension, the host SHALL refuse a worker whose worktree does not lie directly in .claude/worktrees/ of its repository's primary worktree.
Workers never see Git
A worker SHALL have no access to Git metadata through any tool.
Every Git administrative path is hidden
On both backends, wherever the repository lies, the host SHALL deny every Git administrative path that Placement lists to the worker's file tools and sandbox.
Every round is audited
The host SHALL audit the worktree against the grant after every round and before its round validation runs.
A violation is never retried
A run whose audit finds a violation SHALL end failed without another round.
Rounds only repair what the round validation reports
Only when all of these conditions hold, the host SHALL start a resume round:
- the worker ended
ok - its audit was clean
- the caller's round validation reported something to repair
A run has at most its configured resume rounds
The host SHALL start at most the configured number of resume rounds in one run.
Resume from the newest session
Each resume round SHALL continue the run's latest session: on the Claude Code backend the session identifier the previous round returned, on the pi backend the session identifier the run fixed at its first round.
A transient model-service error is retried
When the agent process ends a round with a transient model-service error, the round's audit is clean, the worker returned no valid result and the run has retries left, the host SHALL start a retry round after the round's delay.
A retry continues the session
Each retry round SHALL continue the run's latest session as a resume round does, or, when no round named a session and the worktree has no change since the snapshot, start with the brief.
A run has at most its configured retry rounds
The host SHALL start at most the configured number of retry rounds in one run, counted apart from its resume rounds.
Retries wait longer each time
Before the k-th retry round of a run, the host SHALL wait the configured retry delay times
2^(k−1), multiplied by a random factor between 0.5 and 1.
Any other error ends the run at once
The host SHALL NOT retry a round that a timeout, a limit or an error other than a transient model-service error ended, a round whose audit found a violation, or a round whose worker returned a valid result.
Every retry is recorded
The host SHALL record in each round's node the transient model-service error that ended the round,
whether it retried it and the delay it waited, and in the attempts of a failed run's error every
retry.
Worker claims stay claims
The run record SHALL keep the worker result verbatim and separate from the evidence the host observed itself.
A failed run explains itself
Every run that does not end ok SHALL carry Workers' error link with the worker's own error, the agent process's link (Claude Code's or pi's) or the links the round validation names as its causes, as listed in Errors.
Only the host deletes
Only when all of these conditions hold, the host SHALL delete a file:
- the worker proposed the file
- the file, its directories' symbolic links resolved, is in the
rwlist - the last round's audit was clean
Proposed deletions follow every clean last round
Whatever the run's outcome, the host SHALL perform the worker's proposed deletions when all of these conditions hold:
- The last round's audit was clean.
- The last round's worker returned a valid result.
- The round validation did not answer a violation.
- The run was not interrupted.
A repeated deletion is performed once
The host SHALL take a path the worker's proposed_deletions names more than once, relatively or
absolutely, as one proposed deletion.
An absent target is recorded, not refused
When a proposed deletion in the rw list has a path that does not exist, the host SHALL record
that deletion as already absent.
A failed deletion fails the run without stopping the others
When a proposed deletion fails, the host SHALL still attempt every other proposed deletion and end the run failed with deletion_failed, naming what it deleted and what it did not.
The host creates no file for the worker
The host SHALL NOT create any file or directory in the worktree before or while the worker runs.
No worker process outlives its round
When a round ends, the host SHALL kill the worker's whole process group.
A running run shows its progress
On both backends, the host SHALL keep a run's progress file current from preparation until the run is finished.
A run is recorded before its launch
Before it launches the worker, the host SHALL write the run record of every run it was asked to
start, with status running.
Every run leaves a final record
Once the run's node exists, the host SHALL write the final run record of every run it was asked to start when the run ends, a run refused before launch included.
A run whose node could not be written still has its runtime directory removed.
The transcript is kept before the runtime directory goes
Before it removes the run's runtime directory, the host SHALL move the latest session's transcript into the run directory, also when the run was interrupted while its last round ran.
A cleanup that failed fails the run
When the host cannot keep the transcript or remove the runtime directory, the run SHALL end failed with a cleanup_failed error that holds all of these:
- what remains
- the operating system's error
- as its cause, the link the run would otherwise have ended with
Nothing but the trace outlives a worker
When a worker run ends, however it ends, the host SHALL remove its runtime directory with its credential copies, unless the host process itself is killed without a chance to act.
A failed trace write is reported, never silent
The host SHALL report every refused write of the worker run's or a round's trace.json, without changing the run's status for it, in these places:
- the returned run record's
trace_failures - when the run does not end
ok, the evidence of its error
Every round keeps its standard error
The host SHALL keep each round's standard error in that round's node, never overwriting an earlier round's.