agentleFS
DanaResearchGroupDanaResearchGroup/agent-skills/spar/SKILL.md

spar

Skill1 starsChanged 3 months agoMITView on GitHub

Use when you want adversarial "try to break this" feedback from Codex on the current code, plan, or decision, want a persistent per-project sparring record, or are mid-decision and want a second opinion before committing to an approach.

  • Reads credentials

3 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

24 rules. 0 are common, 9 are distinctive, and 15 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 9

    Distinctive

    This author's own choices. Worth a look.

  • 15

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Run an adversarial Codex sparring round and persist the exchange under ~/agents/adversarial/{slug}/.

    line 7
  2. Very specific:

    Use this skill as an inline Codex wrapper, not as a manual two-terminal file drop.

    line 10
  3. Distinctive:

    Run setup before any new round:

    line 28
  4. Distinctive:

    Run the Codex auth probe:

    line 49
  5. Very specific:

    If it prints AUTH_FAILED or exits non-zero, stop and tell the user: No Codex authentication found.

    line 59
  6. Very specific:

    All Codex calls below use ~/.claude/skills/spar/bin/spar-codex.sh.

    line 135
  7. Very specific:

    If the resolver is absent, fails, or returns an unusable result, the helper leaves the model at Codex's configured default and sets …

    line 140
  8. Very specific:

    Before composing a substantive round (/spar <topic> — NOT reset, log, or review), keep the persistent Codex session from silently …

    line 145
  9. Distinctive:

    Run this right after taking the lock:

    line 152
  10. Very specific:

    Summary turn fails or returns empty: do NOT archive .session-id — keep the working session and retry next round.

    line 212
  11. Very specific:

    After a successful rotation .session-id is gone, so Invoke Codex naturally takes the first-round path and captures a new session id.

    line 215
  12. Very specific:

    Reseed the fresh session: When PENDING_HANDOFF=1, the first-round PROMPT you compose in

    line 222
  13. Distinctive:

    Invoke Codex: MUST be prefixed, in order, with:

    line 223
  14. Distinctive:

    Create the next question file before invoking Codex:

    line 237
  15. Very specific:

    Write the question through a temp file in STORE, then mv it into place.

    line 254
  16. Distinctive:

    Always prepend this boundary:

    line 260
  17. Very specific:

    Use a persistent Codex session per project.

    line 272
  18. Distinctive:

    Use this parser pattern for both new and resumed runs:

    line 277
  19. Very specific:

    If CODEX_EXIT is 124, report a 10-minute timeout.

    line 359
  20. Very specific:

    If resume ever fails because the session is invalid, archive .session-id with a timestamped name, start a fresh session, and explicitly …

    line 363
  21. Distinctive:

    Keep both files. The question/answer pair is the audit trail.

    line 370
  22. Distinctive:

    Use Codex's answer to write the one-line verdict.

    line 378
  23. Very specific:

    If an auto-handoff fired this round (PENDING_HANDOFF=1), also append a line recording the rotation, so the sparring log shows where Codex's …

    line 380
  24. Very specific:

    If Claude Code context is around or above 35%, proactively suggest /handoff at the next natural checkpoint.

    line 412

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Run an adversarial Codex sparring round and persist the exchange under ~/agents/adversarial/{slug}/.

Use this skill as an inline Codex wrapper, not as a manual two-terminal file drop. Claude Code composes the question artifact, invokes codex exec, writes the answer artifact, updates the log, and then presents Codex's findings verbatim.

Subcommands

  • /spar <topic>: start a new round.
  • /spar review [round-N]: re-read the latest answer, or the requested round answer.
  • /spar log: show sparring-log.md.
  • /spar reset: archive .session-id so the next round starts a fresh Codex session (a hard reset — no handoff summary).

Codex context is kept healthy automatically: when the persistent session crosses 35% fill, /spar <topic> has the outgoing session summarize itself and rotates to a fresh, reseeded session before asking the new question. See Codex Context Auto-Handoff below.

