codex-luna-swarm

Launch, start, monitor, and collect independent Codex subagents (gpt-6-luna at high, xhigh or max; gpt-5.6-sol for small review batches) for bounded parallel work, from Codex or from Claude Code, incl

SKILL.md

GitHub

Codex Luna swarm

One independent Codex session per bounded task. The orchestration stays in the session that launches, and the preparation stays smaller than the work it hands out. One script does the transport:

bun --no-env-file .claude/skills/codex-luna-swarm/scripts/luna-sessions.ts --manifest <json> --output-dir <new dir> [options]
bun --no-env-file .claude/skills/codex-luna-swarm/scripts/luna-sessions.ts --drain <output dir>
bun --no-env-file .claude/skills/codex-luna-swarm/scripts/luna-sessions.ts --retry <output dir> [--max-active N] [--start-interval-ms N]

The usage block at the head of luna-sessions.ts lists every flag. --no-env-file is Bun's own: v2 keeps no bunfig.toml, so without it a .env in the working directory would be loaded into the launcher and handed to every session.

The author before the report

A failed session is missing work, never work done by something else. When two Luna lanes of v1 failed on exhausted credits, Claude subagents wrote the reports and the session called them "Luna at max, both completed"; nothing in the reports showed it. So read who ran before reading what was claimed: summary.json holds each session's result, and a report with no result there is not a Codex session's. Exhaustion is an ordinary interruption (AGENTS.md, rule 15): those sessions are typed non-results, retried later, and never a defect of the work.

Model, effort and count

The operator's batch policy of 4 September decides when the request names no model:

independent questions model and effort
2 to 5 --model gpt-6.1-sol --reasoning-effort medium
6 or more --model gpt-6-luna --reasoning-effort xhigh, all in one launch

An explicit choice for this batch replaces the table. Keep an effort exactly as named and never raise it; Luna named without an effort runs at max. One launch runs one model at one effort, so mixed efforts are one launch a group. Do not split two useful questions into six to reach a Luna batch, and do not wait for earlier reports before starting the rest of a batch: the count follows the questions the evidence has not settled. From Claude Code at most two Claude subagents run at once (the operator, 4 September); Codex sessions do not count against that.

Where to stop

  • Launch, start, spawn: start every session and return the start line. Do not wait for reports unless the request also says wait, collect, review or summarise.
  • Wait, collect, review, summarise: stay until the launch completes, drain every report, and return the synthesis asked for.

Say preparing while writing the manifest, launched only once the launcher printed luna_sessions.started, and collecting only when collection was asked for. When the repository, the count, the authority and the output are clear, make bounded assumptions and start in the same turn; do not stop at a plan unless a preview was asked for.

The sessions

Read shared material once and put it in the shared instructions: the commit, the evidence rules, the limits and the report's form. A row then needs a unique lowercase name, one question and its limits, where inspection starts, its authority, and the outcome it returns. A title, a line range or "review this" is too weak; the same generic question numbered six times is one question.

  • A step every session would repeat goes in the instructions once as an exact command. Look for it first in the bun run scripts, the commands under tools/ (bun tools/wri/wri.ts read --log <log> reads a campaign) and the owning skill's scripts/.
  • A causal question asks the session to test at least three hypotheses where the evidence shows a mismatch, an innocent or intended one among them, to count the callers and tests, and to propose a correction only for a defect it proved.
  • Read-only by default. A write session owns named paths and is told others may be editing. Where fixes are asked for and nobody owns the paths, sessions propose and the launching session checks and makes the confirmed fixes. No coordinator, and no session starts another.
  • Nothing the walls keep apart leaves in a report that a Builder, an Examiner or the Referee could read (AGENTS.md, section 4), and no prompt names an account or a credential path.

Reports are research. Check every finding you act on against the source (AGENTS.md, section 11).

The manifest

Write it with the Write tool, never a heredoc:

{
  "workdir": "/absolute/worktree",
  "instructionsFile": "/absolute/shared-instructions.md",
  "sessions": [
    { "name": "readers", "task": "Own the readers of comparison.reported from the log to the view. Report each reader, what it reads, and any two that disagree." },
    { "name": "fixture", "task": "Write the hostile case named in the instructions.", "sandbox": "workspace-write", "ownedPaths": ["test/compare.test.ts"] }
  ]
}

Each session receives the shared instructions (the file's, then instructions), its task, then a line stating its authority. A row takes only name, task, workdir, sandbox and ownedPaths; names match ^[a-z][a-z0-9_]{0,47}$, tasks are distinct, and a workspace-write session declares ownedPaths. Codex starts only inside a Git work tree unless told otherwise, and the launcher tells it so for a read-only session and for one that owns its whole workdir.

Launch

Start the whole batch in one detached launcher, so neither the Bash tool's 600-second limit nor the end of the turn takes it down: a launcher started as an ordinary background command died with its parent on 28 August and took all 24 sessions with it. The output directory must not exist yet, and the log goes outside it:

nohup bun --no-env-file .claude/skills/codex-luna-swarm/scripts/luna-sessions.ts \
  --manifest <scratch>/manifest.json --output-dir <scratch>/luna \
  --model gpt-6.1-sol --reasoning-effort medium [--max-active 12] [--start-interval-ms 1000] \
  > <scratch>/luna.log 2>&1 &

Put the output directory somewhere the host does not clean, in the session's scratchpad or beside the evidence it serves, never under a campaign's folder. The first line of the log is luna_sessions.started; only then report the directory, the count, the model, the effort, the service tier, the active count and the pace as launched. The launcher finds Codex by --codex-bin, then the ChatGPT app's copy, then Homebrew's, then codex on the PATH. It needs Codex's own state, so a sandboxed shell asks once for the launcher command; each read-only session stays read-only.

Watch, drain, retry

Follow the log with the Monitor tool: one luna_session.finished line a session, then luna_sessions.completed. Drain while it runs, not only at the end: --drain <output dir> prints what reports.md gained since the last drain, and sixty lanes of one v1 batch sat unread on 29 September because nothing drained them. reports.md gains each report as its session settles, so it holds every settled report even if the launcher dies.

After luna_sessions.completed, drain once more and read summary.json: one result for each session asked for. A session that did not complete is missing work, and its result says why (failureKind: spawn or rate-limit). A transport warning in a session's stderr is not a failure: a fall-back from WebSocket to HTTPS may still complete. The start line proves the configuration asked for, not the model a remote session bound; where identity carries a claim, read the session's own event log before making it.

Codex retries a request inside a session. A session that still ends without a report gets one more attempt from the launcher (--retries, at most 5): codex exec resume of its thread where Codex named one, a fresh start otherwise. After rate-limited sessions, let the launch settle, then run again only what is missing, under the recorded model, effort and prompts:

bun --no-env-file .claude/skills/codex-luna-swarm/scripts/luna-sessions.ts --retry <output dir> --max-active 4 --start-interval-ms 5000

A retry leaves launch.json and every completed result as they were, appends the rerun reports to reports.md, and refuses while that directory's launcher still runs.

Lanes of a whole-run investigation

whole-run-investigation step 3 writes the manifest, <dir>/launch.json: one workspace-write session a lane built, owning the report it writes, in the checkout the investigation reads. Launch it as it stands. A lane that runs alone (5 and 19) is built, and launched, on its own after the reports it waits on. Then wri.ts collect reads the reports as from any seat.

Not carried from v1

The native luna_worker agent and its configuration snippets (v2 configures no Codex agent and links no .agents/skills), the stop hook that held a parent Codex task open, the bare task list beside the manifest, the temporary output directory by default, the Markdown task-file parser, which no session used in the week to 11 October, and the Codex companion plugin's command, whose path is a user's own install.