diff --git a/.agents/skills/local-review-codex/SKILL.md b/.agents/skills/local-review-codex/SKILL.md
new file mode 100644
index 0000000000..cc932f7a4b
--- /dev/null
+++ b/.agents/skills/local-review-codex/SKILL.md
@@ -0,0 +1,50 @@
+---
+name: local-review-codex
+description: Run the CI Codex PR review locally against this branch's unpushed work (committed + uncommitted) before pushing. Same policy, model, and reasoning effort as the codex-pr-review GitHub action.
+---
+
+# Local Codex Review (pre-push)
+
+Runs the exact same review Codex performs in CI (`.github/workflows/codex-pr-review.yml`),
+but locally and scoped to work you have not pushed yet — so you catch what CI would flag
+before the PR exists. Use this before `git push` on a non-trivial change.
+
+**Correspondence with CI** — identical:
+- Policy: `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test coverage).
+- Model: `gpt-5.6-sol`, `model_reasoning_effort="xhigh"`.
+- Output: markdown starting with `## Codex Review`, findings tagged P0 / P1 / P2 with file:line.
+
+**Differences from CI** — local-only:
+- Scope is the current branch vs `main` at the merge-base, **including uncommitted changes** (CI reviews a pushed PR diff).
+- Sandbox is `read-only` (CI uses `danger-full-access` on an ephemeral runner). Codex reads the diff and files but cannot modify your working tree.
+- Fresh context is inherent: `codex exec` is a separate cold process, so it does not anchor on the current chat session — the same reason `local-review` insists on a subagent.
+
+## Prerequisites
+
+- `codex` CLI **>= 0.144.1** installed and authed (`codex login` or `OPENAI_API_KEY`). Older CLIs reject `gpt-5.6-sol` with "requires a newer version of Codex". Upgrade with `npm install --global @openai/codex@0.144.1` (may need `sudo` for a global install). Keep this in sync with the pin in `.github/workflows/codex-pr-review.yml`.
+- `git fetch` the base ref if it's stale, so the merge-base is accurate.
+
+## Run
+
+```bash
+bash .agents/skills/local-review-codex/run.sh # review vs main (default)
+bash .agents/skills/local-review-codex/run.sh # review vs a different base ref
+```
+
+Invoke with `bash` (or run the executable directly) — the script needs Bash for
+`set -o pipefail`; `sh` is Dash on Debian/Ubuntu and would fail. If `main` isn't a
+local branch (e.g. a fresh single-branch checkout), the runner falls back to
+`origin/main` automatically.
+
+The script computes `BASE_SHA = git merge-base HEAD `, feeds Codex `REVIEW.md` plus a
+diff context pointing at `git diff ` (which folds in uncommitted edits), and prints
+the review. It writes only temp files — nothing lands in the working tree.
+
+## Relaying the result
+
+Print the Codex output verbatim. Do not re-summarize or filter it — the value of a cold Codex
+pass is surfacing what the current session would rationalize away. Then decide with the user
+whether to address findings before pushing.
+
+For a Claude-native review instead, use `local-review` (branch-diff-reviewer subagent). This
+skill is the Codex counterpart; run both for independent perspectives.
diff --git a/.agents/skills/local-review-codex/run.sh b/.agents/skills/local-review-codex/run.sh
new file mode 100755
index 0000000000..d6491099c2
--- /dev/null
+++ b/.agents/skills/local-review-codex/run.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# Local Codex review — mirrors the .github/workflows/codex-pr-review.yml CI job,
+# but scoped to this branch's unpushed work (committed + uncommitted) so you can
+# review before pushing. Same policy (REVIEW.md), same model (gpt-5.6-sol) and
+# reasoning effort (xhigh) as CI. Runs read-only: Codex cannot modify your tree.
+#
+# Usage: run.sh [BASE_REF] (BASE_REF defaults to "main")
+set -euo pipefail
+
+BASE_REF="${1:-main}"
+REPO_ROOT="$(git rev-parse --show-toplevel)"
+cd "$REPO_ROOT"
+
+if ! command -v codex >/dev/null 2>&1; then
+ echo "codex CLI not found. Install with: npm install --global @openai/codex@0.144.1" >&2
+ exit 1
+fi
+
+# Resolve the base to a concrete commit, preferring a local ref but falling back to
+# the remote-tracking ref — checkouts (CI, single-branch clones) often have only
+# origin/main, not a local main.
+if git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null; then
+ BASE_COMMITISH="$BASE_REF"
+elif git rev-parse --verify --quiet "origin/${BASE_REF}^{commit}" >/dev/null; then
+ BASE_COMMITISH="origin/${BASE_REF}"
+else
+ echo "Base ref '$BASE_REF' not found as '$BASE_REF' or 'origin/$BASE_REF'. Try: git fetch origin $BASE_REF" >&2
+ exit 1
+fi
+
+# Diff from the merge-base so only this branch's changes are reviewed. Using the
+# base SHA with a single-ref `git diff` also folds in uncommitted working-tree edits,
+# but `git diff` never sees untracked files — those are gathered separately below so
+# brand-new files (a whole new module, a new skill dir) are not silently skipped.
+BASE_SHA="$(git merge-base HEAD "$BASE_COMMITISH")"
+HEAD_SHA="$(git rev-parse HEAD)"
+UNTRACKED="$(git ls-files --others --exclude-standard)"
+
+if [ "$BASE_SHA" = "$HEAD_SHA" ] && git diff --quiet "$BASE_SHA" && [ -z "$UNTRACKED" ]; then
+ echo "No changes vs $BASE_REF — nothing to review." >&2
+ exit 0
+fi
+
+PROMPT="$(mktemp)"
+OUT="$(mktemp)"
+trap 'rm -f "$PROMPT" "$OUT"' EXIT
+
+# REVIEW.md is the shared policy CI feeds Codex. Append the local output-format
+# and diff context inline (CI reads these from a generated context file; inlining
+# keeps the working tree clean — no scratch files land in the repo).
+cat REVIEW.md > "$PROMPT"
+cat >> "$PROMPT" < /tmp/codex-prompt.md
codex exec \
-C "$GITHUB_WORKSPACE" \
- -m gpt-5.5 \
+ -m gpt-5.6-sol \
-c 'model_reasoning_effort="xhigh"' \
-s danger-full-access \
-o codex-final-message.md \
diff --git a/AGENTS.md b/AGENTS.md
index 83b68f7d36..fa7889c4c2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,7 +16,7 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
- **Frontend UUIDs**: do not call `crypto.randomUUID()` in frontend code. Import `randomUUID` from `$lib/utils/uuid` instead.
-- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi.
+- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi. For a Codex-driven pass that mirrors the `codex-pr-review` GitHub action against your unpushed work (committed + uncommitted) before you push, use `/local-review-codex` (`.agents/skills/local-review-codex/`) — same `REVIEW.md` policy, `gpt-5.6-sol`, `xhigh` reasoning; requires the `codex` CLI >= 0.144.1.
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
- **CLI commands**: when adding/modifying/removing a command, subcommand, option, or description in `cli/src/commands/`, run `python system_prompts/generate.py` to refresh `system_prompts/auto-generated/` and `cli/src/guidance/skills.gen.ts`. The CLI docs the agents use to operate `wmill` are derived from the source — stale generated files give agents the wrong flags.