Setup

Run setup before any new round:

# Clear the ambient git environment FIRST. GIT_DIR, GIT_WORK_TREE and
# GIT_COMMON_DIR each make `git rev-parse` describe a different repository than
# the one you are standing in, so an inherited value silently computes $REPO_ROOT
# — and therefore the slug and the binding — for somebody else's repo. Hardening
# the binding resolver alone does not close this: it faithfully binds whatever
# $REPO_ROOT names.
unset GIT_DIR GIT_WORK_TREE GIT_COMMON_DIR GIT_CEILING_DIRECTORIES \
      GIT_DISCOVERY_ACROSS_FILESYSTEM GIT_INDEX_FILE GIT_OBJECT_DIRECTORY
eval "$(~/.claude/skills/bin/skill-slug 2>/dev/null)" 2>/dev/null || true
[ -z "${SLUG:-}" ] && SLUG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" | tr -cd 'a-zA-Z0-9._-')
SLUG="${SLUG:-unknown}"
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
STORE="$HOME/agents/adversarial/$SLUG"
umask 077
mkdir -p "$STORE"
chmod 700 "$HOME/agents" "$HOME/agents/adversarial" "$STORE" 2>/dev/null || true

Run the Codex auth probe:

source ~/.claude/skills/bin/skill-codex-probe && _skill_codex_auth_probe

The probe file only defines functions — it must be sourced and the function called. Executing it as a command defines the functions in a subshell that then exits, printing nothing and returning 0, so the check below never fires.

If it prints AUTH_FAILED or exits non-zero, stop and tell the user: No Codex authentication found. Run codex login or set $CODEX_API_KEY / $OPENAI_API_KEY, then re-run /spar.

Take a per-project lock before computing round numbers or touching .project, .session-id, round files, or sparring-log.md:

LOCKDIR="$STORE/.lock"
until mkdir "$LOCKDIR" 2>/dev/null; do sleep 1; done
trap 'rmdir "$LOCKDIR" 2>/dev/null || true' EXIT

Then bind the store to the current repository before using any persisted session. This avoids accidentally resuming a session from another repo with the same slug:

# Preserve the exit code: 1 is "bound elsewhere", 3 is "cannot resolve this
# checkout". Collapsing both to 1 throws away the distinction this script exists
# to draw, and the outcomes below document them separately.
# Note the `||` form: inside an `if ! ...; then` branch $? is the NEGATION's
# status, which is always 0, so that spelling would exit successfully on a
# refusal. Measured, not assumed.
BIND=$(~/.claude/skills/spar/bin/spar-binding.sh "$STORE" "$REPO_ROOT") || exit $?

Bind inside the lock, not before it. The binding can WRITE to .project (it upgrades, adopts, and creates), and it does so with a read-modify-write that mv makes atomic only at the final replace — so two sessions binding the same store concurrently can lose each other's lines, and two sessions that both see an unbound store can both decide it is theirs. The lock is what serialises that; an earlier draft of this file ran the binding above the lock and had both races.

Bind to the repository, never to the worktree. $REPO_ROOT is a checkout path, and the worktree-per-feature rule deletes that path at every merge — so a binding recorded as repo_root= names a directory that stops existing partway through the arc, and the next round hard-errors on a store that was never wrong. spar-binding.sh records repo_id=, the canonicalised git rev-parse --git-common-dir, which is identical from every worktree of one repository. Do not reintroduce a --show-toplevel comparison here: 26 of 67 stores on this machine had already died that way, and the gracie store accumulated seven hand-written rebinds before this was fixed.

