# Release notes

> What changed in each release.

Source: https://opendecisio.com/docs/releases/

Every change in every decisio release, newest first, and for the releases that change what a deployment serves, who is affected and what to do. After upgrading, certify your [tasks](https://opendecisio.com/docs/glossary/#task) again ([Upgrade](https://opendecisio.com/docs/upgrading/)). To see which release a checkout has:

 

```
uv run python -c "from importlib.metadata import version; print(version('decisio'))"
```

 

Run against the CPU stand-in at v0.35.0 on 2026-10-26. It printed:

```
0.35.0
```

 

[Figure: The last 8 of 47 releases, newest first. Releases: 0.35.0 on 2026-10-26; 0.34.0 on 2026-10-26; 0.33.0 on 2026-10-25; 0.32.0 on 2026-10-25; 0.31.0 on 2026-10-25; 0.30.0 on 2026-10-25; 0.29.0 on 2026-10-25; 0.28.1 on 2026-10-25.]

 

## Upgrade notes

 

What changes for a running deployment from one release to the next, newest first. The changelog ([Release notes](https://opendecisio.com/docs/releases/)) lists every change; this page is for the ones that change what a deployment serves.

 

**After any upgrade, certify certified tasks again.** A certificate covers one task on one [serving setup](https://opendecisio.com/docs/glossary/#serving-setup), and the setup it is checked against includes the decisio version, the engine and its version, the card and the checkpoint ([Teach a task](https://opendecisio.com/docs/tasks/), "[Certified answers](https://opendecisio.com/docs/glossary/#certified-answer)"): after an upgrade the old certificate is suspended (`serving_stack_changed`) until `POST /v1/tasks/{id}/certify` makes it again.

 

### 0.35.0: thinking on chosen question types, a repository's long-state ceiling, and the commit-think flavor

 

#### What changed

 

- **`--think-types choice,score`** and `defaults.think.types` limit thinking to the named [question](https://opendecisio.com/docs/glossary/#question) types ([running.md](https://opendecisio.com/docs/think/)). Without a list every type may think, as before; with one, the list is in `/health` and the serving-stack fingerprint.
- **`defaults.over_limit_tokens`** (`tuned-config --over-limit-tokens N`) lets a repository state the prompt ceiling a truncating server cuts to; `--over-limit-tokens` still beats it ([running.md](https://opendecisio.com/docs/limits/#2-how-long-a-text-and-how-many-options)).
- **A tuned repository can be the `commit-think` flavor** ([running.md](https://opendecisio.com/docs/bases/#5-serve-a-flavor)). An older decisio refuses such a repository (its flavor name and `defaults` keys are unknown).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| Any existing repository or server | Nothing changes | Nothing |
| A server with `--think` and no list | Every type may think, as before | Nothing |
| A `commit-think` repository on a decisio before 0.35.0 | Refused | Upgrade |

 

### 0.34.0: a tuned model can be an MLX repository

 

#### What changed

 

- **`hub tuned-config --mlx-conversion TOOL@VERSION --mlx-bits N --mlx-group-size G`** writes the `decision_config.json` of an MLX conversion of a tuned model, and `--backend mlx --base owner/name` serves such a repository with its defaults ([running.md](https://opendecisio.com/docs/deploy/mac/)). A server for vLLM refuses it with the reason. An older decisio reads the file's weights section as an unknown dtype and refuses the repository.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| Any existing repository or server | Nothing changes | Nothing |
| An MLX tuned repository on a decisio before 0.34.0 | Refused | Upgrade |

 

### 0.33.0: error-compensating rounding for the FP8 merge, and emulated precision casts on FP8 bases

 

#### What changed

 

- **`hub_fp8 merge --gptq CALIBRATION`** rounds the merged weights to FP8 with the rounding error of each column carried into the columns after it, using the layer-input statistics of a calibration file ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)). The stored format, the scales and the load path are unchanged; only some codes move one step. The report and the tuned record say that it was used. Without the flag the output is what it was.
- **`--emulate-precision-casts on|off|auto`** switches torch inductor's `emulate_precision_casts` (`TORCHINDUCTOR_EMULATE_PRECISION_CASTS=1`) before the engine starts, and `auto`, the default, turns it on for FP8 bases: the 12B FP8, the 31B served FP8 (stored or quantized on load) and a tuned repository of either ([running.md](https://opendecisio.com/docs/limits/#3-set-up-for-production)). The compiled kernels then keep each bf16 rounding step, which narrows the difference between two cold starts (it does not guarantee they agree). It is recorded in the start line, `/health` and the serving-stack fingerprint, and refused when the environment says the opposite. **It changes FP8 numerics once**: an answer can differ in the last digits from the same request on 0.32.0 or earlier. bf16 servers are unchanged.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `hub_fp8 merge` without `--gptq` | Nothing changes | Nothing |
| An FP8 server (12B FP8, 31B, a tuned FP8 repository) with no flag | Emulated casts on; the serving-stack fingerprint changes, so every certificate made on 0.32.0 or earlier is suspended (`serving_stack_changed`); answers can differ in the last digits, so recorded FP8 answers will not match bit for bit; the first start compiles from cold (about 30 to 60 s more on the 12B) | Certify again and record FP8 goldens again; `--emulate-precision-casts off` keeps the earlier arithmetic |
| An FP8 checkpoint served by plain vLLM or another server | Not affected by decisio's flag: two cold starts can still differ | Set `TORCHINDUCTOR_EMULATE_PRECISION_CASTS=1` in the environment before Python starts |
| An FP8 server started with `TORCHINDUCTOR_EMULATE_PRECISION_CASTS=0` in the environment | Refused at start with the reason | Unset the variable, or pass `--emulate-precision-casts off` |
| A bf16 server | Nothing changes | Nothing |
| A checkpoint made with `--gptq` (new) | Same format and speed; some codes differ from round to nearest | Check its answers on a card before relying on it |

 

### 0.32.0: projections can stay in bf16 inside an FP8 checkpoint, and batch-invariant kernels from a flag or a repository's defaults

 

#### What changed

 

- **`hub_fp8 convert|merge --keep-bf16 SPEC`** leaves whole layers, a projection in every layer, or single groups in bf16 inside the FP8 checkpoint ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)); the merge rounds those tensors once, stochastically, from the fp32 merge. Without the flag the output is what it was.
- **`--batch-invariant on|off`** switches vLLM's batch-invariant mode (`VLLM_BATCH_INVARIANT=1`) before the engine starts, and a tuned repository can make it its default (`defaults.batch_invariant`, written by `hub tuned-config --batch-invariant`), so `--base <repository>` runs it with no environment variable ([running.md](https://opendecisio.com/docs/limits/#3-set-up-for-production)). It is recorded in the start line, `/health` and the serving-stack fingerprint, and refused when the environment says the opposite. An older decisio refuses a repository that carries the key. Off, nothing changes.
- **`hub_fp8 convert|merge` and `hub_bf16 merge` refuse an `--out` that already holds files** unless `--overwrite` is given, because vLLM's compiled-graph cache is keyed by the checkpoint's path and a checkpoint rewritten in place can crash or run a stale graph ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)).
- **`hub tuned-config --external-form TOOL@VERSION`** records a tuned FP8 checkpoint that another tool made (for example `llm-compressor@0.8.1`, FP8_DYNAMIC) from a hand-written report, instead of refusing it for having no decisio merge report ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)). An older decisio reads such a file as a merge record without decisio's counts and refuses it.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `hub_fp8` without `--keep-bf16` | Nothing changes | Nothing |
| A checkpoint made with `--keep-bf16` (new) | Its config lists the kept modules in `ignore`; vLLM 0.31.0 loaded and served one such 12B checkpoint on one card; check yours | Check on a card before relying on it |
| A server with no flag and a repository without the key | Nothing changes | Nothing |
| `--batch-invariant on`, or a repository that asks for it | Other kernels: about 1.03 times a single read on FP8, 1.6 times on bf16; the answers are not those of the other kernels; certificates made without it are suspended | Measure on your model; certify again |
| A script that writes a conversion or merge into a folder that already has files | Refused with the reason | Use a new folder per checkpoint, or add `--overwrite` and clear vLLM's compile cache |
| A tuned FP8 checkpoint made by another tool (new) | Recorded with `--external-form`; served as a stored FP8 checkpoint | Needs this release or later on the serving side |

 

### 0.31.0: a tuned repository can be a flavor, and can leave the adapter's repository name out

 

#### What changed

 

- **A tuned repository's `decision_config.json` (`/2`) can carry a `flavor` block and read-twice keys in `defaults`** ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)): a repository of a tuned model is then one of the flavors `calibrated`, `commit` or `read-enhanced`, named by its `served_name`. The server it starts is named by the file, reads twice where the defaults say so, and reports the flavor and any flag that differs from it, as a base's flavors do; `hub tuned-config --flavor ... --served-name ... --read-twice-under N` writes the file and `hub check` refuses one that disagrees with its flavor. A repository with no `flavor` block is served exactly as before, and its digest is unchanged.
- **`tuned.adapter.repository` in a tuned repository's `decision_config.json` is optional** ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)): `hub tuned-config` without `--adapter-repository` writes the file without it, and the file still records the adapter's revision, sha256s, rank and alpha. A file that names the repository is read as before. An older decisio refuses a file without it ("tuned.adapter.repository must be a string").

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A tuned repository with no `flavor` block | Nothing changes | Nothing |
| A tuned repository with a `flavor` block (new) | Served under its flavor's name; an older decisio serves it as a tuned model with no flavor, and refuses one with read-twice keys | Use this release or later |
| A tuned repository whose file names the adapter's repository | Nothing changes | Nothing |
| A tuned repository whose file leaves it out (new) | Needs this release or later | Use this release or later |

 

### 0.30.0: the 31B in bf16

 

#### What changed

 

- **`--base gemma-4-31b-bf16`** serves Google's 31B checkpoint as published, in bf16, with no quantization, beside the default 31B (FP8) ([running.md](https://opendecisio.com/docs/bases/#6-the-31b-in-bf16)). Until now the 31B could only be served in FP8 (stored, or quantized on load). Its profile, temperatures and layout are the 31B's; its key, served name and fingerprints are its own, so tasks and certificates made on the FP8 31B are not applied to it. It needs a card with room for 57.91 GiB of weights and a KV cache; the context ceiling is 65,536, not measured at bf16.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `--base gemma-4-31b`, or a 31B checkpoint without `--base` | Nothing changes (FP8) | Nothing |
| `--base gemma-4-31b-bf16` (new) | The bf16 31B, vLLM only | Check `/health` for the context it chose; compare before relying on FP8 records |

 

### 0.29.0: a header that does not outgrow the context

 

#### What changed

 

- **Under the [header layout](https://opendecisio.com/docs/glossary/#prompt-layout) a request whose rows would be over the context is answered, not refused.** The header lists the instructions of every question on every row, so a request with many long instructions could render a row several times longer than the plain layout's and past the context (a 422). The server now lists as many of the questions as fit, in the request's order, and none when no line fits (the plain layout's row). The response says so (`header_limited`, `x-decisio-layout`, `/health`), `usage.input_tokens` counts the header that was read, and a certified task's answer in such a request is not certified (`header_limited`). A request whose rows fit is built as it always was ([running.md](https://opendecisio.com/docs/layout/#7-know-what-happens-when-the-header-does-not-fit)).
- **`--header-limit auto|off|TOKENS`.** `off` restores the 422; a number also caps the header's tokens.
- **The 422 for a row nothing can bring under the context says where its tokens are** (header, state, longest question, template) and what to do.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| The header layout (the Gemma default) and requests whose rows fit | Nothing changes | Nothing |
| The header layout and a request that was refused for its length | It is answered, with a shorter header, and the response says so | Compare against `--layout plain` where the answer matters; or `--header-limit off` for the 422 |
| Certified tasks | An answer in a limited request has `certified: false`, reason `header_limited` | Nothing |
| `--layout plain` or the Qwen base | Nothing changes | Nothing |

 

### 0.28.1: the 12B's calibrated yes/no temperatures per precision, and a warning from the adapter fit tool

 

#### What changed

 

- **The 12B's calibrated yes/no temperature is now fitted per precision, on items that share no text with a public benchmark's test set:** 4.84 on bf16 (was 4.83) and 4.72 on FP8 (was 4.83); score stays at 7.85 and choice at the base's 3.592 ([running.md](https://opendecisio.com/docs/bases/#5-serve-a-flavor)). The change is inside the fit's noise (bootstrap 95% intervals of about 4.1 to 5.4); no answer changes, only the confidences move, by at most about 0.01 of a [probability](https://opendecisio.com/docs/glossary/#probability).
- **`adapter_temperatures fit` and `fit-split` warn when `--layout` is not given.** The file then records no layout, and a server that loads the adapter treats it as plain-trained and holds a header-default base at plain; the warning says so. Give `--layout header` (or `plain`) to record the layout the reads were taken under.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `--flavor calibrated`, `commit` or `read-enhanced` on the 12B | Yes/no confidences move slightly (FP8 by the larger step) | Certify tasks again under the flavor |
| Anything else | Nothing changes | Nothing |
| A script that calls the fit tool without `--layout` | Same output, and a warning on stderr | Add `--layout header` or `--layout plain` |

 

### 0.28.0: a default adapter, the commit flavor, and the 12B's calibrated temperatures

 

#### What changed

 

- **`--default-adapter NAME`** answers a request that names no adapter with a loaded adapter ([running.md](https://opendecisio.com/docs/deploy/adapter/#6-answer-requests-that-name-no-adapter)), so a client that sends no `adapter` field (a benchmark kit, a plain caller) gets the tuned model. `"adapter": "none"` still asks for the base, and a request naming another loaded adapter gets that one. The serving-stack fingerprint holds the default only when one is set. A server started without it is what it was.
- **The `commit` flavor replaces `thinking-commit`.** The flavors are `calibrated`, `commit` and `read-enhanced`; `commit` is the header layout with the commit rule on, the calibrated temperatures and no thinking, served as `Decisio-31b-commit`, `Decisio-12b-commit` or `Decisio-12b-fp8-commit`. Thinking is an ordinary [option](https://opendecisio.com/docs/glossary/#option) (`--think`), not a flavor.
- **On the 12B (both precisions) `calibrated` and `commit` start,** with yes/no at 4.83 and score at 7.85 (choice stays at the base's 3.592), fitted by minimum log loss on readouts under the header layout and judged on calibration ([running.md](https://opendecisio.com/docs/bases/#5-serve-a-flavor)); `read-enhanced` uses the same temperatures. On the 31B `calibrated` and `commit` still refuse to start until its temperatures are fitted.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| Without `--default-adapter` and without `--flavor` | Nothing changes | Nothing |
| `--default-adapter NAME` (new) | Requests with no `adapter` field are answered by that adapter; certificates made under another default are suspended; a plain request matching a registered task is refused (tasks read the base) | Ask a task's questions with `"adapter": "none"`; certify again under the new setup |
| `--flavor thinking-commit` | Refused: it is not a flavor any more | Use `--flavor commit`, and `--think` if you want thinking |
| `--flavor calibrated` or `commit` on the 12B | Served at the calibrated temperatures | Certify tasks again under the flavor |
| `--flavor calibrated` or `commit` on the 31B | Refused until its temperatures are fitted | Nothing yet |

 

### 0.27.0: read twice, think-mode temperatures, per-type profile temperatures and flavors (all opt-in), and a truncation fix

 

#### What changed

 

Read twice:

 

- **`--read-twice-under N`** writes a request that read once is at most N counted input tokens (the header, the state and each question's text) twice, the second copy after `Read the same input again before answering:`; **`--read-twice-max-questions R`** reads a request with more than R questions once, whatever its size ([running.md](https://opendecisio.com/docs/layout/#6-read-short-requests-twice)). Both are **off by default and read twice is never the default**: with them off every prompt, fingerprint and task key is what it was. On the 31B with the header: N = 2,000 is the measured choice on our own proxies (+3.2 pooled, the smallest within 0.25 points of the best) and +1.8 on a public multi-domain sample with p80 latency about 866 ms; a [threshold](https://opendecisio.com/docs/glossary/#threshold) of 3,000 counted on rows gave +1.9 with p80 at or over a 1,000 ms target. It roughly doubles the input tokens of the requests it applies to (so it is off by default), and `usage.input_tokens` counts both copies. On the 12B the same step added nothing within noise.
- Tasks registered, and certificates made, under one setting are not applied under another.

 

Think-mode temperatures:

 

- **A separate temperature for the answers that thought.** A tuned repository's `defaults.think_temperatures`, an adapter file's `think_temperatures`, and `--think-temperature-choice|noul|score` give a temperature per question type for the mix of the plain read and the read after the reasoning ([running.md](https://opendecisio.com/docs/think/)). The single-pass temperatures and bands are unchanged, and so is which questions think: the unsure test still reads the single-pass temperature. A type with no think temperature keeps its single-pass temperature, so a repository or a server that does not set them answers exactly as before.
- **`python -m decisio.serve.adapter_temperatures fit-think`** adds them to an adapter file from labelled mixes of questions that thought. `python -m decisio.hub tuned-config` carries them into a repository's `defaults` and the think fit's method (never its data).
- **The fingerprint** of a server that thinks holds its think temperatures when it has any (`serving.think.temperatures`); a task certified under other ones is `think_mode_changed`. A server that does not think, or has none, keeps the fingerprint it had.

 

Flavors:

 

- **`--flavor calibrated|thinking-commit|read-enhanced`** serves a named configuration of a Gemma base on the header layout, named `Decisio-31b-<flavor>`, `Decisio-12b-<flavor>` or `Decisio-12b-fp8-<flavor>` ([running.md](https://opendecisio.com/docs/bases/#5-serve-a-flavor)). A flag you give still wins and is recorded as an override. `calibrated` refuses to start until its temperatures are fitted; the other two serve with their pending values marked. **A server with no `--flavor` is what it was.**

 

A base's profile can carry per-type temperatures and bands:

 

- **A base's profile can hold a temperature for yes/no and for score, and choice temperatures by band of option count**, beside the global and choice ones ([running.md](https://opendecisio.com/docs/layout/#4-know-what-is-kept-per-layout)). It is the last word of the precedence (a flag, `--temperatures-file`, a tuned repository's defaults, then the profile). **No base sets them in this release**, so every served temperature is what it was; the exported hub profile is unchanged, so repository files exported before this still agree.

 

Fix:

 

- **`--over-limit truncate` works under the header layout.** In 0.26.0 the room the cut left for the state did not count the header's list of the request's questions, so a truncated request whose list was longer than the 32-token margin came out over the ceiling and the engine refused it with a 422 (27 of 60 items at a 85,000-token target on a 65,536 ceiling, 12B). The cut now counts the list: the state keeps exactly what the rows leave, and every such request is answered. Nothing changes on the plain layout, and nothing for a server that refuses over-limit states (the default).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| Without `--read-twice-under` | Nothing changes | Nothing |
| A Gemma base with `--over-limit truncate` (or a request's `over_limit: truncate`) on the header layout | Requests whose question list was longer than 32 tokens got a 422 in 0.26.0 and are answered now, from a state cut by the list's length | Nothing |
| `--read-twice-under N` (new) | Short requests are read twice; tasks and certificates of other settings are not applied | Choose N and R from a run of your own; register and certify again under them |
| Any server that does not use `--think` or a repository with `defaults.think` | Nothing changes | Nothing |
| A thinking server and a repository or file without think temperatures | Nothing changes | Nothing |
| A repository with `defaults.think_temperatures` | Needs this release or later; an older decisio refuses the file (an unknown key of `defaults`) | Run this release or later |
| A server given think temperatures | Thinking answers are served at them; certified tasks made without them are suspended (`think_mode_changed`) | Certify the tasks again |

 

### 0.26.0: the Gemma bases are served with the header layout (answers change)

 

#### What changed

 

- **The Gemma bases (the 31B, the 12B and the 12B in FP8) now put the request's questions before the state** (`Questions that will be asked about the context:`, a `- <question>` line each), then the state and the question as before. **Answers change.** Measured on the 31B against the plain layout: +5.0 points on a Decision Index sample, +4.4 on H776 and +7.4 on hard390 (95% intervals [2.9, 6.9], [2.3, 6.4], [3.1, 11.8]), -0.4 on dev500 (within noise); on the 12B the gain was smaller (+3.8 on the Decision Index sample, +3.6 on dev500, +0.9 on H776, which is within noise) and concentrated in requests with many questions about one state ([running.md](https://opendecisio.com/docs/layout/)). The Qwen base is unchanged.
- **Who is not affected: a server that loads something fitted or trained on the plain layout keeps serving plain, and says so.** Stored tasks (a directory, a shared store or a file) that apply to the server, [abstention](https://opendecisio.com/docs/glossary/#abstain) tasks written before this release, and adapters or tuned repositories with no recorded layout hold the layout at plain; the start prints `LAYOUT plain (held at plain; this base's default is header): ...` and `/health` carries `systemone.layout`. Nothing that exists changes on upgrade.
- **How to keep the old behaviour everywhere: `--layout plain`.** How to move a taught deployment to the header: register its tasks again under `--layout header` (a task is kept under the layout it was fitted under; the records of the old ones are not applied under the new layout) and measure.
- **The cost, for clients that ask a series of questions about one state** (agents, games, teach-it loops): a state cached for one set of questions is not reused for another, so each later question costs like a first question (the 31B, 3,000 tokens: 485 ms against 41; 16,000 tokens: 3,370 ms against 94). They want `--layout plain`. Every response carries `x-decisio-prompt: layout=...; tokens=...; cached=...` to see it.
- **Tuned repositories and adapters can record their layout:** `defaults.layout` in a repository's `decision_config.json` (`hub tuned-config --layout`; an older decisio refuses a repository that has the key), `layout` in an adapter's `decisio_adapter.json` (`adapter_temperatures fit --layout`). A model is served on the layout it records, plain when it records none; the fine-tuned adapters have not been measured under the header.
- `usage.input_tokens` counts the header (3 to 10% more on single-question items).
- Tasks registered, and certificates made, under one layout are not applied under another (the same rule as `--prompt-tail`).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A Gemma base with nothing stored and no adapter (a new deployment, a board run) | Answers change: the header layout | Nothing, or `--layout plain` to keep the old answers |
| A Gemma base with stored tasks, abstention tasks, or an adapter or tuned repository without a recorded layout | Nothing changes: the server holds `plain` and says why | Register again under `--layout header` when you want to move |
| Clients that ask a series of different questions about one state | Later questions cost like first ones under the header | `--layout plain` |
| The Qwen base | Nothing changes | Nothing |
| A tuned repository that records `defaults.layout` | An older decisio refuses it | Run this release or later |

 

### 0.25.0: taught tasks kept across restarts, and shared between replicas

 

#### What changed

 

- **`--tasks-dir PATH`** keeps every taught task (calibration and head tasks, and abstention tasks) in a directory, written through atomically before the response and loaded at start ([tasks.md](https://opendecisio.com/docs/tasks/#6-keep-it-across-restarts)). A write that fails fails the request (500) and nothing is registered. **`--require-tasks N`** refuses to start with fewer than N usable tasks, so a lost store cannot silently become untaught answers.
- **Abstention tasks** can be imported (`POST /v1/abstention/tasks/import`) and removed (`DELETE /v1/abstention/tasks/{id}`), and `GET /v1/abstention/tasks` is their export. An abstention task now applies only on the served name it was fitted for; a task fitted under another served name is kept and not applied.
- `GET /v1/tasks`, `GET /v1/abstention/tasks` and `/health` say where tasks are saved and how many are loaded and apply.
- **`--task-store URL`** shares taught tasks between replicas through object storage (S3 and S3-compatible services, `gs://`, `az://`; `decisio[task-store]`, in the image): a task taught on one replica reaches the others within `--task-store-poll-seconds` (5) as the exact fitted record. A write names the version it replaces and answers 409 if the store holds another (`?replace=1` overrides); `--task-store-readonly` makes a replica follow without teaching; an unreachable store stops teaching (503), not serving ([tasks.md](https://opendecisio.com/docs/operating/#4-keep-tasks-across-restarts-and-replicas)).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| No `--tasks-dir` | Nothing changes, except that an abstention task fitted under another served name is no longer applied | Nothing; re-register such a task under the served name |
| `--tasks-dir` (new) | Tasks survive a restart; one directory belongs to one server | Put it on a volume that outlives the container; add `--require-tasks N` |
| Several replicas | Each is its own server; `--tasks-dir` does not share tasks between them | Give each its own directory, or start them all with `--task-store` |
| `--task-store` (new) | Tasks are shared; a second registration of one task is a 409, and a certificate is suspended on a replica of another stack | Run one stack if you certify; mark all but one replica `--task-store-readonly` if fitting should stay on one |

 

### 0.24.0: the 12B in FP8, batching that batches, thinking that reasons

 

#### What changed

 

- **The Gemma 4 12B in FP8, as a second precision** (`--base gemma-4-12b-fp8`, [running.md](https://opendecisio.com/docs/bases/#4-the-12b-in-fp8)). The bf16 12B stays the 12B's default and the base every 12B record was measured on. FP8 is its own function (on 200 requests the top answer was the same as bf16's on 96%, the largest difference 0.438), with its own served name, task fingerprint and certificates, and it takes the full 65,536-token context on a 32 GB card where the bf16 12B starts only at 4,096. A stored FP8 checkpoint is loaded as stored on either Gemma base; `--base gemma-4-12b` with an FP8 checkpoint is refused, naming the FP8 base.
- **`--concurrency batched` no longer stalls.** In 0.23.0 it was slower than serial on requests about new states, because vLLM's tokenizer pool deep-copied the whole tokenizer for each thread tokenizing at once. The engine's tokenizer is now used one call at a time. Measured on one RTX 5090 with the FP8 12B and new states of about 1,000 tokens: 8.9 and 9.2 requests a second at 8 and 16 clients against 7.2 serial. Other bases, client counts and traffic were not re-measured; measure on your own. Serial servers are unaffected.
- **Thinking now reasons on a default server.** Think mode (0.21.0 to 0.23.x) did not reason on a default server: decisio's hidden-readout class writes the hidden state into reserved logit columns on every row, a read allows only its labels and never saw them, but the greedy writing of reasoning may sample any token, took those columns, and wrote reserved token ids for the whole budget. Every thinking question ran to `--think-budget` tokens and the read after it followed junk; answers that thought in those releases are not what thinking is meant to give. The writing call now runs with those columns switched off. Measured on one RTX 5090 (vLLM 0.31.0, decisio 0.24.0, the 12B in FP8, 20 questions one at a time, 2026-10-21): real reasoning on all 20, 15 stopped on their own at the end of the turn (median 402 tokens), 5 hard items ran to the 512-token budget, no request failed. Ten ids of the readout range appeared in 6,348 written tokens; each is an ordinary word in coherent text (the range holds real vocabulary tokens as well as the hidden-state columns), not a leak ([`runs/2026-10-21_5090-think24/`](https://github.com/aminry/decisio/tree/v0.35.0/runs/2026-10-21_5090-think24)). That shows the mechanism works on one card and base, not how much thinking helps. A question whose reasoning comes back unusable (mostly reserved ids) is answered at its single pass, marked `thinking: unavailable` with the reason in the response, logged, and counted in `/health` under `systemone.think.unavailable`; the request does not fail. Thinking with decisio's hidden-readout class needs the engine in the server's process (`--engine-process in`, the default); with `--engine-process separate`, `--think` is refused at start with the reason (serve a text-only class with a second engine for the head to think there).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `--base gemma-4-12b-fp8` (new) | A second 12B, in FP8; its answers differ slightly from bf16's | Choose it for memory and speed; refit temperatures on FP8 reads before relying on them |
| `--concurrency batched` | Throughput on new states is now above serial where measured; answers are unchanged | Measure on your traffic |
| A server with `--think`, a request's `"think": true`, or a repository whose `defaults.think` is on | Thinking answers change: the reasoning is text now, usually shorter than the budget, so they are also faster | Check your factor against measured answers; certify tasks again if you certify under thinking |
| `--think` with `--engine-process separate` | Refused at start, with the reason | Use the default engine process, or a text-only class with a second engine |
| Everything else | Nothing changes | Nothing |

 

### 0.23.0: batching concurrent requests (opt-in)

 

#### What changed

 

- **`--concurrency batched`** scores the requests waiting for the engine together, in one engine call, up to `--batch-max-requests` (16) ([running.md](https://opendecisio.com/docs/concurrency/)). The default stays `--concurrency serial`, one request at a time, unchanged. On a batched server a certified task's questions and every task registration and certification read still take the serial path; the other answers can move slightly with the traffic they arrived with.
- **New fields:** `concurrency` in `/health` (`{"mode": "serial"}` on a serial server; the calls and requests per call on a batched one), the header `x-decisio-batch` and `timing.batch` on `/v1/answer` when a request shared a call, and `shared_skipped` in a task monitor's facts when the monitor skipped answers scored in a shared call.
- **A new certificate reason, `concurrency_changed`:** on a batched server the mode is part of the serving-stack fingerprint, so a task certified on a serial server is not served as certified by a batched one, and the other way round. The response schema ([`docs/reference/openapi.json`](https://github.com/aminry/decisio/blob/v0.35.0/docs/reference/openapi.json)) now lists every `certified_reason` the server gives; `state_truncated` and `think_mode_changed` were missing from it.
- **The engine's lock is first come, first served** on the vLLM engine and the [CPU stand-in](https://opendecisio.com/docs/glossary/#cpu-stand-in), on every server: under load a request waits for the requests that arrived before it, in order.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A serial server (the default) | Nothing changes: the same answers and serving-stack fingerprint; `/health` has one more field | Nothing |
| `--concurrency batched` | Uncertified answers can depend on the traffic; certified tasks made on a serial server are suspended (`concurrency_changed`) | Certify the tasks again on the batched server, or keep serial |
| A client that reads `certified_reason` | A new value, `concurrency_changed`, only from a batched server | Treat it as the other suspension reasons |

 

### 0.22.0: a tuned 12B repository

 

#### What changed

 

- **The Gemma 4 12B can be shipped as a tuned repository** (`decisio.decision_config/2`), stored in bf16 as the 12B is served ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)). `python -m decisio.hub_bf16 merge` adds the adapter in fp32 and rounds once to bf16, stochastically and seeded (the seed is in the report and the repository's file); every other tensor is the base's, byte for byte. `python -m decisio.hub tuned-config --base gemma-4-12b` writes the file. A tuned 12B is served at its repository's defaults exactly as a tuned 31B is: temperatures and choice bands, the commit setting, `over_limit`, and `think`.
- **A tuned repository's file no longer says anything about the data.** `tuned-config` keeps the method of the temperature fit and drops the fit tools' notes on data, counts and details; `defaults.fit.data` is no longer required (files that have it still load).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| The bases and `/1` repositories | Nothing changes | Nothing |
| A tuned 31B repository | Nothing changes; a new `tuned-config` writes a `fit` without `data` | Nothing |
| A tuned 12B repository | Needs 0.22.0 or later; an older decisio refuses it by its schema | Run 0.22.0 or later |

 

### 0.21.0: think when unsure

 

#### What changed

 

- **A server can read an unsure question again after the served model has written its own reasoning** ([running.md](https://opendecisio.com/docs/think/)). It is off unless asked: `--think`, a request's `"think": true`, or a tuned repository's `defaults.think` (its factor and budget). A question below its type's threshold times `--think-factor` (choice 0.75, yes/no 0.92, score 0.70) with at most 16 options is sent again with a fixed prefix, the same model writes up to `--think-budget` (512) tokens, a fixed suffix follows and the options are read again; the two reads are mixed in log space and go through the temperature a single read gets.
- **New fields when it is on:** `thinking` in the response, `usage.thinking_tokens` (and `usage.output_tokens` counts the reasoning tokens), `x-decisio-think-ms` and `x-decisio-think-tokens`, and `systemone.think` in `/health`.
- **A new certificate reason, `think_mode_changed`:** the server's think setting is part of the serving-stack fingerprint when it is on, so a task certified one-pass is not served as certified by a server that thinks, and the other way round. Questions of a registered task are always answered at one read.
- **One more kind of vLLM call:** thinking is the only call that generates text; its length is a server setting, never a request field ([SECURITY.md](https://opendecisio.com/docs/operating/#5-secure-it)).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A server without `--think` and a request without `think` | Nothing changes | Nothing |
| A tuned repository whose `defaults` carry `think` | Needs 0.21.0 or later; 0.20.0 refuses the file | Run 0.21.0 or later |
| Certified tasks on a server you start with `--think` | Suspended (`think_mode_changed`) | Run without `--think`, or certify the task again |
| The MLX build | `--think` is refused at start | Nothing |

 

### 0.20.0: tuned repositories, and states over the prompt limit

 

#### What changed

 

- **A tuned model has its own repository format, `decisio.decision_config/2`.** It keeps the base's profile exactly and adds `tuned` (the base, the adapter with its digests, the merge report, every weight file by sha256) and `defaults`: the temperatures per question type, the choice bands, the commit setting, and optionally `over_limit`. A plain request to such a repository gets those defaults with no flag and no adapter field; a flag beats the repository, and `--temperature-choice` drops its bands. An older decisio reads `/1` only and refuses a `/2` repository by its schema, instead of serving the model at the base's temperatures. `python -m decisio.hub_fp8 merge` writes the merged FP8 weights (an adapter added in fp32 and quantized once), and `python -m decisio.hub tuned-config` and `check` write and verify the file ([running.md](https://github.com/aminry/decisio/blob/v0.35.0/docs/running.md#shipping-a-tuned-model)).
- **A state over the engine's prompt ceiling can be answered from its start.** `--over-limit truncate`, a request's `over_limit` field, or a tuned repository's `defaults.over_limit` cuts the state (never a question) once per request; the response carries `state_truncated` and the header `x-decisio-state-truncated`, and no answer of a truncated state is certified (`certified_reason: "state_truncated"`). The default is `refuse`, as before ([running.md](https://opendecisio.com/docs/limits/#2-how-long-a-text-and-how-many-options)).
- **New flags:** `--over-limit`, `--over-limit-tokens`, `--noul-commit` / `--no-noul-commit` (the pair; a tuned repository states its own commit setting).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| The default base or a `/1` repository, no new flag | Nothing changes: the same temperatures, the same refusals | Nothing |
| A script that reads `/health` | It has new fields (`systemone.over_limit`, and for a tuned repository `systemone.repository_defaults` and `choice_bands`) | Nothing, unless it rejects unknown fields |
| A tuned (`/2`) repository | Served at its defaults; an older decisio refuses it | Run 0.20.0 or later |
| Certified tasks | Suspended by the new version as after any upgrade | Certify them again |

 

### 0.19.0: decisio's name only on the wire, in new records and in the served name

 

#### What changed

 

- **Only `x-decisio-debug` and `x-decisio-route` are read as request headers.** The earlier spellings of those two headers, with the project's former name in place of `decisio`, are ignored, not refused: a request that still carries one is served as if it did not, so a client that sent the debug header gets no `decisio_debug` field and one that sent the route header gets the route the server picks. Responses never carried the earlier spellings.
- **New task records and the Qwen base's served name use decisio's name.** A task is written as `decisio-task/1`, with `decisio-task-prior/1`, `decisio-intent-head/1`, `decisio-certificate/1` and `decisio-abstention/1` inside it, and the Qwen base's served name is `decisio-qwen3.6-35b-a3b-letters`; the server writes no record, header or served name that carries the former name.
- **Records and requests that use the earlier names are still accepted.** A task record in the earlier format ids loads and applies as written (it is not rewritten), a task fitted under the Qwen base's earlier default served name applies on a server with the current one, and a request's `model` is not checked against the served name, so the earlier served name is accepted and echoed like any other.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A client or script that sends the earlier debug or route header | The header is ignored: no debug readout, no route choice | Send `x-decisio-debug` / `x-decisio-route` |
| Stored tasks in the earlier record formats | They load and apply as before | Nothing |
| A client that names the Qwen base's earlier served name in `model` | It is accepted; the response echoes it | Nothing; use the name `GET /v1/models` lists |
| Anything else | Nothing changes | Nothing |

 

### 0.18.0: adapter temperatures, more than two images, shares that fit one card

 

#### What changed

 

- **An adapter can carry its own temperatures.** A LoRA adapter changes the scale of the readout, and the base's temperatures were fitted on the base. `decisio_adapter.json` beside the adapter's weights, or `--adapter-temperatures name=file`, gives the adapter a temperature per question type (`choice`, `noul`, `score`) and, for choice, optionally one per band of option count (`choice_bands`: 2-10, 11-26, 27-255 or your own). A request that names the adapter is served at them. A request that does not, and an adapter with no file, are served at the base's temperatures as before. `python -m decisio.serve.adapter_temperatures fit` and `fit-split` write the file ([running.md](https://opendecisio.com/docs/deploy/adapter/#2-give-it-its-own-temperatures)).
- **An adapter's identity and temperatures are in the serving-stack fingerprint.** `/health` shows them in `adapter_identity` and `adapter_temperatures`, so a different adapter, a retrained one or a changed temperature is a different stack. A server with no adapter has none of these keys.
- **The image count of a request is limited by the context, not by a cap of 2.** `--max-images` (default 128) is the most images one request may carry on a server started with `--image-model`. Lowering it saves multimodal profiling memory at start-up.
- **`--image-model` starts with shares that fit one card.** The text engine defaults to 0.47 of the card and the image engine to 0.49, the pair that was measured. Before, the defaults (0.90 and 0.50) could not fit together and the start failed in the image engine after the text engine had loaded. A pair with a default in it that totals over 0.96, or any pair over 1.0, is now refused at start, before either engine is built. A pair you give in full is yours up to 1.0. The start prints `ENGINE SHARES`.
- **A Marlin W4A16 checkpoint works with the default in-process engine.** The 0.16.0 limit that it needed `--engine-process separate` is gone: every thread that calls the engine now runs on the stream the engine was warmed up on.
- **The OpenAPI file and the flags table are generated from the code.** [`docs/reference/openapi.json`](https://github.com/aminry/decisio/blob/v0.35.0/docs/reference/openapi.json) and [Server flags](https://opendecisio.com/docs/flags/#every-flag) are rewritten at release time, and a test fails if they disagree with the code.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `--adapter` without a temperatures file | Nothing changes: the base's temperatures | Fit and ship a file if the adapter's answers are over- or under-confident |
| `--image-model` with no memory flag | It starts now; before, the image engine failed to load | Nothing |
| `--image-model` with only one of the two memory flags | The other takes the measured pair's value (0.47 text, 0.49 image), where before it took 0.90 or 0.50 | Give both, or check that the pair totals 0.96 or less |
| A client that sends more than 2 images in a request | Accepted up to `--max-images` and the context | Nothing |
| A Marlin W4A16 checkpoint with `--engine-process separate` | Still works | You may drop the flag |

 

### 0.17.0: System One's `assets` are read, and too many options is a 422

 

#### What changed

 

- **A choice with more than 255 options now returns 422 instead of 400.** It is System One's rejection, a `too_long` validation error at `body.questions.<name>.choice.criteria` (`Dictionary should have at most 255 items after validation, not 256`); before, the engine refused it with a 400 and a plain `letter readout supports 1 to 255 options` message. The limit itself, 255, is unchanged.
- **System One's `assets` are now read.** A request that carried images as `assets` (placeholder names mapped to images, the placeholders written in the state or a question's text) was accepted before and answered **without its image**: the placeholder stayed text and nothing said so. Now every placeholder becomes the marker `[image N]` and its image goes to the image engine, in JSON (`data_base64`) and in multipart (`part`); a problem with an asset is a 400 in System One's `api_usage_error` shape. Details and the remaining differences from System One: [HTTP API](https://opendecisio.com/docs/api/#where-this-route-differs-from-system-one).
- **A conformance gate, C5,** compares the request and response fields with System One's published OpenAPI document (`python -m decisio.serve.systemone_conformance c5 --snapshot benchmarks/system_one_wire_0.2.0.json`).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A client that checks for status 400 on a choice with more than 255 options | It now gets 422 | Accept 422 (System One's own code) |
| A client that sent `assets` to decisio | Its answers before 0.17.0 were made without the images | Send them again on 0.17.0, on a server started with `--image-model` (the Qwen base); a text-only server answers with "no image engine" |
| A client that sends no `assets` and stays under 255 options | Nothing changes | Nothing |

 

### 0.16.0: certified tasks, drift protection and the unknown-model refusal

 

#### What changed

 

- **A certified task is watched for [drift](https://opendecisio.com/docs/glossary/#drift) by its confidences, and stops certifying while the alert is on.** The traffic monitor gained a second signal beside the answer mix: a CUSUM on how often an answer's [top probability](https://opendecisio.com/docs/glossary/#top-probability) falls below the certification examples' 5th percentile. When it alerts, every answer of the task is `certified: false` with the reason `traffic_changed` until it clears (`"suspend_on_alert": false` in the certify block only reports). Measured on the stored FP8 31B on one 96 GB card, replaying 5,363 CLINC150 messages (a task with 10 examples per intent and no out-of-scope option, out-of-scope messages rising from message 1,000 to 30%): under 0.14.0 the answer mix first alerted after message 5,200, with 1,027 wrong certified answers served before it and certification left on; under 0.16.0 the first answer was withheld at message 1,570, with 40 wrong certified answers served between message 1,000 and it, and certification stopped. Registration also warns (`certificate.warnings`) when a choice task has no catch-all option. Details: [Watch for drift](https://opendecisio.com/docs/tasks/drift/).
- **A task certified before 0.16.0 has no confidence statistic, so it gets no drift protection until it is certified again.** Its monitor baseline was stored without one, and the statistic is built when the certificate is made. It keeps working as before (the answer mix alone, which reports and never suspends). **To get the protection:** certify it again on the running 0.16.0 server with `POST /v1/tasks/{id}/certify`, body `{"examples": [{"request": ..., "answer": ...}, ...]}` (labelled like the task's own; optionally `alpha`, `delta`, `min_examples`, `suspend_on_alert`), on examples that share no input with the task's fit, such as the held-out examples it was certified on. The fit is kept; the call returns the new certificate, and `suspend_on_alert` there is true unless you name it false, whatever the old certificate said. Check it took: `GET /v1/tasks/{id}` shows `status.monitor.confidence` (the statistic and its limit) and the certificate's `warnings`. Importing a task exported by an older version keeps its old certificate until you do this.
- **A certified task's questions are scored by the function that certified them.** A question a certified task matches is scored alone, in request order, by the function the certificate was made with, not batched with the request's other questions, and a task with an [intent head](https://opendecisio.com/docs/glossary/#intent-head) is certified on the hidden-state read alone (registration used to read each example plain first, which on vLLM moved the confidences slightly: one measurement certified 153 answers where serving put 154 above the threshold). Registration records `served_check` (a sample of the held-out examples re-read through the serving path). A certificate made before 0.16.0 keeps its gap until it is made again.
- **A `--model` of a type no base serves is refused** instead of being given the Qwen profile; `--base <key>` with the same `--model` is the explicit way to serve it (the CPU stand-in `--backend hf` is unchanged).
- **The MLX backend downloads `--model` at `--revision`**, so `--model tachara-ai/decisio-gemma-4-31b-mlx-4bit --revision v0.15.0` pins the build.
- **Known limit: a Marlin W4A16 checkpoint (compressed-tensors, such as `google/gemma-4-31B-it-qat-w4a16-ct`) needs `--engine-process separate` on 0.16.0**, because the default in-process engine refuses its first request with a `marlin_locks` error; the in-process fix (#215) shipped in 0.18.0, and `--engine-process separate` is no longer needed.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A certified task made before 0.16.0 | Works as before; no confidence alert, so no suspension on drift | Certify it again on 0.16.0 (above) |
| A certified task made on 0.16.0 | The confidence alert suspends certification while it is on | Nothing; `"suspend_on_alert": false` to only report |
| A certified task with an intent head | Its next certificate is made on the hidden-state read alone | Nothing |
| `--model` of a checkpoint no base serves, on vLLM or MLX | Refused at start with the supported bases named | Add `--base <key>` if it is that base's profile |
| An MLX server with `--revision` | The conversion is downloaded at that revision (before, it was the repository's main) | Nothing |
| A certified task, exported and imported | The export carries the new baseline and `suspend_on_alert` | Nothing |

 

### 0.14.0: the default context follows the card

 

#### What changed

 

- A vLLM server started without `--max-model-len` on a Gemma base now serves **the largest context that fits the card**, up to the base's ceiling (31B 98,304; 12B 65,536) and not below 32,768, instead of a fixed 32,768. The Qwen base stays at 32,768. The server asks vLLM for the longest single request the card's KV cache holds and serves the largest multiple of 8,192 of that; `/health` reports the length (`max_model_len`) and why (`max_model_len_source`, for example "auto: fits 81,920 of 98,304 on this card").
- Why: a fixed longer context can fail to start on a smaller card, and a failed start loses the whole run, while a fixed 32,768 leaves out the prompts of 50,000 to 98,000 tokens that a bigger card holds. The default now never fails where 32,768 would have started, and never goes above the ceiling. How long that is depends on the card and on `--gpu-memory-utilization`, strongly and not linearly: on an H100 PCIe 80 GB the 31B served 98,304 at the default share, 49,152 at 0.85, and did not start at 0.70 (`running.md` has the table); a card where 32,768 does not fit fails to start as it did before.
- Details: [Requirements and limits](https://opendecisio.com/docs/limits/#2-how-long-a-text-and-how-many-options).

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| The 31B or the 12B on vLLM with no `--max-model-len` | A prompt between 32,768 tokens and the chosen length is answered instead of refused with 422; it takes longer the longer it is (the first question about a new 32,000-token state took 8.9 s on the 31B at 600 W) | Nothing, or give `--max-model-len 32768` to keep the old limit |
| The same with `--max-model-len N`, or a `max_model_len` in `--engine` | The same length as before | Nothing |
| The Qwen base, `--image-model`, `--head-engine`, `--engine-process separate`, `--backend mlx` or `hf` | 32,768, as before | Nothing |
| Certified answers | The context length served is part of the serving-stack fingerprint: a certificate made at one length is suspended at another, so one made on 0.13.0 (32,768) is suspended on 0.14.0 on the 31B or the 12B, as it already is by the new version, and a card that fits a different length suspends one made on another card | Register and certify again on the server that will serve; give `--max-model-len` to pin the length across cards |
| A client that relied on 422 for prompts over 32,768 tokens | Those prompts are answered where the card allows it | Check the length on your side, or pin `--max-model-len 32768` |

 

### 0.12.0: a POST that is not JSON is refused, and vLLM is 0.31.0

 

#### What changed

 

- **Every POST must be JSON.** A POST whose `Content-Type` is not `application/json` (parameters such as `; charset=utf-8` are fine), or has none, is refused with 415 and `accept-post: application/json` before its body is read, on every route. Before 0.12.0 such a POST was carried out: a web page could make the server do the work (and register or replace tasks) without being able to read the answer. The one other form is `multipart/form-data` on `/v1/systemone` (the image clients), which is accepted when the request carries an `X-Decisio-Client` header with any value. [Operate](https://opendecisio.com/docs/operating/#5-secure-it), "Cross-site requests from a web page", says why.
- **A non-finite probability is a 500, not a 400.** If an engine returns NaN or infinity, the model's arithmetic on that device is at fault, not the request: the route answers 500 with a `detail` that names the model and the engine.
- **vLLM is 0.31.0** (the lock file and the image pin it). Rebuild the environment with `uv sync` or pull the new image.
- **New and optional:** certified answers and a traffic monitor in `/v1/tasks` ([Teach a task](https://opendecisio.com/docs/tasks/)), an abstention task that matches by option keys, and `/health`'s `state_boundary` reporting `running` while a registration is being sent.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A client that posts form-encoded or `text/plain` bodies, or sends no `Content-Type` | It gets 415 | Send JSON with `Content-Type: application/json` |
| A client that sends `multipart/form-data` to `/v1/systemone` | It gets 415 without the header | Add `X-Decisio-Client: <any value>` |
| A client that treats every 4xx as the request's fault | A model whose arithmetic fails now answers 500 | Treat a 500 with "not finite" as a server fault ([HTTP API](https://opendecisio.com/docs/api/)) |
| An environment built for 0.11.x | It has vLLM 0.30.0 | Rebuild it; measurements made on 0.30.0 are labelled as such in [Evaluation card](https://opendecisio.com/docs/evaluation-card/) |

 

### 0.11.0: the model repositories moved to tachara-ai

 

#### What changed

 

- The three model repositories are now under the Hugging Face organisation `tachara-ai` (Tachara AI Lab), with the same names: `tachara-ai/decisio-gemma-4-31b`, `tachara-ai/decisio-gemma-4-12b` and `tachara-ai/decisio-qwen3.6-35b-a3b`. The code, the weights and the settings are the same as in 0.10.0.
- Each repository has a new revision (a new card, and a `decision_config.json` that names 0.11.0), tagged `v0.11.0`; 0.11.0 pins these. The 0.10.0 revisions are still in each repository, tagged `v0.10.0`, and 0.10.0 still serves them.

 

#### The old names keep working

 

- Hugging Face redirects [`aminry/decisio-gemma-4-31b`](https://huggingface.co/aminry/decisio-gemma-4-31b), [`aminry/decisio-gemma-4-12b`](https://huggingface.co/aminry/decisio-gemma-4-12b) and [`aminry/decisio-qwen3.6-35b-a3b`](https://huggingface.co/aminry/decisio-qwen3.6-35b-a3b) to the new names. A fresh install of 0.10.0 opens all three through the old names at its pinned revisions, with no token (checked on 2026-10-08, after the move).
- 0.11.0 treats an old name as the new one: `--base aminry/decisio-gemma-4-31b` serves `tachara-ai/decisio-gemma-4-31b` at 0.11.0's pin, and `/health` reports the new name. `--base aminry/decisio-gemma-4-31b@<commit>` pins that commit as before.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| A command, script or image that names `aminry/decisio-...` | It works through the redirect, on 0.10.0 and on 0.11.0 | Change the name when convenient |
| A deployment with the models already in its Hugging Face cache | It downloads the new revision of a repository it serves by default (about 31 GB for the 31B) | Pin `@<commit>` to keep the 0.10.0 revision |
| `--base gemma-4-12b` or `--base qwen3.6-35b-a3b` | Nothing: those serve the source checkpoints, as before | Nothing |

 

### 0.10.0: the default base is Gemma 4 31B

 

#### What changed

 

- A vLLM server started with neither `--base` nor `--model` serves `gemma-4-31b`. Before, it refused to start.
- The container, `docker compose` and the README's Docker path served the Qwen base before, because the image set `DECISIO_MODEL=Qwen/Qwen3.6-35B-A3B-FP8`. The image no longer sets it: the entrypoint starts the server with `--base "${DECISIO_BASE:-gemma-4-31b}"`.
- Gemma 4 31B is served from its FP8 repository, [`aminry/decisio-gemma-4-31b`](https://huggingface.co/aminry/decisio-gemma-4-31b) (moved to `tachara-ai/decisio-gemma-4-31b` in 0.11.0, above), so the first start downloads about 31 GB and the server does not quantize Google's weights at every start. `--model google/gemma-4-31B-it` serves Google's weights at the pinned revision instead, quantized to FP8 each time they load, as before.
- Nothing else changed for the Qwen and Gemma 4 12B bases: their checkpoints, settings, served names and answers are the same as in 0.9.0. They are still chosen with `--base qwen3.6-35b-a3b` and `--base gemma-4-12b`.

 

#### Who is affected

  

| You run | What happens | What to do |
| --- | --- | --- |
| `docker compose up`, or the image, with no `DECISIO_MODEL` set | The container now serves Gemma 4 31B, which is measured on a 96 GB card (an 80 GB H100 also runs it, [Get started](https://opendecisio.com/docs/#1-pick-your-machine)) | To keep the Qwen base, set `DECISIO_BASE=qwen3.6-35b-a3b` (in [`compose.yaml`](https://github.com/aminry/decisio/blob/v0.35.0/compose.yaml)'s `environment`, or `-e` on `docker run`) |
| The image with `DECISIO_MODEL` set | Nothing: a checkpoint alone names its own base, so the container serves what it served | Nothing |
| A derived image that relied on `ENV DECISIO_MODEL` coming from this image | It no longer comes from it, so the default base is served | Set `DECISIO_MODEL` or `DECISIO_BASE` in your image |
| `uv run python -m decisio.serve.vllm_engine --base ...` or `--model ...` | Nothing | Nothing |
| A Mac with `--backend mlx`, or Ollama | Nothing: they always named a base or a conversion (`--backend mlx` needs `--model`), so the default base changes nothing for them | Nothing |
| A client that reads the served name from `GET /v1/models`, or matches `decisio-qwen3.6-35b-a3b-letters` | A server on the default base lists `decisio-gemma-4-31b-it-letters` | Read the name from `/v1/models`, or start the server with `--served-name` |
| Registered tasks (`POST /v1/tasks`, or a `--tasks-file`) fitted on the Qwen base | A server on another base does not apply them: it prints `WARNING: tasks fitted under another model or rendering are not applied` at start, and `POST /v1/tasks/import` refuses them | Register them again from their examples ([Teach a task](https://opendecisio.com/docs/tasks/)), or keep serving the Qwen base |

 

#### What differs in what you get from the default base

 

- The wire format and the routes are the same.
- The probabilities are another model's, calibrated with its own temperatures and prompt ([Server flags](https://opendecisio.com/docs/flags/)), so some choices differ from the Qwen base's. [Evaluation card](https://opendecisio.com/docs/evaluation-card/) section 7 has the 31B's measurements and section 3 the Qwen base's; the README's "Choosing a base" says when each is the better choice.
- It reads a new state more slowly: 1.6, 3.2 and 5.1 times the Qwen base's time at 300, 1,000 and 3,000 tokens (80.7, 160.1 and 438.1 ms against 49.9, 50.0 and 85.2, one card at 585 W; [`runs/2026-10-06_latency-585w/`](https://github.com/aminry/decisio/tree/v0.35.0/runs/2026-10-06_latency-585w)).
- It is measured on a 96 GB card on vLLM (an 80 GB H100 also runs it, [Get started](https://opendecisio.com/docs/#1-pick-your-machine)); on a Mac it runs from its 6-bit MLX conversion for states up to 16,383 tokens ([Get started](https://opendecisio.com/docs/#1-pick-your-machine)).
- Its probabilities repeat exactly within a session, not across sessions: near-tied answers moved between two sessions in our measurements ([Evaluation card](https://opendecisio.com/docs/evaluation-card/) 7.5).

 

#### Keeping the previous behaviour

 

```
# the Qwen base, as the container served it
uv run python -m decisio.serve.vllm_engine --base qwen3.6-35b-a3b
# the same in the container
DECISIO_BASE=qwen3.6-35b-a3b docker compose up
```

 

Needs an NVIDIA card, and for the container the NVIDIA Container Toolkit: taken as it is from decisio's checked docs. Its comments are shown above their commands.

 

## The changelog

 

## [0.35.0](https://github.com/aminry/decisio/compare/v0.34.0...v0.35.0) (2026-10-26)

 

### Features

 

- **serve:** think on chosen question types, a repository's long-state ceiling, the commit-think flavor ([#353](https://github.com/aminry/decisio/issues/353)) ([4d3c81a](https://github.com/aminry/decisio/commit/4d3c81af2b0ab986f93cd9d0b90c1830a087a5d4))

 

## [0.34.0](https://github.com/aminry/decisio/compare/v0.33.0...v0.34.0) (2026-10-26)

 

### Features

 

- **hub:** a tuned model can be an MLX repository ([#352](https://github.com/aminry/decisio/issues/352)) ([f1a0c24](https://github.com/aminry/decisio/commit/f1a0c2420733f34e44941a29afa3f394f238daf8))

 

## [0.33.0](https://github.com/aminry/decisio/compare/v0.32.0...v0.33.0) (2026-10-25)

 

### Features

 

- **hub-fp8:** --gptq, error-compensating FP8 rounding in the merge ([#346](https://github.com/aminry/decisio/issues/346)) ([e24efb8](https://github.com/aminry/decisio/commit/e24efb875a204e48d29db00a24e4a8406f2d6360))
- **serve:** --emulate-precision-casts, on by default for FP8 bases ([#349](https://github.com/aminry/decisio/issues/349)) ([abe00c6](https://github.com/aminry/decisio/commit/abe00c6ffab42f10038349aef3480b4a355a033a))

 

## [0.32.0](https://github.com/aminry/decisio/compare/v0.31.0...v0.32.0) (2026-10-25)

 

### Features

 

- **hub:** hub_fp8 convert and merge can leave projections in bf16 (--keep-bf16) ([#340](https://github.com/aminry/decisio/issues/340)) ([5baf1ae](https://github.com/aminry/decisio/commit/5baf1aec7d6140e9ff774c904d6d6afe9ff2361e))
- **hub:** hub_fp8 convert|merge and hub_bf16 merge write into a folder of their own ([#343](https://github.com/aminry/decisio/issues/343)) ([23ef954](https://github.com/aminry/decisio/commit/23ef954429473b890927a2fda9b86c26fbfefb67))
- **hub:** tuned-config --external-form records a tuned FP8 checkpoint made by another tool ([#344](https://github.com/aminry/decisio/issues/344)) ([f3551dc](https://github.com/aminry/decisio/commit/f3551dc7337b98fbafbd6369b8d3ecddc6d00ba4))
- **serve:** --batch-invariant, and a tuned repository can make vLLM's batch-invariant kernels its default ([#341](https://github.com/aminry/decisio/issues/341)) ([cba2245](https://github.com/aminry/decisio/commit/cba2245cefbe9e11fec2e8a9a8dfaaa42a2ff576))

 

## [0.31.0](https://github.com/aminry/decisio/compare/v0.30.0...v0.31.0) (2026-10-25)

 

### Features

 

- **hub:** a tuned repository can be a flavor of the model ([#335](https://github.com/aminry/decisio/issues/335)) ([19fb70a](https://github.com/aminry/decisio/commit/19fb70a08f05459ad8db2a8a01a4c7dfab4ecc7f))
- **hub:** the adapter's repository name is optional in a tuned repository's file ([#337](https://github.com/aminry/decisio/issues/337)) ([fc810a3](https://github.com/aminry/decisio/commit/fc810a3f45866665017c1e29df3da5e1a4fea4f2))

 

## [0.30.0](https://github.com/aminry/decisio/compare/v0.29.0...v0.30.0) (2026-10-25)

 

### Features

 

- **serve:** --base gemma-4-31b-bf16, the 31B served in bf16 without quantization ([#333](https://github.com/aminry/decisio/issues/333)) ([0b0326b](https://github.com/aminry/decisio/commit/0b0326be7e6a17a36d7899e51e7103259855d7f6))

 

## [0.29.0](https://github.com/aminry/decisio/compare/v0.28.1...v0.29.0) (2026-10-25)

 

### Features

 

- **serve:** a header that lists as many of the questions as fit the context ([#329](https://github.com/aminry/decisio/issues/329)) ([23b57e4](https://github.com/aminry/decisio/commit/23b57e4132a30b1a3bedabd63bab09b093dd76b2))

 

## [0.28.1](https://github.com/aminry/decisio/compare/v0.28.0...v0.28.1) (2026-10-25)

 

### Bug Fixes

 

- **serve:** the 12B's calibrated yes/no temperature fitted per precision on items that share no text with a public test set, and a warning from the adapter fit tool ([#325](https://github.com/aminry/decisio/issues/325)) ([8efbff2](https://github.com/aminry/decisio/commit/8efbff2538422a511635906a12eb290caa489cc0))

 

## [0.28.0](https://github.com/aminry/decisio/compare/v0.27.1...v0.28.0) (2026-10-25)

 

### Features

 

- **serve:** --default-adapter, a loaded adapter that answers requests naming none ([7f5d58f](https://github.com/aminry/decisio/commit/7f5d58fa0e45afa7e9f8e19d73f350220e0c465d))
- **serve:** the 12B calibrated and commit flavors start, with their calibrated temperatures ([7392595](https://github.com/aminry/decisio/commit/73925950f6d9ae184bd5816db1c432b6a95d6b05))
- **serve:** the commit flavor replaces thinking-commit ([cf8202a](https://github.com/aminry/decisio/commit/cf8202a9a17b2be1239a08ae05a9319e0928e57b))

 

## [0.27.1](https://github.com/aminry/decisio/compare/v0.27.0...v0.27.1) (2026-10-24)

 

### Documentation

 

- say a rented card, not where it was rented ([#310](https://github.com/aminry/decisio/issues/310)) ([f8868ef](https://github.com/aminry/decisio/commit/f8868ef31bd7ae4d64fadb3075cd0cdf574c9e2d))

 

## [0.27.0](https://github.com/aminry/decisio/compare/v0.26.0...v0.27.0) (2026-10-24)

 

### Features

 

- **serve:** --flavor, named configurations of the same weights (calibrated, thinking-commit, read-enhanced) ([22081c0](https://github.com/aminry/decisio/commit/22081c0a2d532cc9cd9296a3aaa7047126043ca7))
- **serve:** --read-twice-under N, an opt-in that reads short requests twice (never the default) ([#296](https://github.com/aminry/decisio/issues/296)) ([f7f9bd4](https://github.com/aminry/decisio/commit/f7f9bd4a499a6e4d04014ae9e61e03a821c43cc4))
- **serve:** a base's profile can carry per-type temperatures (yes/no, score) and choice bands ([f399c51](https://github.com/aminry/decisio/commit/f399c51f0bedf223f034d36927dc6dbcd0c0d0bb))
- **serve:** think-mode temperatures per question type (defaults.think_temperatures, --think-temperature, fit-think) ([#278](https://github.com/aminry/decisio/issues/278)) ([60516b4](https://github.com/aminry/decisio/commit/60516b4de0db2df90fa79499e4675bb38dd01f20))

 

### Bug Fixes

 

- **serve:** truncate counts the header's question list in the room it leaves for the state ([#305](https://github.com/aminry/decisio/issues/305)) ([46f7c60](https://github.com/aminry/decisio/commit/46f7c6013aeda01b1ebb4a35701d3f5134929075))

 

## [0.26.0](https://github.com/aminry/decisio/compare/v0.25.0...v0.26.0) (2026-10-23)

 

### ⚠ BREAKING CHANGES

 

- **serve:** the 31B, the 12B and the 12B in FP8 now list the request's questions before the state, so their answers change (measured on the 31B: +5.0 points on a Decision Index sample, +4.4 on H776, +7.4 on hard390, -0.4 on dev500). A server that loads tasks, abstention tasks, or an adapter or tuned repository fitted or trained on the plain layout keeps serving plain and says so; --layout plain restores the old behaviour everywhere. Clients that ask a series of different questions about one state want --layout plain: under the header a cached state is not reused for another set of questions.

 

### Features

 

- **serve:** an opt-in header prompt layout (--layout header) and --temperatures-file for the base ([#290](https://github.com/aminry/decisio/issues/290)) ([c0b1c08](https://github.com/aminry/decisio/commit/c0b1c08dbeddf0e58fc2450012245ab9ba2d7b71))
- **serve:** the Gemma bases are served with the header layout; held at plain where tasks or adapters need it ([#293](https://github.com/aminry/decisio/issues/293)) ([fdffb65](https://github.com/aminry/decisio/commit/fdffb65ffd1251455b84e8d79f2f530630d889fe))

 

## [0.25.0](https://github.com/aminry/decisio/compare/v0.24.0...v0.25.0) (2026-10-22)

 

### Features

 

- **serve:** a shared task store for replicas (--task-store), on object storage ([#287](https://github.com/aminry/decisio/issues/287)) ([931367f](https://github.com/aminry/decisio/commit/931367f39a2d63ab6ebf058790111740dc48c177))
- **serve:** taught tasks saved to disk (--tasks-dir), abstention tasks importable and removable, --require-tasks ([#284](https://github.com/aminry/decisio/issues/284)) ([8fae197](https://github.com/aminry/decisio/commit/8fae197aee713f74da56d365f74cce03040ef497))

 

## [0.24.0](https://github.com/aminry/decisio/compare/v0.23.0...v0.24.0) (2026-10-22)

 

### Features

 

- **serve:** the Gemma 4 12B in FP8 beside bf16 (gemma-4-12b-fp8) ([#269](https://github.com/aminry/decisio/issues/269)) ([c935b72](https://github.com/aminry/decisio/commit/c935b72751d900410c46b9de9425d7fa523d03ff))

 

### Bug Fixes

 

- **deps:** vite 8.3.5 in the pong demo, and Dependabot for its npm lockfile ([#271](https://github.com/aminry/decisio/issues/271)) ([3835323](https://github.com/aminry/decisio/commit/3835323cf51d5d9e0de9832d60167c2a08843a02))
- **serve:** batched mode stalled because vLLM's tokenizer pool deep-copied the tokenizer; the engine's tokenizer, one call at a time ([#280](https://github.com/aminry/decisio/issues/280)) ([2a0bc95](https://github.com/aminry/decisio/commit/2a0bc95f1f195e8b0181b6bde867520af4cfbc62))
- **serve:** thinking wrote reserved readout token ids instead of reasoning on the default engine ([#279](https://github.com/aminry/decisio/issues/279)) ([787acc1](https://github.com/aminry/decisio/commit/787acc1e9af5da027e733a7737d8f2d703a29a86))

 

## [0.23.0](https://github.com/aminry/decisio/compare/v0.22.0...v0.23.0) (2026-10-21)

 

### Features

 

- **serve:** --concurrency batched, the requests waiting for the engine scored together ([#270](https://github.com/aminry/decisio/issues/270)) ([8eb8392](https://github.com/aminry/decisio/commit/8eb839223c8d2312ca23ee383a91b348b66411e2))

 

## [0.22.0](https://github.com/aminry/decisio/compare/v0.21.2...v0.22.0) (2026-10-20)

 

### Features

 

- **hub:** a tuned 12B repository - the adapter merged in fp32 and rounded once to bf16, stochastically ([#254](https://github.com/aminry/decisio/issues/254)) ([0d10978](https://github.com/aminry/decisio/commit/0d109789f62ae5a085b682afed46d6fb772ce2ba))

 

## [0.21.2](https://github.com/aminry/decisio/compare/v0.21.1...v0.21.2) (2026-10-20)

 

### Bug Fixes

 

- **ci:** scan the image for its SBOM with TMPDIR on the root volume, not the 8 GB /tmp tmpfs ([#258](https://github.com/aminry/decisio/issues/258)) ([2c30460](https://github.com/aminry/decisio/commit/2c30460bef49cc56ea3db9c79832798feed2cef8))

 

## [0.21.1](https://github.com/aminry/decisio/compare/v0.21.0...v0.21.1) (2026-10-20)

 

### Bug Fixes

 

- **ci:** free disk space before the image is scanned for its SBOM ([#252](https://github.com/aminry/decisio/issues/252)) ([e465c8a](https://github.com/aminry/decisio/commit/e465c8a846c799daaf0f84b31cf786e05848fbd1))

 

## [0.21.0](https://github.com/aminry/decisio/compare/v0.20.0...v0.21.0) (2026-10-20)

 

### Features

 

- **serve:** think when unsure - an unsure question is read again after the served model has written its reasoning (off by default; --think, a request field, or a tuned repository default) ([35a03a8](https://github.com/aminry/decisio/commit/35a03a8faedf36e70591e3b76be6ff4ea5464dfe))

 

## [0.20.0](https://github.com/aminry/decisio/compare/v0.19.0...v0.20.0) (2026-10-20)

 

### Features

 

- **hub:** decision_config /2 for a tuned model's repository (tuned and defaults sections, tuned-config and check commands) ([0f0c092](https://github.com/aminry/decisio/commit/0f0c092595cbea702e47fa9d46988d9bee29e983))
- **hub:** hub_fp8 merge - a LoRA adapter merged into the base in fp32 and quantized once to FP8 ([3ca0e5e](https://github.com/aminry/decisio/commit/3ca0e5e84a055168c960ce9c368236a946c969ea))
- **serve:** --over-limit truncate - answer a state over the prompt ceiling from its start (never certified, reported in the response) ([ad16bbd](https://github.com/aminry/decisio/commit/ad16bbd4b43fc79c32c7681c8a144a602e699d1c))
- **serve:** a tuned repository's defaults are served - temperatures, choice bands and the commit setting, with the flags above them ([#244](https://github.com/aminry/decisio/issues/244)) ([62b3d13](https://github.com/aminry/decisio/commit/62b3d13d6ba10ea126dd3c8eef9a493cafd2273d))

 

## [0.19.0](https://github.com/aminry/decisio/compare/v0.18.1...v0.19.0) (2026-10-19)

 

### ⚠ BREAKING CHANGES

 

- **serve:** the earlier spellings of the debug and route request headers are no longer read; send x-decisio-debug and x-decisio-route

 

### Features

 

- **serve:** the earlier spellings of the debug and route request headers are no longer read; send x-decisio-debug and x-decisio-route ([2b08ccd](https://github.com/aminry/decisio/commit/2b08ccdcbf917738f56ac4fd57555bc679d6e5ae))

 

## [0.18.1](https://github.com/aminry/decisio/compare/v0.18.0...v0.18.1) (2026-10-19)

 

### Bug Fixes

 

- **serve:** a re-certification derives min_examples from its new alpha and delta unless the original call asked for more than they need ([#233](https://github.com/aminry/decisio/issues/233)) ([cd3f3cd](https://github.com/aminry/decisio/commit/cd3f3cdb6203c99cc16d68af40ae9792ca8f0a66))

 

## [0.18.0](https://github.com/aminry/decisio/compare/v0.17.0...v0.18.0) (2026-10-19)

 

### Features

 

- **serve:** an adapter carries its own temperatures per question type, and its identity and temperatures are in the serving-stack fingerprint ([#207](https://github.com/aminry/decisio/issues/207)) ([72e8aff](https://github.com/aminry/decisio/commit/72e8aff27154c57338ab69e432ed6420a567e6dc))
- **serve:** reference docs generated from the code - an OpenAPI file of the served routes and models and a table of the server's flags, written at release time ([#224](https://github.com/aminry/decisio/issues/224)) ([35d4287](https://github.com/aminry/decisio/commit/35d42879babdf398a9ea2fbc538d93f6004d9115))
- **serve:** the image count of a request is limited by the context, not by a cap of 2 (--max-images, default 128) ([#222](https://github.com/aminry/decisio/issues/222)) ([24568fc](https://github.com/aminry/decisio/commit/24568fc8e4ba428a8a3cde563b55d5a862190771))

 

### Bug Fixes

 

- **serve:** --image-model's text and image engines default to shares that fit one card (0.47 and 0.49), and a pair that cannot fit is refused before either engine is built ([#227](https://github.com/aminry/decisio/issues/227)) ([4693205](https://github.com/aminry/decisio/commit/4693205bd6d0bfba2724c288989ff5c4e1396233))
- **serve:** every thread that calls the in-process engine runs on the stream the engine was warmed up on (Marlin W4A16 checkpoints) ([#215](https://github.com/aminry/decisio/issues/215)) ([8454d7d](https://github.com/aminry/decisio/commit/8454d7dc47ff0cd72fb5fe8769eeadfd393f84c6))

 

## [0.17.0](https://github.com/aminry/decisio/compare/v0.16.0...v0.17.0) (2026-10-18)

 

### Features

 

- **serve:** System One's assets - placeholders in the state and the questions are bound to images, in JSON and multipart ([#221](https://github.com/aminry/decisio/issues/221)) ([b6bf2fe](https://github.com/aminry/decisio/commit/b6bf2fe8526c57291a5af2e5631b3a352af5f65e))

 

### Bug Fixes

 

- **serve:** a choice with more than 255 options is a 422 like System One's, and conformance gate C5 compares the wire with System One's OpenAPI ([#219](https://github.com/aminry/decisio/issues/219)) ([d95c891](https://github.com/aminry/decisio/commit/d95c8915d3177c8198e8d1bbab0001a317fab3cf))

 

## [0.16.0](https://github.com/aminry/decisio/compare/v0.15.0...v0.16.0) (2026-10-18)

 

### Features

 

- **serve:** a certified task's questions are scored by the function that certified them, and registration records how far the served confidences are ([#211](https://github.com/aminry/decisio/issues/211)) ([a494297](https://github.com/aminry/decisio/commit/a4942971ae605fd55609b19e591b387bef13b24e))
- **serve:** a confidence signal in the traffic monitor, certification that stops while it alerts, and a warning for a task with no catch-all option ([#214](https://github.com/aminry/decisio/issues/214)) ([f7ba1ed](https://github.com/aminry/decisio/commit/f7ba1edd950964a80c85d3afc746b1182f20689c))

 

### Bug Fixes

 

- **serve:** refuse a checkpoint of a model type no base serves instead of giving it the Qwen profile ([#213](https://github.com/aminry/decisio/issues/213)) ([a1bc43c](https://github.com/aminry/decisio/commit/a1bc43cdc8c21deb6f63c6d1bc505cee52b125bc))
- **serve:** the MLX backend downloads --model at --revision, and the revision is in the fingerprint when given ([#210](https://github.com/aminry/decisio/issues/210)) ([3fdadd8](https://github.com/aminry/decisio/commit/3fdadd808b991bd9238bb5c761d59cb7405d78b2))

 

## [0.15.0](https://github.com/aminry/decisio/compare/v0.14.0...v0.15.0) (2026-10-17)

 

### Features

 

- **serve:** an adapter in the other tensor-name layout is mapped, not refused, and the check runs before the engine is built ([#198](https://github.com/aminry/decisio/issues/198)) ([530971b](https://github.com/aminry/decisio/commit/530971b41123dc376a3997e535129e25eea171ce))

 

### Bug Fixes

 

- **serve:** pin the 0.15.0 Hub revisions ([#204](https://github.com/aminry/decisio/issues/204)) ([02f6229](https://github.com/aminry/decisio/commit/02f6229c34ceb8979e2fb8d0942d2425eea9d6f7))

 

## [0.14.0](https://github.com/aminry/decisio/compare/v0.13.0...v0.14.0) (2026-10-16)

 

### ⚠ BREAKING CHANGES

 

- **serve:** without --max-model-len the Gemma bases serve the largest context that fits the card, up to the base's ceiling ([#196](https://github.com/aminry/decisio/issues/196))

 

### Features

 

- **serve:** without --max-model-len the Gemma bases serve the largest context that fits the card, up to the base's ceiling ([#196](https://github.com/aminry/decisio/issues/196)) ([39b5852](https://github.com/aminry/decisio/commit/39b5852b65140f31fd488ad550c57259cc9b2990))

 

### Bug Fixes

 

- **serve:** /v1/systemone passes a request's adapter to the engine ([#200](https://github.com/aminry/decisio/issues/200)) ([cf90db5](https://github.com/aminry/decisio/commit/cf90db5ff9839b20aa17c4f89909c2d8af982c1b))
- **serve:** pin the 0.14.0 Hub revisions ([#197](https://github.com/aminry/decisio/issues/197)) ([30f43d3](https://github.com/aminry/decisio/commit/30f43d3a0fc2fdf252d96f4505efc32d262ff709))

 

## [0.13.0](https://github.com/aminry/decisio/compare/v0.12.0...v0.13.0) (2026-10-15)

 

### Features

 

- **serve:** a longer context as an opt-in: --max-model-len up to a ceiling per base (12B 65,536, 31B 98,304), and --kv-cache-dtype fp8 ([#179](https://github.com/aminry/decisio/issues/179)) ([f10a364](https://github.com/aminry/decisio/commit/f10a36483b8b027ea4f0776bf3d9f268a1324405))

 

### Bug Fixes

 

- **serve:** pin the 0.13.0 Hub revisions; the 31B's weights.method names what was measured ([#191](https://github.com/aminry/decisio/issues/191)) ([26e4b7e](https://github.com/aminry/decisio/commit/26e4b7ee40f37bede8d91c7dcde56df07acc4314))

 

## [0.12.0](https://github.com/aminry/decisio/compare/v0.11.1...v0.12.0) (2026-10-15)

 

### Features

 

- **reference:** a plain-transformers reference for the model repositories ([#123](https://github.com/aminry/decisio/issues/123)) ([00bc619](https://github.com/aminry/decisio/commit/00bc61991e35384ea9894b8dfccd35f64a07d364))
- **serve:** an abstention task can match by option keys, for items that share their labels and not their texts ([#176](https://github.com/aminry/decisio/issues/176)) ([8bd9d20](https://github.com/aminry/decisio/commit/8bd9d20d872b1172a91be00ae4df7530f7681109))
- **serve:** certified answers and a monitor in /v1/tasks ([#155](https://github.com/aminry/decisio/issues/155)) ([a6dd227](https://github.com/aminry/decisio/commit/a6dd227c4292529855da1b3eb688bd96433cef10))
- **serve:** registration names what it read: inputs_sha256 on the calibration and head records ([#177](https://github.com/aminry/decisio/issues/177)) ([14de77b](https://github.com/aminry/decisio/commit/14de77be4b372498a4866527140299af6f525e53))
- **serve:** the intent head carries a temperature, fitted on its out-of-fold logits ([#171](https://github.com/aminry/decisio/issues/171)) ([0ee2dcc](https://github.com/aminry/decisio/commit/0ee2dcc7c28833860872aad772ac2059fc7d4cc3))
- **serve:** vLLM 0.31.0 ([#137](https://github.com/aminry/decisio/issues/137)) ([3f615a3](https://github.com/aminry/decisio/commit/3f615a30c552cdcdc63e7f4ca78175a36e4e8f7d))
- **serve:** vLLM 0.31.0 ([#144](https://github.com/aminry/decisio/issues/144)) ([8375011](https://github.com/aminry/decisio/commit/83750116c1bd66f29955d7f73184fa8457f44199))

 

### Bug Fixes

 

- **serve:** /health state_boundary reports running, so a registration being sent is not read as idle ([#147](https://github.com/aminry/decisio/issues/147)) ([4d3fa3b](https://github.com/aminry/decisio/commit/4d3fa3bd3d370600e61224bd962678c452554af1))
- **serve:** a NaN or infinity in an engine's probabilities is a clear 500 that names the model and engine, not a 400 ([#175](https://github.com/aminry/decisio/issues/175)) ([e2a9f09](https://github.com/aminry/decisio/commit/e2a9f09fb13230c99089f766284c341148777ee0))
- **serve:** engine_kwargs reads the base from the checkpoint when the caller gives no family ([#150](https://github.com/aminry/decisio/issues/150)) ([e1404ae](https://github.com/aminry/decisio/commit/e1404aea5573c5311df01c0cd571ba799e2c533e))
- **serve:** refuse a POST that is not JSON with 415, before any work, on every route ([#157](https://github.com/aminry/decisio/issues/157)) ([aabd8fd](https://github.com/aminry/decisio/commit/aabd8fdd1fbd47a82372c79c99990d94b022ac3d))
- the MLX backend sets its own memory and cache limits ([#158](https://github.com/aminry/decisio/issues/158)) ([d21a31c](https://github.com/aminry/decisio/commit/d21a31c989622c8271d98b30c11665688b822386))

 

### Reverts

 

- "feat(serve): vLLM 0.31.0 ([#137](https://github.com/aminry/decisio/issues/137))" ([#143](https://github.com/aminry/decisio/issues/143)) ([82f3853](https://github.com/aminry/decisio/commit/82f3853c6b7f859e7427df1c790876402fb29c16))

 

## [0.11.1](https://github.com/aminry/decisio/compare/v0.11.0...v0.11.1) (2026-10-10)

 

### Bug Fixes

 

- **serve:** a request records how long it waited for the engine, and whether a registration was running ([#135](https://github.com/aminry/decisio/issues/135)) ([76d484c](https://github.com/aminry/decisio/commit/76d484cfec1213065efdfdcd8fdcad171cc712d3))
- **serve:** gemma-4-12b registers a state's boundary before the question, as the 31B does ([#128](https://github.com/aminry/decisio/issues/128)) ([b7af9c6](https://github.com/aminry/decisio/commit/b7af9c684d5caead51d7efe093c4a0df0c5d57f4))

 

## [0.11.0](https://github.com/aminry/decisio/compare/v0.10.0...v0.11.0) (2026-10-09)

 

### Features

 

- **hub:** the model repositories are under tachara-ai (Tachara AI Lab), pinned at their 0.11.0 revisions ([#121](https://github.com/aminry/decisio/issues/121)) ([852cce4](https://github.com/aminry/decisio/commit/852cce401a78aeb547e4016c6129adb86c2c8792))
- **serve:** --backend mlx serves gemma-4-31b from an MLX conversion, documented for states up to 16,383 tokens ([#116](https://github.com/aminry/decisio/issues/116)) ([a801341](https://github.com/aminry/decisio/commit/a801341211649def233529359d21d78c43fbe25f))

 

### Bug Fixes

 

- **serve:** --backend mlx refuses any checkpoint whose config.json declares FP8 quantization ([#120](https://github.com/aminry/decisio/issues/120)) ([18229d2](https://github.com/aminry/decisio/commit/18229d25dddf8c19aca6c5c3263bd91fd21947a5))
- **serve:** a prompt over the context is a 422 with a clear detail, not a bare 500 ([#124](https://github.com/aminry/decisio/issues/124)) ([a51be6d](https://github.com/aminry/decisio/commit/a51be6d9e96d84f8cd4dd31b8e3b7fb3a5c01046))

 

## [0.10.0](https://github.com/aminry/decisio/compare/v0.9.0...v0.10.0) (2026-10-08)

 

### ⚠ BREAKING CHANGES

 

- **serve:** a vLLM server started with neither `--base` nor `--model`, and the container, now serve gemma-4-31b (a 96 GB card); set `--base qwen3.6-35b-a3b` or `DECISIO_BASE=qwen3.6-35b-a3b` to keep the previous Qwen behaviour (docs/upgrading.md).

 

### Features

 

- **hub:** the 31B's FP8 checkpoint is made on a CPU as vLLM quantizes it on load, and the engine's FP8 weights can be fingerprinted ([#113](https://github.com/aminry/decisio/issues/113)) ([94d675c](https://github.com/aminry/decisio/commit/94d675c779ad14da9770926a2fdec21559e1ba79))
- **serve:** --base takes a decisio model repository; decision_config.json is exported from each base's profile ([#110](https://github.com/aminry/decisio/issues/110)) ([9d352de](https://github.com/aminry/decisio/commit/9d352def278e79e99eacf4eb6fd1c51ddeb3cfba))
- **serve:** the default base is Gemma 4 31B, served from its FP8 repository ([#114](https://github.com/aminry/decisio/issues/114)) ([389fe13](https://github.com/aminry/decisio/commit/389fe13787ca0831a50b43cdf9e24396c44fab22))
- **serve:** the MLX prefix cache budget is the base's: 7,400 MiB for gemma-4-12b, 2,048 MiB for the Qwen base ([#112](https://github.com/aminry/decisio/issues/112)) ([71facc7](https://github.com/aminry/decisio/commit/71facc77a0e645b8023fd0a61554ea9175bbb741))

 

## [0.9.0](https://github.com/aminry/decisio/compare/v0.8.2...v0.9.0) (2026-10-07)

 

### Features

 

- **serve:** register a state's boundary after the response on the Gemma bases, so a first question does not wait for it ([#99](https://github.com/aminry/decisio/issues/99)) ([787df0e](https://github.com/aminry/decisio/commit/787df0e6d097e4ea3106f3e2b459661f4f8d81aa))

 

## [0.8.2](https://github.com/aminry/decisio/compare/v0.8.1...v0.8.2) (2026-10-07)

 

### Bug Fixes

 

- **serve:** --backend mlx refuses gemma-4-31b by the resolved base, so a 31B conversion without --base is refused too ([#100](https://github.com/aminry/decisio/issues/100)) ([f6cc1e8](https://github.com/aminry/decisio/commit/f6cc1e86d221ebc175c719de6d344084ceb9d5db))

 

## [0.8.1](https://github.com/aminry/decisio/compare/v0.8.0...v0.8.1) (2026-10-05)

 

### Bug Fixes

 

- **serve:** a single question registers its state's boundary on the Gemma bases, so the next question reads the state from the cache ([#93](https://github.com/aminry/decisio/issues/93)) ([573d7d6](https://github.com/aminry/decisio/commit/573d7d698c830890f8624b4b69261c31dda278c2))
- **serve:** close the engine-death races; loop those tests under load nightly with the slow tier on a hosted runner ([#91](https://github.com/aminry/decisio/issues/91)) ([3c51e79](https://github.com/aminry/decisio/commit/3c51e795edd650760ba5c49ee65e55b561dec4c7))

 

## [0.8.0](https://github.com/aminry/decisio/compare/v0.7.2...v0.8.0) (2026-10-05)

 

### Features

 

- **serve:** the engine runs in the server's process by default; gemma-4-31b scores several questions warm ([#86](https://github.com/aminry/decisio/issues/86)) ([efd2b83](https://github.com/aminry/decisio/commit/efd2b83943c5dffaf9b692e138f4bffaaa79d1c1))

 

## [0.7.2](https://github.com/aminry/decisio/compare/v0.7.1...v0.7.2) (2026-10-05)

 

### Bug Fixes

 

- **serve:** a dead engine turns /health to 503, is refused fast and ends the server with code 70 ([#78](https://github.com/aminry/decisio/issues/78)) ([8b367d9](https://github.com/aminry/decisio/commit/8b367d92c39244385069571f59f1d2dbb1f51cf9))

 

## [0.7.1](https://github.com/aminry/decisio/compare/v0.7.0...v0.7.1) (2026-10-05)

 

### Bug Fixes

 

- **serve:** --head-engine gives the second engine the server's prompt format and base ([#76](https://github.com/aminry/decisio/issues/76)) ([993db22](https://github.com/aminry/decisio/commit/993db2200153c1db91854f525b96182a0577fbbb))

 

## [0.7.0](https://github.com/aminry/decisio/compare/v0.6.0...v0.7.0) (2026-10-05)

 

### Features

 

- **serve:** --engine-process in|separate, where vLLM's engine runs (default separate) ([#74](https://github.com/aminry/decisio/issues/74)) ([bbff50f](https://github.com/aminry/decisio/commit/bbff50f7c686b7c55ea00817705c5229699b3d9f))

 

## [0.6.0](https://github.com/aminry/decisio/compare/v0.5.0...v0.6.0) (2026-10-05)

 

### Features

 

- **serve:** Gemma 4 31B as a third base (--base gemma-4-31b), FP8 on load ([#71](https://github.com/aminry/decisio/issues/71)) ([73f0eff](https://github.com/aminry/decisio/commit/73f0eff184d44529a47726a4fc2e98045dca849f))

 

## [0.5.0](https://github.com/aminry/decisio/compare/v0.4.1...v0.5.0) (2026-10-04)

 

### Features

 

- the Gemma base on a Mac with --backend mlx, 6-bit by default ([#69](https://github.com/aminry/decisio/issues/69)) ([587c8b2](https://github.com/aminry/decisio/commit/587c8b26a9eb71ddc0080686a512f4bd73738ed8))

 

## [0.4.1](https://github.com/aminry/decisio/compare/v0.4.0...v0.4.1) (2026-10-04)

 

### Bug Fixes

 

- **serve:** load the pinned revision in packed mode, the hf stand-in and the second-engine head ([#65](https://github.com/aminry/decisio/issues/65)) ([b0b4ea5](https://github.com/aminry/decisio/commit/b0b4ea58dd6cbf5275bdcc627ec0c3cb18d7a93e))
- **serve:** pin the Qwen base's checkpoint revision at 95a723d ([#63](https://github.com/aminry/decisio/issues/63)) ([9a36b96](https://github.com/aminry/decisio/commit/9a36b9681d1a1b7b3d83e90286ef9ffcb90fcc9b))

 

## [0.4.0](https://github.com/aminry/decisio/compare/v0.3.0...v0.4.0) (2026-10-04)

 

### Features

 

- **serve:** Gemma 4 12B as a second base (--base gemma-4-12b) ([#48](https://github.com/aminry/decisio/issues/48)) ([61d7519](https://github.com/aminry/decisio/commit/61d7519bfed0910747970c2ff862ca10074a454d))

 

### Bug Fixes

 

- **conformance:** C4 applies each question type's served temperature ([#60](https://github.com/aminry/decisio/issues/60)) ([4ab98af](https://github.com/aminry/decisio/commit/4ab98afe0de5ce6ef6203d47bf45d913d0a1abd4))

 

## [0.3.0](https://github.com/aminry/decisio/compare/v0.2.0...v0.3.0) (2026-10-04)

 

### ⚠ BREAKING CHANGES

 

- **serve:** tasks registered under the earlier compact layout are not applied by the new default (the layout enters the task fingerprint and the yes/no rendering a [yes/no question](https://opendecisio.com/docs/glossary/#yesno-question)'s task key); register them again, or serve them with --prompt-tail compact --noul-rendering words. --mode packed reads only the compact layout and needs --prompt-tail compact.

 

### Features

 

- **demos:** four demos on a System One server, judged decision by decision, with clips rendered from trajectories ([#33](https://github.com/aminry/decisio/issues/33)) ([86abd52](https://github.com/aminry/decisio/commit/86abd5243fa595a89426e11aa63a60d47cbbc955))
- **serve:** --noul-commit, an opt-in transform reporting yes/no answers inside 0.20 to 0.80 at the band's edge ([#53](https://github.com/aminry/decisio/issues/53)) ([62dfe1f](https://github.com/aminry/decisio/commit/62dfe1fa9af8bb490a53c1a1cccee345adbc2020))
- **serve:** the spaced layout and yes/no as named letters are the served default, with T 1.506 and choice T 1.370 ([6c581d4](https://github.com/aminry/decisio/commit/6c581d46e5062d902763b6aa9adee0600cc022e1))

 

## [0.2.0](https://github.com/aminry/decisio/compare/v0.1.1...v0.2.0) (2026-10-03)

 

### ⚠ BREAKING CHANGES

 

- **serve:** no padding (--pad-policy none) is the MLX backend's default ([#46](https://github.com/aminry/decisio/issues/46))
- --multi-question sequential and --describe-options are the served defaults ([#38](https://github.com/aminry/decisio/issues/38))

 

### Features

 

- --describe-options shows an option's description alone when it has one (off by default) ([#36](https://github.com/aminry/decisio/issues/36)) ([f2d936e](https://github.com/aminry/decisio/commit/f2d936e5cbcdcd8f1880f1569121478e37b0c3ba))
- --multi-question sequential and --describe-options are the served defaults ([#38](https://github.com/aminry/decisio/issues/38)) ([06a1273](https://github.com/aminry/decisio/commit/06a1273fcb1668c856fd182008db26b81f90c1d4))
- --multi-question sequential makes each answer equal the question asked alone; stage timings and --pad-policy; README wording for multi-question requests ([#35](https://github.com/aminry/decisio/issues/35)) ([5461e0e](https://github.com/aminry/decisio/commit/5461e0e90e039a24c0426555d8801e003d81bc9c))
- **serve:** --noul-rendering words|letters|letters-keys, how a yes/no question is asked (words by default) ([#44](https://github.com/aminry/decisio/issues/44)) ([597f383](https://github.com/aminry/decisio/commit/597f3838cdf0e4a7f89b87f2fd5a007f4e850ffe))
- **serve:** --pad-policy row, a single-question row padded to end on the block boundary (off by default) ([#45](https://github.com/aminry/decisio/issues/45)) ([8ccf143](https://github.com/aminry/decisio/commit/8ccf14367f13ff9ceb09726b2959072351873d03))
- **serve:** a cross-request prefix cache for the MLX engine (--prefix-cache-mb) ([#47](https://github.com/aminry/decisio/issues/47)) ([8e3f54f](https://github.com/aminry/decisio/commit/8e3f54f645faaf16329f7be2b317f1071cc9bf89))
- **serve:** an MLX engine for the text route on Apple silicon (--backend mlx) ([#39](https://github.com/aminry/decisio/issues/39)) ([f3c5026](https://github.com/aminry/decisio/commit/f3c5026dc03da3af71958d187a15e997a30d1d27))
- **serve:** no padding (--pad-policy none) is the MLX backend's default ([#46](https://github.com/aminry/decisio/issues/46)) ([2c1204e](https://github.com/aminry/decisio/commit/2c1204e4a6c0db22f1c95f20d0b5cfe1575b7a5b))

 

### Bug Fixes

 

- task registration on a certain readout, and the CPU stand-in's head engine (two HTTP 500s) ([#49](https://github.com/aminry/decisio/issues/49)) ([6e71298](https://github.com/aminry/decisio/commit/6e7129842ef9460ed35897d598e9b86affde6f2a))

 

## [0.1.1](https://github.com/aminry/decisio/compare/v0.1.0...v0.1.1) (2026-10-01)

 

### Bug Fixes

 

- **ci:** attest a CycloneDX SBOM of the image instead of SPDX ([#22](https://github.com/aminry/decisio/issues/22)) ([eef30b4](https://github.com/aminry/decisio/commit/eef30b4a8a99d82a3b1b14303ff1f9586a2b7a7e))

 

## [0.1.0](https://github.com/aminry/decisio/releases/tag/v0.1.0) (2026-10-01)

 

### Features

 

- a generator for the conformance items file from two public sets ([#18](https://github.com/aminry/decisio/issues/18)) ([77f1bbd](https://github.com/aminry/decisio/commit/77f1bbde3c23cf81d77bd38229df0a028a5efe71))

 

### Bug Fixes

 

- the head's output-layer rows load from a Hugging Face repo id as well as a local directory ([9d7d0ab](https://github.com/aminry/decisio/commit/9d7d0ab8133e682a336de8600a7588173c9a94dd))

 

### Documentation

 

- community files, CI and release tooling ([#2](https://github.com/aminry/decisio/issues/2)) ([4dc5388](https://github.com/aminry/decisio/commit/4dc5388eb5743f1ddc19845723d22ca0499e8fdb))
- launch checklist for the public flip ([#4](https://github.com/aminry/decisio/issues/4)) ([c79ec96](https://github.com/aminry/decisio/commit/c79ec9675bc2415a0ae5f045c2051a55022b2849))
- launch checklist gate for the image's first GPU start; README says it is pending ([dff92fb](https://github.com/aminry/decisio/commit/dff92fbe407336858ea26216a81c6ba0587aab96))
- launch checklist records the flip and corrects three steps ([#14](https://github.com/aminry/decisio/issues/14)) ([93a885d](https://github.com/aminry/decisio/commit/93a885dc82441aa77df39df304e0421d2e7ad7ac))
- launch checklist section for the maintainer's commit signing ([80e36ca](https://github.com/aminry/decisio/commit/80e36caa08c013d0a2fa11618baedc30695d5e35))
- README section Run with Docker; launch checklist covers the image ([2e93c65](https://github.com/aminry/decisio/commit/2e93c65f57cb4fc2f77b714666b593763b848616))
- record that the merge settings of checklist 2.1 are applied ([7b60810](https://github.com/aminry/decisio/commit/7b608109eed12618de516bfef6f56928c00fc4b5))
- record the history rebuild of main in the launch checklist ([b5688cc](https://github.com/aminry/decisio/commit/b5688cc3da89bc181855d1da2b6b10e5ece14ab8))

 

## What next

 

- [Upgrade](https://opendecisio.com/docs/upgrading/): the steps of every upgrade.
- [Choosing a model](https://opendecisio.com/docs/bases/): which model is the default, and why.
- [Evaluation card](https://opendecisio.com/docs/evaluation-card/): what each release was measured on.
