npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

codex-goat

v0.1.8

Published

A stronger default for OpenAI Codex CLI: better prompts, one consistent workflow, and runtime helpers. Codex stays the execution engine.

Downloads

351

Readme

codex-goat

A stronger default for OpenAI Codex CLI.

Better prompts, one consistent workflow, and runtime helpers that make "done" auditable. Codex stays the execution engine.

npm License: MIT Node CI

npm install -g codex-goat

codex-goat does not replace Codex, wrap its model calls, or fork its source. It keeps Codex as the execution engine and makes it easier to:

  • start a stronger Codex session by defaultgoat launches codex with raised reasoning effort, no approval prompts and no sandbox (yolo; --safe keeps Codex's own defaults), per-stage model routing, and project guidance already loaded
  • run one consistent workflow from clarification to completion — six stages that share a state directory, an evidence ledger, and one set of operating rules
  • invoke that workflow with $plan, $ultragoal, $team, $code-review, and $ultraqa — each independently, no fixed chain
  • keep plans, goals, reviews, and state in .goat/, surviving compaction and restarts

Install

Requires Node 20+ and a Codex CLI that is already installed and authenticated — codex-goat drives codex, it does not replace it.

npm install -g codex-goat

That is the whole install. The package's postinstall does two things, both best-effort and both reported by goat doctor:

  1. Fetches the native runtime for your platform from the matching GitHub release, verifies it against the release's checksums.txt, smoke-tests it, and installs it as bin/goat-runtime inside the package. Without it the hooks run on Node, which works and is only slower.
  2. Runs the user-scope setup — skills into ~/.agents/skills, the operating rules into ~/.codex/AGENTS.md, the three hooks into ~/.codex/hooks.json — so every project you open in Codex has $plan, $ultragoal, $team, $code-review and $ultraqa. Codex asks once to trust the hooks on the next launch.

Setup is skipped, with a reason, under sudo (HOME would be root's), in CI, when installed as a project dependency, or with GOAT_SKIP_SETUP=1; the native fetch is skipped with GOAT_SKIP_NATIVE=1, and GOAT_SKIP_POSTINSTALL=1 skips both. Whatever postinstall could not do, the first real goat launch finishes: it runs the user-scope setup if it is missing and fetches the native runtime once per version, so --ignore-scripts, package managers that skip lifecycle scripts, and npm's own script gating still end in the same place. Recent npm prints npm warn install-scripts … not yet covered by allowScripts for any package with a postinstall; the script still runs today, and if a future npm blocks it, goat finishes the job on first launch (npm install -g --allow-scripts=codex-goat codex-goat runs it at install time).

goat doctor                            # what got installed, and what Codex will actually run
goat exec "Reply with exactly GOAT-OK" # the real smoke test: Codex authenticates and answers
cd your-project && goat                # a stronger Codex session, from a project

goat doctor checks the install shape, including whether Codex has trusted the hooks. goat exec forces Codex to authenticate and complete a model call. A green doctor with a failing exec means an auth or profile problem, not an install problem. Per-project installs (goat setup --scope project) still exist for repos that should carry their own copy.

Without a global install, npx runs the same CLI. goat setup still writes to the project, so this is a real install path and not just a preview:

npx codex-goat setup --scope project
npx codex-goat doctor

Upgrade, check, and remove:

npm install -g codex-goat@latest   # upgrade; postinstall refreshes the runtime, skills, and hooks
npm view codex-goat version        # what the registry has
goat uninstall --scope user        # removes the user-scope skills, AGENTS block, and hooks; keeps ~/.goat/
goat uninstall --scope project     # same for a per-project install
npm uninstall -g codex-goat

The package installs two identical binaries, goat and codex-goat. Use the second if something else on your PATH already answers to goat.

Published from CI with npm provenance, so the registry can attest which commit and workflow built the tarball you installed:

npm view codex-goat dist.attestations

Then work normally:

goat --xhigh

Inside the session, invoke whichever stage the work actually needs:

$plan       "design the checkout fix"
$ultragoal  "implement the approved plan with checkpoint evidence"
$code-review
$ultraqa    --tests

The repository is also a valid Codex plugin (.codex-plugin/plugin.json), installable through Codex's own plugin system. These are shell commands, not slash commands — Codex's TUI has /plugins for browsing, but installing happens on the CLI:

codex plugin marketplace add https://github.com/hypnguyen1209/codex-goat
codex plugin add codex-goat@codex-goat

The marketplace entry resolves to the npm package, because that is the only channel that carries built code: dist/ is generated at publish time and is not committed, so a plugin installed straight from a git checkout would register three hooks that run, exit 0, and inject nothing.

Plugin mode gives you the skills, the role cards, and the lifecycle hooks. It does not give you the goat CLI, which the skills use for state, contracts, and the evidence ledger — without it, every evidence step in every skill is a no-op. Install both, or use goat setup alone.

Codex must trust a hook before it runs it. A freshly registered hook is Untrusted and is skipped silently; approve it at the next launch prompt or via /hooks. Note that codex exec has no trust prompt, so hooks stay inert there until trust is granted interactively at least once.

Architecture

Codex does the agent work. codex-goat sits on three seams around it — the process it launches, the skills Codex loads, and the lifecycle hooks Codex calls — and keeps durable state in .goat/. Nothing here intercepts a model call.

flowchart TB
    user([You])

    subgraph goat["codex-goat"]
        cli["goat CLI<br/>launch · setup · doctor · status<br/>contract · state · ledger · roles"]
        assets["skills/ · prompts/<br/>templates/AGENTS.md"]
        hook["hooks/goat-hook.mjs"]
        state[("<b>.goat/</b><br/>state.json · ledger.jsonl<br/>plans · goals · reviews · qa · memory")]
    end

    subgraph codex["Codex CLI — the execution engine"]
        proc["codex process"]
        model{{"model turn"}}
    end

    user -->|"goat --xhigh"| cli
    cli -->|"spawn, argv forwarded verbatim"| proc
    cli -->|"goat setup writes<br/>(postinstall and first launch run it)"| assets
    assets -->|".agents/skills · AGENTS.md · hooks.json"| proc
    proc --> model
    model -->|"SessionStart · UserPromptSubmit · Stop"| hook
    hook -->|"additionalContext"| model
    hook <--> state
    cli <--> state

    classDef ext fill:#f6f8fa,stroke:#57606a,color:#24292f
    class codex,proc,model ext

The CLI and the hook are the only two things that run codex-goat code, and they meet only through .goat/ — which is why a resumed session and goat status cannot disagree about what has been proven.

Hook dispatch

Three events, one entry script, two implementations. The native binary is optional and handles only what it can answer without the stage table; anything else falls back to Node.

flowchart TB
    ev["Codex fires a hook<br/>JSON on stdin"] --> mjs["goat-hook.mjs"]
    mjs --> native{"goat-runtime<br/>binary present?"}

    native -->|no| node["dist/hooks/handler.js"]
    native -->|yes| rust["goat-runtime hook"]

    rust --> which{"which event?"}
    which -->|"SessionStart · Stop"| handled["handled natively<br/>exit 0"]
    which -->|"UserPromptSubmit"| delegate["exit 3 — delegate"]
    delegate --> node

    handled --> out["stdout: hook response"]
    node --> out
    out --> codex["Codex merges additionalContext"]

    fail["import fails"] -.->|"stderr note, exit 0"| empty["{}"]
    node -.-> fail

    classDef warn fill:#fff8c5,stroke:#9a6700,color:#24292f
    class fail,empty warn

UserPromptSubmit is always Node's, because evaluating an entry contract needs the stage table — and that has one source of truth, in TypeScript. Duplicating it in Rust is the drift this split exists to avoid.

Three rules hold on every path: never block, never throw, exit 0 with valid JSON. A failure degrades to {} and says why on stderr, because a hook that silently succeeds while doing nothing is worse than one that fails loudly.

The idea: entry contracts, not a pipeline

Most workflow layers give you a chain: clarify → plan → execute → review → QA. Real requests almost never start at the beginning. "Review my changes" has no plan. "Test this properly" has no objective. Forcing a chain makes the tool fight the user.

codex-goat replaces the chain with entry contracts. Each stage declares what it needs to start, and every requirement can be satisfied three ways: by a previous stage, by the working tree, or by the user simply saying it in the invocation.

| Stage | Needs | Produces | | --- | --- | --- | | $clarify | nothing | frozen requirements + a recorded objective | | $plan | an objective | plan with testable acceptance criteria | | $ultragoal | an objective + an approach | goal ledger with per-checkpoint evidence, registered as a native Codex goal when the session offers one | | $team | an objective + 2+ independent lanes | one Codex sub-agent per lane, evidence recorded by the root, merged verification | | $code-review | a change | verified findings, most severe first | | $ultraqa | something runnable | scenario matrix and QA report |

Ask the tool where you stand at any moment:

goat contract              # every stage: what is already satisfied, what to state inline
goat contract ultragoal    # one stage
$ultragoal: ready
     objective: satisfied — objective on record: ship the checkout fix
     plan: inline — supply an approved plan inline, or run $plan first

inline means the user's own message can satisfy this — so the stage proceeds. Nothing is ever hard-blocked, and that is enforced by the type: RequirementVerdict is satisfied | inline, with no third case, and a bundle check fails if the union grows.

flowchart LR
    req["a requirement<br/>e.g. changed-scope"] --> chk{"checkContract"}
    chk -->|"prior artifact, or<br/>the working tree"| sat["satisfied"]
    chk -->|"the user can just<br/>say it"| inl["inline"]
    sat --> go["stage starts"]
    inl --> go

    miss["missing"]:::dead

    classDef dead fill:#f6f8fa,stroke:#8c959f,color:#8c959f,stroke-dasharray: 4 3

There is no third arrow. missing is drawn greyed out because it does not exist in the type — v0.1.0 declared it, nothing ever produced it, and that made the readiness check a tautology and the invariant unenforceable. Deleting the case is what turned the rule into something the compiler holds.

So the six stages are six independent entry points, not a chain. The dotted lines below are what each stage typically unblocks — suggestions the skills print, never gates:

flowchart LR
    subgraph entry[" "]
        direction TB
        e1["your request"]
    end

    clarify["$clarify<br/><i>needs nothing</i>"]
    plan["$plan<br/><i>an objective</i>"]
    ultragoal["$ultragoal<br/><i>objective + approach</i>"]
    team["$team<br/><i>2+ lanes</i>"]
    review["$code-review<br/><i>a change</i>"]
    qa["$ultraqa<br/><i>something runnable</i>"]

    e1 --> clarify
    e1 --> plan
    e1 --> ultragoal
    e1 --> team
    e1 --> review
    e1 --> qa

    clarify -.-> plan
    plan -.-> ultragoal
    plan -.-> team
    ultragoal -.-> review
    team -.-> review
    review -.-> qa

    style entry fill:none,stroke:none

"Review my changes" enters at $code-review with no plan and no objective. "Test this properly" enters at $ultraqa. Neither has to manufacture input it does not need.

Evidence: what makes "done" mean something

Every stage records proof in an append-only ledger:

goat ledger evidence --stage ultragoal --exit 0 -- npm test

goat status then reconciles claims against proof and marks anything that does not hold up as complete*. Three things fail the check: no evidence at all, a command that exited non-zero, and a shell no-op like true that exits 0 without testing anything.

$plan          complete   ready
     artifact: .goat/plans/checkout.md
     last evidence: npm test -> exit 0
$ultragoal     complete*  ready
     unproven: every recorded command failed (last: npm test -> exit 1)

goat warn 1 stage(s) marked complete without evidence that backs the claim (shown as complete*).

goat status exits non-zero when that happens, so CI can gate on it. This is the single rule that separates finished work from a confident claim about finished work, and every bundled skill enforces it.

flowchart TB
    claim["a stage is marked complete"] --> gate{"isSubstantiveEvidence<br/>over its recorded commands"}

    gate -->|"nothing recorded"| bad1["complete*<br/>no evidence recorded"]
    gate -->|"every command exited non-zero"| bad2["complete*<br/>every recorded command failed"]
    gate -->|"every command is true, echo, :"| bad3["complete*<br/>every recorded command is a no-op"]
    gate -->|"at least one real command exited 0"| good["complete"]

    bad1 --> fail["goat status exits 1"]
    bad2 --> fail
    bad3 --> fail
    good --> pass["goat status exits 0"]

    classDef ok fill:#dafbe1,stroke:#1a7f37,color:#24292f
    classDef no fill:#ffebe9,stroke:#cf222e,color:#24292f
    class good,pass ok
    class bad1,bad2,bad3,fail no

The exit-code check is the load-bearing one: v0.1.0 stored exitCode, wrote it to the ledger, printed it — and never compared it, so --exit 1 -- npm test closed the gate. The no-op list is a lint against lazy proof, not a security control; bash -c true defeats it, and the code says so.

Command surface

goat [flags] [codex args...]           launch Codex with stronger defaults
  --high | --xhigh | --medium | --low  reasoning effort (default: high)
  --effort <level>                     the same, explicit
  --safe                               keep Codex's own approvals and sandbox (default is yolo:
                                       approval_policy=never, sandbox_mode=danger-full-access)
  --madmax                             codex --dangerously-bypass-approvals-and-sandbox (same as the default)
  --worktree                           forwarded to codex: its managed worktree (codex >= 0.155)
  --no-goat-defaults                   forward argv to codex untouched
  --print-argv                         print the resolved codex command and exit
  --                                   everything after this goes to codex verbatim

goat setup [--scope user|project] [--force]
goat doctor
goat exec [--role <name>] "<prompt>"
goat status
goat contract [<stage>] [--json]
goat state read|set|clear
goat ledger read|evidence|note
goat skills [--roles]
goat roles install|uninstall|list [--scope user|project]
goat hook
goat uninstall [--scope ...] [--purge-state]

Nothing is hidden. goat --print-argv shows the exact command that would run:

$ goat --xhigh -m gpt-5 "fix the bug" --print-argv
codex -c 'model_reasoning_effort="xhigh"' -c 'approval_policy="never"' -c 'sandbox_mode="danger-full-access"' -m gpt-5 'fix the bug'

$ goat --safe -s read-only --print-argv
codex -c 'model_reasoning_effort="high"' -s read-only

Flags codex-goat does not own are forwarded to Codex in the order you typed them. The two permission overrides are the yolo default; an explicit -s, -a, --full-auto, --yolo, or your own -c for either key replaces the matching one, wherever it appears on the line, and --safe drops both.

Role cards

Nine specialist prompts for focused sub-tasks — planner, executor, verifier, reviewer, security-reviewer, test-engineer, architect, critic, researcher. Each states what the role does, what it refuses, and the shape of its output.

goat skills --roles
goat exec --role security-reviewer "Audit the token refresh path in src/auth/"

Inside a session they are the $goat-roles skill. Adopt one at a time; two role cards at once average into neither.

Runtime helpers

Three Codex lifecycle hooks, registered by goat setup:

| Event | What it does | | --- | --- | | SessionStart | Injects the active objective, in-flight stages with their failing commands, unproven claims, how old the state is, a memory digest, and .goat/SESSION.md. Matches all five sources — startup, resume, clear, compact, fork — so the state is re-injected after a compaction, which is exactly when the model has lost it, and into a forked thread | | UserPromptSubmit | When a prompt invokes a stage, attaches that stage's entry-contract report | | Stop | Records the turn's outcome into session memory. Runs async: it never emits a decision, so nothing waits on it |

Trust. Codex runs a user- or project-layer hook only after you have trusted it, and codex exec never asks. Until then the hooks are registered and inert, and nothing says so. goat doctor reads the trust records Codex keeps in config.toml and reports which handlers will actually run; approve the prompt in the Codex TUI (or /hooks there). goat never writes the trust record for you.

Session notes. Anything the next session must not re-derive — approaches ruled out and why, questions not to ask again — goes in .goat/SESSION.md, in your own words. SessionStart injects its first 4,000 characters and points at the file for the rest, because Codex replaces any hook context over about 2,500 tokens with a preview.

Memory switches. .goat/config.json carries memory.enabled and memory.digestSize; both runtimes honour them, and GOAT_MEMORY=off wins over the file. If you turn on Codex's native memories, goat's digest stays complementary (project-scoped, evidence-shaped), but this is where you turn it off if you would rather not have both.

Three properties hold for every hook, and are covered by tests in both implementations:

  1. Never blocks. No hook can return a block decision.
  2. Never throws. Malformed input exits 0 with {}.
  3. Never phones home, and stays cheap. Local file reads, plus at most one git status --porcelain (5s timeout) — and only when a prompt explicitly invokes a stage whose contract depends on the working tree. Ordinary prompts run no subprocess.

Session memory is a local, append-only log under .goat/memory/. Prose is compressed; code spans, paths, URLs, filenames, and version numbers are preserved byte-for-byte. <private>…</private> is stripped before anything reaches disk.

The native fast path

crates/goat-runtime is an optional dependency-free Rust binary that handles SessionStart and Stop in a few milliseconds instead of paying Node's startup cost.

npm install -g codex-goat fetches it for you: every release publishes a raw binary per platform (linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64) beside the archives, with a checksums.txt, and the package's postinstall downloads the one for your platform, checks its SHA-256, runs it once against garbage input to see it answer {}, and installs it as bin/goat-runtime. If any of that cannot happen — no release for this version yet, offline, an unsupported platform — the hooks run on Node and goat doctor says why.

To place a binary by hand instead, download one from Releases and either drop it next to the installed package as bin/goat-runtime or point at it:

export GOAT_RUNTIME_BIN=/path/to/goat-runtime
goat doctor            # the "native runtime" check should now read PASS

With a Rust toolchain you can build it yourself instead:

npm run build:native

It is genuinely optional — hooks/goat-hook.mjs falls back to the TypeScript handler when the binary is absent, and UserPromptSubmit is always delegated to Node because the stage table has one source of truth in TypeScript. The one piece of logic both implementations share, the memory compressor, is pinned by a fixture that both test suites read, so they cannot drift.

Project structure

Five layers, strictly one-directional. The graph below is generated from the actual imports by node scripts/module-graph.mjs, not drawn from memory — edge labels are how many import statements cross that boundary.

flowchart TB
    cli["<b>cli/</b> — 13 files<br/>argv · launch · setup · doctor · roles<br/>status · contract · state · ledger"]
    hooks["<b>hooks/</b> — 1 file<br/>the lifecycle handler"]
    setup["<b>setup/</b> — 6 files<br/>AGENTS.md merge · hooks.json merge<br/>hook trust · agent roles<br/>native runtime · postinstall"]
    state["<b>state/</b> — 6 files<br/>stages · contract · store<br/>ledger · memory · routing"]
    core["<b>core/</b> — 4 files<br/>paths · atomic fs<br/>process · logging"]

    cli -->|25| core
    cli -->|18| state
    cli -->|10| setup
    cli -->|1| hooks
    hooks -->|4| state
    hooks -->|2| core
    setup -->|2| core
    state -->|10| core

    classDef top fill:#ddf4ff,stroke:#0969da,color:#24292f
    classDef mid fill:#fff8c5,stroke:#9a6700,color:#24292f
    classDef bot fill:#dafbe1,stroke:#1a7f37,color:#24292f
    class cli top
    class hooks,setup,state mid
    class core bot

core imports nothing above it. setup reaches only into core, and only for paths: the two merges and the trust-key computation are pure functions over data, which is why they are easy to test exhaustively, and the three installers that touch disk (agent roles, the native runtime fetch, postinstall) are the whole reason that edge exists. There are no cycles; the script reports them, so a future one fails visibly rather than quietly.

Two entry points, and only two:

| Entry point | Layer path | Runs when | | --- | --- | --- | | dist/cli/goat.js | cli → everything | you type goat … | | hooks/goat-hook.mjs | → hooksstatecore | Codex fires a lifecycle event |

They share no process and communicate only through .goat/.

flowchart LR
    subgraph repo["codex-goat repository"]
        direction TB
        src["<b>src/</b><br/>TypeScript → dist/"]
        crates["<b>crates/goat-runtime/</b><br/>optional Rust helper"]
        assets["<b>skills/ · prompts/</b><br/><b>templates/ · hooks/</b><br/>shipped as data"]
        scripts["<b>scripts/</b><br/>build · verify-bundle<br/>module-graph · run-tests"]
    end

    src -->|tsc| dist["dist/"]
    crates -->|cargo| bin["goat-runtime<br/>5 platforms"]
    scripts -->|"102 contract checks"| assets
    dist --> pkg(["npm: codex-goat"])
    assets --> pkg
    bin --> gh(["GitHub Release assets<br/>5 archives + 5 raw binaries"])
    gh -.->|"fetched by npm postinstall<br/>or the first goat launch"| pkg

    classDef opt stroke-dasharray: 4 3
    class crates,bin,gh opt

The dashed path is optional: goat-runtime is a speed-up for two hooks, and everything works without it. npm install -g codex-goat fetches it for the current platform and verifies it against the release's checksums; when that cannot happen, the hooks run on Node. skills/, prompts/, and templates/ are data, not code — Codex reads them directly, so they get their own contract test rather than type checking.

Native Codex features the stages use

Codex has grown three runtime features that the stages now lean on where they exist, and work without where they do not.

Sub-agents in $team. Codex's spawn_agent tool tells the model not to spawn unless a skill or the user asks for delegation in so many words, so $team says so: one sub-agent per lane, started fresh with the lane's own section of the lanes file as its whole brief, then wait_agent until every lane has returned, closing each once its evidence is recorded (a finished lane holds its slot until closed). Lanes never write the ledger — state is one file and two lanes finishing together would overwrite each other's proof — so the root re-runs each lane's verify command itself and records the exit code it observed. A lane's brief arrives inside the sub-agent as a UserPromptSubmit, which goat's hook recognises by its agent_id and ignores: it is neither a prompt to remember nor a stage to contract. Without spawn_agent, $team runs the lanes serially under the same ownership rules. Multi-agent v1 has been on by default since well before 0.147.0.

Goals in $ultragoal. When the session has create_goal, $ultragoal registers the objective with it. Codex then keeps the run going across turns on its own, audits completion, and asks the model to mark the goal blocked once the same obstacle has held for three consecutive turns; Codex itself only force-stops after three turns of failed commands or three empty turns. That is a per-turn cousin of goat's three-failures rule, not the same rule. The goal is bound to its thread, so it returns on codex resume but a new session never sees it; the .goat/goals/<slug>.md file stays the durable record and the ledger stays the proof. The goal is closed with update_goal in the same breath as goat state set. On by default since 0.133.0.

Agent roles, opt-in. goat roles install [--scope user|project] writes the nine role cards under prompts/ as Codex agent roles (<config>/agents/<role>.toml: name, description, developer_instructions), which makes each one an agent_type a lane or a sub-task can be spawned with. It is opt-in, and the cost is a step rather than a slope: with no roles installed Codex drops agent_type from the spawn schema entirely, and installing even one brings the property back carrying Codex's own built-in roles (default, explorer, worker) alongside yours, on every turn of every session. goat roles uninstall removes exactly the files goat wrote and nothing else; a reviewer.toml of your own is never touched. Codex has discovered role files since 0.115.0 (the loader moved into its own crate in 0.150.0), and goat roles and goat doctor say so when the installed CLI is older. Project-scope roles load only in a project Codex trusts; an untrusted checkout's .codex layer is disabled.

Model routing: plan on Astra, execute on Luna

A Codex session runs one model, so a stage cannot switch models mid-conversation. What makes per-stage routing work is that .goat/ is durable: $plan writes an artifact, the session ends, and a new session on a different model picks it up through the same entry contract. The split is across sessions, not inside one — which is exactly what the entry-contract design was for.

One exception, about threads rather than the conversation: a $team lane is a separate Codex thread, and its model and effort are seeded from the session that spawned it. So $team from a plain goat session puts every lane on whatever Codex's catalog default is, which today is the deliberation model. goat --for team starts the whole session on the execution model, which is the simplest way to keep lanes cheap. Codex can also retarget spawns with agents.default_subagent_model in config.toml, but set agents.default_subagent_reasoning_effort alongside it: setting the model alone resets effort to that model's catalog default, which is lower than what goat injects. codex-goat never writes those keys for you — they would change every spawn in every session, not just a lane.

goat --for plan                 # deliberation session  -> gpt-6-astra (gpt-5.6-sol below codex 0.153)
#   $clarify / $plan / $code-review, writes .goat/plans/…

goat --for ultragoal            # execution session     -> gpt-5.6-luna
#   $ultragoal picks up the plan artifact and runs

| Stage | Routed to | | | --- | --- | --- | | $clarify, $plan, $code-review | gpt-6-astra | deliberation and judgement; gpt-5.6-sol on Codex older than 0.153.0 | | $ultragoal, $team, $ultraqa | gpt-5.6-luna | execution and throughput |

goat skills prints the route for every stage, and goat --for plan --print-argv shows the exact codex command before you run it.

The version gate on Astra

Astra's catalog entry carries minimal_client_version = 0.153.0, and the server refuses it to older clients with "requires a newer version of Codex". So goat reads codex --version once per launch and, below 0.153.0, launches gpt-5.6-sol for the three judgement stages instead — and says so in the launch notes, because a wrapper that silently swaps models is exactly what --print-argv exists to prevent. An unreadable version is treated as too old: launching a model that will be refused is a worse failure than launching the previous default. goat doctor reports which routes are currently gated on your install. Set GOAT_CODEX_VERSION to override the probe if your codex is an unusual shim.

Astra has not been benchmarked here. The numbers below were measured on sol and luna.

Why this split

Two reasons, and it is worth being precise about which is measurement and which is not:

  • Codex positions them this way. Its model catalog (codex-rs/models-manager/models.json) ranks gpt-6-astra priority 1, which makes it the default for a fresh install; gpt-5.6-sol, the previous default, now sits at 6, gpt-5.6-terra at 7 and gpt-5.6-luna at 8. Codex also routes its own auxiliary work to luna — approval review, memory extraction, and guardian scoring all name it explicitly.
  • Measured here: luna finished faster in all six model × effort cells and carries a 1,558-token lighter always-on prefix.

Not measured: output quality. Nothing in this repo grades correctness, and the task benchmark could not separate the two models on token use at all — per-task direction swung from −58% to +176%. Treat the defaults as a sensible starting point, not a proven optimum.

Overriding it

Explicit flags always win, and routing only fills a gap you left:

goat --for plan -m gpt-5.6-terra     # your model wins; goat says so in its notes
goat --for ultragoal --low           # your effort wins over the route and the default
goat --no-goat-defaults              # no model, no effort, argv forwarded untouched

Per-project overrides live in .goat/config.json. A partial entry replaces only the fields it sets, so you can pin an effort without restating the model:

{
  "routes": {
    "ultraqa": { "model": "gpt-5.6-sol", "effort": "low" },
    "plan": { "effort": "xhigh" }
  }
}

If you are on a single-model plan, ignore --for entirely — every stage still works in one session, which is the default behaviour.

Benchmark: Codex with and without codex-goat

Two identical scratch projects, one clean and one after goat setup --scope project. Same prompts, same models, same effort. Every number is Codex's own turn.completed usage event via codex exec --json — nothing is estimated. 160 task samples plus 12 prefix samples, codex-cli 0.147.0.

| | Codex alone | + codex-goat | | | --- | --: | --: | --: | | Output + reasoning tokens per turn | 737 | 340 | −54% | | Answer length (chars) | 719 | 792 | +10% | | Seconds per turn | 15.6 | 9.8 | −37% | | Turns answered in one model call | 10% | 75% | | | Always-on prefix (sol) | 25,106 | 25,845 | +739 |

Half the generation tokens, a third less wall-clock, and slightly longer answers. The saving does not come from terseness — it comes from the model finishing in one pass instead of two.

Why it happens

Codex bills every request in a turn. Without project guidance the model frequently answers, reconsiders, and answers again: two agent_message items, two billed requests, one useful reply. With codex-goat installed that drops from 90% of turns to 25%.

A generic AGENTS.md of similar size does not reproduce it — that control lands at 30% one-call turns and 817 tokens, slightly worse than no guidance at all. So this is not "having a project file"; it is what these particular operating rules tell the model about scope and stopping.

Per task, including where it does not work

| Task | one-call rate | out+reasoning | answer chars | | --- | --: | --: | --: | | react-rerender | 0% → 100% | 711 → 325 | 783 → 676 | | sql-index | 13% → 100% | 734 → 264 | 606 → 686 | | api-versioning | 25% → 100% | 712 → 343 | 842 → 858 | | jwt-expiry | 13% → 75% | 993 → 331 | 559 → 575 | | flaky-test | 0% → 0% | 1,224 → 1,086 | 1,809 → 1,869 |

flaky-test is the honest counter-example: the most open-ended prompt in the set takes two calls in both arms, and the saving collapses to −11%. The effect is real where a question has one good answer, and largely absent where the model genuinely needs a second pass. react-rerender is the one case where the answer also got shorter, by 14%.

The cost side

codex-goat adds ~737 tokens to the always-on prefix, about 3% on top of Codex's own 23.5–25.1k. Measured on a single-call prompt against an empty .goat/:

| Model | Codex alone | + codex-goat | cost | | --- | --: | --: | --: | | gpt-5.6-sol | 25,106 | 25,845 | +739 | | gpt-5.6-luna | 23,550 | 24,286 | +736 |

That is the AGENTS.md block plus the one-line description of each of the eight skills. Skill bodies (481–1,153 tokens) load only on invocation. Mid-session the SessionStart digest adds roughly 130 tokens more.

So the trade is a fixed ~737-token input surcharge against ~397 fewer generation tokens per turn, and generation is the expensive half.

Effort still moves more than either

90 samples, 5 tasks × 2 models × 3 efforts × 3 runs:

| Effort | gpt-5.6-sol | gpt-5.6-luna | vs low | | --- | --: | --: | --: | | low | 652 | 515 | 1.00× | | medium | 777 | 749 | 1.19× | | high | 1,086 | 1,024 | 1.67× |

Monotonic in all six cells. goat sets model_reasoning_effort on every launch and defaults to high, so goat --low remains the single biggest lever available.

How much to trust this

The with/without result was run twice, independently, and reproduced: 25,880 / 359 tokens the first time, 25,879 / 341 the second, against a baseline re-run last in the sequence to rule out ordering. The generic-AGENTS.md control separates the effect from merely having a project file.

Two things it does not establish. Whether the answers are better — they are longer and take fewer passes, which is not the same as more correct, and nothing here grades content. And which model is cheaper: at medium effort the medians differ by ~4% while per-task direction reverses from −58% to +176%, so that comparison stays unresolved at this sample size.

Two methodology notes that changed the numbers

input_tokens is summed across every request in a turn. That is why the prefix table uses a prompt trivial enough to finish in one call, and why the task tables report generation rather than input — input there measures call count as much as content.

Without --ephemeral, runs inherit session history and memories from each other. Identical prompts produced 25k / 51k / 94k input, a 3.8× spread. With it, repeated runs land within ~4 tokens.

codex exec has no hook-trust prompt, so the codex-goat arm passes --dangerously-bypass-hook-trust; without it the hooks are inert and the cost would be understated.

Reproduce it

node scripts/bench-models.mjs --prefix --runs 3 --project /path/to/clean --label baseline
node scripts/bench-models.mjs --runs 2 --effort medium --project /path/to/clean --label baseline
node scripts/bench-models.mjs --runs 2 --effort medium --project /path/to/goat --label goat --bypass-hook-trust
node scripts/bench-report.mjs

Raw samples are in bench/results-*.json, one row per call with Codex's usage object verbatim.

What lives where

.goat/
├── state/state.json     current stage status and recorded evidence
├── ledger.jsonl         append-only record of claims and proof
├── plans/               plans and frozen requirements
├── goals/               goal ledgers and team lane assignments
├── reviews/             review reports
├── qa/                  QA reports and scenario matrices
├── memory/              compressed session observations
├── config.json          per-project routes and the memory switches
└── SESSION.md           your notes for the next session; injected at SessionStart, capped at 4,000 chars

goat setup also writes, and npm install -g or the first goat launch runs it for the user scope:

  • <scope>/.agents/skills/ — the eight bundled skills plus the role cards
  • AGENTS.md — operating rules, merged between <!-- GOAT:AGENTS:START/END --> markers so the rest of your file is preserved byte-for-byte
  • .codex/hooks.json — lifecycle hooks, preserving any entries owned by other tools

goat roles install adds <config>/agents/<role>.toml, one per role card, and only on request. goat uninstall reverses the three setup writes and leaves .goat/ alone unless you pass --purge-state; goat roles uninstall removes exactly the role files goat generated.

Development

npm install
npm run build            # TypeScript -> dist/
npm run build:native     # optional Rust helper
npm run build:bun        # optional single-file binary (bun build --compile)

npm test                 # build + 175 unit tests + 102 bundle contract checks
npm run test:native      #  Rust tests
npm run verify           # lint + everything above

The Bun binary embeds the code but not skills/, prompts/, templates/, or hooks/. Commands that read those — setup, doctor, skills, exec --role — need GOAT_HOME=/path/to/codex-goat. State and workflow commands work without it.

npm run test:contract is the check that catches what unit tests cannot: a SKILL.md whose frontmatter Codex would reject, a declared stage with no skill, a role prompt missing from the $goat-roles index, or a hook registration pointing at a file the package does not ship.

Release pipeline

Pushing a v* tag runs the whole thing. The ordering is the point: npm publish cannot be undone, so it happens only after everything that could still say no has finished.

flowchart LR
    tag(["git push origin v0.1.7"]) --> check

    subgraph check["check"]
        direction TB
        c1["lint · build<br/>175 unit · 102 contract"]
        c2["tag == package.json<br/>== marketplace pin"]
        c3["npm publish --dry-run"]
        c1 --> c2 --> c3
    end

    check --> build

    subgraph build["build-native — 5 targets"]
        direction TB
        b1["linux-x64 · linux-arm64"]
        b2["darwin-x64 · darwin-arm64"]
        b3["windows-x64"]
    end

    build --> publish["publish<br/>npm publish --provenance"]
    publish --> release["release<br/>10 assets: 5 archives + 5 raw binaries<br/>checksums.txt + GitHub Release"]
    build --> release

    classDef irreversible fill:#ffebe9,stroke:#cf222e,color:#24292f
    class publish irreversible

An earlier version put publish before build-native. A cross-compile failure would then have left a published npm version with no matching release assets — and npm will not accept a republish of the same version, so there was no way back. CI cross-compiles the four non-host targets on every push to main, so a target that stops building fails there rather than mid-release.

codex-goat/
├── src/
│   ├── cli/             argv parsing, launch, and the goat subcommands
│   ├── core/            paths, atomic filesystem writes, process, logging
│   ├── state/           stage table, entry contracts, state store, ledger, memory, model routing
│   ├── setup/           AGENTS.md and hooks.json merging, hook trust, agent roles, native runtime fetch, postinstall
│   └── hooks/           the lifecycle hook handler
├── skills/              the eight bundled skills
├── prompts/             the nine role cards (source of truth)
├── templates/           the AGENTS.md guidance block
├── hooks/               plugin hook registrations and the entry script
└── crates/goat-runtime/ optional native helper

Credits

codex-goat follows the shape established by oh-my-codex — the canonical stage names and the "keep Codex as the execution engine" framing come from there. It borrows the preserve-code-and-paths compression idea from cavemem, and the cross-harness plugin packaging conventions from ECC.

License

MIT — see LICENSE.