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
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.
- Claude Code gets the schema inline on
--json-schema, without its root$schemaproperty: the API refuses a property key outside^[a-zA-Z0-9_.-]{1,64}$. phax sets the answer's$schemaitself and tells the model to leave it out. A call whose cell readspromptinstead 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.jsonon--output-schema, onexecand onexec 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
promptcall.
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 likewisegit 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:
bashis 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 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
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
arbitratedQuestionsis refused at its first entry; - a plan document's
sourceSpecandcompletesSpecmust pair up; - a test line's oracle must not contain
—inside, start with—or end with·; - a compliance answer's
perPhasenames each phase of the run exactly once, in any order.
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
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.