Surface whatever the script printed on stderr. Four of its outcomes are worth reading rather than skipping past:

  • Refused (exit 1) — the file vouches for a repository and it is not this one, either through a repo_id= line or a repo_root= path that still exists. Use a different slug. Never answer this with /spar reset: reset discards .session-id, and the sparring arc's whole memory with it.
  • Upgraded — a legacy repo_root= still resolved to this repository, so the store gained a repo_id= line. Nothing else changes.
  • Adopted (a loud NOTICE) — nothing on disk vouched for any repository, so nothing could say which one the arc belonged to, and the script bound it to this one and wrote rebound_at=. This is not an approval gate: the round proceeds. The notice is an audit record, so read it — if the arc is not this repository's, stop before the round runs, or Codex resumes another project's review session against this code.
  • Unresolvable (exit 3) — $REPO_ROOT has a .git that git cannot resolve, usually a linked worktree whose gitdir was deleted. It refuses rather than fall back to treating the checkout as a plain directory, which would mint the worktree path as the repository's identity.

Both kinds of evidence are read, and any match is a match. repo_id= is not authoritative on its own: a store part-way through migration can hold repo_id=A alongside a live repo_root=B that is equally valid, and one arc can legitimately span two repositories. A repo_id= quoted inside a note= line does not bind; the check is whole-line.

What the binding does not protect against: the same repository reachable by two absolute paths (bind mounts, /home vs /export/home) records two identities and will refuse itself through the other path — add the second repo_id= line by hand if that happens.

Stores created before this existed are upgraded in place on their next round. To migrate them all at once instead — worth doing, because a store can only be migrated while one of its recorded paths still exists, and a pruned worktree takes that proof with it:

~/.claude/skills/spar/bin/migrate-spar-bindings.sh            # dry run
~/.claude/skills/spar/bin/migrate-spar-bindings.sh --apply

Model selection

All Codex calls below use ~/.claude/skills/spar/bin/spar-codex.sh. When the optional ~/.claude/skills/model-ladder/bin/ladder.py resolver is installed, the helper uses the model and reasoning effort from resolve review. Set SPAR_TIER=hard in the calling shell (for example, export SPAR_TIER=hard) for a stronger adversarial review of work written by a top-tier model. The setting applies to fresh rounds, resumed rounds, and handoff summaries. If the resolver is absent, fails, or returns an unusable result, the helper leaves the model at Codex's configured default and sets reasoning effort to high.

Codex Context Auto-Handoff

Before composing a substantive round (/spar <topic> — NOT reset, log, or review), keep the persistent Codex session from silently saturating. Because the session is resumed every round, its context window fills monotonically; a saturated session degrades without warning exactly when the late, highest-stakes reviews run. When the session crosses 35% fill, have the outgoing session write its own successor handoff, then rotate to a fresh session seeded with it. This is automatic and self-contained — the user does not ask for it.

Run this right after taking the lock:

THRESHOLD=35                       # Codex context %; at/above this, rotate to a fresh session.
HANDOFF_FILE="$STORE/.handoff.md"
PENDING_HANDOFF=0
PYTHON_CMD=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)

if [ -f "$STORE/.session-id" ] && [ -n "$PYTHON_CMD" ]; then
  OLD_SID=$(cat "$STORE/.session-id")
  # Fresh, per-session fill % straight from THIS session's rollout via the script's stdout
  # (not the shared global status-line file). Fail open: empty/non-numeric PCT -> skip.
  PCT=$("$HOME/.claude/skills/autodev/bin/codex-spar-ctx.sh" "$OLD_SID" "$SLUG" 2>/dev/null | sed -n 's/^pct=//p' | tail -1)
  if [ -n "$PCT" ] && awk "BEGIN{exit !($PCT+0 >= $THRESHOLD)}" 2>/dev/null; then
    echo "Codex context ${PCT}% >= ${THRESHOLD}% -- handing off to a fresh session."
    HANDOFF_PROMPT='You are about to be replaced by a successor Codex reviewer that will have ZERO memory of this conversation. Write a concise handoff, in your own words, that is the ONLY thing that carries over. Use these sections:

