> For AI agents: the complete documentation index is available at https://docs.phax.run/llms.txt, the full documentation bundle is available at https://docs.phax.run/llms-full.txt.

# Upgrading phax \{#upgrading-phax}

Upgrade the way you installed phax:

```bash
npm install -g @lbdremy/phax@latest
```

With a downloaded binary, download the new release from [GitHub Releases](https://github.com/lbdremy/phax/releases) and replace the old one.

## After every upgrade \{#after-every-upgrade}

Two commands keep what phax copied into your repository in step with the new binary. Run both in each repository that uses phax, and commit what they change:

```bash
phax skills install --target claude   # the bundled skills (phax-planning, phax-spec, …); or codex, or agent
phax schema upgrade                   # phax.schema.json, the editor schema of phax.json
```

## Release by release \{#release-by-release}

Only the releases that ask for something are listed. phax never asks for a step silently: a command that needs one refuses with exit 12 and names it.

### 0.35.0 \{#0350}

- **Commit trailers name no path of the machine.** Phase, interactive and held review commits no longer carry `Worktree` or `Session-Id`, and `Gate-Log` is relative to the run's folder (`phase-02/checks-attempt-01.log`), or `none`. A stop whose commit was written by an older version still closes, and no record or run file changes. A script that read those trailers reads the phase folder instead: `<run>/<phase>/agent-binding.json` for the session, the `checks-attempt-NN.log` files for the gate. See [The phase commit](https://docs.phax.run/guide/run.md#the-phase-commit) for history already written.
- **Records: `phax records sync` aligns the local records branch.** It creates the branch at the remote's tip when it is missing, fast-forwards it when it is behind, and refuses when it has diverged from the remote's or shares no commit with it. `--replay` recommits the local-only records onto the remote's tip, keeping their trees, messages and authors, and prints the previous tip; a record the remote already holds file for file is left out, and a key the remote also holds with other content is replayed and named. phax never forces the branch. See [When the records branch and its remote disagree](https://docs.phax.run/guide/records.md#when-the-records-branch-and-its-remote-disagree).
- **Records: `phax records sync` works for the in-repo destination.** It fetches the records branch from `publish.remote` and no longer says `nothing to sync`, so it now needs that remote to answer: a script that runs it unconditionally can see it exit 1 on a network or authentication failure.
- **Records: `phax records sync --json` gains a required `branch` outcome** (`created`, `fast-forwarded`, `replayed` or `unchanged`) on a `cloned` or `fetched` answer, which also gains `remoteName`. The `replayed` outcome carries `skipped` and `sharedKeys`, and the answer gains `replay`. A refusal adds one error: `RecordsBranchDivergedError` (`reason`, `localTip`, `remoteTip`, `localOnly`, `remedy`). A script that matches the answer's keys exactly adds them.
- **Records: `phax records status --json` gains a required `branch` object** (`state`, `localTip`, `remoteTip`), between `remote` and `pending`. The text gains a `Records branch:` line with its remedy; before anything is written or fetched it names `phax records sync` instead of saying every record is pushed.
- **Records: pending counts only local-only records.** On a diverged or unrelated branch, the records the remote already holds are no longer counted.
- **Records: a refused records push names the branch state and the fix.** When the branch is behind, diverged or unrelated, the message gives the state and `phax records sync` or `phax records sync --replay`, not a plain `git push`. A record is also written on top of the remote's tip as phax last fetched it, so a lagging branch catches up with the write.
- **Records: a refused records push is always reported.** `phax publish` and `phax run` print it as a warning, where it was shown only with `--verbose`.
- **Artifacts: the `--json` record of `artifact new --headless` and `artifact decide` gains a required `branchNote`** beside `pushWarning`: the note of a record kept on a diverged or unrelated records branch, or null. `pushWarning` still names only a failed push.

Record format, manifests and versions are unchanged. No migration is needed, but records an earlier version left on a stranded branch (a fresh clone, a second machine) stay pending until you run `phax records status`, then `phax records sync --replay` when it names it.

### 0.34.0 \{#0340}

- **Gate steps accept `partial`.** An optional `partial` object with a `files` and/or a `diff` command, joined by pipes only, is shown in the phase prompt and allowed in secure mode; a malformed one is refused when the config loads, as is a quote, a backslash or a `*` in it. `phax validate` and `phax run --dry-run` list the prefixes each profile's partial commands allow (the `--json` answers gain `partialPrefixes`). See [Partial commands](https://docs.phax.run/guide/extend.md#partial-commands). Records readers: `security.json` `agentCommands[].source` gains the value `"partial"`; the file stays at version 3. No migration is needed.
- **Command line: a refused call exits 64.** An unknown command, an unknown option or a bad argument (a missing argument or option value) now exits 64 instead of 1, with the same text. A group called alone, such as `phax artifact`, writes its help on stderr and exits 64. A command answering through the runner refuses an operand it does not declare, which it ignored before: `phax validate extra` and `phax ls extra` exit 64. A script that checks for exit 1 on a mistyped call checks for 64. The page [Machine answers](https://docs.phax.run/reference/json-output.md) describes `--json`, the message lines and `--json-schema`.
- **Command line: an unexpected crash exits 70.** It writes `defect: …` on stderr, where it wrote `Unexpected error: …` and exited 1. A command's own refusals keep their codes, and the phase guard still refuses with exit 1.
- **Command line: `phax --json-schema` prints the contracts of the commands answering through the runner**, with the program's exit codes and the shapes of its message lines. `--json-schema` after a command that does not answer through the runner is refused with exit 64.
- **Run list: `phax ls --json` rows gain the required `pullRequest`.** It is the URL of the pull request phax opened for the run, or `null` when it opened none or the run's publication record cannot be read. `run-list` is stamped 0.34.0; a list stamped 0.26.0 or 0.30.0 is still parsed by `@lbdremy/phax-schemas` (`parseRunList`), and `toLatestRunList` gives each of its rows `pullRequest: null`. A script that matches the row's keys exactly adds the new one.
- **Schemas package: `artifact-status`, `plan-overview` and `plan-lint` are new formats at 0.34.0.** `@lbdremy/phax-schemas` exports `parseArtifactStatus`, `parsePlanOverview` and `parsePlanLint`, with their schemas, `toLatest` functions and JSON Schemas, and `parseDocument` identifies each. They describe the answers of `phax artifact status --json`, `phax plans overview --json` and `phax plans lint --json`, which phax never saves.
- **Plans: `phax plans overview --json` and `phax plans lint --json` answer versioned documents.** Each prints a `plan-overview` or `plan-lint` document whose first key is `$schema`, followed by the keys the unversioned object had, in the same order. A script that reads the object as it was ignores `$schema`, or reads it with `parsePlanOverview` and `parsePlanLint`. `plans lint` still exits 1 when a finding is an error, with or without `--json`.
- **Artifacts: `phax artifact status --json` is new.** It prints an `artifact-status` document: the path, kind, status, legal transitions, approval, authoring and questions the text reports. Without `--json` the text is unchanged.
- **Command line: `artifact status`, `plans overview`, `plans lint` and `ls` answer through the runner.** Under `--json`, stderr carries only message lines, one JSON object each: a warning (an orphan approval record, a failed architecture provider) is a `note` line, a failure an `error` line, a refused call a `refused` line. `--json-schema` after each prints its contracts. Without `--json`, their text, warnings, error sentences and exit codes are unchanged.
- **Command line: the help of a command answering through the runner** prints its long help before its options, wrapped at 80 columns; its usage line, options, help texts and examples are the same. `phax help <command>` and `phax <group> help <command>` print it too.
- **Plans: `plans status`, `plans overlap` and `plans estimate` answer through the runner.** Their `--json` objects are unchanged except `plans status --apply`, which gains `written` after `applied`, the flipped plans' records. Under `--json` their warnings are note lines. `phax plans` alone and `--json-schema` follow the runner.
- **Command line: `validate`, `session-info`, `path`, `brief`, `architecture`, `completions` and `report` answer through the runner.** Under `--json` each answers one JSON object and message lines on stderr, and `--json-schema` after each prints its contracts. Their text and codes are unchanged; `phax path` prints the same bytes. `phax completions` prints a script that embeds the regenerated usage spec (the exit-code block after the root help, `--json-schema`, each command's `--json`), so its bytes change while completion works as before.
- **Command line: `cost backfill`, `schema upgrade`, `security status` and `skills install` answer `--json`.** The writing ones list the files they wrote as written-file records (`cost backfill` the cost-ledger.json files, by absolute path; `schema upgrade` the two schema files; `skills install` each installed file), and `skills install` names the files already installed when it fails part way. Without `--json` each prints the same text with the same codes. The `cost`, `schema`, `security` and `skills` groups called alone are refused with exit 64 and their help; bare `phax security`, which ran the status report and exited 0, now does the same, so call `phax security status`. `--verbose` and `--trace` on `security` and `security status` are the program's options, accepted anywhere in the call as before.
- **Command line: the `records` commands answer `--json`.** `records init` prompts, so `records init --json` is refused with exit 64 before anything is asked, although its help lists `--json` as every command's does. `records explain --json` carries the text of each artifact its flags ask for (`--prompt`, `--diff`, `--transcript`, `--gates`, `--briefs`), `null` when the record does not carry it. Without `--json` the text, error sentences and exit codes are unchanged, and `phax records` alone is refused with exit 64 and its help.
- **Artifacts: the transitions, `artifact schema` and the migrations answer through the runner.** Under `--json`, `artifact approve`, `reopen`, `stale`, `abandon` and `complete` answer an object (the status reached, the previous status, the path, the baseline, who approved or reopened, the approval record a reopen, complete or abandon deleted, the bound plans, the commit) and list the files they wrote as written-file records (a headless artifact's sidecar only when complete or abandon moves it); `artifact migrate-approvals` and `artifact migrate-documents` answer their report with the record files, or the sidecars and bodies, they wrote. `approve --json` and `reopen --json` without `--by` are refused with exit 64 before anything is written, because a terminal standing for the operator is a person's gesture; the refusal reads `error: --json: artifact approve prompts; pass --by`. `artifact schema` prints the same bytes in both modes. Under `--json`, `migrate-approvals`' orphan-record warnings are note lines on stderr. An unknown `artifact schema` kind now exits 64, with the runner's text.
- **Artifacts: `artifact decide` and `artifact new spec|plan` answer through the runner.** `--verdicts <file|->` and `--brief <file|->` are read before anything runs: a terminal on stdin, a file that cannot be read, and a verdicts file that does not decode or does not fit the artifact (another artifact's path, an unknown question or option, a question named twice, a missing note) are refused with exit 64 and a refused line naming the file or stdin and the field, such as `error: verdicts.json: verdicts[0].chosen is not an option of q-cadence`; a file stamped with an older decide-verdicts release is refused the same way, at `$schema`. These sentences no longer list the question's options or the artifact's question ids, and a field the file's schema refuses is written `verdicts.0.note`. Before, `decide` answered these with exit 12 and `artifact new` waited on a terminal stdin. `--brief` given without `--headless` is now read, and refused when unreadable. `decide --json` without `--verdicts`, or without `--by`, is refused with exit 64 before anything starts, where it exited 12 with `--json needs --verdicts`; the refusal reads `error: --json: artifact decide prompts; pass --verdicts and --by`. `decide --verdicts --json` prints its object two-space indented, not on one line, with `written` (the files its commit holds) and `record` (the decision record's outcome) after its twelve keys, and its `record` line no longer reaches stderr. `artifact new --json` answers the path, kind and lineage of a blank skeleton, or with `--headless` the path, sidecar, commit and authoring record. Under `--json` the sessions' progress lines (`authoring …`, the decide session's lines) are note lines on stderr, and a failed commit's error line lists the files it left written. `phax artifact` and `phax artifact new` alone are refused with exit 64 and their help on stderr, as `phax agent` and `phax agent setup` are: the help puts a group's long help before its commands, lists `help [command]` last, and ends with ``Run `phax --help` for usage.``. `phax artifact archive` keeps its sentence but exits 64, not 1, follows it with ``Run `phax --help` for usage.``, and answers `--help` with that refusal, not with help. The help of `decide` and `artifact new spec|plan` lists `--verdicts` and `--brief` after the other options, beside a `stdin: JSON` or `stdin: text` line. Without `--json` both commands print the same text with the same codes otherwise.
- **Agent: `agent models`, `resolve`, `probe`, `setup providers` and `setup mistral-vibe` answer through the runner.** Each answers `--json` with one indented object (`models` the routing table and priority, `probe` each provider's availability and whether config enables it, the setup commands what they did). `agent resolve --json` is unchanged in its bytes. The setup commands list the files they wrote (providers.json, \~/.vibe/config.toml, their backups, a scaffolded model-routing.json) as written-file records with absolute paths. Under `--json`, an invalid `--effort` or a failed setup is one `error` line on stderr. Without `--json` the text, error sentences and exit codes are unchanged. `phax agent` and `phax agent setup` alone are refused with exit 64 and their help.
- **Root commands: `init`, `unlock`, `archive` and `prune` answer `--json`.** `init --yes --json` answers the config and schema paths, whether the wizard ran interactively, and phax.json, phax.schema.json and phax.user.schema.json as written-file records; `init --json` without `--yes` is refused with exit 64 before anything is asked or written, even off a terminal, naming `pass --yes`. `unlock --json` answers `{ shortName, removed, qualifiedName }`, `removed` one of `stale` (the holder's process is gone), `expired` (the lock outlived its lifetime), `forced` or `none`. `archive --json` answers `{ qualifiedName, crossProject }`. `prune --json` keeps its object byte for byte, but its preview lines (`Prune from namespace …`) are now note lines on stderr, and `prune --json` without `--yes` or `--dry-run` is refused with exit 64 and one refused line, `error: --json: prune prompts; pass --yes to proceed or --dry-run to preview only`, where it printed the preview document and exited 1. A held lock, a refused archive and a failed prune are one `error` line under `--json`. Without `--json` the text, error sentences and exit codes are unchanged, and `prune` keeps `-y` beside `--yes`.
- **Review: `review-handoff`, `publish-pr`, `review-compliance` and `review-code` answer through the runner.** Under `--json` each answers one object: the run's qualified name and whether it is another project's, then `reviewHandoff` and `written` for `review-handoff`, `pullRequest` (the URL, or `null` when the branch was pushed without opening one) for `publish-pr`, `verdict`, `review`, `notice`, `warnings` and `written` for `review-compliance`, and for `review-code --headless` `findings` (a count and its summary), `findingsPath`, `fixHint`, `notice`, `warnings` and `written`. `review-handoff` lists the reconciliation, `review-handoff.md` and `final-report.md` it regenerated, `review-compliance` its Markdown and JSON, and `review-code --headless` `code-review.json` and `code-review-session.json`, each as a written-file record. The records and push warnings are note lines on stderr under `--json`. `review-code --json` without `--headless` is refused with exit 64 before any session starts, since it hands the terminal to a session; without `--json`, its exit is still the session's. Without `--json` the text, streams and codes are the same. In `phax --help`, `publish-pr` and `review-compliance` show `[options]` in their usage, since they now take `--json`.
- **Sessions: `enter`, `enter-phase`, `shell`, `open` and `adjust-plan` answer through the runner.** Each hands the terminal to a session, a shell or an editor, so under `--json` each is refused with exit 64 and one `refused` line before anything is read or started, naming the command that answers instead: `phax session-info <run> --json` for `enter` and `enter-phase`, `phax path <run> --json` for `shell` and `open`, and for `adjust-plan` that it runs from a terminal without `--json`. Before, `--json` was an unknown option. Without `--json` each prints the same lines and exits with the code of the process it handed the terminal to, as before; their error sentences and codes are unchanged. `shell` starts `$SHELL` (or `bash`) through the session port, and `adjust-plan` reads its plan through the file system port. In `phax --help`, `enter`, `shell` and `open` show `[options]` beside their name.
- **Runs: `run`, `resume` and `reset-phase` answer through the runner.** Under `--json` each answers one object naming its outcome: `run` and `resume` answer `{ qualifiedName, crossProject, outcome }`, the outcome a `dry-run` (the report `--dry-run` prints), a `preview` (resume without `--yes`: the state, the next phase, the skipped phases, the owed step), `already-in-review`, `review_open` (the phase count, the pull request, the completed artifacts and the final report's path), `stopped` (exit 13, with the stop report) or `paused`; `reset-phase` answers its `preview` or its `reset`. Every progress line (`Run: <run>`, `using cached extraction`, `would resume …`, the extract warnings, the `--verbose` events) is a note line on stderr under `--json`, written as it happens, so the order of stdout against stderr is no longer a contract there. A paused run (a rate or usage limit, a lost network, a handoff turn, gates exhausted or a refused gate step, a phase with no changes, a refused close) is an answer, not an error: the outcome `paused` names its reason, phase, resume command, resume-instructions path, `resetAt` when known and its exit, and the call exits with that code, as before. A refusal or failure before the run starts or outside a pause is one `error` line with today's code; a held run lock is `LockConflictError` with exit 7 and fields `run` (the short name) and `pid`. `resume` and `reset-phase` without `--yes` answer their preview under `--json` and take no lock. Every exit code stays. `resume --skip-oracle` keeps its sentence but exits 64, not 1, and follows it with ``Run `phax --help` for usage.``. `resume` and `reset-phase` no longer list their own `--verbose` and `--trace`, in their `--help` or in the usage spec: they are the program's options, accepted anywhere in the call as before. Under `--json`, auto-publish's `--verbose` lines (`Publishing branch to …`, `Pull request created: …`) and the run-end reviews' `[phax] …` lines are note lines too, as are `publish-pr --verbose`'s. An interrupted `run` or `resume` (SIGINT or SIGTERM) still saves the run and exits 130; under `--json` its sentence is note lines. A run of short flags is read as one word: `resume x -yv` is refused naming `-yv`, where it named `-v`. The help of `resume` no longer says it prompts: without `--yes` it prints where it would resume and exits 0. Without `--json` the text, streams and codes are unchanged; `-y` stays beside `--yes`, and `run --hold` keeps its optional value.
- **Command line: every command answers through the runner.** `phax --json-schema` lists every command, and `--json-schema` after any command prints its contracts; it is no longer refused after a command. `phax help <command>` answers that command's help, and `phax help <group>` its group's. phax.usage.kdl is written in one style, from the command definitions, under a two-line header: its groups no longer declare `subcommand_required` (a group called alone is still refused with exit 64 and its help), its program name is a bare word (`name phax`), and its global flags list `--usage` and `--usage-format` before `--verbose` and `--trace`. A script that reads the file with the `usage` CLI sees the same commands, arguments and flags. `--version`, `-V` and `--usage` print as before when called alone. Root help gains a `--json-schema` line under Options; `path`, `brief`, `architecture` and `completions` show `[options]` beside `enter`, `shell`, `open`, `publish-pr` and `review-compliance`, and `security` no longer does. `--help` and `--version` are answered alone: with any other word (`phax --help run`, `phax run --version`, `phax -V run`) the call is refused with exit 64, where 0.33.0 printed the help or the version. Bare `phax`, or `phax` with only program options (`phax --verbose`), writes the root help on stderr and exits 64, not 1, ending with ``Run `phax --help` for usage.``. `phax <group> help [command]` answers like `phax <group> [command] --help`. The phase guard refuses inside a phase worktree with today's sentence and exit 1, or one `refused` line under `--json`, while `--help`, `-h` and `--json-schema` still answer; it now runs before the call is read, so a malformed call there (`phax run` without `--plan`) gets the guard's sentence rather than the missing option's, and a group alone gets its help with exit 64.
- **Claude Code: headless sessions load only the repository's harness.** Phase sessions, handoff, authoring, decide's handback, review and structured completions no longer load your Claude Code user settings, skills, plugins, hooks, instructions or auto-memory; interactive sessions and the usage probe are unchanged. The repository's `CLAUDE.local.md` and `.claude/settings.local.json` are left out too. Your own `permissions.deny` rules and hooks no longer guard headless sessions: in unsafe mode nothing else blocks what they blocked, so move a guardrail every session must keep into the committed `.claude/settings.json` or managed settings. If you authenticate through user settings (an `apiKeyHelper`, an `env` block, Bedrock or Vertex set there), run `claude /login` or set `ANTHROPIC_API_KEY` in the environment phax runs in, or, for Bedrock, Vertex or a gateway, export `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX` or `ANTHROPIC_BASE_URL` and its credentials there; otherwise the phase fails with `not-authenticated` and names the remedies. If another credential is available (a `claude /login` session, `ANTHROPIC_API_KEY`), a headless session uses it silently instead of your settings' backend. A setting a headless session needs goes in the repository's committed `.claude/settings.json`. Phase records carry the required `userConfig` (`excluded` for claude-code, `loaded` for codex-cli and mistral-vibe), and `phase-record-manifest` and `decision-record-manifest` are stamped 0.34.0; earlier records read as `loaded`, and `excluded` covers a phase's headless sessions only. See [What a Claude Code session loads](https://docs.phax.run/security.md#what-a-claude-code-session-loads).

Later entries of this release follow.

### 0.33.0 \{#0330}

- **Plan documents, run plans and extraction answers: `plan-document`, `phax-plan` and `phax-plan-extraction` are stamped 0.33.0.** A phase's `oracle: { dischargedBy, files }` key is `acceptance: { passedBy, files }`, a test line's loop `oracle` is `acceptance`, the technique `boundary` is accepted, and the criterion words `acceptance`, `in-phase` and `oracle` are reserved: no criterion id may be one of them.
- **Records and schema readers: `run-status`, `phase-status`, `gate-attribution` and `phase-record-manifest` are stamped 0.33.0.** A run's hold selection `oracle` is recorded `acceptance`, a corrected stop's `oracleStep` is `acceptanceStep`, a gate step's surface `oracle` and a record's verified surface `oracle` are `acceptance`, and a record's role is `acceptance` with `passedBy` or `passing` with `acceptancePhase`, in place of `oracle` with `dischargedBy` or `discharging` with `oraclePhase`.
- **Provider and run folders: the new `acceptance-request` replaces `oracle-request`.** The provider reads an `acceptance-request` stamped 0.33.0 on stdin, with verdict `authored` or `passed` and the keys `phase`, `acceptancePhase`, `base` and `files`, plus `acceptanceCommit` on `passed`. phax saves it as `checks-attempt-NN.acceptance-request.json`. `oracle-request` is frozen at 0.26.0 and read only: phax writes it nowhere. A provider you wrote reads `passed`, `acceptancePhase` and `acceptanceCommit` in place of `discharged`, `oraclePhase` and `oracleCommit`, and refuses any other document, as the example does: a provider that still reads an `oracle-request` receives none, and phax refuses a plan with an acceptance phase when no `acceptance` provider is configured.
- **The Vitest example moved from `examples/vitest-oracles/` to `examples/vitest-acceptance/`.** If you copied it, or your phax.json points at `node examples/vitest-oracles/main.mjs`, change the path to `node examples/vitest-acceptance/main.mjs` under the key `acceptance`. It reads only an `acceptance-request`, names `--skip-acceptance` in its remedy, and reports findings whose ids start with `acceptance-` (`acceptance-unmarked`, `acceptance-not-run`, `acceptance-marker-left`, `acceptance-changed`, `acceptance-unparsable`).
- **Telemetry: the event `oracle.edits.unjudged` is `acceptance.edits.unjudged`.** Its console line is `phax·acceptance.edits.unjudged`, and a failed step named `oracle` in a span or an event is named `acceptance`. A dashboard or an alert on the old name stops matching.
- **Overview and records: `acceptanceRole` and the `acceptance` rows.** `phax plans overview --json` gives `acceptanceRole`, whose kind is `acceptance` (with `passedBy`), `passing` (with `acceptancePhase`) or `none`, in place of `oracleRole`. The overview prints `acceptance role` and `phax records explain` prints `acceptance`, where they printed `oracle role` and `oracle`; the review handoff's table is `## Acceptance pairs`. A script that matches those names changes with them.
- **Skills: rerun `phax skills install`.** The bundled skills (phax-planning, phax-decide-plan, phax-decide-review, phax-review) teach acceptance phases, and a copy made by an earlier release teaches the old names. See [After every upgrade](#after-every-upgrade).
- **Schema readers: renamed exports in `@lbdremy/phax-schemas`.** `parseAcceptanceRequest` reads an `acceptance-request` and an `oracle-request` at 0.26.0, and `toLatestAcceptanceRequest` lifts either to the latest acceptance request (`discharged` becomes `passed`, `oraclePhase` becomes `acceptancePhase`, `oracleCommit` becomes `acceptanceCommit`). `OracleRequestSchema`, `parseOracleRequest`, `toLatestOracleRequest` and the types `OracleRequest`, `LatestOracleRequest` and `OracleRequestShape` are gone; `OracleRequestV0_26_0Schema` stays for the frozen shape. An older `@lbdremy/phax-schemas` cannot read 0.33.0 documents.
- **Older records read under the new names.** A plan sidecar, run plan, status file, gate attribution or record manifest written by an earlier release still reads, lifted to the acceptance names when phax or `@lbdremy/phax-schemas` reads it. `phax records explain` lists a record's `.acceptance-request.json`, and `.oracle-request.json` for a record written before.
- **Configuration: the `oracles` key is `acceptance`.** It applies in phax.json, phax.local.json and \~/.phax/config.json. A layer that still carries `oracles` is refused with `<file>: "oracles" is no longer read: write "acceptance"`. Run `phax schema upgrade` to refresh the editor schemas.
- **Command line: `--hold acceptance` and `--skip-acceptance`.** They replace `--hold oracle` and `--skip-oracle`, refused with `--hold 'oracle' is no longer read: write --hold acceptance` and `--skip-oracle is no longer read: write --skip-acceptance`.
- **Hand-written plans: the header lines and the loop.** `**Oracle phase:** yes | discharged by phase-NN` is `**Acceptance phase:** yes | passed by phase-NN`, `**Oracle files:**` is `**Acceptance test files:**`, and a test line's loop `oracle` is `acceptance`. `phax plans lint` reports the old lines on its `acceptance` check (formerly `oracles`) with `**Oracle phase:** is no longer read: write **Acceptance phase:**` and `**Oracle files:** is no longer read: write **Acceptance test files:**`, and `phax run` and `phax resume` refuse them with exit 11, "Acceptance preflight failed". The old loop is refused with `test line <n> uses the loop 'oracle', which is no longer read: write 'acceptance'`, before any extraction.
- **Run `phax artifact migrate-documents` once.** It restamps every live plan sidecar stamped below 0.33.0, rewrites its plan.md test lines from the loop `oracle` to `acceptance` and its header lines to `**Acceptance phase:**` and `**Acceptance test files:**`, and commits both files. Spec sidecars are already at their current stamp, 0.32.0, and stay as they are. It refuses a plan whose body was edited by hand since its sidecar was written, and a plan with a test line whose criterion is `acceptance`, naming the phase and the line: rename that criterion in the spec and the plan, or re-author the plan, then rerun. Until then `approve`, `reopen` and `decide` refuse, naming the command.
- **Runs in flight.** A `phax-plan.json` extracted by 0.32.0 or older is refused by `phax resume` and `phax validate --plan`, which name `phax archive <run> --force`. Finish the run with the release that started it, or archive it and run its plan again. `phax ls` and `phax session-info` still show it.

### 0.32.0 \{#0320}

- **Records and schema readers: `hand-decision-answer` is two formats.** `spec-hand-decision-answer` and `plan-hand-decision-answer` are each the old variant with a `$schema` const. `hand-decision-answer` stays readable at 0.25.0, and phax no longer reads or asks for it.
- **Schema readers: `plan-document` no longer pairs `sourceSpec` and `completesSpec` in its schema.** phax checks the pair.
- **Schema readers: every answer's `$schema` is a const of its 0.32.0 stamp.** That covers spec and plan sidecars, the two hand-decision formats, `first-reading`, `interactive-handoff`, `code-review` and `phax-plan-extraction`. An older `@lbdremy/phax-schemas` cannot read them, and the package reads a const format only at its own stamps.
- **Schema readers: `phax-plan` is restamped 0.32.0**, its decoding unchanged. An older `@lbdremy/phax-schemas` cannot read a 0.32.0 run plan.
- **Schema readers: the `code-review` answer and file are one shape**, `$schema` in place of `version`.
- **Schema readers: new `phax-plan-extraction` format**, the answer of the plan extraction.
- **Headless authoring answers** carry `$schema` in place of `version: 1`.
- **Records and schema readers: `compliance-review.json` is stamped 0.32.0.** `$schema` is a const, and two ledgers are required: `unplannedChanges` (`phaseId`, `path`, `note`) and `unmetPromises` (`phaseId`, `promise`, `note`), each possibly empty. A 0.32.0 file without either is refused.
- **Records and schema readers: older compliance reviews read without ledgers.** A 0.17.0 or pre-schema review still reads, never with empty ledgers; `@lbdremy/phax-schemas` gives them `Unknown` ledgers.
- **Records readers: `compliance-review.md` is rendered by phax from the answer**, with fixed sections (summary, one per phase, unplanned changes, unmet promises, attention points, pointers).
- **`phax review-compliance` no longer prints `Verdict: unknown`.** A review refused twice fails with exit 1 and writes no file and no records entry; the corrective resume's transcript is `compliance-review-retry.session.jsonl`.
- **The compliance session is read-only** and writes nothing under `.phax-context/`.
- **Run `phax artifact migrate-documents` once.** It restamps every live spec and plan sidecar below its current shape, their bodies unchanged. Until then `approve`, `reopen` and `decide` refuse, naming the command.

See [Structured answers and providers](https://docs.phax.run/guide/structured-answers.md) for what each provider now checks.

### 0.31.0 \{#0310}

- **Records readers: `security.json` is version 3.** Every phase's `security.json` carries a required `protectedPaths`, between `skillEditGrants` and `providerSkippedForSecurity`: `null` when `phax.json` declares none, otherwise `{ globs, enforcement }` with `enforcement` one of `enforced`, `partial` or `commit-check-only`. phax reads version 1 and 2 files as version 3.
- **Records readers: `gate-attribution.json` is stamped 0.31.0.** It carries a required `protectedPaths`: `null`, or `{ result, changes }` (a `pass` with no change, or a `fail` with at least one `{ path, kind, entry }`). 0.26.0 documents and older still read, as `null`. An older `@lbdremy/phax-schemas` cannot read 0.31.0 attributions.

### 0.30.0 \{#0300}

- **Exit 14: the provider could not be reached.** A command whose agent cannot reach its provider (a network failure) now exits 14, which is resumable, instead of 5. Treat 14 like 8, but retry as soon as the network is back.
- **Tools that read a phase folder: a failed session's files are archived.** When a session starts in a folder that holds an `agent-error.log`, phax renames it `agent-error.failed-NN.log`, and the transcript the new session would overwrite `<name>.failed-NN.jsonl`. See [Troubleshooting a failed agent session](https://docs.phax.run/guide/agent-session-failures.md#a-failure-after-a-retry).
- **Review UIs: `review-stop.json` and `phax ls --json` are stamped 0.30.0.** A stop's `lastClose` gains the result `network` (`{ result, at, exitCode: 14, detail }`, no `step`) when its closing turn could not reach the provider; the run stays `awaiting_review`. 0.26.0 documents still read.
- **Records readers: a failed decision record names its failure.** `failure` (the session's failure kind, `answer-refused`, `artifact-changed` or `commit-failed`) is required on a failed decision record, stamped `decision-record-manifest` 0.30.0. Records written earlier still read, without it.
- **Mistral Vibe 3.0.3 or later is required.** Run `uv tool upgrade mistral-vibe`. phax checks `vibe --version` before its first Vibe session and refuses an older Vibe, naming the installed version.
- **phax no longer reads sessions from `~/.vibe`.** A Vibe turn's answer, session id, usage and transcript come from its stream and from the `export.json` Vibe writes beside it. `VIBE_HOME` is no longer a provider store root. `phax agent setup mistral-vibe --install-model-aliases` still appends the phax aliases to your Vibe config.
- **Secure mode runs Vibe.** It is recorded as filesystem jail `strong`, with command enforcement `prefix`, so a `secure` run no longer falls back to Claude Code. Your own `config.toml` settings (bypass, `outside_workdir`, tool allowlists) never reach a secure or read-only phax session.
- **Rename a custom `accept-edits` agent.** If `~/.vibe/agents/accept-edits.toml` (or `$VIBE_HOME/agents/accept-edits.toml`) exists, phax refuses every secure and read-only Vibe session (a stop's turns, decide, authoring), naming the file: it would replace the built-in agent phax runs. Rename it, for example to `my-accept-edits.toml`, and pick it with `--agent my-accept-edits` in your own sessions.
- **Vibe config under an extra writable root.** A secure Vibe session is refused when a root in `security.filesystem.allowWrite` holds `.vibe/` or `.agents/`, naming it: Vibe loads hooks, agents and tools from such a root. Move that config out, or drop the root. Phase agents on Vibe can no longer write `.vibe/` or `.agents/` in the worktree either.
- **A turn that ran under another config is refused.** After each secure or read-only turn, phax compares the config Vibe ran with (its `export.json`) with what phax set, and refuses the result on a difference, naming the field in `agent-error.log`.
- **MCP on Vibe.** MCP servers and connectors are off in a session whose `mcp.mode` is `disabled` or `allowlist`: Vibe cannot load only some servers.
- **Records.** Vibe usage is `{ provider, inputTokens, cachedInputTokens, outputTokens, costUsd }`, summed over the phase's turn, fix attempts and handoff. Older records still read; an older `@lbdremy/phax-schemas` cannot read 0.30.0 records.
- **Two Mistral efforts, `off` and `high`.** The Mistral API serves only `none` and `high` reasoning for mistral-medium-3.5, so `phax-mistral-medium-3.5-low`, `-medium` and `-max` are gone from the catalog. `phax plans lint` and `phax run` refuse a plan that names one: rewrite it to `phax-mistral-medium-3.5-off` (effort `off`) or `phax-mistral-medium-3.5-high` (effort `high`). A Claude Sonnet 4.6 phase routed to Vibe runs the `high` alias, recorded as an `upgrade` from `low` or `medium` and a `downgrade` from `max`.
- **`~/.phax/providers.json` that still lists them.** Every command that reads the provider catalog refuses it, naming each id. Run `phax agent setup providers --write` to drop them: it also enables every provider whose CLI is installed, so set `enabled: false` again on any you do not want. Or delete the three entries by hand.
- **The three aliases in your Vibe config.** `phax agent setup mistral-vibe` installs only the `off` and `high` aliases, and names the old ones it finds in `~/.vibe/config.toml` without touching them. Delete their `[[models]]` blocks if you like; nothing uses them.

### 0.28.0 \{#0280}

- **A spec or plan option named `other`.** `other` is now a reserved id, implicit on every question. A live spec or plan that lists an option, or a recommendation, with the id `other` is refused: an authored answer exits 5, `phax artifact approve` and `decide` on the artifact exit 12, and `phax artifact status` reports a §9 that does not parse. Rename that option (in the sidecar and in the Markdown alike) before upgrading. A `Decision: other — …` line, and a verdict that chooses `other`, are accepted.
- **Plan sidecars: `testStrategy` becomes a list of test lines.** Run `phax artifact migrate-documents` once in each repository with plans under `docs/`. A sidecar whose every test strategy follows the line format is lifted and committed. One holding prose is refused, naming the phase and the line: rewrite it in the line format in the sidecar and in plan.md alike, or re-author the plan, then run the command again. Until then, `phax artifact approve`, `reopen` and `decide` refuse and name the command. Archived plans are not touched and still read.
- **Hand-written plans.** Write each `### Test strategy` entry in the line format: `<guarantee> — <size> · <technique> · <oracle> · <loop>[ · <criterion>]`. `phax plans lint` reports a departing line as a `tests` error. `phax run`, `phax plans overlap`, `phax adjust-plan` and `phax plans status` refuse such a plan before any model call, naming the line and `phax plans lint`, and `phax plans overview` exits 1. A phase with no section, or with `- (none)`, has no line.
- **Runs in flight.** A `phax-plan.json` extracted by an older phax is refused by `phax resume` and `phax validate --plan`. Finish the run with the release that started it, or archive it with `phax archive <run> --force` and run its plan again. `phax ls` and `phax session-info` still show it.
- **New `tests` warnings in `phax plans lint`.** Nothing to do. An uncovered acceptance criterion, or a source spec without a readable sidecar, is a warning that never fails lint or refuses a run.

### 0.27.0 \{#0270}

- **Spec sidecars gain a `summary` on every surface element.** Run `phax artifact migrate-documents` once. It restamps every live spec sidecar, writes a placeholder summary on each element, re-renders §6 and commits; the report counts the summaries left to write per spec. Then write each summary, one sentence in the consumer's words, in the sidecar and on its §6 `Summary:` line. `phax artifact approve` and `complete` refuse (exit 12) a spec until its placeholders are gone, and `approve`, `reopen` and `decide` refuse until the migration has run. Archived specs are not touched.
- **Hand-authored specs:** add a `Summary:` line under each §6 element heading if you want the same layout; phax does not check it.
- **A run whose plan lists an oracle file twice.** `phax plans lint` and `phax run` now refuse a path repeated on an `**Oracle files:**` line, and so does `phax resume` (exit 11, "Oracle preflight failed") for a run an older phax started from such a plan. The refusal names the run's copy of the plan, `~/.phax/runs/<namespace>.<name>/plan.md`: remove the repeated path there, check it with `phax plans lint` on that file, and resume. Or archive the run with `phax archive <run> --force` and run the fixed plan again.
- **A phase an older phax left `committed`.** Nothing to do. Before 0.27, `phax resume` went past a phase interrupted between its handoff and its cleanup, or cut short in its handoff turn, and left it `committed` while later phases ran. phax 0.27 finishes such a phase only when it is the run's last started phase; one with later phases on disk stays as it is, without its record or cleanup, and its worktree stays until the run is archived.

### 0.26.0 \{#0260}

- **Oracle phases** need an `oracles` provider: `phax plans lint` and `phax run` refuse an oracle phase without one. Nothing to run; phase records written earlier read with no oracle role.

- **Plan sidecars gain `assignmentReason`, `mode` and `oracle`.** `modelReason` becomes `assignmentReason`, and every phase gains `mode` and `oracle`. Run `phax artifact migrate-documents` once in each repository with plans under `docs/`. It renames the key, writes `mode: "headless"` and `oracle: null` on every phase, rewrites `**Model reason:**` lines as `**Assignment reason:**`, and commits the result. Until then, `phax artifact approve`, `reopen` and `decide` refuse and name the command.

- **Hand-written plans.** Rename each `**Model reason:**` line to `**Assignment reason:**`. `phax plans lint` reports the old label as an error, and `phax run` and `phax resume` refuse it with exit 11.

- **Runs in flight.** A run started by an older phax keeps a `phax-plan.json` in the older shape, which this phax refuses to resume. Finish the run before upgrading, or archive it with `phax archive <run> --force` and run its plan again.

- **`run-status.json` gains a required `hold`.** Scripts that read it find `{ selection: none }` on every run not started with `--hold`, and runs written by older releases read as `none`. Nothing to run.

- **New `models` warnings in `phax plans lint`.** Nothing to do. Lint now compares every phase with the policy default (`claude-sonnet-5-5` at `medium`) where no assignment bound governs it, so a phase at another model or effort with no `**Assignment reason:**` line gets a warning. Warnings never fail lint or refuse a run. Add a reason line to silence one, or declare [assignment bounds](https://docs.phax.run/guide/assignment-bounds.md).

- **`phax ls --json` prints a run list.** The bare array becomes one `run-list` document: read `.runs[]` instead of `.[]`. `currentPhase`, `gateProfile`, `recordsPending` and `archivePath` are null when unknown, where they used to be dropped or printed as `-`, and every run carries `stop`, its review-stop document or null. Nothing to run.

- **The record manifest gains `mode` and `stop`.** Records written by released phax read as headless phases that never stopped. Records written by 0.26.0 development builds before this change no longer read: `phax records explain` on a commit of such a run fails and names the malformed manifest, and `phax records list` skips the record.

- **`security.json` moves to version 2.** It gains a required `stop`. A version-1 file reads as a phase that never stopped; a close rewrites it as version 2. A run started by a 0.26.0 development build with a closed stop or an `interactive-session.json` does not resume.

- **Closing a stop needs a git identity** (`user.name` and `user.email`) in the main checkout; a close without one is refused with exit 1.

None of these four changes needs a command.

### 0.25.0 \{#0250}

- **Spec and plan sidecars move to their current shapes.** Run `phax artifact migrate-documents` once in each repository with specs or plans under `docs/`. It rewrites every live sidecar below its current shape and commits the result. Until then, `phax artifact approve`, `reopen` and `decide` refuse and name the command.
- **Cost ledgers for older runs.** Nothing to do. `phax plans estimate`, the only reader of cost ledgers, writes one for every kept run that has none before it estimates. `phax cost backfill` does the same by hand.

### 0.19.0 \{#0190}

- **Approval ledgers become one record file per artifact.** Run `phax artifact migrate-approvals` once on a clean tree in each repository that still has `docs/plans/approvals.json` or `docs/specs/approvals.json`. It commits the split by itself. Until then, approve, `plans status`, `artifact status` and `phax run` refuse with exit 12 and name the command.
