A TypeScript CLI for launching and managing Codex subagents without bloating the primary agent's context. Threads run via codex exec --json, with metadata stored under .codex-subagent.
cd dev
npm install
npm run buildAll commands assume Node 20+ and npm 10+. The build outputs codex-subagent to the repo root.
codex-subagent <command> [options]
Clone this repository to ~/.codex/skills/using-subagents-as-codex/:
git clone https://github.com/obra/external-subagents ~/.codex/skills/using-subagents-as-codex
cd ~/.codex/skills/using-subagents-as-codex/dev
npm install
npm run buildCodex will use the relative path codex-subagent when invoking the CLI from the skill.
--root <path>: override the default.codex-subagentroot.--controller-id <id>: override the auto-detected controlling Codex session (use this when multiple Codex windows should share the same subagent state).
| Command | Purpose |
|---|---|
start |
Launch a new Codex exec thread with explicit role/policy (defaults to detached, so it returns immediately; add --wait to block). |
send |
Resume an existing thread with a new prompt (defaults to detached, add --wait to block). |
peek |
Show the newest unseen assistant message (read-only; --verbose prints last-activity info). |
log |
View the stored NDJSON history (supports --tail, --raw, --verbose). |
status |
Summarize the latest activity for a thread (latest message, idle time, optional log tail). |
watch |
Continuously peek a thread at an interval (use --duration-ms to exit cleanly). |
wait |
Block until specific threads (or labels/all threads) reach a stopped state; optional timeout + “follow last assistant” output. |
archive |
Move completed thread logs/state into .codex-subagent/archive/... (with --yes/--dry-run). |
label |
Attach/update a friendly label for a thread so list is easier to scan. |
list |
List every thread owned by the current controller (and show a “Launch diagnostics” section when detached start/send attempts are still pending or have failed, including error logs). |
Per-command notes:
startrequires--role,--policy, and--prompt-file(write prompts to files to avoid shell quoting issues). Policies are mapped to safe--sandbox/--profilecombinations automatically.start --manifest tasks.json(or--manifest-stdin) launches multiple prompts from a single JSON payload. Each task entry acceptsprompt,role,policy,cwd,label,persona,outputLast, andwait. This is the fastest way to spin up a whole squad of helpers; reference prompts inline in JSON so you don’t have to create dozens of temp files.start --json prompt.json(or--json -with stdin) accepts a single structured payload:{ "prompt": "...", "role": "researcher", "policy": "workspace-write", "cwd": "/repo", "label": "Task", "persona": "reviewer", "output_last": "last.txt", "wait": true }. Fields mirror the CLI flags, so you can drop prompt files entirely for ad-hoc work.send --json followup.jsonworks the same way for resume turns (prompt,cwd,persona,output_last,wait).startwarns that long-running Codex sessions may take minutes or hours. Use the default detached mode when you just want the work to continue in the background, and--waitwhen you truly need to stream the run inline.start/sendaccept--cwd <path>to automatically prepend “work inside /path” instructions,--labelto tag new threads, and--persona <name>to merge Anthropic-style agent personas (project.codex/agents/,~/.codex/agents/, superpowersagents/). Model aliases (haiku,sonnet,opus,inherit) are mapped onto safe Codex policies; if a persona setsmodel: sonnet, we’ll useworkspace-write, etc.sendneeds--thread+--prompt-fileand, likestart, runs detached unless you pass--wait. If a persona was set when the thread started, latersendcalls reuse the same persona automatically unless you override it with--persona.peek,log,watchall require--threadand never call Codex (they read the local log/registry).peek/logaccept--verboseto print last activity timestamps even when nothing changed;watchadds--duration-msso you can stop polling automatically instead of relying on Ctrl+C.status --thread <id> [--tail 5] [--stale-minutes 15]gives a one-shot summary (latest assistant turn, idle duration, and a suggestion to nudge if the thread has been idle longer than the threshold).wait --threads id1,id2 --follow-lastpolls the registry/logs until every selected thread stops. Use--labels label-a,label-bor--all-controllerto track batches launched via manifests,--interval-msto tune polling frequency, and--timeout-msto fail fast instead of waiting forever. When--follow-lastis set you’ll also see the final assistant reply for each thread as it finishes.--print-promptshows the fully composed prompt (persona + working directory instructions) before launching Codex. Add--dry-runto skip the Codex invocation entirely after printing—handy for sanity-checking inputs.label --thread <id> --label "Task X"lets you rename an existing thread after the fact (pass an empty string to clear it).archive --thread <id> --yesmoves a completed thread into the archive. Use--completed --yesto archive all completed threads, or--dry-runto preview.listnow prints aLaunch diagnosticssection whenever a detachedstart/sendattempt hasn’t reached Codex yet or failed immediately. Failed launches appear withNOT RUNNINGplus the captured error message and a pointer to.codex-subagent/state/launch-errors/<launch-id>.log; pending launches older than ~2 minutes emit a “still waiting for Codex” warning so you know to investigate.
Skip ad-hoc prompt files by piping JSON straight into start or send:
cat <<'JSON' | codex-subagent start \
--role researcher \
--policy workspace-write \
--json - \
--print-prompt
{
"prompt": "List open bugs, then propose a fix.",
"cwd": "/Users/jesse/repos/service",
"label": "Bug sweep",
"persona": "triage",
"output_last": "/tmp/bugs-last.txt",
"wait": true
}
JSONThe same schema works for send:
codex-subagent send --thread 019... --json followup.json --waitRelative paths inside the JSON payload are resolved against the file’s directory (or the current working directory when using stdin), so you can keep everything self-contained beside your manifest/prompt files.
- Where errors live: Every detached
start/sendattempt writes a record to.codex-subagent/state/launches.jsonuntil Codex produces a thread turn. If launch fails (missing profile, sandbox denial, etc.), the CLI preserves the full stderr/stack under.codex-subagent/state/launch-errors/<launch-id>.logandlistmarks the attempt asNOT RUNNING. - How to detect issues: Run
codex-subagent listafter launching helpers. If theLaunch diagnosticssection shows a pending attempt with the warning “still waiting for Codex (no thread yet),” Codex hasn’t even started—re-run the prompt or inspect the log file. Failed entries include the exact error plus the log path so you can fix the root cause before retrying. - Thread failures after resume: When a detached
sendfails, the owning thread’s status flips toNOT RUNNINGand the reason appears vialist+status.status --thread <id>now prints the failure message directly, so you can summarize the issue without re-reading the log. - Home directory sandboxing: If the environment blocks writes to
~/.codex(common under workspace-only sandboxes),codex-subagentautomatically runscodex execwithCODEX_HOME=./.codex-home(created in the current working directory) and best-effort copies~/.codex/auth.json+~/.codex/config.tomlinto it..codex-home/is gitignored but contains credentials—treat it like a secret. - Claude backend (optional): Claude support is behind a feature flag. Set
CODEX_SUBAGENT_ENABLE_CLAUDE=1to use--backend claude.
npm run demo spins up a throwaway thread and then attaches watch so you can see updates flow through without any manual wiring.
- Format:
npm run format:fix - Lint:
npm run lint - Type-check:
npm run typecheck - Tests:
npm run test - Build bundle:
npm run build
peek/log/watch share NDJSON logs under .codex-subagent/logs/<thread>.ndjson. Registry metadata lives in .codex-subagent/state/threads.json (commit this file only when intentionally sharing test fixtures).
Subagents must never run in "allow everything" mode. The CLI enforces this by refusing dangerous policies and mapping safe ones to explicit --sandbox/--profile parameters when invoking codex exec. Every thread is also tagged with the controller session ID (auto-detected from the parent Codex process or supplied via --controller-id), and commands refuse to act on threads owned by some other controller.