## Broad context
What this sparring arc is reviewing and the current state of the work (branch, what has been built).

## Standing items (open findings)
Every open or unresolved finding, each with its current state made explicit.

## Standing verdicts
Positions you have taken and the reason for each, so the successor does not relitigate them.

## Settled decisions
What is now treated as decided and must not be reopened.

## Must re-check
What the successor should independently re-verify against the real code.

## Insights
Non-obvious things learned: inverted premises, false positives, dead ends not worth re-treading.

Be specific and terse. Reference files by path; the successor can read the code itself.'
    HTMPERR=$(mktemp "${TMPDIR:-/tmp}/spar-handoff-err-XXXXXX.txt")
    HRUN=$(mktemp "${TMPDIR:-/tmp}/spar-handoff-out-XXXXXX.txt")
    # Uses the SAME JSON-stream parser as "Invoke Codex" below.
    (cd "$REPO_ROOT" && timeout 600 ~/.claude/skills/spar/bin/spar-codex.sh resume "$OLD_SID" "$HANDOFF_PROMPT" --skip-git-repo-check -c 'sandbox_mode="read-only"' --json < /dev/null 2>"$HTMPERR") \
      | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c '<same parser as Invoke Codex>' | tee "$HRUN"
    HEXIT=${PIPESTATUS[0]}
    sed '/^SESSION_ID:/d' "$HRUN" > "$HANDOFF_FILE.tmp"
    if [ "$HEXIT" = 0 ] && [ -s "$HANDOFF_FILE.tmp" ]; then
      mv "$HANDOFF_FILE.tmp" "$HANDOFF_FILE"
      cp "$HANDOFF_FILE" "$STORE/$(date +"%Y.%m.%d %H.%M.%S") codex-handoff.md" 2>/dev/null || true
      mv "$STORE/.session-id" "$STORE/.session-id.$(date +%Y%m%d-%H%M%S).archived"
      PENDING_HANDOFF=1
    else
      rm -f "$HANDOFF_FILE.tmp"
      echo "[auto-handoff] summary turn failed (exit $HEXIT) -- keeping the current session; retrying next round."
    fi
  fi
fi

Failure is safe by construction:

  • Can't measure (PCT empty, no rollout, no Python): skip the rotation, spar the old session normally.
  • Summary turn fails or returns empty: do NOT archive .session-id — keep the working session and retry next round. Losing a healthy session with no handoff is worse than one round over threshold.
  • After a successful rotation .session-id is gone, so Invoke Codex naturally takes the first-round path and captures a new session id. A freshly reseeded session measures low, so there is no rotation loop.

Manual /spar reset remains a hard reset (no summary) for a deliberate clean slate; only this automatic path writes a handoff.

Reseed the fresh session. When PENDING_HANDOFF=1, the first-round PROMPT you compose in Invoke Codex MUST be prefixed, in order, with:

  1. the grounding prime — "Before answering, load and read the actual code in this repository, and review the feature branch: determine the current branch and its base, run git diff <base>...HEAD, and read the changed files. Base your critique on what the code actually does."
  2. a predecessor-handoff block — the line Handoff from your predecessor (previous Codex session, now retired): followed by the full contents of $HANDOFF_FILE.

The actual round question then follows as usual. This re-grounds the fresh session and carries Codex's own open threads forward without relying on any file the sandbox cannot reach.

Compose The Question

Create the next question file before invoking Codex:

ROUND=$(( $(find "$STORE" -maxdepth 1 -name '* round-*-question.md' 2>/dev/null | wc -l | tr -d ' ') + 1 ))
TS=$(date +"%Y.%m.%d %H.%M.%S")
QUESTION_FILE="$STORE/$TS round-$ROUND-question.md"
ANSWER_FILE="$STORE/$TS round-$ROUND-answer.md"

