From 411568ae7fec89dd16c8a7fdde554bbf46331a50 Mon Sep 17 00:00:00 2001 From: Ahmed Mohammed <7650147+amElnagdy@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:54:03 +0300 Subject: [PATCH] Add codex-delegate skill and package scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First skill in the delegate-skills umbrella: drive the OpenAI Codex CLI as a background implementer (write a self-contained brief, dispatch via the bundled relay.mjs codex-exec wrapper, poll, review the diff, and commit it yourself — the orchestrator commits because Codex's sandbox can't reliably write .git). Includes SKILL.md, the relay.mjs helper (Node built-ins only; no network, no credentials, no telemetry), four references, README positioning it vs. the openai-codex plugin, MIT license, and skills.sh grouping. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 12 + AGENTS.md | 34 ++ LICENSE | 21 ++ README.md | 125 +++++++ skills.sh.json | 12 + skills/codex-delegate/SKILL.md | 159 +++++++++ .../references/dispatch-and-poll.md | 109 ++++++ .../references/multi-task-queues.md | 66 ++++ .../references/review-and-land.md | 74 ++++ .../references/writing-the-brief.md | 114 +++++++ skills/codex-delegate/scripts/relay.mjs | 316 ++++++++++++++++++ 11 files changed, 1042 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 skills.sh.json create mode 100644 skills/codex-delegate/SKILL.md create mode 100644 skills/codex-delegate/references/dispatch-and-poll.md create mode 100644 skills/codex-delegate/references/multi-task-queues.md create mode 100644 skills/codex-delegate/references/review-and-land.md create mode 100644 skills/codex-delegate/references/writing-the-brief.md create mode 100644 skills/codex-delegate/scripts/relay.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..865d645 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# OS / editor cruft +.DS_Store +*.swp + +# Node +node_modules/ + +# relay.mjs run artifacts, if a user points --out-dir back into the repo +.delegate/ + +# local agent config symlink (see AGENTS.md) +CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6244836 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,34 @@ +# Working on delegate-skills + +This repo is a [Skills CLI](https://github.com/vercel-labs/skills) package of **agent-relay skills** — +skills that let an orchestrating agent drive a separate CLI coding agent as an implementer, then review +and land the result. The first skill is `codex-delegate` (OpenAI Codex); siblings like `gemini-delegate` +can be added later without renaming the repo. + +## Conventions + +- **One skill per directory** under `skills//`, each with a `SKILL.md` plus optional + `references/` and `scripts/`. The verb is the repo (`delegate`); the target agent is the skill name + (`codex-delegate`), mirroring `guard-skills` → `clean-code-guard`. +- **`SKILL.md` frontmatter:** `name` (must equal the directory), `description`, and optionally + `license`, `compatibility`, `metadata.version`, `allowed-tools`. The **`description` is the only + triggering signal** — keep it to what the skill does and when to use it, phrased to trigger reliably. + Provenance, status caveats, and how-it-works detail go in the body or here, never in the description. +- **Progressive disclosure:** keep `SKILL.md` lean; push depth into `references/*.md` that load only + when needed. +- **Executables:** keep them minimal and inspectable. The only one today is + `skills/codex-delegate/scripts/relay.mjs` — Node built-ins only, no dependencies, no network calls of + its own, no credentials, no telemetry. New scripts must hold the same line, and the README's trust + section must stay accurate. + +## Before publishing a change + +- Validate the package locally: `npx skills add . --list`. +- Smoke-test any changed script directly (e.g. `node skills/codex-delegate/scripts/relay.mjs --help`, + and a `--read-only` run against a throwaway repo) before relying on it. +- Keep the README's "Verification status" honest — claim only what's been run. + +## Local Claude Code config + +Claude Code reads `CLAUDE.md`, not `AGENTS.md`. If you want this file active while working here in +Claude Code, symlink it (it's gitignored): `ln -s AGENTS.md CLAUDE.md`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..55e1e59 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ahmed Mohammed (amElnagdy) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4da3340 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# delegate-skills + +[![skills.sh](https://skills.sh/b/amElnagdy/delegate-skills)](https://skills.sh/amElnagdy/delegate-skills) + +Skills for **delegating coding work to a separate CLI agent and landing it yourself**. Your agent (the +orchestrator) writes a self-contained brief, hands it to an implementer CLI, then reviews the diff and +commits — staying the reviewer the whole way. + +The first skill, **`codex-delegate`**, drives the OpenAI Codex CLI. A `gemini-delegate` and other +implementers can live alongside it later — the repo name is the verb, the target agent lives in the +skill name (mirroring how [`guard-skills`](https://github.com/amElnagdy/guard-skills) holds +`clean-code-guard`, `test-guard`, …). + +## Install + +Browse first: + +```bash +npx skills add amElnagdy/delegate-skills --list +``` + +Install the package, or just one skill: + +```bash +npx skills add amElnagdy/delegate-skills +npx skills add amElnagdy/delegate-skills --skill codex-delegate +``` + +Install for a specific agent, or globally: + +```bash +npx skills add amElnagdy/delegate-skills --skill codex-delegate --agent claude-code +npx skills add amElnagdy/delegate-skills --global +``` + +Works with any orchestrating agent the [Skills CLI](https://github.com/vercel-labs/skills) supports. + +## What it does + +The loop, for `codex-delegate`: + +1. **Write a brief** — a self-contained task spec; Codex sees only what you send. +2. **Dispatch** it with the bundled `relay.mjs` (a thin `codex exec` wrapper). +3. **Wait** for completion — the helper writes a structured `result.json`. +4. **Review** the diff — re-run the project's gates yourself; pair with guard skills. +5. **Land** it — *you* commit, because the implementer's sandbox can't reliably write `.git`. + +```text +Use $codex-delegate to have Codex implement the refactor in services/billing/, then review and commit it. +Use $codex-delegate to run this queue of migration tasks through Codex while I review each one. +``` + +## How this differs from the OpenAI Codex plugin + +The official [openai-codex Claude Code plugin](https://github.com/openai/codex) is excellent and +**complementary** — this skill builds on the same `codex` CLI, it doesn't replace the plugin. They +point in different directions: + +- The plugin's `codex:codex-rescue` agent is a **forwarder**: it hands one task to Codex and returns + the output. It deliberately does not poll, review, or commit. +- The plugin's review command and stop-gate run the **inverse** direction: **Codex reviews your work**. +- `codex-delegate` is the **orchestration loop in the other direction**: *you* drive Codex to + implement across one task or a queue, and *you* review and land each result. That loop — brief → + dispatch → poll → review → commit, with the orchestrator owning the commit — is what the plugin + leaves to you, and what this skill encodes. + +If you have the plugin installed, its companion CLI is an optional alternative dispatch backend; the +bundled `relay.mjs` is the default because it needs nothing but the `codex` binary. + +## The skills + +### codex-delegate + +Drive the OpenAI Codex CLI as a background implementer: write the brief, dispatch via `relay.mjs`, +review the diff, commit it yourself. Ships four references (writing the brief, dispatch/poll, review/ +land, multi-task queues) loaded only when needed, and one small helper script. + +**You'll feel it when:** a bounded task — a migration, a mechanical refactor, a removal sweep — gets +handed to Codex, comes back as a clean diff with a structured report, and you commit it after re-running +the gates yourself instead of typing it all by hand. + +### gemini-delegate + +*Planned.* A relay for the Gemini CLI, if and when it gains a comparable non-interactive mode. Reserved +so the umbrella can grow without a rename. + +## Requirements + +- The [`codex` CLI](https://github.com/openai/codex) installed and authenticated (`codex login`). +- Node 18+ and `git`. +- An orchestrating agent that can run shell commands and read files. + +## Trust and validation + +This package is intentionally inspectable: + +- All skill content is Markdown, plus exactly **one** executable: `skills/codex-delegate/scripts/relay.mjs`. +- `relay.mjs` itself makes no network calls, reads or writes no credentials, sends no telemetry, and + has no dependencies (Node built-ins only). It shells out only to `codex` and `git`. The `codex` + process it launches authenticates exactly as you do at the terminal. Read the script before you run it. +- It never commits — committing is always the orchestrator's job, after review. + +**Verification status:** the loop is verified on Claude Code. Other shell-capable orchestrators +(OpenCode, Cursor, …) are designed-for but not yet verified — the skill is written orchestrator-neutral +so they should work, and that line gets upgraded to "verified" with evidence, not assumption. + +## Repository shape + +```text +skills/ +└── codex-delegate/ + ├── SKILL.md + ├── scripts/relay.mjs + └── references/ + ├── writing-the-brief.md + ├── dispatch-and-poll.md + ├── review-and-land.md + └── multi-task-queues.md +``` + +The `SKILL.md` stays small so it loads cheaply; the references load only when the task needs them. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/skills.sh.json b/skills.sh.json new file mode 100644 index 0000000..44c5bf8 --- /dev/null +++ b/skills.sh.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://skills.sh/schemas/skills.sh.schema.json", + "groupings": [ + { + "title": "Agent Relays", + "description": "Delegate a coding task to a CLI implementer agent, then review the diff and commit it yourself.", + "skills": [ + "codex-delegate" + ] + } + ] +} diff --git a/skills/codex-delegate/SKILL.md b/skills/codex-delegate/SKILL.md new file mode 100644 index 0000000..abf473b --- /dev/null +++ b/skills/codex-delegate/SKILL.md @@ -0,0 +1,159 @@ +--- +name: codex-delegate +description: >- + Delegate a coding task to the OpenAI Codex CLI as a background implementer, then review its diff and + land it. Use this whenever the user wants to hand implementation work to Codex — phrasings like "have + Codex do X", "delegate this to Codex", "run it through Codex", or "use Codex to implement/fix/refactor" + — or wants to run a queue of coding tasks through Codex while staying the reviewer. Also reach for it + proactively when the user wants a separate implementation pass on a bounded, well-specified task (an + implementation sweep, a migration, a mechanical refactor, parallel work) and will review and commit + the result themselves. Covers writing the Codex brief, dispatching it via the bundled relay.mjs + helper, waiting for completion, reviewing the result, and committing. DO NOT USE for tasks small + enough to do inline, when the codex CLI is not installed or authenticated, or when the user wants the + code written directly without delegating. +license: MIT +compatibility: Requires the `codex` CLI (OpenAI Codex) installed and authenticated, Node 18+, and git. The orchestrating agent must be able to run shell commands and read files. +metadata: + version: 0.1.0 +allowed-tools: Bash(node:*) Bash(codex:*) Bash(git:*) Read +--- + +# Codex Delegate + +You are the **orchestrator**. This skill lets you hand a bounded coding task to a separate +**implementer** — the OpenAI Codex CLI — then review what it produced and land it yourself. You write +the brief and own the judgment; Codex does the typing in its own sandbox; you verify and commit. + +Nothing here is specific to one orchestrating agent. The loop needs only the ability to run a shell +command and read a file, so it works the same whether you are Claude Code, OpenCode with a selected +model, or any comparable agent. (It has been verified on Claude Code; treat other orchestrators as +designed-for, not yet proven.) + +## When to use this + +- You want to delegate a **bounded, well-specified** coding task and stay the reviewer. +- You have a **queue** of such tasks (a removal, a migration, a refactor sweep) to run one at a time. +- You want a second implementation pass from a different model while you keep control of what merges. + +## When NOT to use this + +- The task is small enough to just do inline — delegation overhead is not worth it. +- The `codex` CLI is not installed or not authenticated (run `codex login`). +- You want to write the code yourself, or you only need a review (use Codex's own `review` command). + +## Prerequisites (check once) + +1. `codex --version` succeeds. If not, install (`npm i -g @openai/codex`) and `codex login`. +2. **Confirm which `codex` is on PATH.** Multiple installs are common (e.g. a current npm/nvm copy and + a stale Homebrew one). `which -a codex` and `codex --version` — an old binary predates flags this + skill relies on (`codex exec --json`, `-o`, `exec resume`). The relay records the version it ran + into `result.json`, so a stale binary is visible after the fact. +3. You are in (or will point `--cd` at) the target git repository. + +## The loop + +Run these five steps per task. Steps 1, 4, and 5 are your judgment; 2 and 3 are mechanical. + +### 1. Write the brief + +Codex sees **only** the text you send — no repo memory, no chat history, no devdocs. Everything the +task needs goes in the brief: the goal, the current state, what to change, what to leave untouched, +the project's **actual** gate commands (discover them from the repo's CLAUDE.md/AGENTS.md/Makefile — +do not assume), and a report contract. Tell Codex it will **not** commit (you will). Keep one task per +brief. Full guidance and a template: [references/writing-the-brief.md](references/writing-the-brief.md). + +### 2. Dispatch + +Send the brief to Codex with the bundled helper. It wraps `codex exec`, captures the run, and writes a +structured `result.json` — so your only job is "run a command, read a file." + +```bash +node "/scripts/relay.mjs" --brief brief.txt --cd /path/to/repo +# read-only (review/diagnosis, no edits): add --read-only +# continue the previous Codex session: add --resume-last (send only the delta brief) +# see all options: node .../relay.mjs --help +``` + +The helper defaults to a write-capable (`workspace-write`) sandbox and writes its artifacts to a temp +dir, so the repo under review stays clean. It **never commits** — see step 5. Mechanics, flags, and the +`result.json` shape: [references/dispatch-and-poll.md](references/dispatch-and-poll.md). + +### 3. Wait for completion + +The helper blocks until Codex finishes, so back it with whatever your orchestrator offers and resume +when it returns: + +- **Claude Code:** run the Bash call with `run_in_background: true`; you are notified on completion. +- **Plain shell / other agents:** run it in the foreground for short tasks, or `… &` and poll the + result file. The run is done when `result.json` exists with a `status`. (A pre-run usage error — + bad args or an empty brief — instead exits non-zero with a stderr message and writes no result + file, so check the exit code too, not just the file.) + +Do not trust progress trackers over reality: a run is finished when `result.json` is written and the +process has exited. Read the working tree, not a status line. + +### 4. Review — do not trust the self-report + +Codex's `result.json` includes its own summary and gate claims. **Re-verify, don't accept:** + +- **Re-run the project's gates yourself** (the test/lint/build commands from step 1). Never take + "gates passed" on faith. +- **Read the diff** against the brief: did Codex do what was asked, nothing more (scope creep) and + nothing less? `touchedFiles` in the result is your starting point. +- **Run the relevant guard skills** on the diff if you have them installed (clean-code-guard, + test-guard, etc. from `guard-skills`) — this skill produces the work; those skills judge it. +- For schema/migration changes, round-trip them; for removals, grep for dangling references. + +Full checklist: [references/review-and-land.md](references/review-and-land.md). + +### 5. Land it + +Because Codex's sandbox cannot reliably write `.git` (it varies by version, OS, and path), **the +orchestrator commits.** Only after the gates pass and the diff holds: + +- Commit the verified work yourself, with a clear message. +- If it needs changes, send a delta brief with `--resume-last` (don't restate the whole task) and + review again. + +## Non-negotiables + +- **Re-run the gates yourself.** The self-report is a claim, not evidence. +- **The orchestrator commits, never Codex.** Don't assume Codex committed; it didn't. +- **One task = one brief = one commit.** Split unrelated work into separate runs. +- **Trust the working tree and process state** over any progress tracker. + +## Authorization model + +Delegation is something the human opts into. Once they have ("run this queue", "proceed"), committing +verified, gate-passing work is the agreed contract — that is the whole point. But: + +- **Surface, don't absorb.** When Codex makes a design decision, takes a defensible-but-unasked turn, + or you spot a non-blocking nitpick, report it to the human rather than silently keeping it. +- **Stop for scope changes.** If correct completion requires going beyond the brief, stop and ask; + don't expand the mandate on your own. + +## Trust and safety + +`scripts/relay.mjs` itself makes no network calls, reads or writes no credentials, and sends no +telemetry; it has no dependencies (Node built-ins only) and shells out only to `codex` and `git`. The +`codex` process it launches does authenticate — exactly as you do at the terminal. Read the script +before you run it. It is the one executable in this package; everything else is Markdown. + +## References + +- [references/writing-the-brief.md](references/writing-the-brief.md) — how to write a brief Codex can + execute blind: structure, XML blocks, the report contract, embedding the real gate commands. +- [references/dispatch-and-poll.md](references/dispatch-and-poll.md) — `relay.mjs` flags, the + `result.json` contract, backgrounding per orchestrator, and recovery when a run misbehaves. +- [references/review-and-land.md](references/review-and-land.md) — the review checklist, the commit + boundary, and the rework cycle via `--resume-last`. +- [references/multi-task-queues.md](references/multi-task-queues.md) — running a sequential queue: + carrying constraints forward, progress tracking, and the end-of-run coherence check. + +## What this skill does NOT do + +- It does not commit for you — that is deliberate (step 5). +- It does not review the code's quality itself — pair it with guard skills. +- It does not run your tests — you re-run the project's own gates in step 4. +- It is not the inverse direction (Codex reviewing your work). For that, use the openai-codex plugin's + review command or stop-review gate. diff --git a/skills/codex-delegate/references/dispatch-and-poll.md b/skills/codex-delegate/references/dispatch-and-poll.md new file mode 100644 index 0000000..be1e2ee --- /dev/null +++ b/skills/codex-delegate/references/dispatch-and-poll.md @@ -0,0 +1,109 @@ +# Dispatch and poll + +`scripts/relay.mjs` is the dispatch layer. It wraps `codex exec`, runs the brief in a sandbox, captures +everything, and writes a structured `result.json`. Your job collapses to: run one command, then read +one file. Everything Codex-specific lives in the helper, which is what keeps the loop portable across +orchestrators. + +## Before the first run: check the binary + +Two gotchas, both worth 30 seconds: + +```bash +which -a codex # more than one? a stale install (e.g. Homebrew) may shadow a current one +codex --version # an old binary predates `exec --json`, `-o`, and `exec resume` +codex login status # must be authenticated +``` + +The Codex CLI moves fast and behavior shifts between versions, so the helper records the version it +actually ran into `result.json` — if something behaves oddly, check which binary answered. + +## Dispatching + +```bash +node "/scripts/relay.mjs" --brief brief.txt --cd /path/to/repo +``` + +Options: + +| Flag | Effect | +| --- | --- | +| `--brief ` | The brief. Omit it to read the brief from stdin (`cat brief.txt \| node relay.mjs …`). | +| `--cd ` | Working root for Codex (default: current directory). | +| `--model ` | Codex model (default: Codex's own configured default). | +| `--sandbox ` | `read-only` \| `workspace-write` \| `danger-full-access` (default: `workspace-write`). | +| `--read-only` | Shortcut for `--sandbox read-only` — review/diagnosis with no edits. | +| `--resume-last` | Continue the most recent Codex session; send only the delta brief (see review-and-land). | +| `--skip-git-repo-check` | Allow running outside a git repo. | +| `--out-dir ` | Where artifacts go (default: a fresh dir under the system temp dir). | + +Artifacts default to the system temp dir on purpose: the repo under review stays clean, so the +touched-files report shows only Codex's edits and nothing of the helper's own. + +## The result + +`/result.json` is the contract. Fields: + +- `status` — `completed` | `failed` | `codex_unavailable` +- `exitCode` — mirrors Codex's exit code; `127` if `codex` isn't on PATH +- `codexVersion` — the binary that actually ran +- `threadId` — feed this to a later `codex exec resume ` (or use `--resume-last`) +- `finalMessage` — Codex's own final report (the `` you asked for) +- `touchedFiles` — `git status --porcelain` lines in the working root: your review starting point +- `eventsPath` / `finalPath` — the raw JSONL event stream and the final-message file +- `workdir`, `sandbox`, `model`, `resumeLast`, `startedAt`, `finishedAt` + +The helper also prints a summary to stdout and exits with Codex's exit code, so a wrapping script can +branch on success/failure directly. + +## Waiting for completion + +The helper blocks until Codex finishes. Back it with whatever your orchestrator offers: + +- **Claude Code:** run the `Bash` call with `run_in_background: true`; you're notified on completion, + then read `result.json`. +- **Plain shell / other agents:** foreground for short tasks, or `node relay.mjs … &` and poll. A run + is done when `result.json` exists with a `status`. **But** a pre-run usage error (bad args, empty + brief) exits non-zero *before* writing any file — so check the exit code too, don't only watch for + the file. + +Trust the working tree and the process state over any progress display. A run is finished when the +process has exited and `result.json` is written — not when a status line says so. + +## When a run misbehaves + +- **`status: codex_unavailable` (exit 127):** `codex` isn't on PATH or isn't found. Install + (`npm i -g @openai/codex`) and `codex login`, then re-dispatch. +- **`status: failed`:** read `result.json`'s `stderrTail` and the tail of `eventsPath` for the cause. + Common causes: an auth lapse, an invalid `--model`, or a sandbox that blocked something the task + needed. Fix the cause and re-dispatch; don't paper over it by doing the work yourself unless that's + what the user wants. +- **Empty `finalMessage`:** Codex exited before producing a final message. Treat as a failed run; + the events log usually shows where it stopped. + +## What the helper is doing (and the alternatives) + +Under the hood the helper runs roughly: + +```bash +codex exec --json -o -s workspace-write [-m model] - < brief.txt # fresh run +codex exec resume --last --json -o - < delta-brief.txt # resume (no -s/-C) +``` + +`resume` deliberately gets no `-s`/`-C` — it inherits the original session's sandbox and working root — +which is why the helper sets the child process's working directory instead. + +Two alternatives exist if you ever want them, but the helper is the recommended path: + +- **Raw `codex exec`** — fine for one-offs; you give up the captured `result.json`, touched-files + summary, and thread-id extraction the helper does for you. +- **The openai-codex Claude Code plugin's companion CLI** (`task`/`status`/`result`) — richer job + tracking if you have that plugin installed, but it depends on the plugin and its background dispatch + can occasionally stall a job in a `queued` state with no worker. The helper sidesteps that by running + in-process. + +## The commit boundary + +The helper never commits — by design, not omission. Whether Codex's sandbox can write `.git` varies by +version, OS, and execution path, so relying on it is a coin flip. The robust contract is: Codex edits +the working tree, the orchestrator reviews and commits. See [review-and-land.md](review-and-land.md). diff --git a/skills/codex-delegate/references/multi-task-queues.md b/skills/codex-delegate/references/multi-task-queues.md new file mode 100644 index 0000000..ab8cb01 --- /dev/null +++ b/skills/codex-delegate/references/multi-task-queues.md @@ -0,0 +1,66 @@ +# Multi-task queues + +The single-task loop scales to a queue, and that's where delegation pays off most — a removal split +across layers, a migration touching many files, a refactor sweep. The discipline that makes a queue +trustworthy is sequencing and bookkeeping, not parallelism. + +## Run sequentially, one commit per task + +Resist the urge to fan out the whole queue at once. Run tasks **one at a time, in dependency order**, +landing each (review + gates + commit) before dispatching the next. Three reasons: + +- **Later tasks assume earlier ones landed.** Task 3's brief can say "the X added in the previous step + exists" only if the previous step actually committed. +- **One commit per task** keeps the history reviewable and any single step revertible. +- **Each review is honest.** A clean working tree before each dispatch means the next task's + `touchedFiles` shows only *its* changes, not a pile-up from earlier tasks. + +Parallelism is occasionally worth it for genuinely independent tasks on separate files, but it +sacrifices the clean-tree-per-task property and makes review harder. Default to sequential. + +## Carry decided constraints forward + +Implementation surfaces facts the original plan didn't have: a helper got named, a fixture lives in a +specific place, an interface was chosen. When a later task depends on one of those, **fold it into that +task's brief** as an explicit line. Codex has no memory of the earlier run, so a constraint that +emerged in task 2 must be restated in task 5's brief or it won't hold. This is the queue equivalent of +keeping briefs self-contained. + +## Keep a progress file + +For anything longer than two or three tasks — especially a run the human steps away from — maintain a +single progress file alongside the work. It's the durable record that survives your own context limits +and lets the human catch up at a glance. A shape that works: + +- **Status table** — each task: queued / at-implementer / reviewed+committed (with the commit hash). +- **Per-task review notes** — what landed, what you verified, the gate outcome. One short paragraph. +- **"Needs your eyes"** — design decisions Codex made, non-blocking nitpicks, anything you want the + human to overrule or confirm. This is the section they read first. +- **End-of-run checklist** — what happens after the last task (push, open/update the PR, manual checks + the human should do). + +Update it as each task lands, not in a batch at the end — if the run is interrupted, the file is still +accurate. + +## Close with a coherence check + +Per-task review proves each step in isolation; it doesn't prove the steps cohere. After the last task, +verify the whole: + +- Run the full test/build once more on the final tree — not just the last task's slice. +- Do a repo-wide check for the thing the queue was about (e.g. after a removal, grep the entire tree + for any surviving reference; after a rename, confirm no stragglers). +- For schema work, replay all the new migrations from a clean state and check for drift. +- Then push and open or update the PR, with a description that reflects what actually shipped. + +## When to stop and ask + +Proceed without asking on anything that follows from the agreed plan — that's the point of the human +opting into the queue. Stop and surface when: + +- A task can't be completed correctly within its brief's scope (a scope change is the human's call). +- A review finds something that calls the *plan* into question, not just the implementation. +- The gates reveal a problem that affects tasks already "done." + +Then report where you are, what's committed, and what the open question is — and wait. A queue that +quietly works around a broken assumption produces a lot of commits in the wrong direction. diff --git a/skills/codex-delegate/references/review-and-land.md b/skills/codex-delegate/references/review-and-land.md new file mode 100644 index 0000000..81be398 --- /dev/null +++ b/skills/codex-delegate/references/review-and-land.md @@ -0,0 +1,74 @@ +# Review and land + +Codex did the typing; you own the judgment. This is where delegation earns its keep or quietly ships a +mistake. The discipline is simple to state and easy to skip under time pressure: **verify against +reality, never against the self-report, then commit it yourself.** + +## Re-run the gates yourself + +`result.json` carries Codex's own claim that the gates passed. Treat that as a claim, not evidence — +re-run the project's actual test/lint/build commands in the working tree and read the output. A run +that "passed" in Codex's report but fails when you run it is exactly the failure this step exists to +catch, and it happens often enough to be worth the minute every time. + +For changes with their own verification shape, go further: + +- **Migrations / schema:** round-trip them (apply, reverse, re-apply on a scratch target) and check for + drift, rather than trusting that "the migration is reversible." +- **Removals / renames:** grep the codebase for dangling references to whatever was removed. +- **Anything stateful:** exercise the actual behavior, don't just confirm it compiles. + +## Read the diff against the brief + +Open the diff (`touchedFiles` in the result is your starting list) and hold it against what you asked +for: + +- **Scope creep** — did Codex change things the brief said to leave untouched? Unasked refactors, + renames, "while I was here" edits. These are the most common quality problem in delegated work. +- **Scope shortfall** — did it do the whole task, including the edge cases and cleanup, or stop at the + first plausible version? +- **Quiet judgment calls** — sometimes Codex makes a defensible decision the brief didn't anticipate. + Don't just accept it because it looks reasonable; understand it and decide. + +## Compose with guard skills + +This skill produces the work; it doesn't judge code quality. If you have the `guard-skills` package +installed, run the relevant guard on Codex's diff before you commit — `clean-code-guard` on production +code, `test-guard` on any tests it wrote, `docs-guard` on documentation. They catch the systematic +failure modes of generated code that a quick read misses. The two packages are designed to pair: +delegate-skills delegates and lands; guard-skills reviews. + +## The commit boundary + +When the gates pass and the diff holds, **you commit** — the orchestrator, never Codex. This isn't a +workaround for a missing feature; it's the deliberate boundary. Codex's sandbox can't reliably write +`.git`, and more importantly, committing should be the act of the party that verified the work. Write +a clear message describing what landed. If your project attributes co-authorship, that's the place +for it. + +## Reworking: send the delta, not the whole task + +If the review turns up problems, don't restate the entire brief. Continue the same Codex session with +just the correction: + +```bash +echo "The fix is right, but the test mocks the DB session — use the real migrated fixture instead, and +drop the now-unused import." | node "/scripts/relay.mjs" --resume-last --cd /path/to/repo +``` + +`--resume-last` keeps Codex's context from the first run, so a short delta is enough. Then review +again — rework gets the same gate-rerun and diff-read as the original, no shortcuts. Repeat until it's +right, then commit. + +## Surface, don't absorb + +The human opted into delegation, so committing verified, gate-passing work is the agreed contract. +But keep them in the loop on anything that changes the shape of the work: + +- **Report design decisions** Codex made, and any defensible-but-unrequested turns it took. +- **Note non-blocking nitpicks** you chose not to block on, so the human can overrule you. +- **Stop and ask** if correct completion requires going beyond the brief — don't expand the mandate on + your own. A scope change is the human's call, not yours or Codex's. + +For a multi-task run, capture these in the progress file rather than letting them scroll past — see +[multi-task-queues.md](multi-task-queues.md). diff --git a/skills/codex-delegate/references/writing-the-brief.md b/skills/codex-delegate/references/writing-the-brief.md new file mode 100644 index 0000000..6b17e29 --- /dev/null +++ b/skills/codex-delegate/references/writing-the-brief.md @@ -0,0 +1,114 @@ +# Writing the brief + +A brief is the entire task as Codex will see it. Codex runs in a fresh process with **no memory of +your conversation, no devdocs, and no shared context** — only the text you send and whatever it can +read from the working tree (including the repo's own `AGENTS.md`, which it picks up automatically). +If a constraint isn't in the brief or discoverable in the repo, it doesn't exist for Codex. The single +most common failure is a brief that assumes context Codex doesn't have. + +## The shape that works + +Codex (a GPT-5.x-class model) responds best to compact, block-structured prompts with XML tags rather +than long prose. State the task, what "done" looks like, how to behave by default, and the few +constraints that actually matter. Add a block only when the task needs it — don't ship empty ceremony. + +```xml + +One or two sentences: the concrete job and where it lives. Then the specifics — current state, what to +change, and explicitly what to leave untouched. The "leave untouched" list is what keeps Codex from +wandering into unrelated refactors. + + + +Run these before finishing and fix anything they surface, don't just report it: + + + +Confirm the working tree shows only the intended changes afterward. + + + +Keep changes scoped to the task. No unrelated refactors, renames, or cleanup unless required for +correctness. Do NOT run git add or git commit — you cannot reliably write .git, and the orchestrator +commits after reviewing. Leave the work uncommitted in the working tree. + + + +End with a report in this exact shape: + 1. What changed and why + 2. Files touched + 3. Gate outcomes (paste the test/lint counts) + 4. Anything you deviated on, left open, or want a decision on + +``` + +That four-block skeleton covers most implementation tasks. Reach for the extra blocks when the task +profile calls for them: + +- **Debugging / open-ended fixes** — add `` (resolve fully, don't stop at the + first plausible fix) and `` (don't guess missing repo facts; find them or + state what's unknown). +- **Review / diagnosis (read-only)** — add `` (ground every claim in evidence; label + inferences) and run with `--read-only` so Codex can't edit. +- **Research / recommendations** — add `` (separate observed facts, inferences, open + questions). + +## Discover the real gates — don't hardcode + +`` is only useful if it names the project's *actual* commands. Read the repo's +`CLAUDE.md` / `AGENTS.md` / `Makefile` / `package.json` first and copy the real ones in (`make test`, +`npm run lint`, `cargo test`, `pytest -q`, whatever it is). A brief that says "run the tests" without +naming them gets you a Codex that guesses — or skips. + +## Honor the repo's conventions + +Codex reads the repo's `AGENTS.md` automatically, so house rules there (style, forbidden patterns, +commit conventions) already apply. If the project forbids certain things in code — spec/ticket IDs in +comments, process language like "MVP"/"for now"/"phase N", specific test conventions — restate the +load-bearing ones in the brief too, because Codex's compliance is only as reliable as what's in front +of it. + +## One task per brief + +Keep each brief to a single, bounded job. "Review this, fix what you find, update the docs, and +suggest a roadmap" produces a muddled run; split it into separate dispatches. One brief → one Codex +run → one commit keeps review and rollback clean, and lets a later task assume the earlier one landed. + +## Expect environment preamble in the reply + +Codex's final message may carry environment noise on top of your requested report — a memory-tool +status line, an `AGENTS.md`-injected banner, etc. That's Codex's local setup, not a relay defect. The +`` is your defense: ask for a clearly delimited report section so you can +find the real output regardless of what wraps it. + +## A worked example + +```xml + +In the payments service at services/billing/, the refund path double-charges when a refund is retried +after a network timeout (the idempotency key isn't checked before re-submitting). Make the refund +submission idempotent: check for an existing refund by idempotency key before creating a new one. +Touch only services/billing/refund.py and its tests. Leave the charge path, the API routes, and the +data models untouched. + + + +Run and make green before finishing: + pytest tests/billing/ -q + ruff check services/billing/ +Confirm git status shows only refund.py and its test file changed. + + + +Scope strictly to the refund idempotency fix. No unrelated refactors. Do NOT git add or commit; leave +changes in the working tree for review. + + + +Report: (1) the root cause and your fix, (2) files touched, (3) pytest + ruff outcomes with counts, +(4) anything you left open or want decided. + +``` + +Send this with `relay.mjs` (see [dispatch-and-poll.md](dispatch-and-poll.md)); review the result and +commit it yourself (see [review-and-land.md](review-and-land.md)). diff --git a/skills/codex-delegate/scripts/relay.mjs b/skills/codex-delegate/scripts/relay.mjs new file mode 100644 index 0000000..70a6f8b --- /dev/null +++ b/skills/codex-delegate/scripts/relay.mjs @@ -0,0 +1,316 @@ +#!/usr/bin/env node +/** + * delegate-skills · codex-delegate · relay.mjs + * + * Dispatch a self-contained brief to the OpenAI Codex CLI (`codex exec`), + * capture the run, and write a structured result the orchestrating agent can + * review. The orchestrator runs this one command and reads the result JSON — + * every Codex-specific mechanic lives in here, which keeps the skill + * orchestrator-agnostic. Verified on Claude Code; other shell-capable agents + * (OpenCode, Cursor, …) are designed-for but not yet verified. + * + * Trust posture: relay.mjs itself makes no network calls, reads or writes no + * credentials, and sends no telemetry; it has no dependencies (Node built-ins + * only). It shells out only to `codex` and `git`. The `codex` process it + * launches does authenticate — exactly as you do at the terminal. Read this + * file before you run it. + * + * It deliberately does NOT commit. Whether Codex's sandbox can write `.git` + * varies by Codex version, OS, and execution path, so committing is always the + * orchestrator's job — after it reviews the diff and re-runs the project gates. + * + * Usage: + * node relay.mjs --brief [options] + * cat brief.txt | node relay.mjs [options] + * + * Options: + * --brief Path to the brief. If omitted, the brief is read from stdin. + * --cd Working root for Codex (default: current directory). + * --model Codex model (default: Codex's own configured default). + * --sandbox read-only | workspace-write | danger-full-access + * (default: workspace-write). + * --read-only Shortcut for --sandbox read-only (review/diagnosis, no edits). + * --resume-last Continue the most recent Codex session; send only the delta brief. + * (Inherits the original session's sandbox and working root.) + * --skip-git-repo-check Allow running outside a git repository. + * --out-dir Where to write run artifacts (default: a fresh dir under + * the system temp dir, so the repo under review stays clean). + * -h, --help Show this help. + * + * Result: written to /result.json and summarized on stdout — + * status, exitCode, codexVersion, threadId (for a later resume), finalMessage + * (Codex's own report), touchedFiles (git porcelain), and the paths to + * events.jsonl and final.txt. + * + * Exit codes: a pre-run usage error (bad/missing args, empty brief) exits 2 + * before any run and writes no result file; a missing `codex` binary exits 127; + * otherwise the exit code mirrors Codex's own (0 success, non-zero failure). + * Once the brief validates, `result.json` is written on every outcome — + * completed, failed, or codex_unavailable. An orchestrator that polls for the + * file must therefore also treat a non-zero exit with no file as a usage error. + */ + +import { spawn, execFileSync } from "node:child_process"; +import { mkdirSync, writeFileSync, readFileSync, existsSync, appendFileSync } from "node:fs"; +import { join, resolve, basename } from "node:path"; +import { tmpdir } from "node:os"; + +const SANDBOX_MODES = new Set(["read-only", "workspace-write", "danger-full-access"]); + +function fail(message, code = 2) { + process.stderr.write(`relay: ${message}\n`); + process.exit(code); +} + +function parseArgs(argv) { + const opts = { + brief: null, + cd: process.cwd(), + model: null, + sandbox: "workspace-write", + resumeLast: false, + skipGitRepoCheck: false, + outDir: null, + }; + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + const next = () => { + const value = argv[i + 1]; + if (value === undefined) fail(`${arg} requires a value`); + i += 1; + return value; + }; + switch (arg) { + case "-h": + case "--help": + process.stdout.write(headerComment()); + process.exit(0); + break; + case "--brief": opts.brief = next(); break; + case "--cd": opts.cd = resolve(next()); break; + case "--model": opts.model = next(); break; + case "--sandbox": opts.sandbox = next(); break; + case "--read-only": opts.sandbox = "read-only"; break; + case "--resume-last": opts.resumeLast = true; break; + case "--skip-git-repo-check": opts.skipGitRepoCheck = true; break; + case "--out-dir": opts.outDir = resolve(next()); break; + default: + fail(`unknown option: ${arg}`); + } + } + if (!SANDBOX_MODES.has(opts.sandbox)) { + fail(`invalid --sandbox "${opts.sandbox}" (expected: ${[...SANDBOX_MODES].join(", ")})`); + } + return opts; +} + +function headerComment() { + // The leading block comment doubles as --help text. + const src = readFileSync(new URL(import.meta.url), "utf8"); + const match = src.match(/\/\*\*([\s\S]*?)\*\//); + if (!match) return "relay.mjs — dispatch a brief to codex exec\n"; + return match[1].replace(/^\s*\* ?/gm, "").trim() + "\n"; +} + +function readBrief(opts) { + if (opts.brief) { + if (!existsSync(opts.brief)) fail(`brief file not found: ${opts.brief}`); + return readFileSync(opts.brief, "utf8"); + } + // No --brief: read from stdin (fd 0). Empty stdin is an error. + let stdin = ""; + try { + stdin = readFileSync(0, "utf8"); + } catch { + stdin = ""; + } + return stdin; +} + +function codexVersion() { + try { + return execFileSync("codex", ["--version"], { encoding: "utf8" }).trim(); + } catch { + return null; + } +} + +function gitTouchedFiles(cwd) { + try { + const out = execFileSync("git", ["status", "--porcelain"], { cwd, encoding: "utf8" }); + return out.split("\n").map((line) => line.trimEnd()).filter(Boolean); + } catch { + return []; + } +} + +function timestamp() { + // Local script (not a workflow): Date is available and fine here. + return new Date().toISOString().replace(/[:.]/g, "-"); +} + +function buildArgv(opts, finalPath) { + const argv = ["exec"]; + if (opts.resumeLast) argv.push("resume", "--last"); + argv.push("--json", "-o", finalPath); + // `-s`/`-C` are not accepted by `exec resume`; resume inherits the original + // session's sandbox and working root, and we set the child process cwd below. + if (!opts.resumeLast) { + argv.push("-s", opts.sandbox); + } + if (opts.model) argv.push("-m", opts.model); + if (opts.skipGitRepoCheck) argv.push("--skip-git-repo-check"); + argv.push("-"); // read the prompt from stdin + return argv; +} + +function extractThreadId(event) { + return ( + event.thread_id ?? + event.threadId ?? + (event.thread && (event.thread.thread_id ?? event.thread.id)) ?? + null + ); +} + +function main() { + const opts = parseArgs(process.argv.slice(2)); + const brief = readBrief(opts); + if (!brief.trim()) fail("empty brief (pass --brief or pipe the brief on stdin)"); + + const version = codexVersion(); + const startedAt = new Date().toISOString(); + // Default the run dir to system temp so the repo under review stays pristine — + // the touched-files report must show only Codex's edits, not relay's artifacts. + const outDir = opts.outDir || join(tmpdir(), "delegate-relay", `${basename(opts.cd) || "repo"}-${timestamp()}`); + mkdirSync(outDir, { recursive: true }); + + const eventsPath = join(outDir, "events.jsonl"); + const finalPath = join(outDir, "final.txt"); + const briefPath = join(outDir, "brief.txt"); + const resultPath = join(outDir, "result.json"); + writeFileSync(briefPath, brief, "utf8"); + writeFileSync(eventsPath, "", "utf8"); + + const writeResult = (extra) => { + const finishedAt = new Date().toISOString(); + const result = { + schema: "delegate-relay.result.v1", + workdir: opts.cd, + sandbox: opts.resumeLast ? "(inherited from resumed session)" : opts.sandbox, + model: opts.model, + resumeLast: opts.resumeLast, + codexVersion: version, + startedAt, + finishedAt, + briefPath, + eventsPath, + finalPath: existsSync(finalPath) ? finalPath : null, + ...extra, + }; + writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, "utf8"); + return result; + }; + + if (!version) { + const result = writeResult({ status: "codex_unavailable", exitCode: 127, threadId: null, finalMessage: "", touchedFiles: [] }); + printSummary(result, resultPath); + process.stderr.write("relay: `codex` not found on PATH. Install it (npm i -g @openai/codex) and run `codex login`.\n"); + process.exit(127); + } + + const argv = buildArgv(opts, finalPath); + const child = spawn("codex", argv, { cwd: opts.cd, stdio: ["pipe", "pipe", "pipe"] }); + + let threadId = null; + let stdoutBuf = ""; + const stderrTail = []; + + child.stdout.on("data", (chunk) => { + stdoutBuf += chunk.toString(); + let nl; + while ((nl = stdoutBuf.indexOf("\n")) !== -1) { + const line = stdoutBuf.slice(0, nl); + stdoutBuf = stdoutBuf.slice(nl + 1); + if (!line.trim()) continue; + appendFileSync(eventsPath, `${line}\n`, "utf8"); + try { + const event = JSON.parse(line); + const tid = extractThreadId(event); + if (tid) threadId = tid; + } catch { + // Non-JSON progress line; it is preserved in events.jsonl regardless. + } + } + }); + + child.stderr.on("data", (chunk) => { + const text = chunk.toString(); + process.stderr.write(text); // surface Codex progress live for the orchestrator + for (const line of text.split("\n")) { + if (line.trim()) stderrTail.push(line.trimEnd()); + } + while (stderrTail.length > 20) stderrTail.shift(); + }); + + child.on("error", (err) => { + const result = writeResult({ status: "failed", exitCode: 1, threadId, finalMessage: "", touchedFiles: gitTouchedFiles(opts.cd), error: String(err && err.message ? err.message : err) }); + printSummary(result, resultPath); + process.exit(1); + }); + + child.on("close", (code) => { + if (stdoutBuf.trim()) { + appendFileSync(eventsPath, `${stdoutBuf}\n`, "utf8"); + try { + const tid = extractThreadId(JSON.parse(stdoutBuf)); + if (tid) threadId = tid; + } catch { + // A newline-less final line that isn't valid JSON; preserved in the log only. + } + } + const finalMessage = existsSync(finalPath) ? readFileSync(finalPath, "utf8").trim() : ""; + const result = writeResult({ + status: code === 0 ? "completed" : "failed", + exitCode: code === null ? 1 : code, + threadId, + finalMessage, + touchedFiles: gitTouchedFiles(opts.cd), + ...(code === 0 ? {} : { stderrTail: stderrTail.slice(-20) }), + }); + printSummary(result, resultPath); + process.exit(result.exitCode); + }); + + // If the child failed to launch, writing to its stdin can emit a stray 'error' + // on the pipe; the 'error' handler above owns that outcome, so swallow it here. + child.stdin.on("error", () => {}); + child.stdin.write(brief); + child.stdin.end(); +} + +function printSummary(result, resultPath) { + const lines = []; + lines.push(""); + lines.push(`relay: ${result.status} (exit ${result.exitCode}) · codex ${result.codexVersion ?? "?"}`); + if (result.resumeLast) lines.push("mode: resumed most recent session"); + if (result.threadId) lines.push(`thread id (resume with: codex exec resume ${result.threadId}): ${result.threadId}`); + const touched = result.touchedFiles || []; + lines.push(`touched files: ${touched.length}`); + for (const file of touched.slice(0, 40)) lines.push(` ${file}`); + if (touched.length > 40) lines.push(` … and ${touched.length - 40} more`); + if (result.stderrTail && result.stderrTail.length) { + lines.push("last stderr:"); + for (const line of result.stderrTail.slice(-8)) lines.push(` ${line}`); + } + lines.push(""); + lines.push("--- codex final report ---"); + lines.push(result.finalMessage || "(no final message captured)"); + lines.push("--- end report ---"); + lines.push(""); + lines.push(`result: ${resultPath}`); + lines.push("relay does not commit. Review the diff, re-run the project gates yourself, then commit from the orchestrator."); + process.stdout.write(`${lines.join("\n")}\n`); +} + +main();