> 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.

# Structured answers and providers \{#structured-answers-and-providers}

Some of phax's jobs end with an answer in a fixed shape: a headless spec or plan, a decide verdict, a plan extraction, a first reading, a closing handoff, a code review, a compliance review. Each of these is a JSON document with its own published schema. This page is for choosing which provider runs those roles: what each provider checks of an answer, and what phax checks after it.

## One schema, handed three ways \{#one-schema-handed-three-ways}

Every answer has one strict schema (closed objects, every key required), published by `@lbdremy/phax-schemas` and stated in full in the prompt. How the provider receives it depends on the provider and, on Claude Code, on the call.

| Call                | Claude Code     | Codex             | Mistral Vibe     |
| ------------------- | --------------- | ----------------- | ---------------- |
| `authoring`         | `--json-schema` | `--output-schema` | schema in prompt |
| `plan-extraction`   | `--json-schema` | `--output-schema` | schema in prompt |
| `decide-handback`   | `--json-schema` | `--output-schema` | schema in prompt |
| `decide-verdicts`   | `--json-schema` | `--output-schema` | schema in prompt |
| `first-turn`        | `--json-schema` | `--output-schema` | schema in prompt |
| `closing-turn`      | `--json-schema` | `--output-schema` | schema in prompt |
| `code-review`       | `--json-schema` | `--output-schema` | schema in prompt |
| `compliance-review` | `--json-schema` | `--output-schema` | schema in prompt |

- **Claude Code** gets the schema inline on `--json-schema`, without its root `$schema` property: the API refuses a property key outside `^[a-zA-Z0-9_.-]{1,64}$`. phax sets the answer's `$schema` itself and tells the model to leave it out. A call whose cell reads `prompt` instead keeps the schema in the prompt alone: it is the fallback for a Claude Code combination (a `--resume`, the read-only tools, stream-json) that the real-CLI smoke test showed failing.
- **Codex** gets the whole schema in a temporary `answer.schema.json` on `--output-schema`, on `exec` and on `exec resume`, and the file is removed when the process ends.
- **Mistral Vibe** has no such option: it reads the schema in the prompt, like Claude Code on a `prompt` call.

## The compliance review \{#the-compliance-review}

The compliance review is a read-only session: it writes no file, it answers. What it may run depends on the provider:

- **Claude Code**: its shell is limited to the same read-only git prefixes as the headless code review (`--allowedTools "Bash(git log:*)"`, and likewise `git diff`, `git show`, `git status`, `git ls-files`, `git blame`). An allowed command runs unconfined, with your user's rights: there is no OS sandbox.
- **Mistral Vibe**: `bash` is prefix-allowlisted to those git commands. There is no OS sandbox either.
- **Codex**: the session runs in Codex's read-only OS sandbox, with no writes and no network, but any reading command may run: the git list is not applied, as Codex has no per-command allowlist.

See [Read-only review sessions](https://docs.phax.run/security.md#read-only-review-sessions) for what this leaves open.

Its answer carries the verdict, a summary, one entry per phase, the two ledgers (`unplannedChanges` and `unmetPromises`, each possibly empty), the attention points and the pointers.

phax writes `compliance-review.json` (the answer as given) and renders `compliance-review.md` from the answer alone, in the run folder, replacing both together. A failed review writes neither: an earlier review's two files stay as they were, and no records entry is made. A review whose answer is refused twice is a failure, not a review with an unknown verdict.

For example, a three-phase run (`phase-01` to `phase-03`) gets an answer that names `phase-01`, `phase-02` and `phase-04`. The schema lets it through; the coverage check refuses it (`perPhase: phase "phase-04" is not a phase of this run; perPhase: phase "phase-03" is missing`). phax prints the refusal and resumes the session once, the model answers again naming the three phases of the run, and the second answer is the one phax writes.

## phax's decoder is the only judge \{#phaxs-decoder-is-the-only-judge}

A provider's check is a convenience, not the verdict. Whatever the provider enforced, phax reads the answer's JSON and decodes it with the format's schema plus the call's coherence checks, and only then accepts it. The providers differ in what they enforce (Codex's strict mode has no `minLength`, so phax's non-empty strings use a pattern; Claude Code enforces `minItems` and `exclusiveMinimum` client-side), so the same answer can pass on one and need a second look on another. The decoder makes the outcome identical on all three.

Some rules cannot be written in a schema every provider accepts. phax keeps them as coherence checks of the call:

- an authoring session returns open questions only, so a non-empty `arbitratedQuestions` is refused at its first entry;
- a plan document's `sourceSpec` and `completesSpec` must pair up;
- a test line's oracle must not contain `—` inside, start with `— ` or end with ` ·`;
- a compliance answer's `perPhase` names each phase of the run exactly once, in any order.

## The one corrective resume \{#the-one-corrective-resume}

When the decoder refuses an answer, phax resumes the same session once with the refusal and asks again. For example, a headless plan authoring answers with a document whose `arbitratedQuestions` holds an entry. The schema lets that through; the authoring check refuses it, naming the first entry. The resume shows the model the refusal, it answers again with an empty list, and the second answer is the one phax writes. A second refusal fails the call and keeps the transcript.

The plan-extraction fallback (the extraction model reading `plan.md`) has no resume: its refusal is final.

## When Claude Code cannot meet the schema \{#when-claude-code-cannot-meet-the-schema}

Claude Code enforces `--json-schema` itself: the model answers through a tool whose validation refuses a value that breaks the schema, and the model retries. On 2026-10-30 a session whose model could not meet the schema ended `success` with a prose result and no `structured_output`. phax reads that prose as the answer text, refuses it as not JSON, and takes the one corrective resume.

When Claude Code gives up on its own retries, the result subtype is `error_max_structured_output_retries`. phax reports that as a `provider-error` and never resumes it.
