phax is a deterministic orchestrator for AI coding agents: you give it a plan, and it executes that plan as a sequence of isolated, gated phases. The orchestration itself is plain code — phase sequencing, gates, retries, and state transitions are all deterministic — so the only non-deterministic part is the agent working inside each phase.
Because every phase declares the files it expects to create or edit, phax makes each run reviewable: it reconciles the plan against what actually changed and surfaces every deviation — planned-but-not-done, done-but-not-planned, deleted, renamed — which the agent must justify in its handoff. Instead of landing on a pull request full of touched files with no idea what was intended, you get a review that's already framed, phase by phase and across the whole run.
Each phase runs in its own Git worktree, must pass its gates before the next one starts, and has a same-session fix loop for repairing gate failures; the final phase is kept open for human review. The agent itself is interchangeable — Claude Code, Mistral Vibe, or OpenAI Codex — selected by the model-routing layer, with Claude Code as the default and terminal fallback.
#1. install the CLI
npm install -g @lbdremy/phax
# 1.1 Install the phax skills (phax-planning + phax-cli) into your agent
phax skills install --target claude
# 1.2 Install phax CLI auto completions (optional - you need usage CLI to be installed first)
brew install usage
echo 'source <(phax completions zsh)' >> ~/.zshrc
# 2. Have any coding agent draft the plan (here: Claude Code). Point it at the
# phax-planning skill so the plan.md it writes matches the format phax expects:
claude -p "Write plan.md for specs/<your spec> using the phax-planning skill."
# 3. extract the plan + run every gated phase
phax run --plan plan.md
# 4. review the agent's work in a pre-prompted session (optional)
phax review-code <short-name>
# 5. push the final branch and open the PR
phax publish-pr <short-name>Via npm (recommended) — the wrapper resolves and downloads the correct platform binary on first run:
npm install -g @lbdremy/phax
# or without installing:
npx @lbdremy/phaxDirect binary download — grab the binary and checksum for your platform from GitHub Releases:
# Example: macOS Apple Silicon
curl -LO https://github.com/lbdremy/phax/releases/latest/download/phax-darwin-arm64
curl -LO https://github.com/lbdremy/phax/releases/latest/download/phax-darwin-arm64.sha256
sha256sum --check phax-darwin-arm64.sha256
chmod +x phax-darwin-arm64
sudo mv phax-darwin-arm64 /usr/local/bin/phax
# macOS: remove the quarantine attribute added by the browser/curl
xattr -dr com.apple.quarantine /usr/local/bin/phaxmacOS Gatekeeper note: binaries are not yet code-signed or notarized. Without the
xattrstep above, macOS will block the binary on first run. Go to System Settings → Privacy & Security to allow it, or run thexattrcommand.
Available targets: phax-darwin-arm64, phax-darwin-x64, phax-linux-x64, phax-linux-arm64.
Binary size note: the compiled binary is ~74 MB. The release build bundles the CLI with esbuild first (tree-shaken to ~1.5 MB of actually-used code) and then runs
deno compile --includeto embed the three runtime-read data files (package.json,phax.usage.kdl,.claude/skills). This avoids the un-bundled path which would embed ~274 MB ofnode_modulesfiles (~360 MB total). The npm wrapper downloads the binary once and caches it per version at~/.phax/bin/<version>/, so the cost is a one-time download per upgrade.
The distributed phax binary is compiled with an explicit Deno permission set:
| Permission | Status | Notes |
|---|---|---|
| Filesystem read/write | allowed | Required to manage run state, worktrees, locks, and artifacts |
| Network | denied | phax itself makes no network calls |
| Environment | allowed | Required so subprocesses can resolve executables via PATH |
| Subprocess execution | unrestricted | phax may spawn any executable; security comes from the provider-native jail and structured argv invocation, not an executable allowlist |
Important: Deno's permissions sandbox phax, not the provider CLIs it
launches. Once phax spawns claude, codex, or vibe, those processes run with
their own provider-native permissions and are not constrained by phax's Deno
permission set. Provider-level security (filesystem jail, network restrictions,
tool allowlists) comes from the provider's own sandbox — see
Security modes and the
Security notes section.
phax open uses the OS opener (open on macOS, xdg-open on Linux) so no editor binary needs to be installed or configured. The meaningful security boundaries are the provider-native jail (filesystem, network, tool restrictions) and phax's structured argv invocation — phax never interpolates user input into shell strings.
Requirements: at least one provider CLI on $PATH:
claude— Claude Code (default, and the terminal fallback provider)vibe— Mistral Vibe (optional)codex— OpenAI Codex (optional)
Most setups want claude installed even when routing prefers another provider, because phax falls back to Claude Code when the preferred provider is unavailable or cannot satisfy the active security posture.
Run phax init to create phax.json and phax.schema.json in the current directory. When stdin is a TTY it launches an interactive wizard (like npm init) that prompts for the project slug, gate commands, and optional compliance/publish toggles:
phax init # interactive wizard (TTY) or non-interactive (detected defaults)
phax init --yes # non-interactive: accept detected defaults without prompting
phax init --force # reconfigure an existing phax.json (prompts again in a TTY)In a non-TTY environment (CI, pipes) phax init automatically falls back to detected defaults with all optional toggles off — it never hangs waiting for input.
The wizard pre-fills the project slug from package.json's name field (slugified), detects the package manager from the packageManager field, and suggests gate commands from existing scripts (typecheck, lint, test:unit, format:check, build). It writes phax.json, phax.schema.json, and phax.user.schema.json.
phax.schema.json is a JSON Schema generated from the installed binary's config contract — wire it up as "$schema": "./phax.schema.json" for editor validation. After upgrading phax, run phax schema upgrade to regenerate it (see Schema upgrade).
Or add a phax.json manually at your repo root:
{
"$schema": "./phax.schema.json",
"version": 1,
"name": "my-project",
"security": { "profile": "secure" },
"fileReconciliation": { "mode": "report_only" },
"review": { "compliance": { "enabled": true } },
"publish": { "auto": true, "remote": "origin", "baseBranch": "main" },
"commands": {
"setup": ["pnpm install"],
"cleanup": ["rm -rf node_modules"]
},
"gateProfiles": {
"full": [
{ "command": "pnpm typecheck", "surface": "local", "firing": "every-phase" },
{ "command": "pnpm test:unit", "surface": "local", "firing": "every-phase" },
{ "command": "pnpm lint", "surface": "structural", "firing": "every-phase" },
{ "command": "pnpm build", "surface": "product", "firing": "terminal" },
{
"command": "pnpm audit:security",
"surface": "structural",
"firing": "every-phase",
"output": "diagnostics"
}
]
}
}Each gate profile is a named list of attributed steps, not a flat command list. Every step carries these dimensions:
surface— a closed enum,local | structural | product, describing what the step verifies (local dev checks, structural/repo-wide checks, or product/build output). This is pure attribution: phax records it and never branches on it.firing—every-phase | terminal. This is behavioral:every-phasesteps run at every phase gate;terminalsteps run only at the final phase gate, in addition to the every-phase steps.output— optional,log | diagnostics, defaults tolog. A"log"step's stdout/stderr are appended to the attempt log as raw text, same as today. A"diagnostics"step's stdout is decoded as a JSON document{ "diagnostics": [{ "rule", "class": "invariant"|"completion", "scopes"?: [...], "location": { "file", "line"? }, "message", "repair" }, ...] }; the verdict comes from that document instead of the exit code. Every diagnostic declares aclass: an"invariant"diagnostic always fails the step. A"completion"diagnostic names one or morescopesand fails the step only once every scope it names is closed, as reported by the registeredscopesprovider (see Scope provider) — otherwise it is pending: it does not fail the phase, and is shown to the fix-loop agent as optional work. A step with only pending diagnostics recordspending(notpass/fail) ingate-attribution.jsonand never counts its surface as verified. Pending findings are persisted aschecks-attempt-NN.pending.jsonnext to the attempt log; a failing document is persisted aschecks-attempt-NN.diagnostics.json, and its failing diagnostics — not the raw log — drive the fix prompt. A missing/undecodable document, or a non-zero exit with an empty list, is a provider error that still fails the step (with the raw log, since there is no document to show).
There is no fast/full depth convention to pick between — a project defines a single profile, and firing carries the cadence that used to be encoded in separate fast/full profile keys. The old flat { "full": ["pnpm test", ...] } array form is rejected at validation, naming the offending profile.
After each phase gate, phax records which steps ran, their surface, and their pass/fail result in <phase>/gate-attribution.json. At run end, the final report's ## Run Summary lists the set of surfaces verified during the run — a surface counts as verified only when every step of it that ran passed. Each phase's run record also names its verified surfaces, so phax records list and phax records explain can show surface coverage without opening artifacts.
The top-level name is the run namespace — run short-names are scoped under it. Provider routing is not configured here — it lives in the global ~/.phax/ config (see Multi-provider model routing). The optional security.profile (secure | unsafe | isolated, default secure) sets the default security posture for runs; see Security modes. The optional fileReconciliation.mode (report_only | warn, default report_only) controls how per-phase file reconciliation reports deviations from the plan; see Run. The optional review.compliance and publish blocks turn on an automatic plan-compliance review and a pushed pull request when each run reaches review; see Compliance review & publishing.
Add an "orient" block to tell phax how to fetch orientation rows for the current project:
{
"orient": { "command": "node ./orient.mjs" }
}The command string is split on whitespace with no shell — use a wrapper script if the path contains spaces or you need a pipeline. phax writes a JSON request to the provider's stdin and reads a JSON response from stdout; the provider must exit 0 on both success and "not found" responses.
- Index request —
{"files": ["src/foo.ts", ...]}: respond with{"rows": [{"id", "title", "severity", "trigger"}, ...]}for every row whose trigger prefix matches any file in the list.severityis one of"error" | "warn" | "info". - Expand request —
{"expand": "<id>"}: respond with{"row": {"id", "title", "severity", "trigger", "body"}}for a known id, or{"row": null}for an unknown one. - All fields are non-empty strings. A non-zero exit, non-JSON stdout, or a response that fails validation is a provider error (exit 1). An empty index or a null row prints "No orientation available." and exits 0.
- During a run phax sends the index request for each phase's planned files and weaves the rows into the phase prompt. When orient is configured,
phax orientis implicitly granted to the in-phase agent without anagentCommandsentry.
Full contract: phax orient.
Add a "scopes" block to register the provider that answers, for a completion diagnostic (see Configure above), which scopes are already closed:
{
"scopes": { "command": "node ./scopes.mjs" }
}The command string is split on whitespace with no shell, same as orient. Before each non-terminal phase's gate — only when that gate has at least one output: "diagnostics" step — phax writes the plan projection to the provider's stdin:
{
"phase": "phase-02",
"phases": [
{ "id": "phase-01", "files": ["src/core/billing/port.ts"] },
{ "id": "phase-02", "files": ["src/core/billing/invoice.ts"] },
{ "id": "phase-03", "files": ["src/adapters/billing/stripe.ts"] }
]
}phases[].files is each phase's planned files to create and edit, deduplicated, in plan order (optionalFilesToEdit is never included). The provider responds on stdout with {"closed": ["<scope>", ...]} and must exit 0. A completion diagnostic fails the step once every scope it names appears in closed; otherwise it is pending. The terminal phase closes every scope without querying the provider — it is never called. If a gate step returns a completion diagnostic but no scopes provider is registered, the gate fails through the fix loop with a configuration-error message naming phax.json. A non-zero exit, non-JSON stdout, or a response that fails validation likewise fails the gate through the fix loop, with the reason in the attempt log — the same treatment as an orient provider error.
Validate it before running:
phax validate
# also validate a phax-plan.json:
phax validate --plan phax-plan.jsonAdd a "planAuditor" block to register a provider that reviews a plan's shape
before a run touches it:
{
"planAuditor": { "command": "node ./audit-plan.mjs" }
}The command string is split on whitespace with no shell, same as orient and
scopes. phax plans lint queries it — never phax run — and only once the
plan's deterministic extraction succeeds. It writes the plan projection to the
provider's stdin:
{
"phases": [
{ "id": "phase-01", "files": ["src/greet.ts"] },
{ "id": "phase-02", "files": ["tests/greet.test.ts"] }
]
}This is the same projection the scope provider receives, minus the gated
phase id: phases[].files is each phase's planned files to create and edit,
deduplicated, in plan order (optionalFilesToEdit is never included). Models,
efforts, prompts, anchors and commit metadata never leave phax. The provider
responds on stdout with {"findings": [{"message", "phases": [...]}]} and
must exit 0. Every finding renders as a warning on the lint's advisory
check, one row per phase named in phases (- when the list is empty). A
non-zero exit, non-JSON stdout, or a response that fails validation is a
single advisory warning naming the reason, as is an auditor that outruns the
30s cap phax spawns it under. Advisory findings never set the lint's exit code,
and with no planAuditor registered — or on a plan the deterministic parser
cannot read — there are no advisory findings.
phax resolves configuration from four layers, least-to-most specific (most personal wins):
| Layer | File | Purpose |
|---|---|---|
| Built-in defaults | — | ~/.phax state root, maxFixAttempts: 1, etc. |
| Project config | phax.json (committed) |
Team baseline: gate profiles, identity, security grants |
| Global user config | ~/.phax/config.json |
Machine-wide preferences: model, state root, MCP mode |
| Per-project user config | phax.local.json (gitignored) |
This user × this repo overrides |
Scalars — the highest present layer wins (e.g. state.root, agent.maxFixAttempts, security.profile).
Allowlists — union across all layers, so user layers can only add to the project's security baseline, never silently remove it. This applies to security.filesystem.allowRead, security.filesystem.allowWrite, security.agentCommands, security.mcp.allow, and gateProfiles (union by key; the higher layer's command array wins for a shared key).
phax.local.json is gitignored — it is the right place for per-developer preferences like model selection or trust overrides that should not be committed. ~/.phax/config.json is for preferences that apply to all repos on your machine. A JSON Schema for both user layers is generated alongside phax.schema.json as phax.user.schema.json.
After upgrading phax, regenerate phax.schema.json to match the new binary's config contract:
phax schema upgradeThis rewrites phax.schema.json and phax.user.schema.json next to the nearest phax.json and reports whether the files changed or were already current. It never modifies phax.json.
Author plan.md with the phax-planning skill — it is the source of truth for the plan format that phax run extracts and phax plans lint checks. The skill defines the per-phase template contract (heading + {#phase-NN-<slug>} anchor, recommended model/effort, the three planned-file lists, gate-profile verification, commit subject/body) and the planning doctrine (plan outside-in, implement inside-out, verify outside-in). Point your agent at that skill when drafting or reviewing a plan; don't hand-roll the format.
In short: plan.md is a Markdown document with one ## phase-NN — <title> {#phase-NN-<slug>} section per phase, each carrying an objective, detailed instructions, planned-file lists, a gate-profile verification step, and a commit subject/body. See examples/hello-world/plan.md for a worked example and .claude/skills/phax-planning/SKILL.md for the full template contract.
phax plans lint docs/plans/NN-<slug>-plan.mdThis is a read-only, model-free check: it reports every structural defect the deterministic parser can find, whether the planned-file lists are coherent with the working tree and with earlier phases, whether every required command is covered, whether each phase's model/effort is in the routing catalog, and — when a plan auditor is registered — every advisory finding it returns about the plan's shape. It exits 1 when any finding is an error; advisory findings are always warnings.
phax run --plan plan.md # full execution (extracts plan.md, runs every phase)
phax run my-feature --plan plan.md # set the run short name explicitly
phax run --plan plan.md --dry-run # preview only — zero side effects
phax run --plan plan.md --allow-dirty # skip clean-tree guard
phax run --plan plan.md --provider-priority mistral-vibe,claude-code # override provider priority for this run
phax run --plan plan.md --security unsafe # override the security mode for this runEach phase:
- Creates a Git worktree at
~/.phax/worktrees/<short-name>/phase-NN/on its own branch<run.branch>--phase-NN. - Runs
commands.setupinside the worktree. - Builds a prompt from the plan and the previous phase's handoff, sends it to the selected provider's agent (resolved by the routing layer; see Multi-provider model routing).
- Runs the gate profile; on failure, resumes the same agent session once and retries.
- After passing gates, resumes the agent to produce
phase-handoff.md. - Commits with the planned message. If the worktree is clean (no changes), the run stops with a non-zero exit and writes
resume-instructions.md— usephax resumeto continue from the next phase. - Reconciles the files actually touched against the phase's planned files, writing
file-reconciliation.{json,md}to the phase folder. Deviations (unplanned creates/edits, missing planned changes) are injected into the next phase's prompt so the agent sees how the prior phase diverged from its plan; withfileReconciliation.mode: "warn"they are also logged (defaultreport_onlyonly records them).
Each phase gets its own branch (<run.branch>--phase-01, <run.branch>--phase-02, …), chained: phase-01 branches off <run.branch>, phase-N branches off the previous phase's branch. The base <run.branch> stays at the run-start commit. The final phase's branch carries the full commit chain and is the ref to review, merge, or push.
Worktrees from every phase persist on disk for the lifetime of the run and are available for inspection until phax archive is run.
The final phase stays open for review. A review-handoff.md is written to the run folder showing the final phase branch as the review target. When the run reaches review, two optional steps run automatically if enabled in phax.json (both are non-fatal — the run stays review_open if they fail):
- Compliance review (
review.compliance.enabled) — a non-mutating plan-compliance pass writes its verdict to the run folder, so it can land in the PR body. - Publish (
publish.auto) — pushes the final phase branch to the configured remote and opens (or reuses) a pull request; details are recorded inpublication.json.
See Compliance review & publishing.
When phax run finishes (or is interrupted), it prints an end-of-run recap to the terminal summarizing the run state, the review target branch, any published PR URL, and the next command to run.
macOS sleep prevention — long-running phax run sessions can be wrapped with
caffeinate to prevent macOS from sleeping while phax executes:
caffeinate -ims phax run --plan plan.mdphax review-code <short-name> # interactive, pre-prompted code-review session in the worktree
phax enter <short-name> # resume the final agent session
phax shell <short-name> # open $SHELL in the final worktree
phax path <short-name> # print the worktree path (script-friendly)
phax open <short-name> # open the worktree in the configured editorphax review-code launches the AI agent in the final worktree already primed with a code-review prompt — seeded with the file reconciliation and, if present, the compliance findings — so the review starts from context instead of a blank prompt. The session is resumable: re-running resumes it, --new-session starts fresh. Override the model/effort with --model/--effort (defaults from review.code, else claude-opus-5 at high effort). You take over the session to investigate, discuss, and apply fixes.
Both steps run automatically at the end of a run when enabled (see Run), and can also be invoked on their own against a review_open run:
phax review-compliance <short-name> # non-mutating plan-compliance review of the agent's work
phax publish-pr <short-name> # push the final branch and open (or reuse) a PRphax review-compliance re-invokes the AI agent with the run's handoff artifacts and the original plan and writes a verdict; it never touches the worktree, registry, or any files. Configure its model/effort under review.compliance in phax.json (default model claude-sonnet-5, effort medium).
phax publish-pr pushes the final worktree branch to the GitHub remote and creates a pull request, reusing an existing PR for the same branch if one exists. It requires a GitHub remote and an authenticated gh CLI. Configure the remote, base branch, and title under publish in phax.json.
When you have more than one plan in flight, these commands answer "is this plan still fresh?", "can these run together?", and "did a landed run invalidate the others?" — using the same declared-file lists the per-phase reconciliation relies on.
phax plans status # report every Approved plan as fresh or stale
phax plans status --apply # flip stale-computed plans Approved -> Stale
phax plans overlap docs/plans/33-a.md docs/plans/35-b.md # predicted: which plans are parallel-safe
phax plans overlap --landed <run> docs/plans/40-other.md # confirmed: which plans the run's real diff invalidates
phax adjust-plan docs/plans/40-other.md --landed <run> # interactively reconcile a plan against a landed runphax plans status reports every live, Approved plan's staleness against what its approval was recorded against: the declared source spec's content, the plan's own content, and the files changed since the recorded baseline intersected with the plan's footprint. Stale entries name their reasons — spec-changed, ground-changed, self-changed — with evidence; a plan with no approval record, or one whose baseline commit no longer exists, reports missing-record and renders as stale. It is a report, not a gate — it exits 0 whether or not stale plans exist. --apply flips the stale-computed plans Approved → Stale as an explicit gesture; --json emits machine-readable output.
phax plans overlap reports which plans can run in parallel without a merge conflict. Without --landed, it reads each plan.md through the content-addressed extraction cache (a cold miss extracts once via the LLM and caches it; --no-extract fails on a miss instead), unions each plan's declared phase file-sets into a footprint, intersects them pairwise, and reports a severity-graded conflict matrix, the clean pairs, the largest fully-disjoint parallel-safe set, and a greedy wave schedule. With --landed <run>, it reads that run's actual git diff from its persisted global-file-reconciliation.json and reports which of the given plans now need re-adjustment. Conflicts are file-level, not hunk-level, and --json emits the raw result.
phax adjust-plan <plan> --landed <run> opens an interactive, pre-prompted session that reconciles a plan against what a landed run actually changed: it establishes which declared files, line references, and decisions are now invalidated, asks clarifying questions, proposes concrete edits, and — only after your explicit approval — edits and commits the plan. The landed run must have reached review (it needs a global-file-reconciliation.json). The session is resumable; --new-session starts fresh.
phax ls # all runs
phax ls --active # created or running
phax ls --failed
phax ls --review-open
phax ls --archived
phax ls --json # machine-readableArchive is the only operation that touches worktrees/. It moves:
~/.phax/runs/<short-name>→~/.phax/archive/<short-name>/runs/~/.phax/worktrees/<short-name>/→~/.phax/archive/<short-name>/worktrees/
Then runs git worktree prune to drop stale admin records. Nothing is destructively deleted — every phase's working state is preserved for later inspection.
phax archive <short-name> # any non-running run; unfinished states require --force
phax archive <short-name> --force # archive an unfinished run, or bypass the dirty-worktree checkFinished runs (review_open, completed) archive without --force. Unfinished runs (created, failed, interrupted, rate_limited, stopped) are refused unless --force is passed — the refusal message names the state. Running, locked, and already-archived runs are never archivable. The run's stoppedReason and lastError survive archival intact.
PHAX can route phase execution through Claude Code, Mistral Vibe, or OpenAI Codex based on a user-editable global routing config (~/.phax/model-routing.json). The routing layer maps requested model IDs to stable model families and PHAX tiers, then selects the best available provider from providerPriority.
phax agent models # print routing table + provider priority
phax agent resolve --model claude-sonnet-4-6 --effort medium [--json]
phax agent probe # check provider executable availability
phax agent setup mistral-vibe --dry-run # preview Vibe alias installation
phax agent setup mistral-vibe --install-model-aliases # install PHAX Vibe aliasesThe default providerPriority is ["mistral-vibe", "codex-cli", "claude-code"] (the spec §12 multi-provider table). On a clean install, mistral-vibe and codex-cli are enabled: false in the default provider config, so all phases run through Claude Code as before. Enabling them via phax agent setup providers (or editing ~/.phax/providers.json) activates the richer routing. See docs/model-routing.md for the full resolution pipeline, tier table, relationship semantics, and worked examples.
Every run executes under a security posture, set by security.profile in phax.json and overridable per run with --security:
| Mode | Behavior |
|---|---|
secure |
Default. Provider-native sandboxing — filesystem jailed to the worktree, network governed by network.profile (enforced only where the provider supports it), MCP disabled. |
unsafe |
Host-unrestricted: full filesystem/network access. Prints a warning. Use only for trusted plans. |
isolated |
External-sandbox mode — planned, not yet available (the CLI rejects it today). |
Provider capability matters under secure: Claude Code and Codex have strong filesystem jails and run natively, while Mistral Vibe has only a partial jail. In strict secure mode a partial-jail provider cannot satisfy the policy, so routing skips it and falls back to Claude Code; the applied posture (including any downgrade) is recorded in each phase's security.json and the final report. Network controls differ too: no provider enforces a domain allowlist, and Codex is the only one with a hard egress toggle (provider-only disables subprocess network); for Claude and Vibe the network.profile is recorded but not enforced as a domain filter.
Shell access for the agent (used to run and fix the phase's gate commands) is also constrained per provider, at different granularities — Claude allowlists exactly the gate commands, while Codex and Vibe rely on their sandbox/approval models. See Shell command execution in the security docs for the details.
pnpm test # unit + integration — fast, no network, no provider CLIs
pnpm test:e2e:real # opt-in real E2E — drives the installed provider CLIs, costs tokensThe E2E suite skips automatically unless PHAX_E2E_RUN=1 is set, so it never runs by accident. It runs one real-flow suite per provider (Claude Code, Mistral Vibe, Codex), each forcing its provider with --provider-priority and gated on that provider's CLI being installed — so only the providers you have set up actually run. See docs/e2e-testing.md for prerequisites, isolation model, and how to read failure artifacts.
Add --verbose to any command to print semantic events (state transitions, adapter calls, gate results) to the terminal:
phax run --plan plan.md --verbose
phax resume <short-name> --verboseAdd --trace to also write one JSON line per semantic event to semantic.jsonl in the run folder (~/.phax/runs/<short-name>/):
phax run --plan plan.md --traceBoth flags can be combined. See docs/observability.md for the full observability architecture and docs/plan-extraction-model.md for how to configure the model phax run uses for the fallback extraction.
phax emits structured semantic telemetry through the SystemTelemetry port — state transitions, adapter calls, gate results, and artifacts. Telemetry is on by default and recorded to a per-run journal; toggle it globally with "enabled": false in ~/.phax/telemetry.json. Two opt-in flags surface it live:
| Flag | Effect |
|---|---|
--verbose |
Print semantic events to the terminal |
--trace |
Write semantic events as JSONL to semantic.jsonl in the run folder |
See docs/observability.md for architecture details, the snapshot rule, and the adapter-boundary failure contract.
phax resume <short-name> # restart from the next pending phase
phax resume <short-name> --yes # skip confirmation
phax resume <short-name> --yes --provider-priority codex-cli,claude-code # override provider priorityResume validates the run state, lock, and worktree before proceeding. It never re-runs committed phases. If the run is review_open, it refuses and points you at phax enter.
phax writes a lock file at ~/.phax/locks/<short-name>.lock for every active run. If a process dies, the lock can become stale:
phax unlock <short-name> # remove stale lock
phax unlock <short-name> --force # remove any lock| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Validation error, config error, or plan error |
| 2 | Gate failure (after fix loop exhausted) |
| 3 | Lock conflict |
| 4 | Unsafe git state (dirty worktree) |
| 5 | Agent invocation error (Claude, Vibe, or Codex) |
| 6 | Handoff generation failed |
| 8 | Rate limit or usage limit hit (resumable) |
| 9 | Phase produced no changes (resumable) |
phax has no runtime-configuration env vars — the state root, telemetry, and security posture are all set in phax.json (and ~/.phax/telemetry.json), not via the environment. The one variable phax honors gates the opt-in real E2E suite:
| Variable | Purpose |
|---|---|
PHAX_E2E_RUN |
Set to 1 to enable the real E2E suite (pnpm test:e2e:real) |
claude not found — install Claude Code and ensure the binary is on $PATH.
Lock conflict — another phax process is running, or a previous process died. Run phax unlock <short-name> to clear a stale lock.
Gate failure loop — increase maxFixAttempts in phax.json, or reduce gate scope. Check ~/.phax/runs/<short-name>/phase-NN/checks-attempt-01.log for details.
Missing phase-handoff.md sections — the phase transitioned to handoff_failed. Check the phase status file and resume the agent session with phax enter.
Format conflicts — run pnpm format, do not add lint exceptions. Knip failures — remove the dead code or wire it into an entry point, do not add ignoreDependencies entries casually.
phax is implemented as an explicit hierarchical state machine. Every signal (gate result, rate limit, agent completion, archive request) is a typed PhaxEvent. The pure reducer returns a Disposition — Handled, Ignored, Stale, Rejected, or Unexpected — plus optional side-effect commands. The single dispatch() entry point is the only writer to status.json and run-status.json.
See docs/state-machine.md for:
- Mermaid diagrams of the run and phase hierarchies
- The full event-disposition matrix
- The event and command vocabularies
- A worked example of adding a new signal
phax never interpolates user-controlled data (branch names, workspace paths, plan fields) into shell command strings. All git and shell invocations pass arguments as separate argv tokens. Gate commands from phax.json are treated as opaque pre-validated arrays, not shell strings.
phax.usage.kdl is a machine-readable CLI contract generated from the Commander.js program in src/cli/. It is a derived artifact — Commander is the source of truth — and must be regenerated after any change to a command, flag, or argument:
pnpm gen:usage-specThe integration gate tests/integration/usageSpecDrift.test.ts asserts the committed file is byte-identical to the generator output, so a CLI change without regenerating the spec will fail the gate. Downstream tooling (phax --usage, shell completions, docs/cli/reference.md, and external consumers such as a generated client library or editor integration) all derive from this spec.
phax ships a generated shell completion script via phax completions <shell>. Supported shells: zsh, bash, fish, nu, powershell.
Prerequisite: the usage CLI must be installed — it is needed both to generate the script and at Tab-time (the generated script calls back into usage complete-word):
brew install usagePer-shell install:
# zsh (default shell on macOS) — write a _phax completion onto your $fpath.
# If you already have a completions dir on $fpath, just drop the file in:
phax completions zsh > "${fpath[1]}/_phax"
# bash
source <(phax completions bash)
# or add to ~/.bashrc:
echo 'source <(phax completions bash)' >> ~/.bashrc
# fish
phax completions fish > ~/.config/fish/completions/phax.fish
# nushell — add to your nu config
phax completions nu | save --force ~/.config/nushell/completions/phax.nu
# source it in env.nu or config.nu
# powershell
phax completions powershell >> $PROFILEzsh on macOS, from scratch — if you don't already have a completions directory on $fpath, set one up once:
# 1. Create a directory for personal completions and put the _phax file in it.
mkdir -p ~/.zsh/completions
phax completions zsh > ~/.zsh/completions/_phax
# 2. Make zsh load it (add to ~/.zshrc). Skip the compinit line if your setup
# already runs it — frameworks like oh-my-zsh do.
cat >> ~/.zshrc <<'RC'
fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit
RC
# 3. Reload your shell, then Tab-complete:
exec zsh
phax <Tab>After this, phax <Tab> lists subcommands and phax enter <Tab> completes run short-names live from your registry.
phax --usage and phax completions work from the release binary as well as from source. Both commands read phax.usage.kdl, which is embedded in the binary at build time via deno compile --include.
Once the completion script is installed, Tab also completes run short-names for commands that take one (phax enter, phax resume, phax archive, and others). Candidates are fetched live from phax ls --complete, so they reflect the actual runs in your registry at Tab-time.
Full CLI reference: docs/cli/reference.md.
phax validate [--plan <path>]— Validate phax.json and its user overlays without any side effects; pass --plan to also validate a phax-plan.jsonphax unlock [--force] <short-name>— Remove a stale run lock; use --force to remove any lockphax enter <short-name>— Attaches to the kept-open agent session in the final worktree, so you can review the agent's work, ask follow-up questions, or apply manual fixes interactively.phax enter-phase <short-name> <phase-id>— Attaches to the agent session for a specific phase worktree. Useful for inspecting intermediate state or debugging a phase that has not yet been committed to main.phax session-info [--debug] <short-name>— Prints diagnostic information about a run: its current state, active phase, worktree path, and agent session id. Read-only — no side effects.phax shell <short-name>— Opens an interactive shell in the final worktree. Useful for manually inspecting files, running tests, or executing commands outside the agent session.phax path <short-name>— Prints the absolute path to the final worktree on a single line. Useful in scripts: cd $(phax path my-run) or for piping to other tools.phax open <short-name>— Opens the final worktree in the editor configured in phax.json (or the EDITOR environment variable). Equivalent to running your editor with the worktree path as an argument.phax ls [FLAGS]— Lists runs from the local registry (~/.phax/runs/). With no filter flags, shows all runs. Use status filters to narrow output: --active (created or running), --failed, --review-open (awaiting human review), or --archived. Use --json for machine-readable output.phax archive [--force] <short-name>— Archives a run by moving its worktrees under ~/.phax/archive/./ and marking it archived in the registry. Nothing is destructively deleted — every phase's working state is preserved.phax run <FLAGS> [short-name]— Extracts a plan from the plan.md given by --plan, creates a run entry in the registry, and executes each phase sequentially in its own Git worktree using the configured AI agent. Each phase runs its gate profile's every-phase steps after execution; the final phase also runs the profile's terminal steps. Each step's surface (local, structural, or product) is recorded per phase and the run's verified surfaces are reported at run end.phax review-handoff [--allow-partial] <short-name>— Regenerate review-handoff.md and global file reconciliation for a review_open runphax publish-pr <short-name>— Pushes the final worktree branch to the GitHub remote and creates a pull request, or reuses an existing PR for the same branch. Requires a GitHub remote and gh CLI authentication.phax review-compliance <short-name>— Runs a non-mutating plan-compliance review by invoking the AI agent with the run's handoff artifacts and the original plan. Does not modify the worktree, registry, or any files.phax review-code [FLAGS] <short-name>— Opens an interactive, pre-prompted code-review session for a review_open run by launching the AI agent in the run's worktree with the code-review prompt. The session is resumable: re-running resumes the existing session, while --new-session starts fresh. The developer takes over the session to investigate, discuss, and apply fixes.phax adjust-plan <FLAGS> <plan>— Opens an interactive, pre-prompted session to help you adjust a plan.md after a landed run has introduced drift. The session establishes which of the plan's declared files, line references, and decisions are invalidated by the landed run's actual changes, asks clarifying questions where needed, proposes concrete edits and waits for your explicit approval, and only then edits and commits the plan — all interactively within the session. The command itself mutates nothing.phax init [--force] [--yes]— Creates phax.json and phax.schema.json in the current directory. Use --force to overwrite an existing phax.json. Does not connect to any network or external service.phax report [--no-gist] [short-name]— Creates a GitHub issue from local run telemetry. By default, uploads the full log as a secret GitHub gist and links it in the issue body. Use --no-gist to inline the log directly.phax orient [--file <path>] [id]— Requires an orient provider in phax.json:phax completions <shell>— Generate a shell completion script (zsh, bash, fish, nu, powershell). Requires the usage CLI.phax resume [FLAGS] <short-name>— Picks up a run from its next pending phase, re-entering the same execution loop as phax run. Prompts for confirmation before proceeding unless --yes is set.phax reset-phase [FLAGS] <short-name> [phase-id]— Reset a stuck or failed phase so phax resume re-runs it from scratchphax agent <SUBCOMMAND>— Inspect and manage model routing and provider configurationphax agent models— Print the routing table and provider priorityphax agent resolve <FLAGS>— Show how a model+effort request resolves to a provider and concrete modelphax agent probe— Check which provider executables are available on PATH; never throws on an unavailable providerphax agent setup <SUBCOMMAND>— Set up provider integrationsphax agent setup mistral-vibe [--dry-run] [--install-model-aliases]— Append PHAX-owned Mistral Vibe model aliases to ~/.vibe/config.toml (append-only, atomic)phax agent setup providers [FLAGS]— Reconcile ~/.phax/providers.json enabled flags from live executable probes (dry-run by default)phax security [--verbose] [--trace] <SUBCOMMAND>— Security-related commandsphax security status [--verbose] [--trace]— Show provider security capabilities and availabilityphax skills <SUBCOMMAND>— Manage PHAX skillsphax skills install <--target <target>> [--scope <scope>] [skill]— Install bundled PHAX skills into an agent's native skill directoryphax schema <SUBCOMMAND>— Manage the local phax.schema.jsonphax schema upgrade— Regenerate phax.schema.json from the installed binary's config contract; never modifies phax.jsonphax artifact <SUBCOMMAND>— Parent command for inspecting and transitioning the lifecycle status of a spec (docs/specs/) or plan (docs/plans/). Specs carry Draft, Approved, Abandoned, or Completed; plans additionally carry Stale. Transitioning to a terminal status (Abandoned, Completed) moves the file into the artifact's archive/ subdirectory as part of the transition. Illegal transitions and validation failures (missing frontmatter block, unknown status, status/location disagreement) refuse with exit code 12.phax artifact status <path>— Reports an artifact's kind (spec or plan), current status, and the legal transitions from that status. For Approved specs, also reports the approval date and baseline, and whether the spec has been edited since that approval (recorded) or has no approval record (unrecorded). Read-only — no side effects.phax artifact approve <path>— Transitions an artifact to Approved. Legal from Draft (both kinds) and from Stale (plans only); re-approving an already-Approved artifact re-records the approval, refreshing its timestamp and baseline — this is the correct way to record an in-place revision of a spec, not editing the date by hand. Rewrites the frontmatter status key in place.phax artifact stale <path>— Manually marks a plan Stale. Legal from Approved only — Stale has no automatic trigger (that belongs to a future lineage spec). Rewrites the frontmatter status key in place.phax artifact abandon <path>— Abandons an artifact — a terminal status distinct from Completed, for work dropped without execution. Legal from Draft or Approved (specs) or Draft, Approved, or Stale (plans).phax artifact complete <path>— Completes an artifact — a terminal status for work that ran to completion. Legal from Approved (specs) or Approved or Stale (plans).phax artifact reopen <path>— Reopens a Stale plan back to Draft, for when re-planning is needed before re-approval. Legal from Stale only. Rewrites the frontmatter status key in place.phax plans <SUBCOMMAND>— Parent command for reporting on plans: the mechanical defects of a single plan (lint), staleness of Approved plans against their recorded approval, and cross-plan file overlap.phax plans status [--apply] [--json]— Reports every live, Approved plan's staleness against the ground it was approved against: the declared source spec's content, the plan's own content, and the files changed since the recorded baseline intersected with the plan's footprint. Each stale entry names its reasons (spec-changed, ground-changed, self-changed) with evidence; a plan with no approval record — or one whose baseline commit no longer exists — reports missing-record, which renders as stale. This is a report, not a gate: it exits 0 whether or not stale plans exist. Use --apply to flip stale-computed plans Approved -> Stale as an explicit gesture (the flip is never automatic). Use --json for machine-readable output.phax plans overlap [FLAGS] <plan>— Reports which of two or more plans can run in parallel without a merge conflict — predicted from each plan's declared file-sets, or confirmed against a landed run's actual diff.phax plans lint [--json] <plan>— Reports every mechanical defect of a plan.md that phax can establish before a run, as findings with a severity (error or warning), a check, and the phase they concern.phax records <SUBCOMMAND>— Manage phax run recordsphax records init [--force]— Configure records for this project (transcript, destination, auto-push)phax records sync— Bring the local records clone in line with its configured remotephax records status— Show pending (unpushed) records, by run and phasephax records list [--run <id>]— List records present, by run, phase, and verified surfacesphax records explain [FLAGS] <sha>— Explain a commit from its record: prompt, diff, gates and verified surfaces, handoff, transcript, usage