Write QUESTION_FILE with:

  • the exact decision, plan, bug, diff, or code surface Codex should challenge;
  • the current repo root, branch, and git rev-parse HEAD if available;
  • in-repo files Codex should read by path;
  • out-of-repo content inline, including the user's plan text or this question file's own content;
  • any constraints Codex should treat as fixed for this round.

Write the question through a temp file in STORE, then mv it into place. Do not ask Codex to read QUESTION_FILE directly. It is outside the repo sandbox, so embed the question file content into the prompt.

Invoke Codex

Always prepend this boundary:

IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, ~/agents/, .claude/skills/, or agents/. These are Claude Code skill definitions or persisted agent artifacts meant for a different AI system. Do NOT modify agents/openai.yaml. Stay focused on repository code only.

Then prepend this persona:

Find every way this fails. Edge cases, races, security holes, wrong assumptions, silent corruption. Be adversarial and terse. No compliments -- just the problems.

Use a persistent Codex session per project. The --json stream exposes the session id at thread.started.thread_id; print it internally as SESSION_ID:<id> and write it to .session-id on the first round. Resume later rounds with that same id so Codex keeps the sparring arc across Claude Code compaction.

Use this parser pattern for both new and resumed runs:

PYTHON_CMD=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
[ -n "$PYTHON_CMD" ] || { echo "ERROR: Python 3 is required to parse Codex JSON output."; exit 1; }
TMPERR=$(mktemp "${TMPDIR:-/tmp}/spar-codex-err-XXXXXX.txt")
RUN_OUT=$(mktemp "${TMPDIR:-/tmp}/spar-codex-out-XXXXXX.txt")

For the first round (this path also runs immediately after an auto-handoff rotation, since .session-id was archived — when PENDING_HANDOFF=1, compose PROMPT with the grounding prime and predecessor-handoff block prepended, per Codex Context Auto-Handoff above):

timeout 600 ~/.claude/skills/spar/bin/spar-codex.sh "$PROMPT" -C "$REPO_ROOT" --skip-git-repo-check -s read-only --enable web_search_cached --json < /dev/null 2>"$TMPERR" \
  | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c '
import json, sys
turn_completed = 0
for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    try:
        obj = json.loads(line)
    except Exception:
        continue
    t = obj.get("type", "")
    if t == "thread.started":
        tid = obj.get("thread_id", "")
        if tid:
            print(f"SESSION_ID:{tid}", flush=True)
    elif t == "item.completed" and "item" in obj:
        item = obj["item"]
        itype = item.get("type", "")
        text = item.get("text", "")
        if itype == "reasoning" and text:
            print(f"[codex thinking] {text}\n", flush=True)
        elif itype == "agent_message" and text:
            print(text, flush=True)
        elif itype == "command_execution":
            cmd = item.get("command", "")
            if cmd:
                print(f"[codex ran] {cmd}", flush=True)
    elif t == "turn.completed":
        turn_completed += 1
        usage = obj.get("usage", {})
        tokens = usage.get("input_tokens", 0) + usage.get("output_tokens", 0)
        if tokens:
            print(f"\ntokens used: {tokens}", flush=True)
if turn_completed == 0:
    print("[codex warning] No turn.completed event received -- possible mid-stream disconnect.", file=sys.stderr, flush=True)
' | tee "$RUN_OUT"
CODEX_EXIT=${PIPESTATUS[0]}
if [ "$CODEX_EXIT" != 0 ]; then
  echo "[codex exit $CODEX_EXIT] $(sed -n '1p' "$TMPERR" 2>/dev/null || true)"
  exit "$CODEX_EXIT"
