Delegating to Codex
The user requests the work; the coordinator chooses and synthesises the composition; one Codex
agent performs one deliverable under rights declared in its prompt.
Everything below is addressed to the coordinator. What reaches the user is prose you write in their own
language: name the agent, say what it did, and say in ordinary words what it may write and where.
Composition
Apply all five rules:
- Announce the composition before starting any Codex run, naming the count and which agents are Codex; a
read agent's rights need no sentence, since nothing is being approved.
- Treat refusal as composition: for “no codex” or “just you”, run zero Codex agents and say the resulting
panel is all-Claude and shares one model bias.
- Attribute every finding; if a Codex agent failed or returned nothing, say so and never backfill it with
a Claude answer.
- Knowing the answer is not a reason to skip a requested second opinion.
- Never add allow-rules on the user's behalf.
| What the user says |
Composition |
| “no codex”, “just you” |
zero Codex agents |
| nothing |
panels, refutation, competing designs: one dissenting Codex agent; mechanical fan-out or one ordinary task: zero |
| “a codex agent”, “one of them codex” |
exactly one |
| “half codex” |
half the agents, rounded up |
| “mostly codex” |
every agent except the coordinator |
| “only codex”, “all codex” |
every agent, including a one-agent task |
| “two of five codex” |
exactly as stated |
A dissenting agent pays for decorrelation; mechanical fan-out does not. “Only codex” means Codex does the
task while the coordinator orchestrates and checks it.
One call
One Agent call per agent: a native subagent, the wrapper, that launches the driver, waits for
it and returns when the run has ended. Only a subagent is a subagent to Claude Code: a Bash task, whatever
its description says, is not on the agent map, is not stopped from it and is not continued by a message
(measured 2026-09-12 against the VS Code extension 2.1.269, whose map lists local_agent tasks alone). The
wrapper is what makes a Codex agent read like a Claude agent: one card under its description, Stop on the
card, one completion notification, and a message to continue it.
Write the prompt with one Bash call, the launcher's --new, which makes the agent's directory beside the
report and takes the prompt on stdin; then spawn the wrapper with the Agent tool:
subagent_type: entrust:codex-agent, run_in_background: false for the one agent you wait for and true
for agents that run side by side or while you work (measured 2026-09-17: a foreground call brings the
hand-back message inside the same turn and no task notification after it, so you answer once — the owner's
native foreground subagent showed one message after the hand-back frame — and an eleven-minute call ended
normally, so the call has no ceiling of its own), and a description of
Codex <short name> <id>: <task in a few words> — Astra for gpt-6-astra, Sol for gpt-5.6-sol,
Terra for gpt-5.6-terra, Luna for gpt-5.6-luna — so the card the user sees names the agent, its
vendor and its task, and not the command line. That type is the agent this plugin ships,
agents/codex-agent.md: a relay with the Bash tool alone and its model pinned
in its own file, so its context is half a general-purpose subagent's (measured 2026-09-12: 8.2k against
15.4k tokens on the same agent). Pass it no model; the agent's model is the MODEL: line in its prompt
file. A clone-and-symlink install links that file into ~/.claude/agents/ (README),
where its type is the bare codex-agent.
The wrapper's message is the block below with its two placeholders filled in and nothing added or
removed: the command and the four steps, which the wrapper's own file repeats (measured 2026-09-17: with the
steps in the file alone, Haiku kept them in one run of three and paraphrased the lines, narrated, and read
the output file in the other two; with them in the message, three of three). It never sees the agent's
prompt. The command is the
launcher scripts/agent-run.mjs, one foreground call and no & of your own: it opens prompt.txt only
as the driver's argument, passes the driver --prompt-file and --report-file and its own environment
untouched, writes the driver's exit status to a file of its own beside the two output files, last, and
prints the nine status lines, which are what the wrapper hands back, so a Codex agent's card shows one Bash
and its return, as a native subagent's does. The launcher is idempotent, which is what the tool's ten-minute
ceiling needs: this harness moves a foreground command that reaches it into the background instead of ending
it, the wrapper runs the same command again, and the second call finds the driver its directory already started
and waits for it (measured 2026-09-17: an eighteen-minute agent took two calls, one driver, one report). The driver prints its pid on the first line of <DIR>/err.txt
once it has accepted the report path, and a refusal before that point prints none; a launch the launcher
itself refused (no prompt.txt, a relative report path, an exit marker already there) puts its reason
there instead, with an exit of 2 and PATH=none. A SIGTERM to that
pid cuts the turn, sweeps its codex and publishes the report as turnStatus: interrupted, exit 1,
nothing left running.
The prompt, one Bash call, the heredoc quoted so nothing in it expands:
node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --new --report-file "<REPORT>" <<'PROMPT'
MODEL: gpt-5.6-terra
TASK: …
CHECK: …
RETURN: …
PROMPT
The Agent call, its message this block:
1. Run this command with the Bash tool, in the foreground, with timeout 600000, and description "<DESCRIPTION>". Write no text before it.
CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --run --report-file "<REPORT>"
2. If its result has no REPORT= line — the harness moved the command into the background at its ceiling, or it was cut — run the very same command again at once, as many times as needed, until a result has one. Each run is safe: the command waits for the run it already started. Do not open, tail or wait on the output file the harness's notice names, and write nothing in between.
3. Call SubagentHandback with exactly the lines that result printed, nothing added, nothing removed.
4. After the hand-back result, and whenever the harness asks you for a visible response, write exactly one line, "<DESCRIPTION>: report delivered", and nothing else.
Both calls may go in one turn: the launcher waits ten seconds for a prompt a --new has not written yet.
<DESCRIPTION> is the Agent call's own description. <DIR>, where this page names it, is the agent's directory,
agent/ beside <REPORT>, which --new makes at 0700 with the prompt at 0600: one per report path, so a
relaunch gets a fresh report path and the earlier run's four files stay where they were (the launcher refuses a
directory that ran for another report; measured 2026-09-17 on the earlier shape, a reused one lost its record),
while the same command run again for the same report reads the run it started, which is what the ceiling's
second call is. None of the launcher's files is left in $TMPDIR; a read agent's own writable root stays there. <REPORT> is an absolute path of this agent's own: put it under
the driver's state directory, <state>/reports/<run>/report.json with <run> unique, or, under the orchestrate
mode, <run>/<agent>/report.json in the run directory that page names, one directory per agent; the launcher and
the driver make every directory those paths need, at 0700, so they may name a root your own Write and mkdir
are refused.
The wrapper's completion notification is the agent's completion: read the wrapper's own lines first —
what the driver exited with, whose run the file at <REPORT> belongs to, whether it is there, the answer
where it is short and its first line where it is not, the refusal where no turn ran, and the receipt — and
read the file itself after a PATH=own when those lines leave a question (measured 2026-09-17: on a one-line
task and on a pre-turn refusal, a hand-back without the answer and the refusal cost the coordinator one more
turn each). To continue an agent, write a second
prompt file with RESUME: <threadId> and send the wrapper one more command of the same shape; it runs it the same way and notifies again (measured 2026-09-12). A session with no
message tool, headless -p among them, continues the thread with a second wrapper given the same file,
at the cost of a second card (measured: the thread held both ways).
Every launch forwards that variable under its own name — the plugin's own data directory, where the
driver's state and every Codex artifact the report names (answerPath, a worktree harvest) live — and
the launcher hands its environment to the driver as it found it. The
driver reads ENTRUST_STATE_DIR first and that variable second, and with neither it exits 2; only
--help needs none. A clone-and-symlink install substitutes nothing for the placeholder, so the forwarded
value is empty there and the ENTRUST_STATE_DIR the user exports decides (README
says where).
A read agent's prompt needs no header at all:
TASK: …
CHECK: …
RETURN: …
For an isolated writer, one rights line above it (see
Worktree lifecycle for what it contains):
RIGHTS: worktree <repo>
Write a prompt you were handed VERBATIM: not a quote, not a $, not a header line it has, and add
nothing. A prompt with no RIGHTS: line is a read agent in the current directory; the driver decides that,
not you. Never create a directory, change a level or re-run with different flags to make a refused agent
succeed: measured, a wrapper that created the missing directory ran Codex with rights nobody granted.
Rights
Choose the smallest RIGHTS that can complete and check the work:
| Prompt header |
Codex may |
Settle first? |
RIGHTS: read [<dir>] or no header |
read any readable path, reach the network, run commands, write only $TMPDIR; the sandbox refuses a write anywhere else, and an approval request in its place is declined and recorded in escalations |
no |
RIGHTS: worktree <repo> |
write in a driver-managed detached tree |
say that a worktree will be made |
RIGHTS: write <dir> |
write under the live directory |
yes; this chooses the blast radius |
$TMPDIR is granted at every level and /tmp at none; a write agent adds each settled WRITABLE: root
to what its row names. The driver refuses a server whose sandbox answers differently.
Every level reaches the network, as a native subagent does, and NETWORK: no denies the sandbox that —
not the provider's web search, which is WEB_SEARCH:'s own channel. Egress moves nothing on disk:
whatever an agent can read it can send, which at read level is every readable path. Each WRITABLE: <dir>
widens a write agent, as does removing a NETWORK: no the user settled: settle each with the user before
adding it, and never translate a refusal into broader rights. Every field is in
Header fields below; model, effort, gates, continuation and answer-shape choices
belong in that header, and the agent's rights in its RIGHTS: line, which is why the prompt is copied
into the file rather than rewritten: measured, a wrapper that rewrote one widened malformed rights and
reported false success
(A relay on a small model).
Read agents may share one cwd, but a repository whose tooling keeps a daemon, a socket, or a pid/state
file needs a distinct cwd or its own TMPDIR per concurrent agent; the failure is a native crash, not a
sandbox refusal.
A write agent sharing a live tree must not change what the tree shares: no stash, branch switch, reset,
clean or rebase while another writer holds part of it. Those move or discard work the other agent is
still editing, and no sandbox refuses them.
Header fields
The header is the leading run of upper-case NAME: value lines at column 0; the body starts at TASK: or
at the first line that is not one; a non-field upper-case NAME: above it is exit 2 naming it.
| Field |
Value (booleans: yes, true or 1; no line means off, and for NETWORK: means on) |
A coordinator sets it when |
RIGHTS: |
read [<dir>], worktree <repo>, write <dir> |
first, or not at all: no header is a read agent in the current directory |
NETWORK: |
no |
this agent's own commands must not reach the network; no line leaves it the egress every level has, and WEB_SEARCH: is untouched either way |
WRITABLE: |
<dir>, repeatable |
a write agent needs one more root than the directory it was given |
RESUME: |
<threadId>, last |
this agent continues an earlier thread instead of opening one |
EXPECT: |
<regex> |
the answer is only evidence if a command matching it ran AND succeeded; a matching command that exited non-zero does not count, and none matching is exit 5. Do not point it at a check whose failure IS the finding |
OUTPUT_SCHEMA: |
<path to a strict JSON Schema file> |
the answer must parse as one JSON object |
MODEL: |
<slug>: gpt-6-astra (Astra), gpt-5.6-sol (Sol), gpt-5.6-terra (Terra), gpt-5.6-luna (Luna) |
this agent needs a model other than the configured default; the short name is for prose, the slug for this line |
EFFORT: |
low, medium, high, xhigh, max; ultra on Astra, Sol and Terra (the catalogue of 2026-09-17: none and minimal are on no model and exit 2 before the turn); no line inherits ~/.codex/config.toml |
the task is worth more or less thinking than the configured default; low for a one-line task |
WEB_SEARCH: |
cached, indexed, live |
the agent needs sources it cannot read locally |
BRIEF: |
yes |
a short answer is enough; omit it beside an output schema — it clips only the inline answer (answerJson is parsed from the whole one) yet still asks the model for 20 lines |
ALLOW_NO_COMMANDS: |
yes |
the agent is recall-only and will run nothing |
One field is missing from that table on purpose. VERIFY is refused in a prompt file without --allow-prompt-verify,
a flag the one call above does not pass: it runs a caller-declared command after the turn, so an agent that could
write its own would be grading itself. Declare gates on the command line instead
(result-gates.md).
Worktree lifecycle
- A new thread's worktree starts at current
HEAD; a resumed worktree starts at its recorded base and
restores its harvested diff and untracked files; neither copies live edits nor applies a stash.
- Staged, unstaged, untracked, ignored and installed files are absent. To put current work in, commit it
first with the user's approval, or use an authorised live tree.
- The driver creates the tree under the repository's own
.claude/worktrees/, and removes it after a
successful harvest.
- A completed turn harvests tracked work to
worktreeDiffPath.
- It archives non-ignored untracked files at
worktreeUntrackedPath; worktreeCommitsRef is populated
only where the caller's own --verify committed — an agent cannot commit without WRITABLE: <repo>/.git,
a widening to settle first.
- After a successful harvest the driver removes the worktree.
- When the turn failed or harvest failed, the driver preserves it and reports
worktreePreserved.
- A worktree run cut or refused before its turn reports those same two fields:
worktreePath is the
path the run named, and worktreePreserved the reason it was left there, or null where it was
removed. A git worktree add that failed over a destination already on disk names that destination,
which is not a tree this run made; a --resume rebuild that could not finish tries to remove its
half-restored tree and reports null where it did, or the refusal where git kept it.
- A preserved tree is not a harvest:
worktreeDiffPath, worktreeUntrackedPath and worktreeCommitsRef
can all be null, so the landing recipe has nothing to apply. The tree itself is the artifact, at
worktreePath; read it, take what is worth keeping, then remove it with
git -C <repo> worktree remove --force <path>. Removing it discards whatever was never harvested.
Reading the result
<REPORT> is the report, the same JSON the run also wrote to <DIR>/out.json once a turn ran. Read
the file: it is written whole or not at all, and a missing one means unknown, never success.
- Under a background call, the hand-back message and the task notification that follows it are one
completion: read the first, and give the second the shortest reply the harness accepts (measured
2026-09-17: a coordinator told the user that the notification duplicated the answer). A foreground call
has no notification.
- On
EXIT=0 what reaches the user is the agent's name and its answer; the other lines are yours and stay with
you (measured 2026-09-17: two coordinators retold RECEIPT= and the report's model field, slug included, as
prose, so the status line now carries the short name).
PATH=own says the driver accepted <REPORT> and published there; PATH=taken says an entry was
already there or another run published first, so the file is an earlier run's, whatever the numbers
beside it say; PATH=none says the path was never accepted and no file of this run's exists.
DRIVER_EXIT is what this invocation's driver exited with, EXIT the code inside the file.
- A refused path — not absolute, an unusable parent, an entry already there, a symlink included — makes
no report for this run; an entry already there is left as it was, and
<DIR>/err.txt names the
refusal. Once the path is accepted, a refusal before the turn does reach the file, as
{ok: false, exitCode, turnStatus: null, error}, while out.json stays empty.
FILE=missing beside a DRIVER_EXIT is a run that ended without a report of its own: read
<DIR>/err.txt for the reason and <DIR>/out.json for the report a turn wrote where publication
failed; otherwise treat the result as unknown, and relaunch under a fresh report path where the work
still needs doing.
exitCode: 0 means the completed turn passed its declared evidence gates. answer is the agent's text;
with an OUTPUT_SCHEMA: line, answerJson is that answer already parsed.
exitCode: 3 is a cut; read the retained answer or partial and the RESUME: hint. Give the continuation a
report path of its own: the driver refuses one already taken and exits 2 without publishing, which
reaches you as PATH=taken over the earlier run's file.
exitCode: 10 is a held lock or a busy resumed thread: the report says ok: false and carries the
refusal in error, and <DIR>/err.txt has it in full.
- Exit 2 has two shapes, and the report tells them apart. With
turnStatus: null no turn ran: the reason
is in error and there is no receipt. With any other turnStatus the turn ran and the server rejected
the request: the reason is in turnError, and the commands, any retained answer and the receipt are
real. Read them before relaunching, or a paid turn is thrown away.
- Exit 4 has two shapes. With
turnStatus: null it is a refusal or an abort (a sandbox assertion, a
signal before the thread, a transport failure): read error and <DIR>/err.txt; a threadId beside
it means the thread had started and its rollout is the only record. With any other turnStatus — the
server died mid-turn, or the report could not be published — the report is complete: read it like any
post-turn code (commands, answer, answerPath, receipt).
escalations is one entry per approval request the driver declined, whichever thread asked, and
exitCode: 6 is its rung — below timeout and the other cuts, so a cut run carries its entries and
exits 3. An entry says a request was made and refused and no more: detail is the server's own wording
clipped to 200 characters and is empty where it sent none, a command the sandbox denied outright need
not raise one, and an entry is neither evidence that work was lost nor a reason to widen the rights.
- Any other non-zero is a gate verdict on the run; read the answer before deciding what to do.
receiptOk: false on a run that claims success is a red flag; what the receipt proves and does not
prove is in
environment-and-internals.md.
- Evidence of success is root-thread-only: a Codex subagent thread's commands are liveness, not evidence.
- To stop an agent, stop its wrapper — Stop on the agent map or
TaskStop — or send SIGTERM to the pid on the first line of <DIR>/err.txt:
the driver interrupts the turn, writes the report it had earned and sweeps the codex process group.
Prompt shape
Write a concrete, checkable body:
TASK: what to do
CHECK: the ground truth, preferably something the agent cannot guess
RETURN: exactly what to hand back
Give one deliverable per agent. Split a return that asks for unrelated artifacts or decisions. Write TASK: in the
user's language: the agent answers in the language it is asked in (measured 2026-09-17: a task written in English
about a Russian «хай» came back in English). Whatever RETURN:
asks for, its first line is one sentence a reader can take on its own: the name you gave the agent in the prompt
("you are Codex Terra T1"), its status and what it did. Give the name; the model does not know its short name and
answers with whatever it calls itself (measured 2026-09-17: «GPT-5 Codex, id T1»). That line is what the coordinator
retells, and not itself a message to the user; the rest is the return's own shape.
The standing rules are already on the thread — unattended, its egress and its web search each named
whichever way they went, a one-line record for a step that cannot run (the command, whether it started, its
exit status if any, the exact diagnostic), never claim a test passed without the count — so do not repeat them. A follow-up continues a thread with RESUME: <threadId>; a
recall-only one runs no commands, so it also needs ALLOW_NO_COMMANDS: yes (--allow-no-commands on a
command line).
What the user reads
Every word on this page is addressed to the coordinator, and an agent's return is too. What reaches the user is
prose the coordinator writes: in the user's own language, naming an agent by its model and id and saying what it
did ("Sonnet W5 replaced four flaky width checks", "Codex Astra A6 reviewed the retry instructions") and not by
this page's own vocabulary. Keep Codex on a Codex agent: it is the only word in the name that says whose model ran. The sentence about an agent has one shape: the agent by name is the subject and what it does or did is the verb ("Codex Sol R1 reads the diff"); whatever runs beside it, and how long, follows in the user's own words for the tools. The model slug is machinery too, and so are wrapper and driver: the name is Codex Sol R1, never gpt-5.6-sol. A header field name, a status block, an internal
table's row name and an absolute path are machinery; they belong in a prompt or a report, and putting them in
front of a person says nothing they can act on. Rights are the one thing that must survive the translation: say
what an agent may write, and where, in ordinary words, because that is what the user is being asked to approve.
Traps
- Phrase defensive work as robustness under unusual states; attack wording can trip a safety classifier.
- Read a non-zero result's answer; the exit judges evidence, not whether the answer exists.
- Treat
commandsPipedToPager as sliced evidence: head, tail, and less can hide a failure and supply
the pipeline status.
- Arm cleanup before background load and record each pid as it starts; trailing cleanup can orphan load.
References
node "${CLAUDE_SKILL_DIR}/scripts/driver.mjs" --help is the canonical inventory of the flags a coordinator sets; --help-all adds the rarely needed ones, the ENTRUST_* variables and the internals.
node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --help is what the wrapper's one command does: the run, its refusals and the nine status lines.
- Flags, fields, delivery, bounds, environment, receipts, and worktree internals:
environment-and-internals.md.
- Evidence gates and verifier semantics: result-gates.md.
- Capability and concurrency parity: parity.md.
- The measured failures behind the rules: incidents.md.
- Commit blast radius: environment-and-internals.md.
- Locks: environment-and-internals.md.
- Config drift: environment-and-internals.md.
- Pasted images: parity.md.
- Browser tests: parity.md.
- Adversarial review: adversarial-review.md.
- Integration alternatives: why-not-the-plugin.md.
- Installation and upgrades: README.md.
1---2name: codex3description: Delegates tasks to Codex as a subagent with per-call rights: analysis that writes nothing of yours, or writing and tests in a managed git worktree, each reaching the network unless the call denies it. Use when a panel, refuters, or competing designs need an agent that does not share Claude's bias; when fanning out reviewers or adversarial verifiers; after two hypotheses fail; when a second independent implementation is wanted; or when the user names Codex, GPT, or "the other model" (через codex, через gpt, вторая имплементация, панель ревьюеров), or names a Codex model by its short name (Astra, Sol, Terra, Luna; астра, сол, терра, луна). It also governs requested mixes ("one of them codex", "half codex", "only codex") and refusals ("no codex", "just you"). Skip trivia and mechanical fact-gathering.4license: MIT5---67# Delegating to Codex89The **user** requests the work; the **coordinator** chooses and synthesises the composition; one Codex10**agent** performs one deliverable under rights declared in its prompt.1112Everything below is addressed to the coordinator. What reaches the user is prose you write in their own13language: name the agent, say what it did, and say in ordinary words what it may write and where.1415## Composition1617Apply all five rules:18191. Announce the composition **before** starting any Codex run, naming the count and which agents are Codex; a20 read agent's rights need no sentence, since nothing is being approved.212. Treat refusal as composition: for “no codex” or “just you”, run zero Codex agents and say the resulting22 panel is all-Claude and shares one model bias.233. Attribute every finding; if a Codex agent failed or returned nothing, say so and never backfill it with24 a Claude answer.254. Knowing the answer is not a reason to skip a requested second opinion.265. Never add allow-rules on the user's behalf.2728| What the user says | Composition |29| --- | --- |30| “no codex”, “just you” | zero Codex agents |31| nothing | panels, refutation, competing designs: one dissenting Codex agent; mechanical fan-out or one ordinary task: zero |32| “a codex agent”, “one of them codex” | exactly one |33| “half codex” | half the agents, rounded up |34| “mostly codex” | every agent except the coordinator |35| “only codex”, “all codex” | every agent, including a one-agent task |36| “two of five codex” | exactly as stated |3738A dissenting agent pays for decorrelation; mechanical fan-out does not. “Only codex” means Codex does the39task while the coordinator orchestrates and checks it.4041## One call4243One Agent call per agent: a native subagent, the **wrapper**, that launches the driver, waits for44it and returns when the run has ended. Only a subagent is a subagent to Claude Code: a Bash task, whatever45its description says, is not on the agent map, is not stopped from it and is not continued by a message46(measured 2026-09-12 against the VS Code extension 2.1.269, whose map lists `local_agent` tasks alone). The47wrapper is what makes a Codex agent read like a Claude agent: one card under its description, Stop on the48card, one completion notification, and a message to continue it.4950Write the prompt with one Bash call, the launcher's `--new`, which makes the agent's directory beside the51report and takes the prompt on stdin; then spawn the wrapper with the Agent tool:52`subagent_type: entrust:codex-agent`, `run_in_background: false` for the one agent you wait for and `true`53for agents that run side by side or while you work (measured 2026-09-17: a foreground call brings the54hand-back message inside the same turn and no task notification after it, so you answer once — the owner's55native foreground subagent showed one message after the hand-back frame — and an eleven-minute call ended56normally, so the call has no ceiling of its own), and a `description` of57`Codex <short name> <id>: <task in a few words>` — `Astra` for `gpt-6-astra`, `Sol` for `gpt-5.6-sol`,58`Terra` for `gpt-5.6-terra`, `Luna` for `gpt-5.6-luna` — so the card the user sees names the agent, its59vendor and its task, and not the command line. That type is the agent this plugin ships,60[agents/codex-agent.md](../../agents/codex-agent.md): a relay with the Bash tool alone and its model pinned61in its own file, so its context is half a `general-purpose` subagent's (measured 2026-09-12: 8.2k against6215.4k tokens on the same agent). Pass it no `model`; the agent's model is the `MODEL:` line in its prompt63file. A clone-and-symlink install links that file into `~/.claude/agents/` ([README](../../README.md#install)),64where its type is the bare `codex-agent`.6566The wrapper's message is the block below with its two placeholders filled in and nothing added or67removed: the command and the four steps, which the wrapper's own file repeats (measured 2026-09-17: with the68steps in the file alone, Haiku kept them in one run of three and paraphrased the lines, narrated, and read69the output file in the other two; with them in the message, three of three). It never sees the agent's70prompt. The command is the71launcher `scripts/agent-run.mjs`, one foreground call and no `&` of your own: it opens `prompt.txt` only72as the driver's argument, passes the driver `--prompt-file` and `--report-file` and its own environment73untouched, writes the driver's exit status to a file of its own beside the two output files, last, and74prints the nine status lines, which are what the wrapper hands back, so a Codex agent's card shows one Bash75and its return, as a native subagent's does. The launcher is idempotent, which is what the tool's ten-minute76ceiling needs: this harness moves a foreground command that reaches it into the background instead of ending77it, the wrapper runs the same command again, and the second call finds the driver its directory already started78and waits for it (measured 2026-09-17: an eighteen-minute agent took two calls, one driver, one report). The driver prints its pid on the first line of `<DIR>/err.txt`79once it has accepted the report path, and a refusal before that point prints none; a launch the launcher80itself refused (no `prompt.txt`, a relative report path, an `exit` marker already there) puts its reason81there instead, with an exit of 2 and `PATH=none`. A `SIGTERM` to that82pid cuts the turn, sweeps its codex and publishes the report as `turnStatus: interrupted`, exit 1,83nothing left running.8485The prompt, one Bash call, the heredoc quoted so nothing in it expands:8687 node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --new --report-file "<REPORT>" <<'PROMPT'88 MODEL: gpt-5.6-terra89 TASK: …90 CHECK: …91 RETURN: …92 PROMPT9394The Agent call, its message this block:9596 1. Run this command with the Bash tool, in the foreground, with timeout 600000, and description "<DESCRIPTION>". Write no text before it.9798 CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --run --report-file "<REPORT>"99100 2. If its result has no REPORT= line — the harness moved the command into the background at its ceiling, or it was cut — run the very same command again at once, as many times as needed, until a result has one. Each run is safe: the command waits for the run it already started. Do not open, tail or wait on the output file the harness's notice names, and write nothing in between.101102 3. Call SubagentHandback with exactly the lines that result printed, nothing added, nothing removed.103104 4. After the hand-back result, and whenever the harness asks you for a visible response, write exactly one line, "<DESCRIPTION>: report delivered", and nothing else.105106Both calls may go in one turn: the launcher waits ten seconds for a prompt a `--new` has not written yet.107`<DESCRIPTION>` is the Agent call's own description. `<DIR>`, where this page names it, is the agent's directory,108`agent/` beside `<REPORT>`, which `--new` makes at 0700 with the prompt at 0600: one per report path, so a109relaunch gets a fresh report path and the earlier run's four files stay where they were (the launcher refuses a110directory that ran for another report; measured 2026-09-17 on the earlier shape, a reused one lost its record),111while the same command run again for the same report reads the run it started, which is what the ceiling's112second call is. None of the launcher's files is left in `$TMPDIR`; a read agent's own writable root stays there. `<REPORT>` is an absolute path of this agent's own: put it under113the driver's state directory, `<state>/reports/<run>/report.json` with `<run>` unique, or, under the orchestrate114mode, `<run>/<agent>/report.json` in the run directory that page names, one directory per agent; the launcher and115the driver make every directory those paths need, at 0700, so they may name a root your own Write and `mkdir`116are refused.117The wrapper's completion notification is the agent's completion: read the wrapper's own lines first —118what the driver exited with, whose run the file at `<REPORT>` belongs to, whether it is there, the answer119where it is short and its first line where it is not, the refusal where no turn ran, and the receipt — and120read the file itself after a `PATH=own` when those lines leave a question (measured 2026-09-17: on a one-line121task and on a pre-turn refusal, a hand-back without the answer and the refusal cost the coordinator one more122turn each). To continue an agent, write a second123prompt file with `RESUME: <threadId>` and send the wrapper one more command of the same shape; it runs it the same way and notifies again (measured 2026-09-12). A session with no124message tool, headless `-p` among them, continues the thread with a second wrapper given the same file,125at the cost of a second card (measured: the thread held both ways).126127Every launch forwards that variable under its own name — the plugin's own data directory, where the128driver's state and every Codex artifact the report names (`answerPath`, a worktree harvest) live — and129the launcher hands its environment to the driver as it found it. The130driver reads `ENTRUST_STATE_DIR` first and that variable second, and with neither it exits 2; only131`--help` needs none. A clone-and-symlink install substitutes nothing for the placeholder, so the forwarded132value is empty there and the `ENTRUST_STATE_DIR` the user exports decides ([README](../../README.md)133says where).134135A read agent's prompt needs no header at all:136137 TASK: …138 CHECK: …139 RETURN: …140141For an isolated writer, one rights line above it (see142[Worktree lifecycle](#worktree-lifecycle) for what it contains):143144 RIGHTS: worktree <repo>145146Write a prompt you were handed VERBATIM: not a quote, not a `$`, not a header line it has, and add147nothing. A prompt with no `RIGHTS:` line is a read agent in the current directory; the driver decides that,148not you. Never create a directory, change a level or re-run with different flags to make a refused agent149succeed: measured, a wrapper that created the missing directory ran Codex with rights nobody granted.150151## Rights152153Choose the smallest `RIGHTS` that can complete and check the work:154155| Prompt header | Codex may | Settle first? |156| --- | --- | --- |157| `RIGHTS: read [<dir>]` or no header | read any readable path, reach the network, run commands, write only `$TMPDIR`; the sandbox refuses a write anywhere else, and an approval request in its place is declined and recorded in `escalations` | no |158| `RIGHTS: worktree <repo>` | write in a driver-managed detached tree | say that a worktree will be made |159| `RIGHTS: write <dir>` | write under the live directory | yes; this chooses the blast radius |160161`$TMPDIR` is granted at every level and `/tmp` at none; a write agent adds each settled `WRITABLE:` root162to what its row names. The driver refuses a server whose sandbox answers differently.163164Every level reaches the network, as a native subagent does, and `NETWORK: no` denies the sandbox that —165not the provider's web search, which is `WEB_SEARCH:`'s own channel. Egress moves nothing on disk:166whatever an agent can read it can send, which at read level is every readable path. Each `WRITABLE: <dir>`167widens a write agent, as does removing a `NETWORK: no` the user settled: settle each with the user before168adding it, and never translate a refusal into broader rights. Every field is in169[Header fields](#header-fields) below; model, effort, gates, continuation and answer-shape choices170belong in that header, and the agent's rights in its `RIGHTS:` line, which is why the prompt is copied171into the file rather than rewritten: measured, a wrapper that rewrote one widened malformed rights and172reported false success173([A relay on a small model](references/incidents.md#a-relay-on-a-small-model)).174175Read agents may share one cwd, but a repository whose tooling keeps a daemon, a socket, or a pid/state176file needs a distinct cwd or its own `TMPDIR` per concurrent agent; the failure is a native crash, not a177sandbox refusal.178179A write agent sharing a live tree must not change what the tree shares: no stash, branch switch, reset,180clean or rebase while another writer holds part of it. Those move or discard work the other agent is181still editing, and no sandbox refuses them.182183## Header fields184185The header is the leading run of upper-case `NAME: value` lines at column 0; the body starts at `TASK:` or186at the first line that is not one; a non-field upper-case `NAME:` above it is exit 2 naming it.187188| Field | Value (booleans: `yes`, `true` or `1`; no line means off, and for `NETWORK:` means on) | A coordinator sets it when |189| --- | --- | --- |190| `RIGHTS:` | `read [<dir>]`, `worktree <repo>`, `write <dir>` | first, or not at all: no header is a read agent in the current directory |191| `NETWORK:` | `no` | this agent's own commands must not reach the network; no line leaves it the egress every level has, and `WEB_SEARCH:` is untouched either way |192| `WRITABLE:` | `<dir>`, repeatable | a write agent needs one more root than the directory it was given |193| `RESUME:` | `<threadId>`, `last` | this agent continues an earlier thread instead of opening one |194| `EXPECT:` | `<regex>` | the answer is only evidence if a command matching it ran AND succeeded; a matching command that exited non-zero does not count, and none matching is exit 5. Do not point it at a check whose failure IS the finding |195| `OUTPUT_SCHEMA:` | `<path to a strict JSON Schema file>` | the answer must parse as one JSON object |196| `MODEL:` | `<slug>`: `gpt-6-astra` (Astra), `gpt-5.6-sol` (Sol), `gpt-5.6-terra` (Terra), `gpt-5.6-luna` (Luna) | this agent needs a model other than the configured default; the short name is for prose, the slug for this line |197| `EFFORT:` | `low`, `medium`, `high`, `xhigh`, `max`; `ultra` on Astra, Sol and Terra (the catalogue of 2026-09-17: `none` and `minimal` are on no model and exit 2 before the turn); no line inherits `~/.codex/config.toml` | the task is worth more or less thinking than the configured default; `low` for a one-line task |198| `WEB_SEARCH:` | `cached`, `indexed`, `live` | the agent needs sources it cannot read locally |199| `BRIEF:` | `yes` | a short answer is enough; omit it beside an output schema — it clips only the inline `answer` (`answerJson` is parsed from the whole one) yet still asks the model for 20 lines |200| `ALLOW_NO_COMMANDS:` | `yes` | the agent is recall-only and will run nothing |201202One field is missing from that table on purpose. `VERIFY` is refused in a prompt file without `--allow-prompt-verify`,203a flag the one call above does not pass: it runs a caller-declared command after the turn, so an agent that could204write its own would be grading itself. Declare gates on the command line instead205([result-gates.md](references/result-gates.md)).206207## Worktree lifecycle208209- A new thread's worktree starts at current `HEAD`; a resumed worktree starts at its recorded base and210 restores its harvested diff and untracked files; neither copies live edits nor applies a stash.211- Staged, unstaged, untracked, ignored and installed files are absent. To put current work in, commit it212 first with the user's approval, or use an authorised live tree.213- The driver creates the tree under the repository's own `.claude/worktrees/`, and removes it after a214 successful harvest.215- A completed turn harvests tracked work to `worktreeDiffPath`.216- It archives non-ignored untracked files at `worktreeUntrackedPath`; `worktreeCommitsRef` is populated217 only where the caller's own `--verify` committed — an agent cannot commit without `WRITABLE: <repo>/.git`,218 a widening to settle first.219- After a successful harvest the driver removes the worktree.220- When the turn failed or harvest failed, the driver preserves it and reports `worktreePreserved`.221- A worktree run cut or refused before its turn reports those same two fields: `worktreePath` is the222 path the run named, and `worktreePreserved` the reason it was left there, or `null` where it was223 removed. A `git worktree add` that failed over a destination already on disk names that destination,224 which is not a tree this run made; a `--resume` rebuild that could not finish tries to remove its225 half-restored tree and reports `null` where it did, or the refusal where git kept it.226- A preserved tree is not a harvest: `worktreeDiffPath`, `worktreeUntrackedPath` and `worktreeCommitsRef`227 can all be null, so the landing recipe has nothing to apply. The tree itself is the artifact, at228 `worktreePath`; read it, take what is worth keeping, then remove it with229 `git -C <repo> worktree remove --force <path>`. Removing it discards whatever was never harvested.230231## Reading the result232233- `<REPORT>` is the report, the same JSON the run also wrote to `<DIR>/out.json` once a turn ran. Read234 the file: it is written whole or not at all, and a missing one means unknown, never success.235- Under a background call, the hand-back message and the task notification that follows it are one236 completion: read the first, and give the second the shortest reply the harness accepts (measured237 2026-09-17: a coordinator told the user that the notification duplicated the answer). A foreground call238 has no notification.239- On `EXIT=0` what reaches the user is the agent's name and its answer; the other lines are yours and stay with240 you (measured 2026-09-17: two coordinators retold `RECEIPT=` and the report's model field, slug included, as241 prose, so the status line now carries the short name).242- `PATH=own` says the driver accepted `<REPORT>` and published there; `PATH=taken` says an entry was243 already there or another run published first, so the file is an earlier run's, whatever the numbers244 beside it say; `PATH=none` says the path was never accepted and no file of this run's exists.245 `DRIVER_EXIT` is what this invocation's driver exited with, `EXIT` the code inside the file.246- A refused path — not absolute, an unusable parent, an entry already there, a symlink included — makes247 no report for this run; an entry already there is left as it was, and `<DIR>/err.txt` names the248 refusal. Once the path is accepted, a refusal before the turn does reach the file, as249 `{ok: false, exitCode, turnStatus: null, error}`, while `out.json` stays empty.250- `FILE=missing` beside a `DRIVER_EXIT` is a run that ended without a report of its own: read251 `<DIR>/err.txt` for the reason and `<DIR>/out.json` for the report a turn wrote where publication252 failed; otherwise treat the result as unknown, and relaunch under a fresh report path where the work253 still needs doing.254- `exitCode: 0` means the completed turn passed its declared evidence gates. `answer` is the agent's text;255 with an `OUTPUT_SCHEMA:` line, `answerJson` is that answer already parsed.256- `exitCode: 3` is a cut; read the retained answer or partial and the `RESUME:` hint. Give the continuation a257 report path of its own: the driver refuses one already taken and exits 2 without publishing, which258 reaches you as `PATH=taken` over the earlier run's file.259- `exitCode: 10` is a held lock or a busy resumed thread: the report says `ok: false` and carries the260 refusal in `error`, and `<DIR>/err.txt` has it in full.261- Exit 2 has two shapes, and the report tells them apart. With `turnStatus: null` no turn ran: the reason262 is in `error` and there is no receipt. With any other `turnStatus` the turn ran and the server rejected263 the request: the reason is in `turnError`, and the commands, any retained answer and the receipt are264 real. Read them before relaunching, or a paid turn is thrown away.265- Exit 4 has two shapes. With `turnStatus: null` it is a refusal or an abort (a sandbox assertion, a266 signal before the thread, a transport failure): read `error` and `<DIR>/err.txt`; a `threadId` beside267 it means the thread had started and its rollout is the only record. With any other `turnStatus` — the268 server died mid-turn, or the report could not be published — the report is complete: read it like any269 post-turn code (commands, `answer`, `answerPath`, receipt).270- `escalations` is one entry per approval request the driver declined, whichever thread asked, and271 `exitCode: 6` is its rung — below timeout and the other cuts, so a cut run carries its entries and272 exits 3. An entry says a request was made and refused and no more: `detail` is the server's own wording273 clipped to 200 characters and is empty where it sent none, a command the sandbox denied outright need274 not raise one, and an entry is neither evidence that work was lost nor a reason to widen the rights.275- Any other non-zero is a gate verdict on the run; read the answer before deciding what to do.276- `receiptOk: false` on a run that claims success is a red flag; what the receipt proves and does not277 prove is in278 [environment-and-internals.md](references/environment-and-internals.md#receipt-validation-and-reporting).279- Evidence of success is root-thread-only: a Codex subagent thread's commands are liveness, not evidence.280- To stop an agent, stop its wrapper — Stop on the agent map or `TaskStop` — or send `SIGTERM` to the pid on the first line of `<DIR>/err.txt`:281 the driver interrupts the turn, writes the report it had earned and sweeps the codex process group.282283## Prompt shape284285Write a concrete, checkable body:286287 TASK: what to do288 CHECK: the ground truth, preferably something the agent cannot guess289 RETURN: exactly what to hand back290291Give one deliverable per agent. Split a return that asks for unrelated artifacts or decisions. Write `TASK:` in the292user's language: the agent answers in the language it is asked in (measured 2026-09-17: a task written in English293about a Russian «хай» came back in English). Whatever `RETURN:`294asks for, its first line is one sentence a reader can take on its own: the name you gave the agent in the prompt295("you are Codex Terra T1"), its status and what it did. Give the name; the model does not know its short name and296answers with whatever it calls itself (measured 2026-09-17: «GPT-5 Codex, id T1»). That line is what the coordinator297retells, and not itself a message to the user; the rest is the return's own shape.298299The standing rules are already on the thread — unattended, its egress and its web search each named300whichever way they went, a one-line record for a step that cannot run (the command, whether it started, its301exit status if any, the exact diagnostic), never claim a test passed without the count — so do not repeat them. A follow-up continues a thread with `RESUME: <threadId>`; a302recall-only one runs no commands, so it also needs `ALLOW_NO_COMMANDS: yes` (`--allow-no-commands` on a303command line).304305## What the user reads306307Every word on this page is addressed to the coordinator, and an agent's return is too. What reaches the user is308prose the coordinator writes: in the user's own language, naming an agent by its model and id and saying what it309did ("Sonnet W5 replaced four flaky width checks", "Codex Astra A6 reviewed the retry instructions") and not by310this page's own vocabulary. Keep `Codex` on a Codex agent: it is the only word in the name that says whose model ran. The sentence about an agent has one shape: the agent by name is the subject and what it does or did is the verb ("Codex Sol R1 reads the diff"); whatever runs beside it, and how long, follows in the user's own words for the tools. The model slug is machinery too, and so are `wrapper` and `driver`: the name is `Codex Sol R1`, never `gpt-5.6-sol`. A header field name, a status block, an internal311table's row name and an absolute path are machinery; they belong in a prompt or a report, and putting them in312front of a person says nothing they can act on. Rights are the one thing that must survive the translation: say313what an agent may write, and where, in ordinary words, because that is what the user is being asked to approve.314315## Traps316317- Phrase defensive work as robustness under unusual states; attack wording can trip a safety classifier.318- Read a non-zero result's answer; the exit judges evidence, not whether the answer exists.319- Treat `commandsPipedToPager` as sliced evidence: `head`, `tail`, and `less` can hide a failure and supply320 the pipeline status.321- Arm cleanup before background load and record each pid as it starts; trailing cleanup can orphan load.322323## References324325- `node "${CLAUDE_SKILL_DIR}/scripts/driver.mjs" --help` is the canonical inventory of the flags a coordinator sets; `--help-all` adds the rarely needed ones, the `ENTRUST_*` variables and the internals.326- `node "${CLAUDE_SKILL_DIR}/scripts/agent-run.mjs" --help` is what the wrapper's one command does: the run, its refusals and the nine status lines.327- Flags, fields, delivery, bounds, environment, receipts, and worktree internals:328 [environment-and-internals.md](references/environment-and-internals.md).329- Evidence gates and verifier semantics: [result-gates.md](references/result-gates.md).330- Capability and concurrency parity: [parity.md](references/parity.md).331- The measured failures behind the rules: [incidents.md](references/incidents.md).332- Commit blast radius: [environment-and-internals.md](references/environment-and-internals.md#git-directory-grant).333- Locks: [environment-and-internals.md](references/environment-and-internals.md#lock-design).334- Config drift: [environment-and-internals.md](references/environment-and-internals.md#configuration-key-oracle).335- Pasted images: [parity.md](references/parity.md#pasted-media-handling).336- Browser tests: [parity.md](references/parity.md#browser-mode-sandbox).337- Adversarial review: [adversarial-review.md](references/adversarial-review.md).338- Integration alternatives: [why-not-the-plugin.md](references/why-not-the-plugin.md).339- Installation and upgrades: [README.md](../../README.md).