fi
SESSION_ID=$(sed -n 's/^SESSION_ID://p' "$RUN_OUT" | tail -1)
[ -n "$SESSION_ID" ] && printf '%s\n' "$SESSION_ID" > "$STORE/.session-id"
sed '/^SESSION_ID:/d' "$RUN_OUT" > "$ANSWER_FILE.tmp" && mv "$ANSWER_FILE.tmp" "$ANSWER_FILE"
# Optional: record Codex's context-window fill for the status line (silent no-op
# without the autodev/status-line infra).
[ -n "$SESSION_ID" ] && "$HOME/.claude/skills/autodev/bin/codex-spar-ctx.sh" "$SESSION_ID" "$SLUG" 2>/dev/null || true

For later rounds:

SESSION_ID=$(cat "$STORE/.session-id")
(cd "$REPO_ROOT" && timeout 600 ~/.claude/skills/spar/bin/spar-codex.sh resume "$SESSION_ID" "$PROMPT" --skip-git-repo-check -c 'sandbox_mode="read-only"' --enable web_search_cached --json < /dev/null 2>"$TMPERR") \
  | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c '<same parser as above>' | tee "$RUN_OUT"
CODEX_EXIT=${PIPESTATUS[0]}
if [ "$CODEX_EXIT" != 0 ]; then
  echo "[codex exit $CODEX_EXIT] $(sed -n '1p' "$TMPERR" 2>/dev/null || true)"
  exit "$CODEX_EXIT"
fi
sed '/^SESSION_ID:/d' "$RUN_OUT" > "$ANSWER_FILE.tmp" && mv "$ANSWER_FILE.tmp" "$ANSWER_FILE"
# Optional: record Codex's context-window fill for the status line (silent no-op
# without the autodev/status-line infra).
[ -n "$SESSION_ID" ] && "$HOME/.claude/skills/autodev/bin/codex-spar-ctx.sh" "$SESSION_ID" "$SLUG" 2>/dev/null || true

If CODEX_EXIT is 124, report a 10-minute timeout. If it is non-zero, include the first stderr lines from TMPERR and do not update .session-id, the answer file, or sparring-log.md. If stderr contains auth/login/unauthorized text, tell the user to run codex login or set the API key.

If resume ever fails because the session is invalid, archive .session-id with a timestamped name, start a fresh session, and explicitly tell the user that Codex memory was reset. If resume memory is not working, fall back to embedding prior round Q/A excerpts from sparring-log.md and recent round files into each prompt.

Record The Round

Keep both files. The question/answer pair is the audit trail.

Append one line atomically to sparring-log.md while still holding the lock:

- YYYY.MM.DD HH.MM.SS | round-N | <topic> | <one-line verdict> | Q: <question path> | A: <answer path>

Use Codex's answer to write the one-line verdict; do not invent a verdict before reading it.

If an auto-handoff fired this round (PENDING_HANDOFF=1), also append a line recording the rotation, so the sparring log shows where Codex's memory was rolled over:

- YYYY.MM.DD HH.MM.SS | round-N | AUTO-HANDOFF | Codex ctx <pct>% -> fresh session | predecessor summary: <path to archived codex-handoff.md>

Present:

CODEX SAYS (/spar round-N):
------------------------------------------------------------
<answer file content verbatim>
------------------------------------------------------------
Recommendation: <one sentence engaging one specific Codex finding>

Capture what the round taught

A confirmed finding is the cheapest learning available: an outside model just showed you something your own reading missed. Once you have verified it against the code — verified, not merely received — log it.

~/.claude/skills/bin/skill-learnings-log '{"type":"pitfall","key":"<kebab-slug>","insight":"<the defect class, and what made it invisible>","confidence":8,"source":"cross-model"}'

Log what generalises past this diff, above all a finding that inverted a premise you held — that class is the likeliest to repeat, because the belief that produced it is still yours. Skip what Codex got wrong, and skip findings that only describe this change. One call per learning, never a reason to delay the report. Rules: COMPOUNDING.md.

If Claude Code context is around or above 35%, proactively suggest /handoff at the next natural checkpoint. Note that $STORE/.session-id persists on disk, so a compacted or fresh Claude Code session can resume the same Codex sparring session.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.