Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to **Nika** are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Nika follows [semantic versioning](https://semver.org); 0.124 is the current release line, focused on maintenance, security and the media capabilities workflows need, with native video planned for 0.125 ([ROADMAP.md](ROADMAP.md)).

Nika Diamond is the ground-up rewrite developed on `main`, beginning at
v0.80.0. The legacy v0.79.3 tree remains on the read-only `brouillon` branch.

---
## [Unreleased]

One file per change under [`changelog.d/`](changelog.d/), assembled into the
section below at tag time (`bash scripts/release/changelog-assemble.sh --fold
<version>`). Do not write bullets here: this file is where four concurrent
pull requests collided on 2026-08-24 with no source overlap between them, and
`--check` refuses a hand-written bullet in this section.

## [0.124.3](https://github.com/supernovae-st/nika/compare/v0.124.2..v0.124.3) - 2026-11-10

Nika 0.124.3 lets an application ask what a workflow's declared ACP route offers before anything
runs. On `nika serve`, `GET /v1/access-offers/{name}` opens the sessions a run of that file would
open, selects the file's model in each, reads the models, the exact reasoning efforts and the
service tiers the route lists for that model, and ends without a prompt. The answer says what was
read and when, and it authorizes nothing: a run still selects and reads back on its own session.
Measured on one codex seat with codex-acp 1.13.1 on 2026-11-10, with a debug build of the engine:
reading the `infer:` session of a file took 4.1 s and 5.3 s, reading an `agent:` session took 1.3 s,
and a second request within the minute returned the kept answer in 0.02 s. For
`openai/gpt-6-luna` that seat listed the efforts `low`, `medium`, `high`, `xhigh` and `max` and the
tiers `standard` and `fast`; for `openai/gpt-6.1-sol` it confirmed the model and listed no effort
and no tier. These are observations of one seat on one day, not a description of the route. The
language and the SDK protocol are unchanged; an engine that answers this request lists
`accessSessionOffer` in `supportedCapabilities`.

### Added

- **`nika serve` answers what a workflow's declared ACP route offers,
  without a prompt.** `GET /v1/access-offers/{name}` (capability
  `accessSessionOffer` on `/health`) admits a served workflow by name, reads
  its `run.access`, and, when the file selects ACP sessions (`via` under
  `protocol: acp`), opens one session per shape its tasks run in and per
  model they resolve to: the route's one-shot completion profile for
  `infer:` tasks, its agent session for `agent:` tasks. Each session is
  opened as a run opens it, selects the file's model for that session only,
  lists the models, the exact reasoning efforts and the service tiers the
  route has for that model, and ends: no effort, tier or mode is set and the
  path holds no `session/prompt`, so an offer uses no turn. The answer is a
  `nika/access-offer@1` document: the file's declared route, then one row
  per session with `observed_at` and `fresh_until`. A model the session does
  not list is a row state, and its efforts and tiers are then left
  `unobserved` instead of being read from another model. A session that
  gives no offer is a row with the code a run would meet (`NIKA-1803`,
  `NIKA-1804`, `NIKA-1805`). Rows are kept 60 seconds (15 when unavailable),
  one reading runs at a time, a ninth concurrent request answers `503
  access_offer_busy`, and every listed value reaches the client as a bounded
  token. The request names a served workflow and nothing else, and the
  answer authorizes no run: `POST /v1/jobs` still selects, reads back and
  refuses on its own session. In the library, `nika_harness::session_offer`
  reads one session and `nika_service_execution::offer` names the sessions
  of a workflow.
## [0.124.2](https://github.com/supernovae-st/nika/compare/v0.124.1..v0.124.2) - 2026-11-10

Nika 0.124.2 lets a workflow ask its route for a service tier. `run.access.service_tier` takes
`standard` or `fast` on the codex seat over ACP (`via: codex`, `protocol: acp`): the session selects
the tier with the model and the effort and reads the selection back before its first prompt. When
the session cannot confirm the tier, the call is refused before the prompt (`NIKA-1805`), and a
route that cannot carry a tier is refused before task 1 (`NIKA-1800`). The receipt keeps what was
requested, offered, sent and read back, and says `unknown` for the tier a turn carried and the tier
that served it, because no ACP answer names them. Omitting the key changes nothing: no option is
sent, and the file keeps its resume identity and its receipt. An ACP task also starts its adapter
once instead of twice. Measured on one codex seat with codex-acp 1.13.1 on 2026-11-10: the single
start saves about 1.6 s per task; an answer of about 400 words took 12.5 s and 13.1 s on the route's
default tier and 9.3 s and 9.1 s with `fast`; one image from the agent's image tool took 15.8 s to
20.7 s on the default tier (3 runs) and 14.8 s to 15.7 s with `fast` (3 of 4 runs; the fourth ended
without an image). These are observations of one seat, not a promise of the tier. A refused seat and
a refused route declaration no longer suggest a pin the same file refuses, `nika check
--infer-permits` names the host an `exec:` URL reaches, and the embedded language pack follows
nika-spec `b4c692f`. The SDK protocol is unchanged; an engine that reads the key lists
`accessServiceTier` in `supportedCapabilities`.

### Added

- **A workflow can ask its route for a service tier.** `run.access` gains
  `service_tier`, with the two words `standard` and `fast` (spec 01
  §run). On the codex seat over ACP (`via: codex`, `protocol: acp`) the
  session selects the model, the effort, the tier and the confinement
  mode in that order, then reads the whole selection back before its
  first prompt; `fast` is sent as codex-acp's `fast-mode: on` and
  `standard` as `off`. The binding is held for codex-acp 1.13.1 only:
  another release of the adapter, an adapter with no measured speed
  control, a model whose session lists no such option, or a read-back
  that does not hold the value refuses before the prompt (`NIKA-1805`).
  A tier no route can carry is refused before task 1 by `nika check` and
  by the run (`NIKA-1800`): an API or local route, a seat without a
  measured control, and an `infer:` task outside `protocol: acp`. The
  `access_selection` receipt gains a `service_tier` block only when the
  file declares a tier: `requested`, `option`, `offered`, `transmitted`,
  `configured` and `configured_source`, plus `turn` and `effective`,
  which read `unknown` because no ACP answer names the tier a turn
  carried or the tier that served it. Omitting the key is not
  `standard`: nothing is sent, the route keeps its own tier, and the
  file keeps its resume identity and its receipt. A resumed run whose
  tier changed re-runs its `infer:` and `agent:` tasks. A child workflow
  resolves its own declaration and never inherits its parent's tier.
  `nika --sdk-identity` and a resident's `/health` list
  `accessServiceTier` in `supportedCapabilities`: an engine without the
  token refuses the key at parse, so a client requires it before it
  admits a file that declares a tier. On `nika serve` the tier is a field
  of the file, never of the request, and the per-call receipt stays in
  the sealed journal. An editor served by an older `nika lsp` refuses the
  new key and loses hover and navigation on the file until it is updated.

### Changed

- **An ACP task starts its adapter once.** For an agent application whose
  identity is its `initialize` answer (codex-acp, claude-agent-acp,
  opencode, copilot), every `agent:` task and every `infer:` task over ACP
  used to start the adapter twice: once to ask `initialize` and stop it,
  once for the session. The session's own first answer is now the one
  judged, before `session/new`. A hosted codex seat saves one adapter
  start per task, measured at about 1.6 s. The refusals are unchanged: an
  adapter that does not answer `initialize` within 30 s, exits before
  answering, answers as another program or sits outside its version pin
  is unavailable (`NIKA-1803`) and is not retried. `nika doctor` and the
  access rows still probe on their own.
- **The embedded language pack follows nika-spec `b4c692f`.** `SPEC_PIN`
  and `crates/nika-pack/pack` advance together from `5cd95cd`, the commit
  0.124.0 and 0.124.1 embed. The schema `nika spec --schema` prints, and
  the one `nika check` validates against, gains
  `run.access.service_tier` (`standard` · `fast`); spec 01 §run states its
  rules and the coverage matrix lists its fixtures. The three status
  amendments that name 0.124.0 and 0.124.1 as released ride along
  (`QUICKSTART.md`, spec 00 and 07). `canon.yaml`, the examples, the
  templates and the stdlib pages are unchanged.

### Fixed

- **`nika check --infer-permits` names the host an `exec:` URL reaches.**
  For `exec: { command: ["curl", "https://example.com/"] }` it printed
  `exec: ["curl"]` alone, and `nika check` then refused that very block
  with `NIKA-SEC-004` (`exec URL host example.com is outside
  permits.net.http`). A literal or `const:`-backed URL in an argv, or in a
  shell line, now adds its host to `permits.net.http`, read from the same
  tokens the check judges. When an HTTP client (`curl`, `wget`, `xh`,
  `http`, `https`) gets only a computed argument, or a template decides a
  URL's host, the block gains a review note naming the task and the
  inferred net face is marked partial, instead of a block that checks
  green and is refused at run. `nika check` also stops asking for
  `"${{inputs.host}}"` in `permits.net.http` when a template written
  without spaces fills an exec URL's host: that host is computed, and the
  run judges it.
- **A refused seat no longer teaches a flag its own file refuses.** When a
  seat fails at the call, the `access_refused` record on `task_failed`
  names the next ready path and the flag that pins it. Under a declared
  `run.access.via`, that next path could be a route the declaration
  itself excludes: a machine with a ready `openai` key answered a refused
  `via: codex` call with `"pin": "--access openai"`, a flag the same file
  refuses before task 1. The record now names a next path only when the
  declaration allows it, and names none otherwise.
- **A refused route declaration no longer suggests a pin that cannot help.**
  When a run is refused before task 1 (`NIKA-1800`) and this machine has
  a signed-in seat, `nika run` adds "or use a seat present on this
  machine: `--access <seat>`". It also did so when the refusal was about
  the file's own `run.access` declaration, for example a `service_tier`
  its route cannot carry. Under a declared route, another seat is refused
  by that declaration and the same seat changes nothing. The line is now
  printed only when the refusal is about what the machine lacks, such as
  a missing credential.
## [0.124.1](https://github.com/supernovae-st/nika/compare/v0.124.0..v0.124.1) - 2026-10-28

Nika 0.124.1 is the first maintenance release of the 0.124 line. On a direct seat route, without
`run.access.protocol: acp`, an `infer:` task with `vision:` files no longer runs without its
images: the run refuses it with `NIKA-432` before the seat starts, `nika check` blocks RUN READY
on the same code, and both name a route that carries the files; `temperature`, `max_tokens` and
`thinking` are still neither sent nor refused there. `nika run --dry-run` refuses what the run's
seat refuses. Where `nika check` said a mock rehearsal, or a file with no model route, dials
nothing, it names the tasks that can still reach the network; its estimate line adds the paid
media calls and the least they cost instead of reading `≤$0.0000`, and `nika arm` prints a cost
floor's refusal code once. `nika explain` answers the MCP pin refusals `NIKA-MCP-003` to
`NIKA-MCP-006` and leads `NIKA-PARSE-001` with the causes its refusal names, a file saved with a
byte-order mark is framed on the right line, and `nika serve` lists the files a `for_each`
fan-out saved. The workflow language, the file format and the SDK protocol are unchanged.

### Changed

- **The SDK guard reads what the published `@supernovae-st/nika` 0.124.0 closes.**
  `published_sdk.json` is re-read from that package's `dist`, and every closed set of 0.123.2
  is unchanged. The one runtime change in that `dist` names three more native task frames
  (`task_skipped`, `task_cancelled`, `task_cache_hit`) in a mapping that reads any other frame
  as an engine event, so it closes nothing new.

### Fixed

- **`nika check` no longer says a mock rehearsal dials nothing.** With
  `--model mock/echo`, the ACCESS row read `mock · never dials · nothing to
  judge` and a file with no `infer:` or `agent:` task read `access ready n/a
  · nothing dials`, while its `nika:fetch` reached the network. The mock row
  now says `this model route never dials` and, under it, names the tasks
  whose own effects can still reach the network (a fetch or webhook to a
  real host, an MCP tool, a paid media call), judged on the PERMITS row; the
  moot line reads `access ready n/a · no model route` and names them the
  same way. Both lines name three tasks and count the rest (`· +17 more`);
  `--json` lists every one as `verdicts.network_tasks`, and the TUI's ACCESS
  READY reason no longer says `nothing dials`.
- **A workflow saved with a byte-order mark is framed on the right line
  (#1608).** The parser strips a leading UTF-8 byte-order mark, but every
  span then indexed the stripped text, 3 bytes left of the file `nika check`
  frames: an unknown key at column 1 was framed at the end of the line above
  it (`5:24` instead of `6:1`), and the `--json` spans, trace and debugger
  lines carried the same shift. Spans now index the text the caller passed
  in, byte-order mark included. The frame and the browser checker's `col`
  count line 1 from the character after the mark, as an editor shows it, so
  a finding on line 1 keeps its column (`1:1`, as without the mark). A file
  without one is unchanged.
- **A direct seat refuses an `infer:` with `vision:` instead of running it
  without the images.** On a seat route without `run.access.protocol: acp`
  (Codex's `codex exec`, Claude Code's `claude -p`, Grok Build, GitHub
  Copilot CLI), the files were judged and read, then never sent: the command
  carries the prompt, system and model (and a `schema:`, except on Copilot),
  never an image. The run now refuses the task with `NIKA-432` before the
  seat is spawned, as the ACP route refuses a `vision:` URL, and `nika
  check` blocks RUN READY with the same code. Both name `run.access: { via:
  <seat>, protocol: acp }` where that seat's ACP one-shot carries `vision:`
  files and the rest of the task; a task that also sets what that session
  refuses (`temperature`, `max_tokens`, `thinking`, a `vision:` URL) is
  pointed at an API route, with those fields named. `temperature`,
  `max_tokens` and `thinking` are still not sent on the direct route.
- **`nika run --dry-run` refuses what the run's seat refuses.** An `infer:`
  task on a Codex or Claude Code seat over ACP that sets `max_tokens`,
  `temperature`, an enabled `thinking` budget or a `vision:` URL, an
  `agent:` with a `tools:` whitelist on a seat, and an `agent:` declaring
  `images:` with no seat previewed as `dry-run · plan only`, then the run
  refused them before anything was sent (`NIKA-432`, `NIKA-465`). The
  preview now refuses with the same lines `nika check` blocks `run ready`
  on, and exits 2 like its other file refusals.
- **`nika explain` answers the MCP pin refusals a run sends you to (NIK-254).**
  A run refused with `NIKA-MCP-006` printed `fix: nika explain
  NIKA-MCP-006`, and explain answered `unknown code` for it and for
  `NIKA-MCP-003`, `004` and `005`, the other codes the pin store mints. Each
  now says what failed and the remedy the refusal prints: re-approve a
  drifted server deliberately (`nika mcp approve <server>`), restore or
  re-enroll an untrusted `.nika/mcp_pins.json`, check the OS sandbox an MCP
  server is confined by, approve the server a tool belongs to. The MCP
  `nika_explain` tool answers the same.
- **Paid media no longer reads as free or doubles its code on the cost
  lines.** `nika arm` printed `NIKA-1709 · NIKA-1709 · refusing to start: …`
  for a beat a cost floor refuses, a paid media call included, and broke the
  line before `(budget_floor)`; the readiness blocker now carries the
  sentence alone, the code once on its own field, in the text and in
  `--json`. The audited line of `nika check`, and the green answer of the
  MCP `nika_check` tool, read `est out ≤$0.0000` for a workflow whose only
  spend is a priced media call; they now add the calls and the least they
  cost (`+ 1 paid media call from $0.0006`), counted as the `--max-cost-usd`
  floor counts them: a literal `for_each` multiplies its call, while a call
  behind a `when:` gate, over an expression `for_each` or made by a child
  workflow is named as one only the run counts. Metered calls are named
  beside unpriced ones.
- **`nika explain NIKA-PARSE-001` names the causes the refusal names
  (#1608).** Its checklist sent a `prompt: Note: here` author looking for a
  missing `:` when the line holds one too many. It now leads with the causes
  the refusal itself names on the pointed line: a `: ` inside a plain value
  (quote the whole value, use a block scalar, or put a key of its own on its
  own line), an unquoted `${{ … }}` inside a flow mapping (quote it), and a
  `{` or `[` the file never closes; then a stray tab, unbalanced quotes, a
  key missing its `:` and a de-commented modeline.
- **`nika serve` lists the files a `for_each` fan-out saved.** A job whose
  `for_each:` called `nika:chart` once per item wrote each item's file, yet
  `GET /v1/jobs/{id}/artifacts` listed only the single calls' files: the
  manifest read the tool from an `invoke · <tool>` note, and a fan-out's
  note is `for_each · <n> items`. A fan-out's tool is now read from the
  task's own journal: the one tool every tool-plane permit decision of the
  task allowed, when every item settled ok, no agent frame came from the
  task and its value holds one answer per item. Each item's file is listed
  with its journaled size and digest and downloads like any other. A
  fan-out with a recovered or failed item still lists none of its files.
## [0.124.0](https://github.com/supernovae-st/nika/compare/v0.123.2..v0.124.0) - 2026-10-27

Nika 0.124.0 opens the 0.124 release line with media capabilities that workflows need, so a
contact sheet or a narration no longer needs `exec magick montage` or `exec /usr/bin/say`:
`nika:image_compose`, the 30th builtin, lays 1 to 64 PNG images out as one grid, row or column,
and `nika:tts_generate` speaks with no key, offline, through `provider: system`. An `agent:` task
on a Codex seat hands the images it generates to the next task as values (`images:`, saved inside
`permits.fs.write` from the bytes its own session received). Over ACP
(`run.access.protocol: acp`), an `infer:` task on a Codex or Claude Code seat carries a `schema:`
and its `vision:` files, and `nika check` blocks what the seat cannot carry instead of passing
what the run refuses; on a direct seat route, without that protocol, the run still sends no
`vision:`, `temperature`, `max_tokens` or `thinking`, and `nika check` does not say so.
`nika doctor` and `nika welcome` name the `nika` that is running and every other copy on `PATH`,
and the first parse refusals a newcomer meets teach the shape they refused and point at the line.

Some workflows that 0.123.2 checked clean or ran unattended are now refused or paused.
`NIKA-SEC-009` follows untrusted content through a child workflow call and through a file a
builtin reads; a blocking `nika:prompt` that dominates the path still clears it, but a gate
inside the child does not count for its caller: put the gate in front of the call.
`NIKA-SEC-014` holds a confirm gate in front of a child workflow that acts: a bare
`after: { ask: success }` edge still runs the child on « no », so bind the answer on the call
(`with: { go: "${{ tasks.ask.output }}" }` and `when: ${{ with.go == true }}`), as an inline
effect already needs. On the `openai` provider, `nika check` refuses a `temperature:` on
`gpt-5`, `gpt-5-mini`, `gpt-5-nano` and the o-series, which accept only the default: drop it
there. On a Codex seat, a command that asks to leave its sandbox always pauses for you, under
`permits.exec: true` too, and a turn Codex reports as failed fails its task instead of
succeeding with the error as its output; what Codex does without asking, such as opening pages
with its hosted web tool or calling MCP tools marked read-only, is stated by `nika check`, not
bounded by `permits:`. On a seat, one `--answer` now decides one ask per task: an attempt that
`retry:` replays pauses on its ask instead of reusing the answer.

A `vision:` file is now a read inside `permits.fs.read`, a breaking change. 0.123.2 sent any file
an `infer:` task named to the model provider, under any `permits:` or none, and read a relative
path from the process directory. The run now reads each file under the workflow's own boundary,
on its resolved path, before any provider call, and fails `NIKA-SEC-004` outside it. `nika check`
refuses an ungranted literal path (`NIKA-AUTH-006` with no `permits:` block, `NIKA-SEC-004`
outside a declared one), and refuses a computed path, or a computed `images: { save: }`, under a
boundary that grants no path at all (no block, `permits: {}`, an empty `fs.read` or `fs.write`).
Grant each file in `permits.fs.read`: `nika check --infer-permits` prints the block for a literal
path, but for a computed one only a note and a partial `fs`, so the grant must cover every file
the run may name, for example `fs: { read: ["./in/**"] }`. A `source: url` entry needs no grant,
a secret in a vision entry now needs `egress: [{ to: "infer" }]` like one in a prompt, and
`NIKA-SEC-009` follows untrusted content that picks a vision file, as for a `nika:read` path.

A run under a cost cap now bills paid media at dated public prices and refuses what it cannot
price, a breaking change for capped runs, scheduled beats included: a beat always runs under
its `plafond:`, and a project `ceiling:` caps a plain `nika run`. Under a cap,
`nika:image_generate` on OpenAI `gpt-image-2` or one of the five Gemini image models the catalog
prices, and `nika:tts_generate` on OpenAI `tts-1` or `tts-1-hd` or a priced ElevenLabs model, are
floored at launch and billed against the cap; OpenAI's `gpt-image-2.5-sunburst` and
`gpt-image-2.5-flare` publish token rates only, so they are billed from the usage they report and
refused at launch only under a zero cap. 0.123.2 ran these OpenAI, Gemini and ElevenLabs calls
with no bound (xAI images were already floored and metered). `nika check` shows each paid media
call's floor, rate and price date, or says it is `METERED`, `UNPRICED` or `DECIDED AT RUN`. A
paid call the catalog cannot price is refused before any request, with `NIKA-1709` at launch, or
with `NIKA-1704` when only the run decides its provider or model or an `agent:` makes it through
its tools: OpenAI's default voice `gpt-4o-mini-tts`, `gpt-image-1.5` and `gpt-image-1-mini`
(which OpenAI shuts down on 2026-12-01), and any model id the catalog does not list. `nika arm`
shows a beat on one of them UNREADY. For speech, set `model: tts-1` or `tts-1-hd` (OpenAI shuts
all three of its speech models down on 2027-01-06), use a priced `elevenlabs` model, the keyless
`provider: system` voice, which bills no provider and runs under any cap as unpriced local work,
or `local` / `mock`; for images, use `gpt-image-2` (the default) or a 2.5 model. A paid call the
catalog cannot price runs only with no cap, and the settlement then counts it unpriced.

The file format and its nine top-level keys are unchanged and the SDK machine protocol stays 2;
the language gains `nika:image_compose`, `provider: system` and the `images:` field of `agent:`.
0.124 is the current release line. Work stays focused on maintenance, security and the media
capabilities workflows need; native video is planned for 0.125. Known limits are listed in plain
words, each with a way around it:
[Known limits in 0.124](https://docs.nika.sh/changelog/known-limits). In short: 0.124.0 makes no
video; `nika:image_compose` reads PNG only; the system voice needs macOS `say` or Linux
`espeak-ng`; only a Codex seat delivers images to `images:`; a seat carries no `temperature`,
`max_tokens` or `tools:` whitelist; the VS Code extension stays at 0.120.

### Added

- **`nika doctor` and `nika welcome` name which `nika` is running.** A
  shadowed install used to read healthy: no path, no installer, no other
  copy. Doctor now prints the running binary's resolved path, version,
  commit and installer (read from the path: `install.sh`, `homebrew`,
  `npm-global`, `npm-payload`, `cargo`, `nix`, `release-dir`,
  `source-build`, else `unknown`), says what typing `nika` runs, warns
  when that is another build, and lists every other `nika` on PATH with
  the version it reports. Each copy is asked `--version` under a
  two-second deadline: one that hangs is reported as unknown, one that
  fails warns, neither is fatal. A relative or literal-`~` PATH entry is
  never executed, and a copy that is asked runs from `/` with no
  credential in its environment and no relative PATH entry; its whole
  process group is killed after the answer. A removal is suggested only
  for a known install layout. `--json` adds `running`, `others[]` and
  `others_truncated`; `nika welcome` says the same in one line under
  `this binary` and carries the same keys.
- **An agent application's images are task values.** An `agent:` task on a seat can declare
  `images:` (spec 02 §Harness images): `.output` becomes `{ message, images }`, each image with
  its `index`, `path`, `sha256`, `size_bytes`, `mime_type`, `width` and `height`, and
  `images: { save: <path> }` has Nika write the received bytes through `nika:write`, inside
  `permits.fs.write`: every path the save can take (image *n* at `-n`, up to four) is judged
  before any file is written, and a save rides the same-path law (`NIKA-SEC-012`) like a
  `nika:write`. `check` types `.output` as `{ message, images }`, so
  `.output.images[0].path` and `.output.message.<key>` resolve and a schema key read at the
  top is refused. Only the bytes sent on the task's own session count: the path an
  application reports (Codex's `~/.codex/generated_images`, which every session shares) is
  never opened, so a workflow no longer asks Codex to copy its image and cannot pick up another
  session's. A save with no image received fails the new `NIKA-AGENT-007`. The image's
  `agent_image_observed` frame records the `saved_path`, and `nika serve` lists the file among
  the run's artifacts with the received digest. On an API or local model, `check` and the run
  refuse `images:`.
- **`nika:image_compose` assembles several PNG images into one.** The 30th
  builtin (stdlib §Media graduate #5) lays an ordered list of 1 to 64 inputs
  out as a `grid` (columns and rows derived from the count when absent), a
  `row` or a `column`; each image keeps its own size, is scaled to fit its
  cell with the aspect kept (`fit`), or is scaled and center-cropped to fill
  it (`fill`), with `gap`, `padding` and a `background` colour. Like
  `nika:image_fx` it is pure, zero-dependency pixel work: the same input
  bytes and args give the same PNG bytes, the `image_compose/v1` recipe rides
  the tEXt chunk, and the output carries path, sha256, width and height,
  never bytes. Every input is an `fs.read` that `nika check` judges, infers
  and counts as a use, and `out:` an `fs.write`; caps on each input's side,
  the summed input pixels, each scaled `fit`/`fill` box and the canvas fail
  `NIKA-BUILTIN-IMAGE_COMPOSE-005` from the PNG headers before any
  decompression (an input count outside 1..=64 is an argument, `-001`), an
  input with a second IHDR is `-004`, and each of the six codes has its
  `nika explain` lesson. Its inputs are file reads the `NIKA-SEC-009`
  content flow follows, like `nika:image_fx`'s. A contact sheet no longer needs `exec magick
  montage`.
- **`nika:tts_generate` speaks with no key through `provider: system`.** The
  host's own synthesizer, offline: macOS `/usr/bin/say`, or Linux
  `espeak-ng` found in an absolute directory of the engine's `PATH`. The
  engine starts it itself as part of the builtin, with the text on standard
  input, so the workflow needs no `permits.exec`. The text is spoken as
  text: every `[[` that `say` reads as an embedded command (`[[volm 0]]`
  would mute the file) or `espeak-ng` as phonemes is broken by a space, and
  the audio is capped at 64 MiB. The WAV still lands under `output_dir:`
  through `permits.fs.write`. `nika check` names the program in its plan and
  refuses a literal `model:` other than `say`/`espeak-ng` or a voice that is
  not a host voice name, as the run does. Every start is journaled as a
  `permit_checked` frame on the `builtin` plane, and the cost is reported as
  unpriced local work (`cost_usd: null` · `cost_unpriced: local_model` on
  the output and the task's frame), never as priced or free; only this
  builtin can label the run's cost that way. A named voice must be installed
  (`say` would otherwise read with its default voice), and a host with no
  synthesizer refuses `NIKA-BUILTIN-TTS_GENERATE-002` naming what is
  missing, with no fallback to another provider. A narration no longer needs
  `exec /usr/bin/say`.

### Changed

- **Capped runs bill common paid media at dated catalog prices, and refuse what the catalog cannot price, beats included.** A beat always runs under its mandatory `plafond:`, and a plain `nika run` in a project with `ceiling:` runs under that cap. Under a cap, `nika:image_generate` on OpenAI `gpt-image-2` (the default) or Gemini (`gemini-3.1-flash-image`, the default, `gemini-3-pro-image`, `gemini-2.5-flash-image`, `gemini-3.1-flash-lite-image`, `gemini-nano-banana-2.1`) and `nika:tts_generate` on OpenAI `tts-1` or `tts-1-hd` or on ElevenLabs (its models mapped to the price page's families) now run, floored at launch and billed against the cap; OpenAI's `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare` publish token rates only, so they run billed from the usage they report, refused only under a zero cap. 0.123.2 ran these OpenAI, Gemini and ElevenLabs calls with no bound (xAI images were already floored and metered). A paid call the catalog cannot price is refused before any request (`nika arm` shows the beat UNREADY with NIKA-1709): OpenAI's default voice `gpt-4o-mini-tts` (no per-call price is published and its speech response reports no usage), `gpt-image-1.5` and `gpt-image-1-mini` (shut down by OpenAI on 2026-12-01), and any model id the catalog does not list; the refusal names every model the catalog prices. A beat on the default OpenAI voice is therefore UNREADY: set `model: tts-1` or `tts-1-hd` (OpenAI shuts every speech model down on 2027-01-06), use a priced `elevenlabs` model, the keyless `provider: system` voice (it bills no provider and settles as unpriced local work), or `local` / `mock`. Only a run with no cap at all makes a paid call the catalog cannot price.
- **The SDK guard reads what the published `@supernovae-st/nika` 0.123.2 closes.**
  `published_sdk.json` is re-read from that package's `dist`. Every closed set of 0.123.1 is
  unchanged, and one is new: an artifact manifest's `verdict` must be `sealed` or `anchored`,
  the two values `GET /v1/jobs/{id}/artifacts` serves. The manifest and its entries are read
  open otherwise, so a later key rides through.
- **The embedded language pack follows nika-spec `5cd95cd`.** `SPEC_PIN`
  and `crates/nika-pack/pack` advance together from `c5dcd97`, the commit
  0.123.2 embeds. nika-spec#397 (`aac8de5`) adds the contract of
  `nika:image_compose` and the keyless `system` voice: `canon.yaml` counts 30
  builtins, the schema's builtin enum and the stdlib gain
  `nika:image_compose`, its caps list each input side, the summed pixels,
  each scaled box and the canvas, the `system` voice speaks its text as text
  with its audio capped at 64 MiB, and the `image-compose-sheet` example
  joins the pack. nika-spec#399 (`ebc6448`) makes the images an agent
  application delivers on a seat a value of the `agent:` task: the schema
  gains `images: { save: }`, chapter 02 its §Harness images, chapter 17 the
  `saved_path` of a saved image and the error registry `NIKA-AGENT-007` (112
  codes), and a seat's `infer:` meets a JSON or JSON Schema need when the
  engine validates its answer. nika-spec#400 (`479b0bc`) makes an `infer:`
  `vision:` file a read under `permits.fs.read` (`NIKA-AUTH-006` under an
  absent block, `NIKA-SEC-004` outside a declared one, and a refusal at
  check, whatever the path's form, under a boundary that admits no read; a
  `source: url` entry needs no grant), makes every `vision:` entry a
  provider-egress sink of its task (`NIKA-SEC-006`, `NIKA-SEC-009`), and
  bounds an `images: { save: }` to four files and names it, with its `-2` to
  `-4` siblings, in the same-path law (`NIKA-SEC-012`). nika-spec#401
  (`5cd95cd`) lets the `cost_usd` of `nika:image_generate` and
  `nika:tts_generate` come from the engine's dated price catalog, marked by
  `cost_basis` with the rates' date in `cost_as_of`, and keeps it `null`
  when neither the provider's report nor the catalog yields a figure.

### Fixed

- **`nika check` accepts the `$key` variables `nika:jq` binds from its
  input.** At run, every key of an object `input:` is a jq variable, so
  `input: { rows: [...], batch: B1 }` with `$rows | map(. + {batch: $batch})`
  works, but the check refused it with NIKA-VAR-005 « undefined filter or
  variable `$rows` » and the documented form could never run. The check now
  binds the same names; a templated `input:` leaves an undefined `$name` to
  the run, and any other undefined name still refuses.
- **The first parse refusals a newcomer meets teach the shape they
  refused.** A scalar `infer:`, `invoke:` or `agent:` body (`infer: "say
  hi"`, `invoke: nika:jq`) now reflects it into its mapping (`infer: {
  prompt: "say hi" }`, `invoke: { tool: nika:jq, args: { … } }`, or
  `invoke: { workflow: ./child.nika }` for a child workflow); a bare builtin
  name is taught under its prefix (`invoke: fetch` → `tool: nika:fetch`). An
  untyped `inputs:` entry names the typed form it meant (`topic: { type:
  string, default: "x" }`, `tags: { type: { array: string }, default: [...] }`
  for a list) and the ten primitives. A builtin written as a task key
  (`fetch:` or `nika:fetch:`) says it is called through `invoke: { tool:
  nika:fetch, args: { url: … } }`. The codes are unchanged (`NIKA-PARSE-019`,
  `NIKA-PARSE-005`).
- **A YAML syntax refusal (`NIKA-PARSE-001`) names its site and its usual
  cause.** `prompt: Note: this has a colon: here` and `infer: {prompt: ${{
  inputs.t }}}` printed one raw sentence with no line frame, and the `--json`
  finding had no span. The refusal now carries the position the YAML scanner
  stopped at (where the content ends, when the scanner ran out of input), so
  `nika check` prints the `-->` frame with its caret and the `--json` parse
  finding carries `span: {start, end}` like a CONFORM finding (every
  span-bearing parse refusal does). A colon inside a plain value is told to
  quote the value or use a block scalar, or to nest a key that meant to be
  one; an unquoted `${{ … }}` inside a flow mapping is told to quote the
  expression; a flow mapping or list the file never closed is told so.
- **A bare `{{ name }}` that names a declared input or a `with:` key draws
  the `[silent-literal]` hint.** `{{ inputs.name }}` was already named as
  literal text, but `content: "Hello {{ name }}"` passed `nika check` in
  silence and the run wrote the braces. The hint now names the reference it
  meant: `${{ with.name }}` for a `with:` key of that task, otherwise
  `${{ inputs.name }}`. A bare word that names neither stays silent, and
  so does a dashed name, whose dotted reference would not parse.
- **`nika welcome` no longer calls an API key « ready now ».** The key rung
  read `OPENAI_API_KEY is set · ready now · billed per run`, but nothing on
  that path checks the key, and a revoked one answered 401 on the first
  paid call. The rung now says the variable is set and is not checked until
  the first call; `welcome --json` carries the same reason.
- **A task-level key from another workflow dialect names the verb that owns
  it.** `prompt:`, `max_tokens:` and `system:` written straight on a task now
  point to `infer:`; `http:`, `url:`, `method:` and `request:` to `invoke:`
  with `tool: nika:fetch`; `command:`, `shell:` and `script:` to `exec:`
  and `permits.exec`; `tool:` and `args:` to `invoke:`. The field list still
  follows, so `check --fix` and the editor repairs read it as before. `nika
  explain NIKA-PARSE-005` now teaches that list and those mappings instead of
  sending the author to `nika spec --schema`.
- **`nika serve` says so when the project arms nothing.** In a project whose
  `nika.yaml` carries no `arm:` entry, `serve --once` exited 0 in silence,
  `serve --dry` never returned and a bare `serve` waited without a word.
  `--once` and `--dry` now print the sentence `nika arm` prints, plus the
  `--bind` command and the token recipe for running workflows over HTTP,
  and exit 0. The resident prints the same lines once on stderr and keeps
  watching `nika.yaml`, so arming it later still takes effect. The token
  recipe, here, in the `--bind` refusal and in `nika doctor`, now creates
  `.nika/` before it writes the token, so it works in a project fresh from
  `nika init`.
- **`nika check` judges a declared route over this machine's access rows.**
  A file that declares `run.access` (a `via:` or a `protocol:`) and has no
  `infer:` or `agent:` task was judged over no rows, so ACCESS said the binary
  was built without agentic CLI adapters and `run ready` was refused, while
  `nika run` resolved the same route and finished. Check now also probes for
  a declared route, as the run does, beside a task that dials a model and an
  `--access` pin.
- **`nika check` no longer calls the members of a folded group dead spend.**
  A task that declares `group: <name>` is read by every `${{ group.<name> }}`
  fold, but the `[dead-spend]` hint only counted `tasks.<id>.output`
  references and told the author to remove each member. The hint now reads
  group membership with the same law the DAG uses to fold.
- **An image save names at most four received images.** The four-image
  bound counted path-only reports, so one delivered image beside four
  reported paths was refused; and the agent verb wrote a fifth image from a
  lent backend at a `-5` path no check judged. The bound now counts images
  whose bytes arrived, and the writer refuses a fifth before any write.
- **A turn Codex could not complete no longer settles as a success.** codex-acp returned a
  failed turn (a 400 `model_not_found`, a retry limit exhausted on 429) as answer text and
  `end_turn`, so an `agent:` or `infer:` task on the Codex seat succeeded with the error as its
  output. Nika now advertises the `sessionFailure` extension, which codex-acp and
  claude-agent-acp implement, and the failure the adapter then reports fails the task with its
  own error: transient (`NIKA-1804`) when the adapter offers a retry, an expired sign-in when it
  asks you to sign in again, a refusal (`NIKA-1805`) otherwise. `nika explain` now describes
  both codes for such a failed turn. An adapter without that extension still returns its error
  as text.
- **`[jq-as-map]` no longer fires on a binding whose scope has ended.**
  `map(. as $r | {n: ($r.a | length)})` binds each element, so the map that
  encloses the binding maps what it was written to map, and so does a later
  map of its output (`map(. as $r | …) | map(.n)`); the run's result was
  right. The hint only checked that a `. as $name` binding and a bare `map(`
  both appeared; it now counts a bare `map(` only inside a dot binding's
  scope, which ends at the bracket that closes the construct the binding sits
  in, so `. as $c | map(.n)` still draws it.
- **`scan` and `IN` in jq expressions give jq's results.** `scan` with a
  regex that has capture groups returned each whole match where jq returns
  the captures (`"abab" | [scan("(a)(b)")]` gave `["ab","ab"]` instead of
  `[["a","b"],["a","b"]]`), and `IN(...)` was an undefined filter. `IN/1`,
  `IN/2` and `scan` over a match where every group takes part now follow
  jq 1.7 in `nika:jq`, output bindings, `nika check` and the compile
  verifier, which load the same definitions. A group that takes no part in
  a match (`a(b)?` on `a`, one side of `(a)|(b)`) still differs: jq yields
  `null` for it, while this engine leaves it out, and yields the whole match
  when no group took part.
- **`--max-cost-usd` and the run settlement now see paid media spend.** `nika:image_generate` and `nika:tts_generate` on OpenAI, Gemini and ElevenLabs billed their provider and reported no `cost_usd`, so a capped run never charged them and the settlement left them out (a run whose only spend was such a call settled `unmetered`). The engine now carries a hand-sourced media price table apart from the models.dev snapshot: each row names its provider, model, unit (per image by size class and quality, per million characters, or per million tokens of text input, image input, image output and text and thinking output), USD rate, official source page and the date it was read (2026-10-27). The builtins bill every priced call from it, each response on its own: at the token rates times the usage it reported, at the per-image price times its images when an image response reports no usage (the dearest row over what the request leaves open, such as `quality: auto`), or at the per-character rate times the characters sent, and say how on the output and the manifest (`cost_basis`, with the rates' date in `cost_as_of`); xAI images keep the spend xAI reports (`provider_reported`). A paid call that fails after its provider billed keeps that spend: a Gemini `n:` batch whose later request fails keeps the bill of the images already rendered, a render that fails after its answer keeps its bill, and a request that broke after it left is counted unpriced. The run ledger debits all of it as it debits a model's, per attempt, so a retry is judged against what is left. Under a cap every paid media call is judged on its rendered arguments before its request, a `${{` in a rendered text being text the provider bills: a provider, model, `quality:`, `size:`, `n:` or `text:` only the run decides refuses NIKA-1704 when the catalog cannot price the call or its floor exceeds what the cap leaves. An admitted call holds its floor until it returns, so the items of a capped `for_each` fan-out, parallel or not, cannot launch past the cap together; a call dropped in flight (a `fail_fast` sibling, a `timeout:`) is counted unpriced and its floor stays consumed; once a paid call billed without a figure, a call billed only from its usage is refused under the cap. An `agent:` loop judges its media tool calls the same way against what its task's remainder leaves after the loop's own model turns, its returned tools and the floors its concurrent calls hold, so a model batching image calls cannot spend one remainder twice; concurrent agent tasks each judge against their own snapshot, as text siblings in flight do. A paid call the catalog cannot price runs only with no cap and is counted in `spend.unpriced_calls`, so the settlement reads `unpriced` or `partially_priced`; a child workflow's unpriced calls reach its parent's settlement, and a grandchild's unpriced media is refused at the root's launch. The run's prologue records the table (`media_pricing`, beside `pricing` and `admission_pricing`), and `nika trace verify` refuses the cost replay of a journal that pinned other media rates. `nika check` names each paid media call on its COST rung with its floor, the rate and unit that set it and the table's date (METERED for a call billed from its usage or, for speech, per character of the text the run renders; UNPRICED; or DECIDED AT RUN), including a composed child's and the paid media an agent's tools can reach, and lists `unpriced_media_cap`, `priced_image_over_cap` and `priced_speech_over_cap` in `failure_plan`.
- **`nika check` refuses a `temperature:` on `openai/gpt-5`, `gpt-5-mini`,
  `gpt-5-nano` and the o-series.** These models accept only the default
  temperature, and OpenAI answers any other value with HTTP 400 at run
  (« Only the default (1) value is supported »), while check showed
  `capacity fit ✔`. The catalog now records that GPT-5, GPT-5 mini and
  GPT-5 nano (and their 2025-08-07 snapshots) take no temperature, as it
  already did for the o-series, and the fixed-sampling finding covers
  OpenAI's own API beside Anthropic's for the models the catalog lists. A
  Session label call on these models, a dated snapshot included, no longer
  sends a temperature. The finding does not yet name a dated snapshot id
  (`gpt-5-mini-2025-08-07`), which the catalog's model list does not carry.
  OpenRouter and other compatible routes, which drop the control, and the
  later dot variants (`gpt-5.1` onward) are unchanged.
- **An oversized PNG input is a size cap, and a PNG has one IHDR.**
  `nika:image_fx` refused an input over 16384 pixels on a side as
  `NIKA-BUILTIN-IMAGE_FX-004` (corrupt, re-produce it); the stdlib files that
  input cap under `-005`, which it now returns. The PNG decoder behind
  `image_fx` and `image_compose` now refuses an IHDR that is not the first
  chunk, or a second IHDR, so the size a pixel budget judged from the header
  is always the size decoded.
- **`check` and the run agree on what a Codex or Claude Code seat can carry.** `check` said
  `run ready` for `schema:` and `vision:` on a seat while the run refused them. An `infer:`
  task over ACP now carries a `schema:`: the prompt carries the schema and Nika validates the
  answer, and each repair is a new one-turn session that receives the earlier answer and the
  errors (`NIKA-INFER-002` after the budget). Its `vision:` files ride as ACP image blocks when
  the application declares image prompts at `initialize` and the file carries an image
  signature. An `agent:` task's `schema:` is validated on the seat's final message, with no
  repair turn. What a seat still cannot carry (`temperature`, `max_tokens`, an enabled
  `thinking` budget, a `vision:` URL on `infer:`, a `tools:` whitelist on `agent:`) is a `check`
  blocker, and `check` no longer suggests `max_tokens` or `max_tokens_total` on a seated task.
- **A `--var` refusal names its code in the `--json` envelope.** An unknown
  input, a value its declared type refuses and a malformed pair reached
  `nika run --json` callers as `{"error":{"code":null,…}}`, while the
  `--inputs-json` channel and the server say `unknown_input` and
  `input_type_mismatch`. The envelope now carries `unknown_input`,
  `input_type_mismatch`, `malformed`, `env_unset`, `env_undeclared_in_ci` or
  `env_name_missing`; the sentence on stderr and exit 3 are unchanged.
- **A streamed WAV reports its real duration.** `nika:tts_generate` read a
  streamed WAV header's placeholder `data` size (`0xFFFFFFFF`) verbatim, so
  an OpenAI narration reported `duration_ms: 89478485` (24.8 hours). The
  size is now clamped to the bytes actually present, which also gives
  `espeak-ng --stdout` audio its true length.

### Security

- **`NIKA-SEC-014` holds a confirm gate in front of a child workflow call.** The consent check only looked at egress tasks written in the workflow itself, so a gate that the trifecta credited in front of an `invoke: workflow:` call was never checked to stop the child's effect on a « no » answer. `nika check` and `nika run` now judge a call as the egress-capable task its child (or a workflow below it) reaches, whatever content reaches that task: every route from a confirm gate to the call must close on a refusal, as for the inline form, and the finding names the call. A confirm gate inside the child owns the effect behind it, as inline. When only a child gate the check cannot decide from the caller stands before the effect (a `when:` on an input the caller may bind to its answer), the call gets the advisory hint instead of the refusal. A child the check cannot resolve counts as acting. `check --json` names the child task under `composed_calls[].effect`, with `effect_conditional` for the advisory case.
- **A Codex command that asks to leave its sandbox now always pauses.** In `read-only`, Codex runs
  ordinary commands in its sandbox without asking, so every command it asks about asks to run
  with no sandbox at all (`require_escalated`). Its ask carries the command as a string and the
  reason in `_meta.permission.description`, which Nika read as `execute · <prose>`: under
  `permits: { exec: true }` it was allowed, and Codex fetched a site outside its sandbox. It now
  pauses for you on the exec plane (`sandbox · command`) whatever `permits.exec` grants, with the
  command and the reason in the question and in the witness. Every pause question now names the
  command and the reason an ask carries unless its title shows it, on one line a harness cannot
  steer: control characters become spaces and invisible ones (direction overrides, zero-width
  characters) are removed, so a Claude Code title, which is the model's own command, can no
  longer erase itself on your terminal. A long command keeps its end in view (its beginning and
  end around the bytes left out and their SHA-256; whole up to 16 KiB in the witness), and a
  sandbox widening's witness names the command it is for. One `--answer` now decides one ask for
  the task: an attempt that `retry:` replays never reuses it, where a typed 429 failure could
  otherwise replay the session and approve a different command with the same answer.
  `nika check`, `nika explain harness` and docs/usage/harness-agents.md now also state what Codex
  does without asking: it calls its MCP tools marked `readOnlyHint`, opens pages with its hosted
  web tool, writes its own `~/.codex`, starts every configured MCP server and applies your global
  `~/.codex/AGENTS.md` and plugins.
- **`NIKA-SEC-009` now judges a child workflow through the call that runs it.** A parent that
  read a private file and sent data out passed `nika check` and ran when part of the flow sat
  in a child reached through `invoke: workflow:`, while the same tasks written inline were
  refused. `nika check` and `nika run` now judge each call as the tasks it stands for, through
  every workflow below it. The call is a source of untrusted content when content the child
  brings in (a `nika:fetch`, an `exec:`, an `mcp:` tool, an `agent:` whose tools admit one)
  reaches its outputs or a file it writes. It is an egress task when untrusted content reaches
  an egress task inside it: content the child brings in, the call's arguments, or a file
  written before the call that a child `exec:` reads. The parent is refused unless a blocking
  `nika:prompt` of its own dominates the path, as for the inline form; a gate inside the
  child is not credited to the caller. The finding names the child task behind the call. A
  child the check cannot read, parse or resolve counts as all of these (fail-closed).
  `check --json` lists every direct call under `composed_calls`, resolved or not, and the
  runtime's report check re-derives the trifecta over the same calls. `NIKA-DRIFT-001` no
  longer tells you to remove a grant a child needs (its program, its tools, the files and
  hosts its own `permits:` lets its subprocess reach); removing it failed `NIKA-COMP-002` or
  the child's run. A check of the source alone (an editor, the MCP `nika_check` tool) reads
  no child: its trifecta judges the parent's own tasks only, and its `NIKA-DRIFT-001` makes
  no `permits:` claim on a file that calls a child.
- **`NIKA-SEC-009` follows untrusted content through a file a builtin reads.** A gate could clear the task that saved untrusted content to a file, and a `nika:read` of that file then fed an ungated egress: the trifecta counted only an earlier `exec:` as a reader of such files, so `nika check` passed the workflow. A builtin read (`nika:read`, `nika:grep` (its default root `.` included), `nika:glob`, `nika:edit`, a `nika:decide` bundle, the files `nika:image_fx`, `nika:chart` and `nika:image_generate` read, a `nika:fetch` multipart upload) of a path the write may have reached now carries the content on, inline and inside a child workflow, and, when the write's path is literal, whatever the order: a read in the writer's own wave, or before it in the run (the next run reads the file), counts too. Paths compare lexically: folded, case-blind, a segment past ASCII may be any segment, `/private/tmp` is `/tmp`, and a templated path may be any file. A read whose path cannot reach the written one stays clean.
- **A run refuses a report that judged no child for a workflow call.** The runtime's report check re-derives the trifecta over the child calls the report resolved, and a call missing from that list counted as having no ingress: an embedder pairing a plain `nika_check::check()` with `Runtime::run` and a child runner could run a fetching child into an ungated egress. A call the report does not list for its task and target now counts as unresolved, and the run refuses with `NIKA-1707`, naming it. `nika run --task` re-checks the kept tasks with their children, so a scoped run of a composed workflow keeps its calls resolved.
- **A `vision:` file is a read inside `permits.fs.read` (breaking).** The
  `infer:` verb opened every `vision: [{ source: file }]` path itself, on
  every route (API and ACP; `nika run`, serve, Session, child workflows,
  `nika test` and rehearsal rooms), with no `permits` check: an absent block,
  a narrower `fs.read` grant, a symlink or a `..` did not keep the file from
  the model provider, and a relative path was read from the process
  directory instead of the run's root. The run now reads each file through
  `nika:read` under the workflow's own boundary, on its resolved path,
  before any provider call: outside it the task fails `NIKA-SEC-004` (one
  `permit_checked` fs frame), and a granted file that cannot be read fails
  with the read's own code (`NIKA-BUILTIN-READ-001` when no file is there,
  `-002` otherwise) instead of the internal `NIKA-432`. `nika check` judges
  a literal or bare-`const` path (`NIKA-AUTH-006` with no `permits:` block,
  `NIKA-SEC-004` outside a declared one), and refuses a computed path, or a
  computed `images.save`, under a boundary whose side grants no path at all.
  `--infer-permits` prints the grant, the drift scan counts it as used, a
  child's vision reads join `NIKA-COMP-002`, and the check card no longer
  calls such a body pure compute. The `vision:` entries are sink fields of
  the task, on check and run alike: a secret in a vision URL or path is the
  `infer` leak (`NIKA-SEC-006` unless `egress: [{ to: "infer" }]`), and
  untrusted content that picks an entry reaches the answer, so the trifecta
  (`NIKA-SEC-009`) follows it as it follows a `nika:read` path; the file
  channel follows a vision read of a file an untrusted writer left. A
  `source: url` needs no grant: the provider fetches it. Migration: grant
  each vision file in `permits.fs.read` (`nika check --infer-permits` prints
  the block); embedders hand the verb bytes they read (`VisionPart::Loaded`),
  never a path.
## [0.123.2](https://github.com/supernovae-st/nika/compare/v0.123.1..v0.123.2) - 2026-10-26

Nika 0.123.2 is the second maintenance release of the 0.123 reference line. An `agent:` task on
a harness seat (Codex, Claude Code, Gemini CLI) is now held to the workflow's `permits:` as far
as the seat allows: Nika selects the seat's asking mode, judges every ask against your grants and
pauses for network, sandbox-widening and the harness's own MCP approvals, and `nika check` says
what the seat still does without asking. `nika serve` can return the files a settled job made,
and verifies journals sealed with an explicit run key pair. A mock rehearsal no longer calls
itself offline when a step can reach the network, and `crypto-bigint` leaves its yanked release.
The workflow language, the file format and the SDK protocol are unchanged.

### Added

- **`nika serve` downloads the artifacts a settled job's sealed journal
  records.** `GET /v1/jobs/{id}/artifacts` lists them, bound to the job's
  receipt: each file `nika:chart`, `nika:image_generate`,
  `nika:tts_generate` or `nika:image_fx` saved inside the project, and each
  image an agent's harness sent, with its size, SHA-256 and media type.
  `GET /v1/jobs/{id}/artifacts/{name}` streams one, with `Content-Length`
  and `Nika-Artifact-Sha256`. Both need the bearer token, answer only once
  the job settled (`409 job_not_settled`), and serve nothing unless the
  journal verifies sealed and intact with the receipt's chain head. A
  name is looked up in the manifest, never on disk, and the file opens
  below the project root with no link on the way. One artifact is served
  up to 256 MiB. Bytes that no longer match the journal are refused
  (`409 artifact_changed`), and a file changed mid-stream never arrives
  whole. Health advertises `artifacts`; ADR-117 records the decision.

### Changed

- **`crypto-bigint` moves from the yanked 0.7.6 to 0.7.9.** Upstream
  yanked 0.7.6, 0.7.7 and 0.7.8 for a breaking change, so `cargo deny
  check` refused the lockfile and blocked every push. The crate reaches
  the build through `p384`, which verifies the Sigstore timestamp
  authority's ECDSA P-384 leaf in trace anchoring. 0.7.9 keeps the same
  dependency set and has no build script or procedural macro.
  `Cargo.lock` and `fuzz/Cargo.lock` move together.
- **The SDK guard reads what the published `@supernovae-st/nika` 0.123.1 closes.**
  `published_sdk.json` is re-read from that package's `dist`: machine protocol 2, a scheduled
  origin's `missed_first` and `missed_count`, `choice` questions and the three compile cognitions,
  on both compile generations. It still recorded 0.120.3 on protocol 1, so its test passed on a
  moved clock; it now compares what the widest resident serves with what the published SDK reads.
- **The embedded language pack follows nika-spec `c5dcd97`.** `SPEC_PIN`
  and `crates/nika-pack/pack` advance together from `6390b08`, the commit
  0.123.0 and 0.123.1 embed. The examples and templates that `nika try` and
  `nika compile` serve no longer call a mock rehearsal offline: the mock
  replaces the model, not the network, so a fetch step still reaches its
  host (#1605, the pack half of #1833). `release-notes` no longer drafts
  notes from a sample log when `git log` fails (the sample is now an
  explicit `--var rehearse=true`), and `pr-review-fanout` closes its fan-out
  on a « no » at the gate instead of failing. The spec chapters and stdlib
  pages carry current model ids and the 0.123 reference-release amendments.
  The schema and `canon.yaml` are unchanged.

### Fixed

- **`nika explain NIKA-SEC-004` and `NIKA-SEC-005` say a redirect is held to the same
  boundary.** `nika:fetch` follows a 3xx itself and judges every hop like the URL in the file:
  a hop onto a host outside `permits.net.http` is SEC-004, a hop into loopback, private,
  link-local or metadata space (or onto an undeclared loopback name) is SEC-005. Neither page
  named a redirect, and the SEC-005 closer promised that `nika check` catches it before a run;
  it now says that check catches a literal URL and that a hop is judged at run.
- **`nika explain NIKA-BUILTIN-JQ-001` names `decode: json` for `exec:` stdout.** A command that
  prints JSON hands `nika:jq` a string, and the page taught only the `nika:read` + `fromjson`
  path for files. It now names the exec side's explicit decoder,
  `exec: { command: [...], decode: json }` (`jsonl` for one value per line), whose output is
  the parsed value.
- **`nika explain NIKA-SEC-001` names the OS sandbox.** A confined `exec` child refused at run
  reads « seatbelt refused the confined process », and the page it points at named no
  sandbox. It now says that `exec` runs inside the sandbox built from `permits:` (Seatbelt
  through `/usr/bin/sandbox-exec` on macOS, bubblewrap through `/usr/bin/bwrap` on Linux, the
  backend `nika doctor` reports). It also says that a denied exit is classified as that
  sandbox's refusal from its stderr text alone, so a program's own permission error (a file
  the OS refuses inside the grant) reads the same, and the file's own permissions are worth
  checking too.
- **An unresolved `${{ fetch.output }}` now teaches the task-output form.** When the root of a
  NIKA-VAR-001 reference is a declared task id, the finding says so and names
  `tasks.<id>.output` (bound in `with:` on a body surface), and `nika explain NIKA-VAR-001`
  teaches the same `with:` binding. Neither surface names `config:` any more: the nine-key
  envelope refuses that key (NIKA-PARSE-005), so a typo'd root is no longer offered it.
- **`nika check --help` says what `--fix` really applies.** It read « Apply typed rename
  repairs and re-audit », while the repair ladder also hoists `with:` bindings (NIKA-VAR-021)
  and migrates retired forms (`needs:`, `depends_on`, `vars:`, a string `exec:` …). The help
  now names the three repair families and says that other findings stay the author's, and the
  `○ FIX` line printed when nothing applied names the same families instead of « typed rename
  suggestions only ».
- **`nika try` no longer calls its mock rehearsal offline, and names the hosts it may reach.**
  The mock seat replaces the model only: `nika try snippets/use-a-tool` still GETs
  `https://nika.sh`, and `snippets/weekly-radar` and `ceo-monday-brief` reach `hnrss.org` and
  `hn.algolia.com` once their gate is answered. The `nika try` help, the first-run shelf heading,
  the `--all` listing, the rescue tips, the `try` door blurb, the `model` glossary entry, the
  `MODELS` row of `nika check`, the `NIKA-INFER-001` lesson and the provider connection hint now
  say what is true (« on the mock model by default: zero keys, no model call; a step that fetches
  still reaches its host »), and the rehearsal line of `nika explain <file>` names the declared
  hosts (`# mock model · zero keys · may still reach nika.sh`). Before a mock rehearsal runs,
  `nika try` prints `may reach: <hosts> — the mock replaces the model, not the network` from the
  workflow's `permits.net.http`; a workflow that declares no host prints nothing new, and
  `--quiet` keeps its compact card. The network itself is not blocked.
- **`nika explain NIKA-PARSE-019` teaches the `permits.net` mapping.** A host list
  (`net: [api.example.com]`) refuses with « `permits.net` must be a YAML mapping », and the
  page answered with `tasks:` and `exec:` forms only. It now teaches the shape the parser
  accepts, `permits: { net: { http: [api.example.com] } }`, ahead of the exec lesson: list the
  host, not the URL (an `https://` entry is refused), and the one `http:` key covers `https://`
  URLs too.
- **`nika doctor --ping` no longer calls a web page on `:8080` a ready engine.** Its model-list
  sweep asked `…/v1/chat/completions/models`, a 404 on every engine, and the ping row said
  `ok … listening` for any open port. The sweep now asks the chat route's sibling,
  `…/v1/models`. Only an answer that is clearly not an API (a 2xx body that is not JSON, such as
  a web app's page) drops the `ok`: the row then reads « accepts connections but answers a page,
  not an API — not a model server », and `--json`'s `adoption_state` no longer counts it as a
  reachable engine. A 401 or 403 is named `key-protected`, a 404 `no /v1/models route` (the
  `nika model serve` sidecar), and a timeout keeps the connect verdict. Ollama's
  `{"object":"list","data":null}` reads as an empty model list.
- **A `nika try` rehearsal that pauses at a gate names the try form that finishes it.** The
  paused card said `nika run --answer approve=true FILE`, a file the verb discards when it
  returns. Under `nika try` it now says `nika try <slug> --answer approve=true` (the answer is
  pre-seeded on a fresh rehearsal, the form `nika try --help` documents), nested slugs such as
  `snippets/weekly-radar` included. It carries the rehearsal's own flags as typed (`--var`,
  `--model` with `self` kept as `self`, `--access`, `--max-cost-usd`, `--resume` and the answers
  already given), so the pasted line reruns the same rehearsal, and the rehearsal card names
  that slug too (`rehearsal · snippets/weekly-radar`, no longer the room's folded stem). A
  workspace `nika run` keeps its own card.
- **A recorded run's card names `nika trace verify`.** The run footer taught
  `nika trace outputs` and never the proof itself. When the run recorded a trace, the card
  (and the piped close) now lists `verify it: nika trace verify` beside `wrote .nika/traces`:
  zero-arg, the verb judges the workspace's latest trace and prints the head the `trace:`
  line names. The note is short enough that the card keeps its width.
- **The ADR index carries `amends`.** `docs/adr/README.md` says `docs/adr/index.json` holds the
  full relation graph, but `scripts/adr/generate-index.sh` emitted every edge except `amends`,
  so the twelve ADRs that amend another (ADR-154 amends three) read as amending nothing. The
  generator now emits it, the index is regenerated, and a hygiene self-test pins the edge.
- **Release tarballs record root ownership.** The v0.123.1 archives recorded the build runner's
  own uid (1001 on the Linux legs, 501 on macOS), so `tar -x` run as root in a rootless
  container failed to restore an unmapped owner. The packaging step now writes every member
  as 0:0 by number (GNU tar `--owner=0 --group=0 --numeric-owner` on Linux, bsdtar
  `--uid 0 --gid 0 --numeric-owner` on macOS); the archive contents, the checksum step and the
  replay of existing assets are unchanged.
- **`nika serve` attributes the journals it seals with an explicit run key
  pair.** A resident started with `NIKA_RUN_KEY_FILE` and `NIKA_RUN_PUB_FILE`
  naming a pair outside `~/.nika/keys` sealed its jobs with that pair, but
  `GET /v1/jobs/{id}/trace/verify` called the seal unattributable and both
  artifact routes refused the job with `409 artifact_journal_unverified`.
  The verifier now weighs that pair's public half after `--key` and before
  `~/.nika/keys/run-signing.pub` and the retired-key ledger, the order the
  signer takes them, so `nika trace verify` also attributes such a journal
  without `--key`. A host that names no pair verifies as before.
- **The `nika serve` OpenAPI document declares what the server answers.**
  The trace verify response's `tier`, the 412 schedule error's
  `error.currentRevision`, the schedule 422 `{findings}` body (kept as the
  published SDK reads it) and the 404 of a by-name `POST /v1/check` and
  `POST /v1/jobs` were sent but undeclared. `GET /v1/workflows/{name}` now
  says a name containing `/` is sent with literal separators, and the
  cancel route states its fixed 5-second grace before a run is
  interrupted.
- **The Session guide says what 0.123.1 suggests on an unsigned run.** It still named 0.123.0's
  `nika sign`, which signs workflow files; `/proof` and `nika trace verify` now point at the run
  key, `nika key init`, which seals later runs.

### Security

- **An `agent:` task on a harness seat is held to the workflow's `permits:` as far as the seat
  allows, and `nika check` says where that stops.** On codex-acp the session kept the adapter's
  default `agent` mode, whose own reviewer approves most actions, so a Codex task wrote `./out`
  and read files outside the project without a single `permit_checked` frame. Nika now roots the
  session in the admitted project (never the server's or the shell's directory), selects the
  seat's measured asking mode (codex-acp `read-only`, claude-agent-acp `default` instead of your
  `auto` default, gemini-cli `default`) and reads it back before the prompt or refuses to start,
  never picks a mode that approves on its own, and ends the session if the mode moves mid-turn.
  The mode, every ask and every refused client `fs/` or `terminal/` call are journaled as
  `permit_checked`, also when the run pauses on the ask (a paused trace used to carry the
  question and no decision). Asks are judged in the project's own terms: an absolute path the
  harness names now matches `./out/**`, and every path is judged where the operating system
  lands, walked through each symlink (a dangling one included) and each `..`: `out/link/../x`
  with `out/link` pointing outside the project was judged as `out/x` and allowed; a path that
  cannot be resolved now pauses. An ask for network access pauses for you even under
  `permits.exec: true`: codex-acp's SOCKS approvals (`socks5Tcp network access to <host>`) carry
  no URL and were answered `allow_once` as plain commands. So does a command asking the harness
  to widen its own sandbox, which `exec: true` also answered (codex-acp `additionalPermissions`,
  Claude Code `dangerouslyDisableSandbox`). What the mode still leaves to the harness is printed
  on every such task as a `[harness]` hint (`nika explain harness`,
  docs/usage/harness-agents.md): Codex, for one, reads any file and writes the project, `/tmp`
  and `$TMPDIR` inside its own sandbox even with no `fs.write` grant, and a Codex command that
  `permits.exec: true` admits after it asked to leave that sandbox runs with none.
  `NIKA-DRIFT-001` no longer tells you to remove a `permits.exec` or `permits.fs` grant that the
  permission bridge judges.
- **A Codex MCP tool approval no longer rides `permits.exec: true`.** codex-acp asks to approve
  its own MCP tool calls as a command ask with no command, which `exec: true` answered
  `allow_once`; it now pauses for you on the tools plane, because `permits.tools` names your
  workflow's MCP tools, not the harness's.
## [0.123.1](https://github.com/supernovae-st/nika/compare/v0.123.0..v0.123.1) - 2026-10-25

Nika 0.123.1 is the first maintenance release of the 0.123 reference line. It carries a security
update (`crypto-bigint` 0.7.6, RUSTSEC-2026-0387) and corrections to what Nika tells you on your
first runs: the welcome screen no longer advertises a hosted Nika Cloud, which does not exist;
access errors, hints and help name commands and steps that exist; `nika init --example` copies the
files its lesson reads; and a founded project keeps the `nika serve` token and job state out of
Git. The workflow language, the file format and the SDK protocol are unchanged.

### Changed

- **The repository's documents describe the released 0.123.0 and its maintenance.** The README,
  `llms.txt`, the Session guide, CONTRIBUTING, SECURITY, ROADMAP and DIAMOND say that 0.123 is
  maintained with security fixes, bug fixes and compatibility, with no hosted service and no 1.0
  launch planned; SECURITY's code table lists only codes `nika explain` resolves, and it names the
  MCP pin codes it does not. The serve operations page gives a first run that works (a project
  `nika.yaml` it keeps when one exists, a served rehearsal workflow, a health and job request, the
  served cancel route), `llms-install.md` adds the container and provenance paths and an
  uninstall that names what `~/.nika` holds, and the README and the Session guide say that runs are
  signed once `nika key init` minted a key. The README says what the TypeScript SDK's server door
  changes (`allowInsecureHttp` for plain loopback HTTP, an `idempotencyKey` and no `maxCostUsd`),
  and the Session guide lists the routes a Run admits. The README clips are captured again from
  0.123.0, and a new `sdk-doors` clip shows the SDK's two doors (the bundled engine and
  `nika serve`, with the two lines whose options change) from a capture of the published
  `@supernovae-st/nika` 0.123.0.

### Fixed

- **The `AGENTS.md` that `nika init` writes no longer says the shipped binary cannot serve models.**
  It said `nika model serve` « needs a `local-infer` build — the default binary prints the build
  recipe », but release binaries are built with `local-infer`, and 0.123.0's `nika doctor` reports
  « local inference built in ». The guide now says release binaries include it and that only a
  source build without the feature prints the recipe.
- **`nika arm --emit --mode system` states its limit instead of a coming release.** It refused with
  « la portée root arrive avec serve (W5) », promising a release. It now says « la portée root n'est
  pas émise » and that units go to the operator's session (LaunchAgents · systemd --user), with the
  same exit 2.
- **A project without `nika.yaml` is pointed at plain `nika init`.** `nika arm`, `nika arm fire` and
  `nika serve` answered « fix: `nika init --project-file` lays a commented starter », naming a hidden
  flag and leaving out what init writes. Plain `nika init` has laid that starter by default since
  #1283 (the flag only keeps older scripts working), among the project's starter files (agent
  briefs, hooks, editor and MCP wiring, a first workflow). The hint now says « fix: `nika init`
  writes the project's starter files, a commented `nika.yaml` among them (existing files are
  skipped) ».
- **Unsupported schedule policies state the present limit, not a future release.** `nika arm fire`
  refused `chevauchement: remplacer`, `après_saut: à-complétion`, `manqué: rattraper` and
  `décalage:` with « arrive avec serve v0.2 », the unit emitter said the same, and `nika arm` read
  « not supported by this firer (arrives with serve v0.2) ». No such release is planned. The firer
  now says « ce tireur ne tient pas cette politique » (this firer does not keep this policy), then
  what it does today, as before. For the two catch-up policies, `rattraper` and
  `{ rattraper: N }`, which the resident `nika serve` already keeps, the refusal adds « `nika serve`
  la tient » and `nika arm` adds « `nika serve` keeps it ».
- **A home or project path through a symbolic link is refused by name.** Nika opens its owned
  directories (the Session history under the home, among others) through real directories only. A
  path with a symbolic link on the way stopped the terminal Session with « Not a directory (os error
  20) »; the refusal now names the link and the resolved path to use instead.
- **`nika catalog --tools` names a command that exists.** Its listing began « nika tools — 29
  builtins » and ended « machine: nika tools --json », but `nika tools` is not a command and exits
  2. The listing now names `nika catalog --tools`, and its footer points at
  `nika catalog --tools --json`, the projection that carries the model-facing JSON Schemas.
- **`nika check --fix` no longer renames an argument when its value is the mistake.** A literal
  outside a builtin's closed set (`nika:inspect` `view: record`) carried its nearest member as the
  key-rename suggestion, so `--fix` rewrote the file to `records: record`. The nearest member is now
  said in the refusal (« did you mean `records`? ») and `--fix` leaves the value for you to change.
- **`nika init --example` founds a project its lesson can run in.** The example lane copied the
  workflow alone: 19 lessons that read files under `examples/fixtures/` (`csv-chart-report`,
  `meeting-actions`, …) failed their first suggested run with « file not found », and
  `10-compose-pipeline` founded without the child it invokes, so its own audit refused
  NIKA-COMP-001. Init now stages what `nika try` stages: the fixtures under the project root, where
  runs resolve them, and composition children beside the lesson in `workflows/`. Each staged file
  is a `created` row, and an existing file is never overwritten.
- **`nika init` keeps the `nika serve` token and job state out of Git.** The generated `.gitignore`
  covered only `.nika/traces/`, while every `nika serve` surface teaches minting the HTTP Bearer
  token at `.nika/serve.token` and the resident keeps job events and results under `.nika/serve/`.
  In a founded repository `git status` listed the token as a file to commit. Init's ignore section
  now covers both, as does the same adds-only section `nika compile` writes beside a workflow, and
  `NIKA.md` says so. Repositories founded earlier keep their section; add the two lines by hand.
- **The agent kit links the `nika arm` reference instead of a guide that was never published.** The
  operating and migration skills, the language rule, the session map, the kit README and the
  check-on-edit message on a `nika.yaml` finding pointed at docs.nika.sh/guides/projects-and-schedules,
  which answers 404; they name docs.nika.sh/reference/arm, and `nika init` lays the corrected files.
  The operating skill and the project reference also say what 0.123.0 fires: `sauter` and
  `rattraper-une-fois`, with `rattraper` and the bounded `{ rattraper: N }` read UNREADY. The Cursor
  manifest lists `/compile` where it still named the retired `/new`.
- **The agent kit separates signing a workflow from sealing a run.** The operating skill's audit
  checklist asked for a run « signed (`nika sign`) », but `nika sign` binds a workflow file to its
  author, and only the run key `nika key init` mints seals a journal. The checklist now asks for
  both, each with its own command. The portable plugin manifest's description opens with the
  README's « Say it once. Keep it as a file. » instead of the retired intent-as-code pitch.
- **The editor's `secrets` hover names the sources that work.** `nika lsp` described `secrets:` as
  « Masked, vault-backed references ». Spec 01 §secrets withdrew `vault`, and no engine resolves it.
  The hover now says the references point at an environment variable (`source: env`) or a file
  (`source: file`).
- **A registry child is refused as a present limit, not a coming lane.** `nika check` and the run
  both refused a pinned `registry:owner/name@1.2.0` child with « registry child execution lands with
  the registry lane », which promised a release. They now say « this engine does not run registry
  children; run a filesystem child (`./child.nika`) », still as NIKA-COMP-001 and in the same words
  at check and at run.
- **`nika trace verify --help` lists every exit the verdict returns.** The help named exits 0, 2 and
  3, while a journal that never reached a terminal frame (a run in flight or a writer that died)
  exits 5 INCOMPLETE. It now lists 5, names buried seals and replay divergence under exit 2, and
  counts its four named refusals as four.
- **`nika try` no longer hands over a command for a file that is gone.** After a rehearsal, the run's
  closing line said « explore: nika run /var/folders/…/nika-try-01-hello/01-hello.nika --json > … »,
  but the rehearsal room is deleted when `try` returns, so that command could never run. A try
  rehearsal now ends on its own line, which names the example and `nika compile --list`; workspace
  runs keep their explore line.
- **Refused type names state their status instead of a coming wave.** A `returns:` or `type:` naming
  `result`, `artifact`, `secret` or `money` was refused with « is reserved — lands with outcomes
  (W5) » (or W4, W-DEC). Spec 09 keeps only `artifact<mime>` reserved, never as a delivery claim,
  and withdrew the other three from the grammar. The refusal, still NIKA-TYPE-001, now says « is not
  a type — reserved by the spec and not implemented » for `artifact` and « withdrawn from the type
  grammar (spec 09) » for the others.
- **An unsealed journal now points at the run key, not at `nika sign`.** `nika trace verify` said
  « `nika sign` seals future runs » and the Session's `/proof` said « `nika sign` signs future runs ».
  `nika sign` binds a workflow file to its author; it never seals a run. Both now say that a run key
  (`nika key init`) makes later runs seal, which is what turns the next journal SEALED.
- **Secret teachings no longer offer the withdrawn `vault` source.** The refusals for a missing or
  unknown `source:` listed « vault · env · file », and the `secrets-store` hint said a `vault`
  secret would work « until vault resolution ships » and fail with NIKA-1702. Spec 01 §secrets
  withdrew `vault`, and the run reports the failure as NIKA-VAR-001. The teachings now list `env`
  and `file`, and the hint says `vault` is withdrawn from the contract and names NIKA-VAR-001. A
  `vault` secret still parses as before.
- **The welcome screen and the access errors no longer advertise a Nika Cloud.** `nika welcome`
  said « Local first. Cloud when you want it. » and « Coming: Nika Cloud · our models, our tools, your
  workflows online. », and its envelope listed an unavailable `cloud` rung; access errors told the
  person to « pick Nika local / Nika Cloud ». There is no hosted Nika service: the screen now opens
  with the README's « Say it once. Keep it as a file. », the cloud rung is gone, and the errors
  point to an API key or a local engine (`nika doctor --verbose` lists them). The featured local
  model is described as open weights that need no API key, not as « ours » or « free » (a local
  model is unpriced).

### Security

- **Big-integer arithmetic is past its October constant-time advisory.** `crypto-bigint` 0.7.6
  keeps subtraction, zero counting, division and Montgomery multiplication free of secret-dependent
  branches (RUSTSEC-2026-0387), in the workspace and fuzz lockfiles.
## [0.123.0](https://github.com/supernovae-st/nika/compare/v0.123.0-preview.1..v0.123.0) - 2026-10-23

Nika 0.123 is the open-source reference release of Nika: the Rust engine, the `nika` command
line and its terminal workspace, and a TypeScript SDK (`@supernovae-st/nika` 0.123.0) that runs
`.nika` workflows from an application. After this release the project is maintained: security
fixes, bug fixes and compatibility. No hosted service is planned.

What it does:

- **Say it once, keep it as a file.** Describe an outcome in the terminal workspace (`nika`);
  Nika asks only for what is missing, writes a `.nika` workflow, checks it and, where it can,
  rehearses it on a copy of your files before you save or run anything.
- **Audited before it runs.** `nika check` reads the whole file first: the effects it may
  perform and nothing else (`permits:`), where secrets go, literal credentials, cost estimates
  and the lethal trifecta, and it names the repair where it knows one.
- **Metered and recorded while it runs.** `--max-cost-usd` caps a run, every run is recorded
  under `.nika/traces/`, and a run paused at a human gate resumes with the person's answer.
- **Scheduled by your own machine.** `nika arm` emits launchd or systemd units, with an env file
  for the provider keys a scheduled run needs.
- **Your choice of model.** The workflow file names its model, and Nika never substitutes
  another one silently.

Known limits are listed in plain words, each with a way around it:
[Known limits in 0.123](https://docs.nika.sh/changelog/known-limits). In short: a rehearsal
does not call models and replays only plain GETs of pages already observed; writing a workflow
with a large model can take minutes; the terminal mixes English and French lines; the VS Code
extension stays at 0.120; scheduled runs need your keys in an env file.

The changes since the 0.123.0-preview.1 prerelease follow; those from 0.122.0 to that
prerelease are in its own section of this changelog.

### Added

- **An ACP agent can lead a Session conversation with Nika's tools alone.** The harness opens
  one persistent session per conversation (`SpawnedHarness::converse`) and sends one prompt per
  user entry; the agent keeps the history and runs its own loop with no turn limit. Claude Code
  gets the audited one-shot surface without `maxTurns` (no built-in tools, strict MCP, no
  settings, plugins, skills or agents, nothing persisted, summarized thinking) and exactly one
  MCP server, Nika's; Codex mounts it from its configuration, read back before the spawn with
  every other server disabled. The transport follows the agent's advertised HTTP MCP capability
  and is recorded; an agent that cannot mount what Nika offers is refused, never sent down the
  one-shot path. Only Nika's own tools are allowed, once; every other permission is rejected and
  recorded. Stop sends `session/cancel` once per turn, and each turn ends with its activity
  record, where tool frames are expected activity.
- **An admission tariff can name the dated models its provider serves, and a
  cache-write rate.** `nika/inference-admission@1.2` adds two optional fields.
  `served_models` lists the models the provider's own documentation names as
  served for the selected model (a dated snapshot its replies report): a reply
  naming one of them answers that same route at settlement, in a restored
  checkpoint and in the per-dispatch record, and any other name leaves the
  charge unknown. `cache_write_nano_per_token` prices input written to the
  provider's cache, and a write rate above input reserves the whole context as
  written; a tariff without it leaves a call that wrote the cache unknown, now
  at settlement as well as in the per-dispatch record. No current row uses
  either field. Pricing observations name `table_schema` `@1.2` and a
  `cache_write_rate`, and records written under `@1.1` still read.
- **Claude Sonnet 5.5, Opus 5.5, Haiku 5.5 and Fable 5.1 are admitted on the direct API.**
  Each row reads Anthropic's pricing page of 2026-10-15: input, output, cache hit and
  5-minute cache write, a 1M window (Claude Haiku 5.5 up to its 100,000-token lower
  tier) and 128K output. The model choice offers the four first for an Anthropic key,
  Sonnet 5.5 leading, each with its admitted output price. A Session allowance still
  refuses a Claude author until the Anthropic wire settles bounded calls: a qualified
  route is never sent in another wire's shape.
- **Anthropic requests ask for prompt caching.** A conversation, or a request with tools,
  now carries the automatic cache marker (five minutes) and a breakpoint on its last tool,
  so a later turn reads its prefix at a tenth of the input price. A one-shot request sends
  no marker, since a cache write costs a quarter more than the input it holds, and a
  request that names its own `cache_control` keeps it unchanged.
- **`nika arm --emit <OS> --beat <label>` emits one schedule's unit.** With `--write`, only that
  beat's unit is written, and every other beat's unit stays as it is. Another beat that could not
  arm no longer refuses the emission. An unknown label refuses with the labels `nika.yaml` gives.
- **An authoring call's receipt says how many requests it sent.** Each
  authoring call in a compile's receipt records `requests_sent`, read
  from the provider's own record of its dispatches: a call that left
  twice says two, a call its deadline cut says one, and a harness route,
  which counts its own invocations, says nothing.
- **A kept run says whether it ran a component of saved bytes.**
  `KeptRun::component_run` reads a journal's frames of the run's own execution:
  its one start must name the hash of the exact saved bytes, and each of the
  component's tasks is settled by that execution's frames alone (`completed`,
  `skipped`, `failed`, `recovered`, `cache_hit`, … or `absent`). The run's exit,
  the last start a journal holds and a child run's task of the same name never
  stand in for it; what the frames cannot attribute is `Unknown`, with why.
- **A new conversation in the same project.** `/new`, and `SessionRuntime::begin_new_conversation()`
  for hosts, seal the current conversation beside its journal under a dated name and start the
  next one empty. Nothing is deleted, and its receipts and any unknown charge stay on record. The
  project's workflows, schedules and runs, the chosen intelligence and a paused run's gate carry
  over, and the Session says so. Nothing begins while a decision waits for the person or a run is
  under way. A restart resumes the new conversation.
- **A run is told the instant it serves.** `${{ run.scheduled_for }}` reads the slot a
  `nika arm fire` or a `nika serve` schedule fired the run for, the instant
  `nika test --at <instant>` names, or else the run's own start, as RFC 3339 UTC. The
  run's opening frame journals it with `scheduled_for_source` (`slot` · `at` · `start`),
  and a child workflow the run calls serves the same instant. Under `manqué:
  rattraper-une-fois`, `${{ run.missed }}` reads the silence a catch-up covers, `{ first,
  last, count }` (journaled as `missed`), and null otherwise. A `nika serve` catch-up's job
  carries that silence on its schedule origin as `missed_first` and `missed_count`.
- **A run's result says the ceiling it was admitted under.** The result of a run said its cost
  but not the most it was allowed to cost, so asked whether her $0.10 ceiling held, the
  conversation answered that it could not tell which ceiling applied and pointed to the trace
  file. The result now says « ceiling · $0.10 · the most this run was allowed to cost », read from
  the run's own start frame, and the conversation is told the same.
- **A Session can speak the host's language before the person writes.**
  `SessionRuntime::with_initial_language` takes the host's guess from its locale. The restored
  notice and `/status` then read in French under `fr_FR` from the first screen. A line the
  person types, or one a restored conversation kept, still decides. The restored notice is now
  said in the restored language. A turn that parks with nothing to ask says how to go on,
  rather than showing an empty question.
- **A knowledge release can carry a block proven on recorded worlds.**
  Profile r2 admitted a block only as `CHECKED`, so a block whose
  outcome was qualified on recorded worlds could not say so. A block may
  now claim `OUTCOME_PROVEN`. Its check receipt stays bound as before,
  and an `outcome_receipt` binds the same bytes, the target's
  specification and the target binary to its worlds: at least one, each
  judged `OUTCOME_CORRECT` and run at least once. Releases that do not
  claim the level are admitted unchanged.
- **`manqué: { rattraper: N }` fires the N latest missed slots.** A `nika serve` project beat
  declared with the bounded catch-up fires up to the N latest missed slots, one run each,
  oldest first, each run told only its own slot; the older slots are skipped first, journaled
  once. `nika check` reads the form (N a whole number from 1, `{ rattraper: 1 }` being
  `rattraper`); `nika arm` refuses it by name, as it refuses `rattraper`: catch-up is served.
- **The catalogue lists current Anthropic, OpenAI, DeepSeek, xAI and
  OpenRouter models, each with the source and date of its limits.** Claude
  Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 (1,000,000-token window,
  128,000 output tokens), GPT-6 Astra, GPT-6.1 Sol and GPT-6 Luna (1,050,000
  and 128,000), DeepSeek V4 Pro (1,000,000 and 384,000), Grok 4.7 and Grok 4.3
  (500,000 and 1,000,000-token windows; their output caps are OpenRouter's
  listing, xAI publishes none), and the five admitted OpenRouter routes at the
  smallest window and output of their endpoints. Every admitted route now
  reads a dated window and output cap. Claude Haiku 4.5's output cap is
  corrected to 64,000 tokens (it read 8,192), from Anthropic's model page.
- **The provider catalogue records where a model's limits were read and when
  its provider retires it.** `nika/llm-providers@1.2` lets a model row carry
  `limits_source` with `as_of`, and a `lifecycle` (`retires_not_before`,
  `successor`, `source`, `as_of`) whose successor is another model of the same
  provider; `nika_catalog::model_facts` returns them. Two nicknames may name
  one model only as aliases with identical limits and facts. Rows without
  these facts keep their exact shape.
- **`nika-catalog` states a model's output facts.**
  `model_output_limit` returns the output limit the catalog records with
  where it is recorded, `generated_cap` the cap a caller generates for a
  reasoning model within that limit, and `output_caps` the facts an
  authoring seat reads before it writes (reasoning recorded or not, the
  documented effort levels, the suggested cap). `nika-compile` now reads
  them there; no record changed.
- **The catalog records how a model reasons, and what a `.nika` file can do about it.**
  `ModelCapabilities.reasoning_facts` says, per capability rule, whether the model reasons
  when the request names no reasoning control, whether that reasoning is spent inside the
  output cap, and how a `.nika` file turns it off on this engine (`ThinkingOff`: by default,
  a zero thinking budget, not reached, or never). DeepSeek Flash and V4 Pro, Gemini 2.5
  Flash and Pro on the Gemini API, and Claude on Anthropic's API carry recorded facts; every
  other model stays unknown rather than assumed. Tests pin the facts to what each wire sends.
- **A conversation's workflow can be verified like any compiled one.**
  `verify_document` verifies a document a Session conversation wrote
  exactly as the compiler verifies its own candidates: the fidelity laws
  over the world the host observed for the request, the core's finish, a
  run in the room when the host lends one, then the whole-request verdict
  of the permitted judge (a decision model, else the authoring model),
  never asked again on bytes it rejected. It is ready only on that
  verdict: a doubt nothing located is held, a located defect keeps the
  workflow as the preview with what to repair, and with no judge the
  request stays pending.
- **A dropped value needs words that remove it, as the judge reads them.**
  When a conversation's revision drops a value an earlier proposal bound
  (a source, an output), the Session cites the person's words that remove
  it. Those words were only checked to be the person's: « I wanted
  today's articles » could drop a source no one asked to change. The
  verifier now asks its judge, for each such claim, whether the words
  remove that value; a claim it does not confirm keeps the workflow from
  being proposed, and the conversation is told which value to keep or
  ask about.
- **The decision seat's journal says how long each request took.** Every
  System One request that left, a single question or one request of a
  batch, now records `elapsed_ms` in the Session's decision-seat
  observation (`nika/session-decision-seat@2`, additive): the time its
  transport took, measured by the host. A request never sent records none.
- **`nika doctor --json` states its scope.** The report carries
  `"scope": {"mode": "audit-wide"}`: it audits the machine as a whole. A
  question about one workflow stays with `nika check`.
- **A draft read whole runs at its model's documented low effort.** When
  every model step of a request is a draft (write, summarize,
  translate), the reader admits the request whole with no constraint
  beside it, and the model's exact catalog row documents a `low`
  reasoning effort (DeepSeek Flash and V4 Pro today), the compiled
  workflow declares `run.reasoning.effort: low` and the proposal says
  so. On Flash, one measured 16.7k-token summary spent 83% of a
  16,384-token cap reasoning at the default effort and 69% at low; at
  the default effort the same step had ended with no answer. A
  candidate that already declares an effort or a route keeps it, and a
  model-written candidate gains one block, never a reformatted file.
  The level reaches only the model's exact direct route: behind a
  base-URL override (a gateway), every request that carries it is
  refused with nothing sent.
- **What a workflow reaches reads in French.** A French proposal card showed « à l'exécution ·
  model output estimate · unbounded: `grade` declare(s) no `max_tokens`; Run admission is
  separate » and « connected: anthropic/claude-sonnet-5-5 (remote model) ».
  `nika_display::check_render::review::effect_row_fr` says each row `effect_rows` writes in
  French, and `World::summary_fr` the reach summary. Paths, hosts, tools, models, tasks and
  amounts stay as written. A row they do not know is returned as it is.
- **`egress/001` warns about a private key in a prompt.** A `prompt:` or
  `system:` text holding a PEM private-key block (a line
  `-----BEGIN … PRIVATE KEY-----`) is a credential bound for the model
  provider: the linter flags it and the check verdict lists it in
  `hints[]`, on inline text and on the text of a prompt file alike, where
  the door read that file. It refuses nothing and never repeats the text;
  a secret the provider needs belongs in `secrets:`.
- **A finding has a canonical key a host can compare across revisions.**
  `fidelity::keys::finding(kind or target, message)` keys a finding on
  its kind, its law's head and the subject it quotes, for example
  `conversation:invented-literal:./news/digest.md` or
  `semantic_verification:write the digest`. A Session can tell whether
  the same defect still stands on a new revision without comparing
  prose: rewording or an appended remedy keeps the key, and another
  subject or another law changes it. No finding's target changes.
- **A harness authoring call's record tells thinking from answering.** The
  `activity` of a terminal ACP authoring record now also says when this
  session's first thought and first answer arrived (`first_thought_ms`,
  `first_answer_ms`) and which update it received last, by its closed word
  and time (`last_update`), each null before any. A receipt can now say a
  call was silent until its first answer, or that its last update before a
  silence was a thought. The fields are additive; counts, deadlines and
  acceptance are unchanged, and no frame text is kept.
- **Hosts name a zone as the Session does.** `nika_session::zone_words` (« heure de Maurice »,
  "Mauritius time") and `nika_session::zone_place` (« Maurice », "Mauritius") give a host the
  words the Session itself uses for a schedule's zone, so a run list and a cadence never name the
  same zone two ways.
- **Hosts read a declaration's schedule in words.** A candidate that declares a schedule in
  `nika.yaml` carries `Candidate::schedule` (`ScheduleWords`) in the person's language: when it
  runs, what a missed run does, and its ceiling per run. A host shows these words instead of
  the cron. The work snapshot's compile draft is kept only while one of that compile's own
  questions waits, so a host never shows a draft beside another question.
- **A declared schedule can be installed on this Mac from the conversation.** Once a schedule
  lands in `nika.yaml`, the terminal Session asks « Installer le déclencheur sur ce Mac ? ». The
  question names the workflow, its cadence and zone, the missed-run policy, the ceiling per run
  and the effect (one launchd unit run by the program that wrote the workflow). « Installer et
  activer » writes the unit as `arm --emit launchd --write` does, loads it and says when it runs
  next. « Garder déclaré seulement » installs nothing, and « désactive-le » unloads and removes
  it. Nothing is installed without that yes. Hosts other than the terminal on macOS keep the
  schedule declared, as before.
- **The embedded knowledge release is r3, the Foundry enrichment.** It adds 16 families,
  14 facets, 23 packs and 83 patterns written in the studio's own words. A family now
  recommends its packs, so the door's family, pack, pattern and block path reaches the
  qualified blocks (`feeds-items-on-local-date`, `json-threshold-report`).
  The r2 release it replaces stays embedded for earlier pins, which reopen its exact bytes.
- **`nika-mcp` serves a Session's tools to the agent leading its conversation.**
  `conversation::ToolServer` lists the session's `nika/author-tools@0` definitions and relays
  each call of a listed tool to the session over Streamable HTTP, bound to `127.0.0.1` on an
  ephemeral port with a bearer minted for that conversation alone and always required. A tool the
  session does not list never reaches it; a failed call returns `isError: true`. Connections are
  served side by side under the same bounds as `nika mcp --http`, and the session receives one
  call at a time. The server interprets nothing and grants nothing. A stdio bridge to the same
  server exists as a library; no CLI starts it yet.
- **A pinned `mcp:` step is judged against its approved schema.** Once the
  project registers the server, approved its pins under the command,
  arguments or URL the registry names now, and the pins hold the tool,
  `nika compile` judges an `invoke:` step's literal arguments against the
  pinned input schema (an argument a template builds is wired, the run's to
  judge) and records each verdict on `decision.mcp`, without reaching the
  server. A registry re-pointed after its approval keeps the hold until the
  server is approved again, as the run's own verification requires. The
  served compile door judges the same step on the server project's pins and
  records the verdict, but keeps the step held: that server runs no MCP
  step. Any other answer keeps the source-only hold, in words that say what
  to do; permits stay the only authority over what a step may do.
  `nika check` advises, without blocking, on a registered server whose pins
  were never approved or do not hold the tool.
- **An agent can list the models each role can use and have its pick
  validated.** `nika_providers::model_choice` keeps separate offers for
  the Run, Author and decision roles, built from this machine's routes
  and the catalogue, and answers a selection with one exact target, a
  delegated pick with its rule, a targeted choice, or an exact refusal
  with real alternatives. No id is invented and no closed provider,
  route or protocol is changed.
- **`nika check` names a model's published retirement floor and its
  successor.** A workflow on `anthropic/haiku` or Claude Haiku 4.5 still
  checks clean, with one advisory line under the green MODELS line: Anthropic
  does not retire it before 2026-10-15, and `claude-haiku-5-5` is available to
  name. The nickname keeps its model; nothing switches silently.
- **OpenAI's GPT-5.2, GPT-5 mini and GPT-4o mini are admitted on the direct API.**
  Each standard-tier row reads its model page of 2026-10-12: the input, output and
  cached-input prices, the context window and output cap, and the dated snapshot
  OpenAI's replies report (`gpt-5.2-2025-12-11`, `gpt-5-mini-2025-08-07`,
  `gpt-4o-mini-2024-07-18`), which settles the same route. The pages list no
  cache-write price, so a call that wrote the cache stays unknown. GPT-6 direct
  rows are not admitted.
- **An OpenAI conversation keeps its prompt cache together.** A request can name the conversation
  it continues (`InferRequest::cache_key`, a hash of the conversation's id); the OpenAI wire sends
  it as `prompt_cache_key`, so one conversation's calls route and account their cached prefix
  together. Other providers ignore it, and a caller's own `prompt_cache_key` stays unchanged.
- **Five paid OpenRouter routes are admitted.** GLM 5.3 Flash, MiMo V2.6 Flash, Step 5
  Preview, HY4 Preview and GPT-6 Luna have exact admission tariffs read from
  OpenRouter's endpoint listings of 2026-10-12: the highest price of any endpoint, the
  smallest context and output limit, and, for GPT-6 Luna, an input bound below its
  long-prompt surcharge. A Run no longer needs an unknown-cost choice for them, and a
  Session can reserve them.
- **`prompt:`, `system:` and `schema:` can name a project file, and
  `nika check` judges it.** `{ file: <path> }` resolves from the folder of
  the workflow that names it, stays inside the root workflow's folder, and
  is read only inside that workflow's `permits.fs.read` boundary, never
  through a symbolic link: a broken path law is `NIKA-FILE-001`, a missing
  file `NIKA-FILE-002`, more than 65,536 bytes or bytes that are not UTF-8
  `NIKA-FILE-003`, a schema file holding no JSON Schema `NIKA-TYPE-007`.
  The text or schema read then meets every law the same field written
  inline meets, in a composed child too, where each finding names the
  child, task, field and path. A door without the files names them as
  unread (`judged.project_files: false`). No run carries project files
  yet: `nika run`, `nika test` and a captured world refuse such a workflow
  before any task, and the runtime refuses a file no compose step read,
  at launch and at the `infer:` or `agent:` dispatch.
- **An unstable build trims the authoring prompt for evaluation.** The
  `unstable-prompt-trims` feature of `nika-compile-seats`, off by default,
  makes the document door show the language's schema without its
  descriptions (29,749 bytes compact, 9,705 without them). It also narrows
  the callable contracts to what the reader's typed plan reaches: `fetch`
  only for a fetched, looked-up or explored source or an outbound effect,
  `convert` for a computation, `prompt` for an approved effect, `notify`
  for a sent one. A default build sends the whole prompt, as before; the
  feature exists so an evaluation can measure authoring quality with and
  without the trims.
- **An Anthropic key can be checked on request, without running a model.** `check_key` (and
  the Session's `check_api_key`) sends one GET of Anthropic's model list with the key, its
  API version and the endpoint a run would use (`NIKA_ANTHROPIC_BASE_URL` honored). It
  answers valid, refused (401 or 403, in the provider's words with the key withheld),
  unreachable or unknown, spends no token, never follows a redirect or retries, and runs
  only when a person asks. Another provider answers unknown without a request: an
  OpenAI-compatible model list may answer without a key, so it would prove none.
- **A trial run shows what each step received and returned.** A host's
  rehearsal report can carry each task's flow, the items it received
  and returned. The verifier is shown it with the trial's outputs, a step
  that returned nothing from a non-empty input marked, so a filter that
  kept no item of today's news is a fact the verifier weighs; a host's
  card can show a passing trial's flow.
- **A rehearsal runs `nika:validate`.** A candidate that checks data against a JSON
  schema is tried in the room before its proposal, instead of being refused as a
  tool the rehearsal does not know; the check reaches no network and no file.
- **`nika check` reads the facts of the run.** `${{ run.scheduled_for }}` and
  `${{ run.missed }}`, the closed set of facts the engine tells a run, resolve in
  every value position and in `when:` and never make a data edge. Any other
  `${{ run.X }}` is an unresolved reference (`NIKA-VAR-001`) whose message names
  the two facts; a near miss (`run.schedule_for`) is offered as its rename, a
  typo'd root is offered `run`, and `${ run.missed }` or
  `{{ run.missed }}` without `${{` is caught as for the other namespaces.
- **A schedule's history says its firer, its last skip's reason and its catch-ups.**
  `ScheduleHistory` now says whether a resident `nika serve` holds the project's server lease
  (`serve_running`), and when the unit was last disarmed and how many runs fired since
  (`last_disarmed_at`, `fired_since_disarm`). `LastDecision` now carries a skip's reason
  (`missed:1`, `overlap`) and the slots a catch-up run answered. Both types have `new()`
  constructors. The unit's presence is asked of the Session's trigger host.
  `nika_cadence::ledger::decisions` returns each decision with its instant, reason and slots.
- **Activation offers its ceiling and missed-run policy as choices.**
  `SessionRuntime::waiting_activation_choices` states the activation's
  finite question (its sentence and one short line, its options, and
  whether another typed value is admitted). The ceiling per scheduled run
  offers 0.10, 0.25, 0.50 and 1.00 USD, recommending the smallest that
  holds about three times the workflow's last observed run (its trace's
  total) and 0.25 when none was observed, with why in the label. Another
  amount is still typed, the same grammar validates every answer, and no
  amount is ever applied without the person's answer.
- **The selected intelligence leads the Session's conversation.** With an API or a local route,
  a person's line now goes to one loop over that model and the Session's tools
  (`nika/author-tools@0`): it reads the project and the language, writes and edits the candidate,
  asks several questions together with their own identities, and proposes. Every value the
  candidate binds carries its provenance (named, delegated, derived, answered, offered or kept)
  checked against the person's own cited lines; a value they saw bound is never dropped
  silently; a reopen keeps the values and their provenance, never a question or a proposal.
  Saving and running stay the consent door's acts: the person's words open it only for the exact
  candidate they were shown, and a candidate whose effects changed waits for their consent.
  `NIKA_SESSION_DRIVER=rounds`, read when a host door opens the Session, keeps the round driver
  during the transition.
- **A Session host answers one question by its identity.** Both Session doors take `answer`
  (`command`, `snapshot`, `question`, `line`) and `delegate` (`command`, `snapshot`,
  `question`) and advertise `sessionQuestion`: a client that read several questions asked
  together, or the choices a proposal shows, binds its reply to the one it names and the others
  keep waiting, where a plain line answers every question whose offers it names. The host takes
  the identity only among those the current snapshot asks (`unknown_question` otherwise, with
  nothing sent), and `delegate` leaves that choice to Nika as the person's act, never as words.
- **Every model and tool call of a Session turn reaches the host.** The
  conversation's model requests are activity frames naming their rank in
  the turn, the route asked, when they left, how long they took and the
  tokens the provider reported; tool calls also name when they began and
  their reply's size. Neither carries a prompt, an answer, arguments or a
  reply.
- **A conversation's candidate is tried before it is shown.** Each public
  GET source the candidate names is observed once for the conversation
  through the guarded observer (no credentials, no private address,
  bounded), and the observed room replays those pages to a run of the
  candidate where nothing leaves: a GET of exactly an observed address is
  answered with its page, every other request is refused. A step the trial
  cannot run (a model step, a request other than GET, an effect outside
  the room) is named, never run as itself. A failed trial, or a filter that
  keeps nothing of the items it read, goes back to the author with its
  facts before any proposal; a passed trial is bound to the proposal, which
  says what it ran on and what it did not run.
- **A Session conversation proposes only a workflow its judge found
  ready.** Every `verify` and `propose` of the conversation's candidate
  now faces the verification a compile round of the same request faces:
  the laws over the files the request names, then the whole-request
  verdict of the Session's judge (its decision model, else its authoring
  route; a seat's agent judges through its own one-shot). Anything short
  of ready goes back to the author with the findings, and nothing is
  shown. A verdict that declined some bytes is kept for the conversation
  and never asked again on those bytes, and a ready verdict on unchanged
  bytes, words and selections is reused.
- **A conversation's run can be steered, stopped and watched while it works.** The Session
  host (`nika/session-host@1`, both doors) gains `steer` and `follow_up`: a line sent while a
  turn of the conversation runs is queued with its identity (`l1`, `l2`, …) and shown in
  `busy.queued`, then enters after the calls under way (an ACP agent is asked once to stop) or
  when the run would end, as the person's next cited line; a line no run reads is refused,
  never kept for later. Stop of such a turn now settles as the typed outcome `stopped` (how it
  reached the intelligence, the lines returned unsent, the draft kept). Each tool the
  conversation's intelligence calls reaches the event stream as typed activity (call, tool,
  started/finished/failed, elapsed), never its arguments. On `nika/session-work@0`, additively:
  `queued`, and a `choice` on each offered model this machine's inventory knows (route,
  billing, list price).
- **One edit call, several passages.** `candidate_edit` takes `edits`, a list of old and
  new passages applied as one revision: each old passage is found exactly once in the
  candidate and none overlaps another, so an author repairs several places in one call.
- **A failed run goes back to its author.** When a run the person asked
  for fails, the author receives the run's facts and proposes the repaired
  revision with what changed; a complaint about a run's result starts the
  same repair. The repaired revision runs again only on the person's words
  or consent. A revision that replaces a value the person saw bound says
  so in words.
- **A failed tool step says why.** The Session's activity frame for a
  tool call that failed carries the tool's reply (`tool.reply`) as a host
  may show it: its obvious secret shapes redacted first, on one line, at
  most 512 bytes. A call that started or finished carries none, and no
  frame carries a call's arguments.
- **A gate shows its values, structured.** A run paused at a
  `nika:prompt` gate journals the values its message renders as one
  object (`values`), bounded, and names the keys it withheld with why
  (`withheld`: a value derived from a secret, one past the bound, one
  that did not resolve). A Session's gate outcome carries both beside
  its text, so a host or a consent review binds the exact values shown.
- **The Session host's activity frames say what each finished call cost.** An activity frame's
  `call` now carries `cost` once the call returned, as the Session settled it:
  `{"kind": "priced", "nano_usd": "120000000"}` (nano-USD at list price, as a decimal string: an
  estimate, never an invoice) or `{"kind": "unpriced"}`. It is absent while the call runs, when
  nothing settled, and when its producer states nothing. A host can sum the priced calls for a
  live preparation cost, as the terminal does. The published contract now describes the frame's
  `call` (`SessionCall`) and its `cost` (`SessionCallCost`).
- **Intelligence options state each model's prices, context and lifecycle.**
  Every option naming a model now carries typed facts for any host:
  `ModelFacts` gains `input_usd_per_million`, `context_tokens` and `legacy`
  beside the output price, from the route's qualified tariff or else the
  catalogue's list row on a metered route. Unknown stays absent, never
  zero; nothing is selected or called by stating them.
- **The intelligence picker recommends one model.** While the choice is
  open, `Work.intelligence.recommended` names the option recommended for
  the conversation: the operator's saved default when this machine serves
  it, else the default model of the first configured provider in catalogue
  order (an Anthropic key yields Claude Sonnet 5.5). It selects nothing.
- **The bytes a verification judged are kept.** A conversation's Session keeps each
  candidate its verifier judged, redacted and bounded, in its history as
  `verified/<sha256>.nika`, the digest its verify record names, so a hold can be
  checked after the candidate is rewritten.
- **A proposal shows its open choices with their defaults.** When the author
  can recommend a value the person left open, such as the run model or an
  output folder, it writes the recommendation in the candidate and proposes
  at once, showing the choice with its default and the other options
  instead of asking first. The person's yes keeps every default, bound as
  the option they picked with that line; naming another option binds it and
  the author revises; nothing is saved while a choice the proposal does not
  show is open. The author tool contract is `nika/author-tools@1`.
- **After a result, an optional « what would you like to adjust? ».** When a run of the workflow
  the conversation proposed succeeds and writes its result, the terminal offers, at the free
  prompt, the dimensions that workflow's own tasks have (depth, length, topics, sources,
  presentation), « Nothing to change » and « Later ». Picking dimensions asks Nika to ask only
  what is still missing and propose an amended workflow, approved as any other; the run of a later
  revision of that same request then offers one neutral opinion (suitable for this use, still
  needs changes, wrong result, not evaluated yet, later), with no default. Only a choice selected
  in the offer answers it, and only while nothing else waits: words typed instead always go to the
  conversation, whatever they read as; asking for a run or the next run closes the offer, the same
  workflow bytes are never offered twice (a reopened conversation included), and Esc answers
  nothing. An answer changes nothing by itself: it is kept with the exact request, revision, run
  and files it is about, never as a permission to save, run or send.
- **A conversation can switch its decision seat and save its default.**
  `choose_decision` makes Jev (only the seat the host named) or the author
  alone consult the next turn's finite choices; the author alone clears a
  refusal the earlier seat caused, and an unavailable Jev is refused with
  its reason. `save_operator_default` writes the kind, model, decision seat
  by name and time to `~/.nika/session-intelligence.json` atomically, never
  a key; a default saved deciding alone opens new conversations that way.
- **A Session Stop now reaches the Run it handed off.** While a `save & run`
  or a run line executes through the native door, a `stop` command asks that
  Run to stop at its next wave boundary, as a first Ctrl-C does: the work in
  flight completes and is counted, the next tasks are cancelled and the trace
  seals one cancelled terminal. A Stop that arrives before the run child
  exists is applied when it starts (before the spawn, nothing starts). The
  receipt says `stop_requested` until the Run takes the signal and
  `run_stopping` once it has; the snapshot's phase reads `stopping`; the
  result adds `run_stopped` when the trace sealed, `run_aborted` when the Run
  ended without sealing it, and the work snapshot's run gains `sealed`, so an
  interrupted run that stopped is told from one cut mid-flight. A replayed or
  second Stop sends nothing more and never escalates to an abort. A door that
  cannot stop its runs (the resident's job door, for now) still answers
  `run_underway`.
- **The last run a Session keeps names its job, its receipt, its outputs and its verified seal.**
  For a run `nika serve` ran, the kept run now records the job it ran as, the receipt and the
  declared outputs that job serves (kept whole up to 16 KiB encoded; larger ones stay with the
  job, at `GET /v1/jobs/{id}`), and whether `nika trace verify` accepts the journal's
  `run_sealed` record and its signature (`seal_verified`, `signed`), never a seal read from the
  receipt's chain head. Forward
  compatibility: these parts are written only for served runs, and an engine older than this one
  reports such a kept run as unreadable and keeps its bytes unchanged, so nothing is lost; the
  kept value of a native run is unchanged.
- **Both Session doors advertise `sessionSteering`.** The native identity
  (`nika --sdk-identity`) and a resident serving Sessions (`/health`) name
  the word beside `sessionHost` and `sessionIntelligence`, so a client tells
  a door that takes `steer` and `follow_up` from an older one, and refuses
  those lines itself, before it sends one.
- **One contract for the tools a Session serves.** `nika_session_change::tools` types the
  `nika/author-tools@0` tools once for Nika's own agent loop and for an ACP agent reaching them
  over MCP: the 17 fixed tool names, `ToolDef`, `ToolCall` (with the caller's tool-use id),
  `ToolReply` (text, failure, and whether the turn ends on it) and the `SessionTools` trait the
  Session implements. It grants nothing: saves, runs and consents keep their own doors.
- **A proposal's activity carries its original verification records.**
  The activity of a successful `propose` tool step carries a separate,
  versioned `tool.verification` receipt: the compiler's semantic
  verification records for the candidate it shows, unchanged, with the
  candidate's digest and whether this proposal reused an earlier
  verification. A receipt the secret-shape filter would alter is withheld
  whole, and no other tool step carries one.
- **The work snapshot carries a conversation's values, delegations and questions.**
  `nika/session-work@0` gains, additively, `bindings` (each value, its role and its provenance
  with the person's citation and words), `delegations` and `questions` (each with its identity,
  open or waiting for its prerequisites, and the concrete values its options carry), and
  `waiting.kind: questions` when several questions are asked together. A snapshot without a
  conversation keeps its bytes.
- **A Session snapshot says how long its knowledge and trial stages took.**
  `work.authoring.stages` now carries, as the compile's decision record
  states them, the wall time of the Foundry knowledge qualification
  (`qualification_ms`) and each trial of a candidate in order (`trials`:
  how far it went, `elapsed_ms` and the host's `runtime_bound_ms`). The JSON
  door and `/v1/sessions` carry it unchanged; a time the record does not
  state is null, nothing is summed, and no output or failure text passes.
- **A System One request can write its options in a chosen order.** `system_one::body` serializes
  a request with each question's criteria in an `Order`: `Alphabetical` (today's bytes exactly),
  the question's own order, or its mirror, the pair a both-order reading averages. Nothing sends
  it yet: requests keep today's alphabetical order until the both-order setting lands.
- **The Session reads each schedule's history, and journals its removal.**
  `SessionRuntime::schedule_history()` returns every schedule `nika.yaml` declares, with how many
  runs its arm ledger records as fired, skipped, paused, failed or disarmed, its last decided slot
  and whether this Mac holds its unit. Reading it creates, locks and repairs nothing.
  « désactive-le » now journals a `disarmed` decision in the schedule's ledger and says so.
  Installing a trigger from the conversation installs that schedule's unit only: a schedule kept
  declared stays without one.
- **The Session says its own sentences in the person's language.** Every French-speaking person
  met English questions and cards between the author's French answers. The Session now reads the
  language of the person's own lines, English or French, and asks the activation questions (time
  zone, missed run, ceiling), proposes the declaration and says what saving and declaring did in
  it. A host reads it (`SessionRuntime::language`) to say its own words in it too.
- **The activity card a turn leaves in the workspace now uses the language you write in.** In
  a French conversation it reads « Terminé · en moins d'1 s · 1 appel auteur · routage de la
  conversation et appels du service de décision non comptés ». Its phases (« Compréhension »,
  « Génération », « Réparation ») and its call row (« appel 1 · planifier le workflow · demandé
  deepseek/v4 · revenu après 3 s ») follow too. What the Session reported, the model and the
  times stay as they are. The card's text from other doors is unchanged:
  `ActivityCard::lines_in` is a new variant that takes the door's words, and `lines` still
  says English.
- **The workspace's project list and header now use the language you write in.** In a French
  conversation the aside reads « Projet » and « Nika · Fichiers ». It names « cette
  conversation », titles its groups (« FICHIERS », « MÉMOIRE », « RÉGLAGES ») and states a
  workflow's verdict in French (« 2 constats », « non conforme », « run échoué »), and so are
  its counts, footer and notes. The header says « projet local », « pas de git · pas de
  nika.yaml » and « Préparer avec : » before the selected intelligence, or « non choisie ».
  Project names, paths, hosts and the intelligence's own words stay as they are. A click on
  « Fichiers » turns the list as « Files » does.
- **A finished run's card lists its steps, models and files.** Below a run's story, the
  terminal's card now opens on how the run ended in the theme's own state glyph (`✔ Ran in
  16.5s · 14 tasks · $0.034 · 1 model call`, `✖ Failed after …`), then lists each step
  with its glyph, wall time, model and cost (`✖ notify · failed: the webhook answered 500`
  in the failure colour, beside its words; `⊘ tidy · not run`), what each model cost and
  how often it was called, and every file the run reported writing with its path
  (`→ ./out/digest.md (write)`). A long run lists its first twelve steps and every
  failure, then counts the rest; unknown times, models or costs are left out.
- **One live line says what Nika does while it prepares or runs.** In the terminal, a turn
  that prepares reads `Preparing · 45s · 12 author calls · Ctrl+C stop`, then the Session's
  own phase words; a workflow the turn runs reads `Running veille-tech.nika · 12s · 6/14
  tasks · now: pick (claude-sonnet-5-5) · $0.012 of $0.25 · Ctrl+C twice leaves` (a Run is
  never stopped there). Its card opens on how it ended: `Ran in 16.5s · 14 tasks · $0.034 ·
  1 model call`. Outside the workspace, whose activity card already says it, a preparation
  that called its author ends with `Prepared in 2m 10s · 35 calls · $0.91`. Every fact is
  one the terminal observed (typed calls, the run's frames, its clock, the Session's known
  cost estimate after the turn); an unknown one is left out, never a placeholder, and the
  facts refresh at most twice a second. Hosts can report typed activity with
  `Seen::activity` and the spend with `Conversation::spend`.
- **A question that recommends an answer needs no typing.** The terminal opens such a
  question with the recommendation selected (each recommended answer checked in a multiple
  choice), marked `recommended`, so `Enter` alone accepts it; nothing is sent before that
  `Enter`. A « Let Nika choose » row follows the offers and leaves the choice to Nika as the
  person's own act, never words: the Session binds the recommended answer as delegated,
  whatever another answer's label reads, and on a choice a proposal shows that keeps the
  request's save and run. The own-reply field stays. Decisions, settings and the knowledge
  choice keep opening on nothing.
- **A source's address reads beside its name in a question's options.** When an option's
  label names a site or a data source and then its address after « · » (`TechCrunch ·
  techcrunch.com/feed`), the terminal paints the name first and the address dimmed in one
  column aligned over the list, beside `recommended` where the author recommends it; a
  narrow row cuts the address with a visible mark before it ever cuts the name. The ASCII
  glyph column keeps every fact.
- **`PgUp` and `PgDn` page through a long choice list.** In a question,
  decision or `/intelligence` list they move the selection eight rows at a
  time and stop at either end; the list follows the selection page by page.
- **Reception proofs for the choice surfaces.** The TUI prototype takes
  `--demo-surface intelligence|multi` and says back what it receives, and
  `qa_surfaces` proves at 80x24, 120x40 and 180x48 that a long model list
  pages, filters and applies the selected row, that `Esc` steps back
  without answering, and that a multiple choice validates with `Enter`.
- **Question options are counted, checked together and picked by their digit.** In the
  terminal, each of a question's first nine options shows the digit that selects it: with
  nothing typed yet, pressing that digit moves the selection there and sends nothing (once
  words are typed, digits are words, and the `/intelligence` list keeps digits for its
  expert route). A multiple choice counts `N of M selected` in its title, `Ctrl+A` checks
  every answer (or none when all are), `Space` still toggles one and `Enter` validates the
  checked set. The Submit row no longer repeats the count.
- **A decision's question and acts now use the language you write in.** In a French
  conversation the proposal's decision asks « Que faire de cette proposition relue ? » over
  « Enregistrer le workflow », « Enregistrer et lancer une fois », « Écarter cette
  proposition » and « Examiner les changements exacts ». The same holds for a run's cost
  decision (« Continuer ce run une fois ? », « Lancer une fois », « Annuler ce run »), a gate's
  acts, a schedule's and a run ceiling's way out, and the recovery of a drifted workflow. Every
  act keeps its key, so `yes`, `no`, `save & run`, `/show` and `details` answer as before. The
  Session's own questions and options stay as it says them, and English stays the default.
- **Digits select a decision's act in the terminal.** In a decision list (Save · Save and
  run once · Discard · Inspect, a gate's answers, a missed run's handling), each act now
  wears the digit that selects it, and pressing that digit with nothing typed moves the
  selection there and does nothing else; only `Enter` acts, once. A decision still opens
  with no act selected, and the `?` keys list the digits beside it.
- **The workspace says what a schedule's trigger fired, and the rail reads « Actif ● » only once
  a fire is proved.** At the opening and after each turn, the workspace reads the Session's history
  of each declared schedule's arm ledger (`SessionRuntime::schedule_history`, a read only) and
  says, once, what changed: « `compiled-workflow.nika` a tourné tout seul : 1 run par son
  déclencheur », its last slot with its exit and its trace, a slot caught up late (« son créneau
  … a été rattrapé à … »), and the slots skipped, the runs a gate parked and the failures. The
  list counts the runs fired under their workflow (« 1 run déclenché · 10-16 06:00 ✔ »). The rail's
  activation field stays declared (◐) until a fire of an installed, active trigger is proved,
  then reads ●, and goes back to declared once the trigger is removed.
- **Card names, band titles and the `?` keys overlay now use the language you write in.** In a
  French conversation a refusal's card reads « Impossible de continuer », your own messages
  are labelled « Vous » and a result « Nika · Résultat ». A decision's band is titled
  « Décision · obligatoire », and a question's card names the answer it takes (« Une réponse
  en texte »). The keys overlay lists every key in French under « Conversation », « Décision »
  or « Pendant que Nika travaille », its reader frame included. The Session's words inside
  every card stay as written, and English stays the default.
- **A held message shows its way on.** When the knowledge your
  configuration names is refused, the message Nika holds stays in view
  with the refusal and its two acts: `/knowledge embedded` resumes it
  once with the knowledge built into Nika, `cancel` drops it.
- **The TUI's hint row now uses the language you write in.** When the Session reads French in
  your lines, the keys under the composer are shown in French: « ↑↓ choisir · Entrée valide »
  at a decision, « Entrée répond · annuler l'abandonne » under a typed question, and
  « oui + Entrée : enregistrer · non : annuler » at a proposal. The same goes for the notices a
  key leaves (« rien n'est envoyé · tapez oui ou non… »), what a second `Ctrl+C` does, a
  proposal's standing and the composer's caption. The words the Session reads when you type
  them keep their form: `oui`, `non`, `annuler`, `details`, `save & run` and the slash
  commands. The Session's words, your words, values, paths and model ids are never rewritten.
  English stays the default and the fallback, and an ASCII screen shows the French without
  accents.
- **The whole-words reader opens the block at the reading position.**
  Scrolled back in the workspace conversation, `F2` (and its palette
  entry) opens the Session's own words of the newest block shown there
  short of whole: a summarized refusal, the current proposal, the question
  its card carries. A plain press on such a block opens the same words:
  the key and the pointer read the rows painting uses. Closing returns
  the exact rows, the draft and the keys. At the latest rows the question
  waiting comes first, then the latest such block, as before.
- **A fire while the workspace sits open shows without a keystroke.** About once a minute, while
  no turn or run is busy, the TUI reads the Session's schedule history again (a read only) and says
  a schedule's new fires as it says them after a turn: once, with the rail. Before, a fire while
  the TUI stood open stayed unsaid until the person typed something. `Options::idle_every` sets
  the period (`None` never asks); a conversation answers through `Conversation::idle`.
- **The workspace installs a declared schedule's trigger on your yes (D1).** On a Mac, in your
  own terminal, once « active-le » has declared a schedule in `nika.yaml`, the workspace asks
  « Installer le déclencheur sur ce Mac ? » with two rows, « Installer et activer » (recommended)
  and « Garder déclaré seulement », under the Session's report of what it wrote. « oui » writes
  and loads the launchd unit run by the program you use and says when it fires next (« Installé
  et actif · … prochain run : … »); « désactive-le » removes it. Nothing is installed without that
  yes; any other door, and every test, keeps the schedule declared and names the command.
- **Type a model's name anywhere in `/intelligence`.** From the home
  screen or the connection list, typed words now also find models by
  their display name under every connection (`son` finds Claude Sonnet
  5.5 under Anthropic), and `Enter` applies the one selected.
- **`/intelligence` opens on a short home screen.** The recommended model
  comes first and is selected, so one `Enter` applies it; then keep the
  current choice, other choices (every connection and its models), and the
  Author, Decision and Run roles with their values, each row opening its
  change: Jev or the author deciding alone for decisions, and the model for
  the next workflows' AI steps, told to the author. `Set as default` keeps
  the choice for new conversations. `Esc` steps back one list at a time.
- **`/intelligence` names the key a provider uses and can test it.** API
  rows show the variable that supplies the key (`key: ANTHROPIC_API_KEY`)
  and warn when two set variables differ, naming the one Nika uses. A
  `Test the key` row on the current or opened API connection sends one free
  model-list request only when you pick it and shows the verdict inline
  (valid, refused, unreachable, unknown); the key is never printed.
- **Models in `/intelligence` read by name, with prices and context.** A
  model row now shows a display name (`Claude Sonnet 5.5`, `GPT-6.1 Sol`,
  `DeepSeek Flash`) and, in a quiet aligned column, its current and legacy
  marks, its input and output prices per million tokens when known, its
  context window and a key it still needs. Typing filters on these words
  too; the key sent is always the exact model id.
- **Type to filter the `/intelligence` lists.** Typed words narrow the
  rows to those holding every word, case aside; the first match is
  selected and `Enter` applies it. Words that match no row are sent as
  typed, so `/intelligence 2 provider/model` still works for an unlisted
  model. Questions and decisions keep their own reply field unfiltered.
- **The live line shows what preparation costs while Nika works.** While Nika prepares, the line
  now sums what the returned author calls cost as the Session settled them, at list price:
  `Preparing · 45s · 12 author calls · ~$0.12 · Ctrl+C stop`. A call that returned without a
  price is counted beside the sum (`~$0.12 + 2 unpriced`); while no call is priced, no cost is
  shown at all. A finished call's activity mark now carries its settled cost (`CallCost`), read
  from the preparation scope's own journal of returned requests: an estimate, never an invoice.
- **`/new` and `Ctrl+N` begin a new conversation in the workspace.** Through the Session's own
  door (`begin_new_conversation`): the conversation is kept as it was, the opening is painted
  again (the banner, then the Session's words of what carries over: the project's workflows,
  schedules and runs, a gate still waiting), and the prompt is free. While a decision waits, the
  Session refuses in its own words (« une décision vous attend encore … ») and the decision is
  shown again. `/new` is listed in the palette before `/quit`, and `Ctrl+N` among its keys.
- **The notices around your own words now use the language you write in.** In a French
  conversation a draft kept aside reads « votre brouillon « … » · il est dans la zone de saisie,
  non envoyé », a correction « correction en attente « … » · la préparation s'arrête, puis elle
  est envoyée », a command typed while Nika works « Nika continue · /intelligence attend votre
  tour », and the decision card says « cette proposition n'a pas changé depuis votre dernier
  message ». What an earlier session kept reads « dernier run ✓ sortie 0 dans une session
  antérieure » on the status row (« Run ○ (précédent ✓) » on the rail), with its kept line and
  its earlier context, and the conversation panel names « Message pour … », « Joint : … ». Your
  words stay exactly as typed, a brace included. For other doors, nika-display adds
  `correction_queued_in` and `correction_unsent_in`, and nika-tui-view `Thread::speaking`;
  English is unchanged.
- **The palette and the slash list now use the language you write in.** In a French
  conversation each command and view key says what it does, where it applies and its one
  sentence of help in French (« /status · Où vous en êtes », « Ctrl+L · Redessiner l'écran »),
  and the chooser's own words follow too: the band's title, what waits (« Une proposition attend ·
  aucune commande n'y répond »), its keys (« Entrée envoie /show · ↑↓ choisir · Échap masque
  la liste ») and the search prompt « commandes › ». Command names stay as you type them, and a
  French word finds the command it describes while the English search words still work.
- **The prompt beside the line and the own reply's placeholder now use the language you write
  in.** In a French conversation a proposal's line reads « Enregistrer ? › » and a question's
  « réponse › », and the field beside a choice invites « ou tapez votre propre réponse »,
  « ou tapez oui ou non » or « tapez pour filtrer ». The line keeps the cells the longer
  prompt leaves, and a field already on screen changes its placeholder at once while keeping
  what you typed. `action ›` and `nika ›` read the same in both languages.
- **`?` shows the keys of what is on screen.** With nothing typed, `?` opens a read-only
  frame listing the keys of the surface in view (the conversation, the intelligence list,
  a question, a decision, or the work under way), its hint row's own words first, then the
  keys that always hold and the view keys the palette offers now, from the same catalog.
  `Esc`, `Enter` or `?` closes it with everything as it was; once words are typed, `?` is a
  word. The stage line ends with `? for shortcuts`, the palette lists it, and the ASCII
  glyph column reads the same keys.
- **`PgUp` reads the conversation behind a question's or a decision's card.** On a full screen the
  card covers the conversation down to the box, and a mandatory decision left no way to read the
  activity behind it before answering. `PgUp` now steps behind the card: the conversation shows
  and scrolls with the page keys and the arrows, the hint says « PgUp/PgDn scroll · any other key:
  back to the card », and any other key brings the card back as it was, answering, selecting and
  typing nothing. The card's hint names the key (« PgUp: history » where the row holds it), and so
  does `Esc` with nothing left to undo. A decision still opens with nothing selected; a question
  still opens on its recommended answer.
- **The request beside its question.** A typed choice shows what the
  Session keeps of your request, its goal and the questions still open,
  above the question, so nothing already understood has to be typed
  again.
- **A proposal's review card now uses the language you write in, around the Session's own
  facts.** In a French conversation the card reads « crée compiled-workflow.nika (33 lignes) »,
  « remplace … · sur les octets attestés … », « révisé sur place · … » and « run demandé ·
  report.nika une fois · plafond $0.25 ». Its headings follow (« à l'exécution · », « atteint,
  comme déclaré · », « répétition · aucune liée à cette identité »), and so does its identity
  on the border (« proposition 74189e5877e0 · F2 : tous les mots ») and the faces' first rows.
  What the Session's audit says a workflow reads, writes and reaches, and its rehearsal words,
  stay as the Session says them. On an ASCII screen these French words lose their accents.
- **A proposal's check face details the compile record of its bytes.**
  Below the static check, the object now shows how the proposed bytes
  were made (the mode, their sha256, the base a revision rests on, what
  changed and the preservation claimed) and every admitted component
  they hold, with its version, release and admitted-file digests, its
  bindings and what the bytes show of it now. `invoked` is admitted
  reuse like `expanded`; only a revised, absent, unreadable or
  unwitnessed component asks for attention.
- **A run's ceiling is chosen from rows.** When the Session asks the
  ceiling of a run it cannot prove one for, the TUI shows the amounts as
  rows, the recommended one preselected with why, another amount typed
  beside them and a way out; the transcript keeps the save report and
  the one short question.
- **The run in view now speaks the language you write in.** In a French conversation its
  faces read « [run] sorties fichiers preuve », its standing « un nouveau run · terminé ·
  réussi » (on its pinned row too), and the facts above its graph say the bytes it binds
  (« graphe · les octets … sur lesquels il a été demandé, tels que le run les nomme »), how far
  it went (« en cours · 4 tâche(s) vue(s) »), its settlement (« clôture · normal · 4/4 tâches ok
  · 9857 ms »), its stream (« flux · 19 événements et la clôture, entiers ») and its evidence
  (« attestation · scellée, comme le run l'a déclarée · la preuve est la vérification propre de
  la trace »). Its graph counts in French, its tasks wear « ✔ réussie » or « ○ planifiée », and
  the list says « tâches · ↑↓ choisir · Entrée détails ». The workflow, the cause, counts, times
  and digests stay as they are; a task's detail and the outputs, files and proof faces still
  read English. English is unchanged.
- **A settled run's story folds to one row.** In the workspace
  conversation, a run's story is folded once, when it is committed and
  the shell observed its run typed, whole and settled (never paused), to
  one quiet row: the run's label, its state and `F2`. The fold is never
  recomputed: a later block never folds or unfolds it. `F2` at the reading
  position, or a press on the row, opens the whole story; an untyped or
  incomplete run keeps its words.
- **A run's story now speaks the language you write in.** While a workflow runs from a French
  conversation, the busy row and the activity card read « en cours · meteo-maurice-quotidienne »,
  « ↺ today · depuis le cache », « ◇ en pause · `approve` vous attend » and « succeeded · 4/4
  tâches · 9857 ms », and the story the transcript keeps reads the same. The workflow, the
  tasks, the engine's status word, counts and times stay as they are. For other doors, a
  `RunSink` gains a `say` method that defaults to English, so `nika`'s plain output and every
  existing sink are unchanged.
- **The stage line, the live line and a run's card now use the language you write in.** In a
  French conversation the stage line reads « Brouillon ✓ · Enregistré ✓ · Vérifié ✓ · Actif ○ ·
  Exécution ○ » beside « ~$0.31 dépensés » and « ? pour les raccourcis ». The line under work
  in progress says « Préparation · 45s · 2 appels auteur · Ctrl+C arrête » or « Exécution de
  veille-tech.nika · 2/3 tâches · en cours : write · $0.012 sur $0.25 », and a run's card opens
  on « Échoué après 16.5s · 3 tâches exécutées · 1 échouée ». Times, amounts, file, task and
  model names keep their form, the Session's phase words stay as it says them, and English
  stays the default.
- **The stage line carries what the session spent so far.** After its stages (`Draft ✓ ·
  Saved ○ · …`), the terminal now adds `~$0.62 spent`: the Session's known estimate of
  this session's preparation plus the metered spend of the runs it observed. Nothing is said
  while the spend is unknown, a row too short for it drops it before the stages give way,
  and the model and decision seat keep their one home, the header.
- **`/welcome` brings the welcome back.** Once the first exchange took its space, the welcome and
  the example it offers (« Try: aggregate-by-key ») could not be read again. `/welcome`, listed
  with the commands, now brings it back as the object in view beside the conversation until
  another object takes its place; outside the workspace its words are said in the conversation.
  Nothing is sent to the Session.
- **The butterfly is drawn in braille.** Outside the ASCII column the
  welcome's mark takes Unicode braille renditions, 2×4 dots per cell, read
  from the same samples of `media/brand/nika-logomark.svg` as the ASCII
  density ramp, for the same five sizes; the reveal lights its dots in the
  same five frames. A test now regenerates every rendition from the pinned
  mark and fails on any difference, so a handful of ramp cells moved one
  step to the reproducible sampling.
- **A workflow's faces now speak the language you write in.** In a French conversation the
  object pane names its faces « source plan graphe vérification », and says the facts above the
  face (« workflow Nika · source · 1.9 Ko · identité … · 34 lignes »), what it cut (« 7 lignes
  plus larges que 65 colonnes, coupées au bord »), the plan's waves, the graph's summary
  (« Définition · 4 tâches · 3 arêtes ») and the check's reasons (« la définition est
  conforme »), as well as the compile record a proposal binds to its bytes and the rows around
  an opened file (« les octets de ce fichier …, tels que lus en dernier · r le relit »). The
  object's own action reads « [+] Agrandir · F4 », and a click on a face's tab finds it by the
  word painted there. The bytes, the engine's findings, the check's layer names, digests and
  values stay as they are; English is unchanged, except that the check's closing reminder
  now wraps rather than being cut on a narrow pane. For other hosts, `Canvas::speaking`,
  `Inspected::speaking`, `Checked::importing` and `Face::label_in` are new additive calls.

### Changed

- **An Anthropic key starts on Claude Sonnet 5.5.** The catalog's Anthropic default and
  the first-run suggestion for an Anthropic key name `claude-sonnet-5-5`, so a person who
  picks Anthropic without naming a model runs it; an explicit model keeps running as
  named.
- **The Anthropic nicknames name the latest Claude models.** By maintainer decision of
  2026-10-15, `anthropic/sonnet` runs Claude Sonnet 5.5, `anthropic/haiku` Claude Haiku
  5.5 and the new `anthropic/opus` Claude Opus 5.5, each with that model's 1M-token
  window and 128K output cap; the catalog's cheap Anthropic model is Claude Haiku 5.5.
  `anthropic/claude-sonnet-4-6` and `anthropic/claude-haiku-4-5-20251001` keep running
  as named, the latter with its retirement advice.
- **The conversation's author offers only what one workflow can carry.** Its instructions now say
  that every proposal is verified against the whole request, so splitting the work across several
  workflows is no option; that an option dropping a file says so in its label and one asking for
  an approval says who approves before what, so picking it settles the question; and that a
  refusal is told in plain words, never by a finding's name.
- **The workflow author shapes its choices for picking, not typing.** The author's
  instructions and the `ask` tool now say: whenever an answer is among known alternatives,
  give two to seven native options rather than a free-text question, with `multi_select`
  when several may apply; label an option that names a website or a data source with its
  display name, then « · », then its domain or feed address (`TechCrunch ·
  techcrunch.com/feed`); whenever a default can stand, show the choice in the proposal's
  `open` with exactly one recommended option. The assumptions-first rules are unchanged: a
  default is still never asked, and a question asked with `ask` recommends nothing.
- **The author waits for a fan-out and keeps independent items.** Its card showed `fail_fast: true`
  as the one form of a fan-out, and never said that a fan-out over an empty collection is skipped
  with a `null` output: an inbox workflow failed with NIKA-VAR-006 on an empty folder, its steady
  state, and one failed summary of two lost both. The card now shows the plain fan-out, has every
  task that reads a fan-out's output wait for it with `after: { <id>: success }` (an empty
  collection then ends the run with nothing to do), and, when each item stands alone and the
  request does not want all or nothing, keeps the other items with `on_error: { skip: true }`.
- **Provider defaults and nicknames follow the served model ids.** OpenRouter uses
  `anthropic/claude-sonnet-4.6` and `anthropic/claude-haiku-4.5` with their sourced
  limits; `deepseek/chat` aliases `deepseek-flash`, while the explicit retired wire
  id remains unchanged. OpenAI defaults to `gpt-6.1-sol` and `gpt-6-luna`; xAI
  defaults to `grok-4.7` and `grok-4.3`, with the first-run suggestion naming
  `xai/grok-4.3`. Retired grok-3 prices are removed, and the human catalogue lists
  each aliased wire once. Groq defaults retain their models.
- **`nika catalog` lists providers in the spec's presentation order.** Within its
  LOCAL and CLOUD sections the rows follow the vendored design tokens'
  `presentation.providers_order` (local engines first, then Mistral, Hugging Face,
  OpenAI, xAI, Anthropic, DeepSeek, Google, Groq, NVIDIA and OpenRouter), the
  order the language server already teaches; a provider the tokens do not name
  comes after every named one, by id. The command keeps no order of its own.
- **The TypeSafe decision seat lives in a size-cap member.** `nika-cli-host-typesafe` now holds
  the System One seat (its transport, the Session's journaling seat and the batch exchange);
  `nika-cli-host` re-exports it whole as `compile::typesafe`, so no consumer changes. The host
  crate drops from 14,995 to about 14,076 production lines. The seat's key accessor now
  returns a `Secret`, read only through an explicit `expose`.
- **Decision partitions retain the exact serialized request bytes.**
  `Partition::begin` returns the optional carried request as `Vec<u8>` instead of
  a JSON value. Callers must preserve those bytes rather than serialize a parsed
  value again. Model-part qualification also exposes its typed findings; this
  preview's public API change is not exclusively additive.
- **A decision batch is not refused twice at a size it already learned.**
  When the System One decision service refuses a request of several items
  for capacity, the rest of the batch never sends a request that large
  again: a waiting half as large as the refused request is split before it
  leaves, in order, instead of being sent whole to be refused. The seat a
  Session keeps also remembers what its batches learned, so a later
  compile's batch starts at the largest size the service answered, below
  every refused size; a batch that never met the capacity teaches nothing,
  and nothing persists beyond the process. Each journaled request split
  before it left names that bound (`split_below`). Single items, order,
  answers and the rest of the journal are unchanged.
- **`nika doctor` says "unknown" when it could not see.** A harness seat
  whose login check did not answer in time was a warning; it is now its own
  level, `unknown` (glyph `?`), counted apart in the verdict line and in
  `--json` as `summary.unknown`, never as ok, warn or fail. The exit code
  is unchanged: an unknown row never makes `nika doctor` exit 3.
- **Groq's catalogue row lists its self-serve gpt-oss models, and its defaults
  move to them.** `groq/gpt-oss-120b` and `groq/gpt-oss-20b` name
  `openai/gpt-oss-120b` and `openai/gpt-oss-20b` (131,072-token window, 65,536
  output tokens, from Groq's models page of 2026-10-12), and Groq's default and
  cheap models move to them from the Llama rows, which that page now lists for
  enterprise contracts only. The Llama nicknames keep their models, and the same
  ids stay usable on Scaleway, NVIDIA and local engines.
- **The knowledge door ranks families by the light French stems of their words.** A request and a
  family now meet whatever the inflection: each informative word of both is read by a light French
  stem (a plural, « -aux », then one inflectional or common derivational ending), so « importées »
  meets « importer » and « feeds » meets « feed ». On 32 held-out French requests, the family each
  belongs to now ranks first for 20 instead of 19 and is recalled for 31 instead of 28.
  Patterns, blocks, examples and skills reached by their own text keep their words; each family
  recalled still brings its packs' patterns and blocks, presented whole. The pack names its
  builder `knowledge-door-v8`.
- **The embedded knowledge release is r3.5** (`knowledge-0.123.0-r3.5-foundry-20261016`). It adds
  seven blocks for recurring jobs, each qualified in recorded worlds: an API value watched with a
  state file that asks only on the first run and on a change, a report of the previous month on a
  day-of-month cadence with amounts in integer cents, a folder translated file by file with each
  failure named in a report, an inbox processed and emptied, a weekly reading list from feeds, a
  daily news digest posted only on approval, and a CSV cleaned with the file line of every removed
  row. Nine patterns (among them the run's spending ceiling, which lives in `--max-cost-usd`, the
  project's `ceiling:` and a beat's `plafond:`, never in the workflow file), eight packs, two
  families and French phrasings come with them, and the cadence knowledge reads a day of the month
  (`TZ=<zone> 0 8 1 * *`). The r3.2 release it replaces stays embedded for earlier pins, which
  reopen its exact bytes.
- **Machine protocol 2.** This engine reports `machineProtocolVersion: 2` on `/health` and
  `nika --sdk-identity`.
  - **Why.** Compile questions may be `choice`, which `@supernovae-st/nika` 0.120.3 refuses
    mid-compile. From now on, any change to an object a published SDK reads closed raises the
    protocol in the same commit, held by a contract test against that SDK's closed sets. That SDK
    then refuses this engine at connect (`NikaCompatibilityError`), before any job is admitted,
    never in the middle of a run. Use the SDK of the same release. It reads protocols 1 and 2, so
    upgrade the SDK first, then the engine.
  - **Resident stores.** A resident stamps its stores with the protocol once it holds the lease,
    so stores a 0.123 resident wrote are refused by an older resident, by design. Before
    upgrading, copy the state root (by default `.nika/serve`). To roll back, serve that copy, or
    a fresh `--state-root`.
  - **Doctor.** Its resident row fails for stores a newer protocol wrote, whether a resident runs
    or not, and names those two ways out. It used to read a stopped newer resident's stores as
    fine and suggest a restart this binary would refuse.
- **The models.dev pricing snapshot is refreshed to 2026-10-12.** It carries
  729 rules (716 before): 38 rows arrive, among them Claude Sonnet 5.5 and
  Haiku 5.5, GPT-6.1 Sol and Step 5 Preview, and 25 rows models.dev no longer
  lists leave, among them `openrouter/qwen/qwen3.8-27b:free`, with the three
  measured energy figures that rode on them. DeepSeek V4 Pro's snapshot price
  moves from $0.435/$0.87 to $0.66/$1.98 per million tokens; its admission
  tariff, at the peak rate, still prices its runs. DeepSeek's retired
  `deepseek-chat` and `deepseek-reasoner` names, which models.dev no longer
  lists, keep their `deprecated` status: priced for a recorded run, never
  offered.
- **The knowledge door lives in a size-cap member.** `nika-onboard-knowledge` now holds the strict
  admission of a Foundry knowledge release, the releases this build embeds, the catalogue and the
  authoring pack, with their assets and pinned vectors; `nika-onboard` re-exports every item at
  `knowledge::`, so no consumer changes, and keeps `knowledge::pin`. The onboarding crate drops
  from 14,999 to about 11,090 production lines.
- **The access plan, the probe facts and route identity live in a size-cap member.**
  `nika-providers-access` now holds `resolve_access`, `route_identity`, the probe's data types and
  `canonical_provider`; `nika-providers` re-exports every one at its historical path, so no consumer
  changes. The provider crate drops from 14,991 to about 12,800 production lines.
- **A rehearsal bounds the values a run builds.** A candidate whose templates
  build a value larger than the rehearsal room holds is run, and stopped where
  the value is built: the rehearsal reports a data bound naming the task and
  the value's size, instead of refusing every amplifying template before any
  run.
- **A served `rattraper` catch-up fires the latest missed slot once.** As spec 01 states, and as
  the activation's one-ceiling worst spend assumes: a `nika serve` schedule under `manqué:
  rattraper` (the job API's `catch-up`) no longer fires every missed slot, up to 10,000 runs. The
  older slots are skipped, journaled once. A catch-up due while its schedule's previous run still
  runs now waits for that run instead of being skipped as an overlap.
- **A new run reads earlier work lean.** When the person writes, a superseded candidate's
  arguments and findings and a page read again later reach the model as one-line stubs;
  the person's lines, the provenance replies and the current candidate stay whole, the
  record is unchanged, and nothing before a replayed thinking block is edited.
- **A Session conversation is compacted within its route's window.** On
  a route a tariff prices, the conversation's earlier entries are folded
  into a summary before a request its route could not take, instead of
  every request carrying the whole conversation. A route no tariff prices
  keeps its whole conversation, as before.
- **The session's intelligence, reasoners, line router and authoring door
  now live in the `nika-session-intelligence` member crate.** A structural
  split (ADR-150) that brings `nika-session` back under its production-size
  wall: every `nika_session::{authoring, intelligence, reasoner, turn}` path
  and root re-export names the same items, and no behaviour changes.
- **A default picked at a proposal needs no author round trip.** When a
  proposal shows its choices with their defaults, picking a choice's
  recommended option (Enter on its preselected row, its key or label, or
  `yes`) binds it and keeps the proposal shown, with its next choice or
  its save decision, instead of asking the author to propose again. The
  bytes, identity and card stay those the person saw. Another option, a
  sentence, a changed candidate, the last pick of a proposal whose request
  asked to save or run, and a conversation an ACP agent leads still reach
  the author.
- **The author reads the language up front.** A new conversation's instructions carry
  the engine's one-page language, its canonical fragments and output conventions as
  reference, and an index of what `language` and `knowledge` hold, so a missing detail
  costs one narrow lookup.
- **The observations of one message run side by side.** An observation reads one public
  page outside the Session's lock, and consecutive ones run at once: three sources cost
  about one page's time. Their replies keep the message's order; every other call stays
  sequential.
- **A Session turn keeps one connection to its model route.** The
  conversation's calls in one turn share one client and its runtime,
  built at the turn's first call, instead of a new client per request;
  each request is still admitted and can still be stopped on its own.
- **The author knows the run model's efforts.** The `models` tool names the
  reasoning efforts each offered run model documents and its output limit, and the
  author declares a `low` effort for tasks that only write or summarize text, so a
  reasoning model's thinking no longer exhausts the answer's tokens.
- **A saved default naming Jev opens new conversations with Jev.** When
  the operator default names a decision seat and the host names none, the
  session builds that seat by name, its key read through the seat's own
  boundary, and consults it while it can; with the key missing the seat is
  shown unavailable and never blocks the author. The header now reads
  `Decision: Jev 1.13.0` (or `Decision: same model`).
- **The properties you state are checked before an output is written.** The Session's
  author is asked to check each property your words require of an output (an order, an
  exclusion, a count or a total) with an assertion that the trial and every run evaluate
  before the output is written; a candidate that breaks it fails its trial and is repaired
  before you see it.
- **A line typed during a turn holds back only the calls that hand the turn over.** A
  steering line waits before ask, propose and new_request; a candidate write or a read
  the author already decided still runs, and the line enters after them.
- **A Session's last run names its job, receipt, outputs and the verifier's seal facts; `sealed`
  keeps its meaning.** `work.run` gains `job`, `receipt`, `outputs` (kept whole up to 16 KiB
  encoded, absent past it: the job keeps them) and `seal_verified` / `signed` as `nika trace
  verify` attests them for the run's journal, absent when no verifier judged it. The Session
  judges a native run's local journal when it observes the run and holds those facts beside its
  kept value, which stays the one an earlier engine reads; an opaque trace identity is never read
  as a local journal. `sealed` still names what the Stop's story reads, the settlement's receipt
  naming the chain head: a run that stopped at a wave boundary without a run key reads stopped,
  never cut, and no verifier's judgment is read from it.
- **The author writes and proposes in one message.** A write's reply now
  carries Nika's check findings, the laws over the person's words and
  the values it dropped, so a candidate needs no separate check or verify
  round before it is proposed; a shown proposal ends the turn instead of
  costing one more model call; `compose` is lent the pinned release's
  components and the release is admitted once per turn.
- **The size walls are 25,000 production lines per crate and 2,500 lines per file.** The CI
  ratchets and the pre-push gate now block a crate above 25,000 production lines and a source
  file above 2,500 lines; the hygiene dashboard warns, without blocking, above 15,000 and 1,500
  and names the crate closest to its wall, and a file with a `LOC-EXEMPT` marker stays capped at
  5,000 lines. One ceiling applies to every crate: the native renderer's 19,000-line exception
  is absorbed. ADR-154 records the decision and amends ADR-004, ADR-023 and ADR-143.
- **A conversation's document needs an executable check for each count,
  total, order or cut its request states.** A document the Session's author
  wrote was READY although a typed property the request states had no check
  any run evaluates, so its trial proved nothing about it. The conversation
  door now holds such a document before its trial and its judge, INCOMPLETE,
  naming each property with the request's words and the check to add: a
  `nika:jq` boolean asserted by a `nika:assert` that reads it through
  `with:` and quotes those words, the write waiting for it. A bound on text a
  model writes is not asserted and stays the judge's. No model is called to
  decide it, and the other doors follow once their author prompts teach the
  same checks.
- **`/status` says first whether the saved workflow is scheduled.** A person asking « is my Monday
  recap on? » got the intelligence, the seat, the knowledge and the money lines and no word about
  the schedule. `/status` now opens with the workflow this session saved, whether a schedule is
  declared for it and whether a firer is proven on this machine, and how its last run ended.
- **The conversation's author speaks as Nika and leaves the Session's acts to it.** The author's
  instructions now say to speak as Nika in the first person. Installing, removing, activating
  and deactivating a trigger belong to the Session: the author never claims them, nor advises a
  terminal command or a hand edit of `nika.yaml` instead. They also cover plain text without HTML
  entities, and « montre-le-moi avant de l'enregistrer » as a request to see the proposal. The
  author never advises a stronger model unless the verifier asks for it, and a recommendation
  never contradicts the person's explicit requirement. Each source of a multi-source result
  recovers on its own, and a thinking model's `max_tokens` leaves room for its thinking.
- **Schedule questions show their options once.** The missed-run policy
  and the ceiling per scheduled run are chosen from rows the Session
  states, the recommended amount preselected and another one typed
  beside them; the transcript keeps the question's sentence and one
  short line instead of repeating the options as text. A host without
  rows still reads every option as text.
- **A chosen command runs once.** `Enter` on a command in the palette
  or the slash list runs it once, your unsent message left as it was;
  `Tab` still only inserts it. A repeated `Enter` after choosing or
  answering counts once, and queues nothing from a message waiting out
  of view.
- **One surface above a fixed composer.** The command palette, the slash
  list and a typed question's offers open as one opaque surface above the
  composer. The composer and the transcript keep their rows and the reading
  position: opening, searching or closing it moves nothing, and nothing
  hidden under it takes a key or a click. A typed choice keeps your unsent
  message out of view, untouched, and gives its own reply a field of its
  own; the arrows choose whatever that message holds.
- **A decision names the act the Session recommends.** Its rows now say « recommandé » /
  « recommended » beside that act, as a question's rows already did: the trigger's install marks
  « Installer et activer », or « Garder déclaré seulement » when a scheduled run would not see the
  workflow's keys, and a run's or a schedule's ceiling marks its recommended amount. A decision
  still opens with no act selected, so Enter consents to nothing by itself; a decision the
  Session recommends nothing for, such as a save, marks nothing.
- **The workspace wears electric blue.** The brand, focus, selection,
  primary action and interaction chrome take the electric-blue accent
  and selection fill of the ratified design, replacing violet. Success,
  caution, failure and each verb keep their own hue; `NO_COLOR` still
  carries no hue and the ASCII column keeps its marks.
- **`Esc` steps back and never answers a decision.** In a choice list it
  first empties what you typed, then the selection; on a Save, cost, gate
  or other decision it then sends nothing and says so, so its discard or
  cancel row is chosen only with `Enter`. In `/intelligence` it returns
  from a connection's models to all connections, and at the top keeps the
  current choice.
- **A connection's models open on the current generation.** In
  `/intelligence`, earlier generations, dated snapshots and legacy models
  now fold behind one `Older models (N)…` row, so Anthropic opens on its
  Claude 5 models; the model in use and the recommended one always stay
  in view.
- **Reopened, `/intelligence` opens on `Keep current`.** Opening the picker again selected
  `Recommended for you`, so a reflexive `Enter` replaced the author in use with the
  recommendation. Once an intelligence is chosen, the picker now opens on `Keep current ·
  <model>` and one `Enter` keeps it; where a first choice is required, the recommendation is
  still selected and one `Enter` applies it.
- **The intelligence guide no longer teaches a typed route.** `/intelligence`
  and an opened connection now say only to choose below; every model this
  machine offers is a row. Typing `/intelligence 2 provider/model` still
  works as an expert fallback.
- **`Enter` validates a multiple choice.** Space checks or unchecks an
  answer; `Enter` on any row now sends every checked answer at once instead
  of toggling the row under the cursor. With nothing checked it sends
  nothing and says that Space checks an answer.
- **The lifecycle rail names its stages in the Session's words, in the person's language.** The
  workspace now asks the Session for the rail in the language it reads from the person
  (`nika_session::Lang::rail`: « Brouillon ✓ · Enregistré ✓ · Vérifié ✓ · Actif ◐ · Run ○ ») and
  paints it as it comes, instead of translating the Session's English stage names word by word
  with its own table (which said « Exécution » where the Session says « Run »). A first
  question's or a waiting proposal's own rail still gives its row to the card in either language.
  Under the ASCII glyph column the Session's words keep their accents, as its other words do.
- **The connections you can use now come first in « Choose an AI connection ».** Within each
  access (agent accounts, APIs, local servers), the connections usable now lead: an API whose
  key is present, an account or a local server the Session detected that can write workflows.
  The rest keep their order below, and every other order is unchanged. With only
  `NIKA_ANTHROPIC_API_KEY` set, Anthropic is now the first API row, where it stood twelfth under
  eleven « key required » rows. Nothing is probed to tell.
- **The workspace reads the Session's schedule histories as they are.** `ProjectView::schedules`
  now holds `nika_session::ScheduleHistory` itself (built with its `new`), and the TUI's own copy
  of it (`ScheduleView`, `SlotView`, both added in this preview) is gone. Whether this machine
  holds a schedule's unit is the lent trigger host's answer (`TriggerHost::installed`), so a test
  host proves the rail's « Actif ● » without touching launchd. Nothing a person sees changes.
- **A stopped call wears the cancelled mark, not a failure's.** In the activity card a call the
  person stopped read « ✖ call 1 · … · stopped, a charge may be unknown », the glyph the run views
  use for a failure. It now reads « ⊘ call 1 · … », the theme's cancelled mark (« - » in ASCII):
  a stop is the person's own act, not a failure.
- **A typed « je voudrais l'enregistrer et le lancer » at a shown proposal is pinned through the
  TUI.** The save decision takes the person's words through its bound door: the Session saves the
  workflow and hands its run over, and the door runs it once. A test now holds that journey end to
  end in the TUI, nothing run before the door performs it.
- **The welcome names the Session's own demo.** « Try: aggregate-by-key » on the welcome screen
  now reads the Session's `nika_session::WELCOME_DEMO`, the demo it gives a person who asks for
  « l'exemple de l'écran d'accueil »: the screen and that answer cannot drift apart.
- **The welcome's butterfly wears the brand's ice.** Where `COLORTERM`
  proves 24-bit colour the mark takes the logomark's own ice, and while it
  is revealed what each frame has just lit glows until the last frame
  settles it. A 256-colour terminal gets the nearest xterm indices, the 16
  ANSI slots bright cyan and bright white, `NO_COLOR` no hue; reduced
  motion draws the settled mark at once.
- **The wordmark locks up with the butterfly.** The welcome's spaced
  `N I K A` becomes the wordmark `nika.` under the mark, the name strong and
  its dot in the exec hue, and the header opens with the same wordmark in
  place of `◆ NIKA`. Both glyph columns draw the same characters; without
  colour it is plain `nika.`, and every other welcome word stays.
- **A workflow named exactly `v1`, `v2`, … must be renamed.** `nika:`
  names the file, and `v1` is the retired schema tag it once held. A
  workflow named that way is now refused at parse (`NIKA-PARSE-003`) with
  a remedy that says so, as `nika.yaml` already refused it. Only the
  whole marker is refused: `vault`, `v2ray` and `v1-migration` stay
  ordinary names.

### Fixed

- **« active-le » on a schedule declared earlier offers its trigger.** On a Mac whose terminal
  Session installs triggers, activating a schedule `nika.yaml` already declares no longer stops at
  « déjà déclaré · pas prouvé actif ». When no unit is installed, Nika asks « Installer le
  déclencheur sur ce Mac ? ». When the unit is there, it says the schedule is active and when it
  runs next. A line asking for another cadence still points to the entry to change.
- **The activation honours « choisis pour moi », says when a schedule is already declared, and
  never runs instead.** On the activation's questions, « choisis pour moi » or « fais au mieux »
  was dropped or refused (« is not a positive amount in USD »): it now takes the run-once policy
  and the recommended ceiling, both shown in the declaration consented to. Activating a workflow
  already declared said so only after asking the missed policy and the ceiling again; it is now
  said at once. « Vous pouvez l'activer pour moi, directement ici ? » ran the workflow: a filler
  around the activation words keeps them the activation, and a line about activating or
  scheduling never reaches Run. A green run that recorded no priced cost is still a run observed:
  the ceiling says it cost nothing priced, not « no run of this workflow observed yet ».
- **A second schedule joins the `arm:` list Nika wrote.** Activating a second workflow of the
  same project was refused with « Nika does not rewrite a list it did not write », about the
  `nika.yaml` Nika had written minutes earlier, and the person was sent to edit YAML by hand. An
  activation now appends its entry to an `arm:` list that ends the file; a list followed by other
  settings is still the person's to edit, and a workflow already declared keeps its entry.
- **An agent no longer runs the calls of a message cut at its output limit.** Each call of
  such a message is answered « not run: your message was cut at the output limit; re-issue the
  call with complete arguments » and the loop goes on under the task's own limits, as the
  Session's agent does. A turn the provider interrupted on its side is never acted on: the task
  fails `NIKA-INFER-001`, transient, naming the provider's own word.
- **An answer the provider cut at its output limit now fails instead of passing as the
  output.** An `infer:` task whose provider stopped at `max_tokens` used to return the
  truncated text as a success (a model that reasons by default can spend most of the cap
  on reasoning: 4,096 of 4,096 output tokens, 3,860 of them reasoning, the answer cut
  mid-sentence). It now fails with `NIKA-INFER-005` (not transient), before any `schema:`
  validation or repair. The run record's `details` carry the text received
  (`partial_output`) and the provider's stop reason, and an `on_error:` recovery can use
  them. An agent's final message cut the same way fails with `NIKA-AGENT-006`. A blank
  cut answer keeps its earlier failure: `NIKA-INFER-004` on a text task, the schema check
  that names the token limit under `schema:`.
- **Anthropic calls are priced again.** Every Anthropic reply now carries `"speed": "standard"` in
  its `usage` object, a key the completeness rule did not know, so each call counted as « provider
  did not report usage »: runs on Claude showed « cost · UNKNOWN · 1 unpriced call(s) » and no
  allowance settled them. The standard speed is now a known axis beside the standard tier and
  global inference; a fast reply, billed at another rate, still leaves its call unpriced.
- **« Active-le tous les matins à 7h » activates that schedule.** An activation line that states
  its cadence re-proposed the saved workflow, then asked for the English word « activate ». The
  cadence it states is now the schedule declared, and a plain « activate » after a workflow saved
  without one takes the cadence the person's latest line stated (« qu'il tourne toutes les
  heures »). The activation questions read the person's words: a city for the time zone
  (« Paris », « heure de Paris »), a sentence for a missed run (« le faire une fois quand il se
  rallume »), an amount with its currency (« 0,10 $ ») and a choice left to Nika after another
  clause (« je ne comprends pas, choisissez pour moi »).
- **« Active-le tous les matins » activates the saved schedule.** An activation word with only
  the period the saved schedule already runs on reached the author, which proposed the saved
  workflow again. It now opens the same activation « active-le » opens; another period is a change.
- **An activation question takes its recommended option in words.** « le recommandé, celui à
  10 cents » was refused as no amount in USD, and « comme la dernière fois, vous choisissez » was
  not one of the three, while « comme vous voulez » was. The recommended option, a choice left
  to Nika in other words, and one amount said with its unit inside a sentence are now read.
- **An answer at a gate is recorded when the paused run spent part of its ceiling.** A run that
  paused at a `nika:prompt` gate after a priced step resumes under what its ceiling leaves, and
  its admission priced the whole workflow again, the finished step included. « non » and « oui »
  were both refused (`NIKA-1709 · … unavoidable cost floor $0.196608 exceeds --max-cost-usd
  $0.195214`), the answer was never recorded and the run stayed paused, from the Session, the
  terminal and `nika run --resume <trace> --answer <task>=<value>` alike. The CLI preflight and
  the runtime's own launch gate now price the work still to run: a task the resumed journal
  completed is a cache hit and costs nothing, with any child workflow or priced builtin it
  calls, and a branch the answer closes was already priced closed. A step edited since the
  pause runs again and is still priced by the runtime's gate. What still runs is judged as
  before, and a fresh run's floor is unchanged.
- **A question Nika asked no longer blocks a proposal that shows it.** When the author asked a
  question and then proposed with that same choice shown on the card, `yes` was held (« Nika asked
  `topics`, which this proposal does not show ») and the asked question could not be answered while
  the proposal waited. The card's choice now is that question: `yes` keeps its default, another
  option revises. A question the card does not show waits first and is answered by its own
  identity (that reply never names the workflow's file); a proposal shown before that answer is
  revised before it saves.
- **A reasoning effort the model does not offer is a finding, not « clean ».** A workflow
  declaring `run.reasoning.effort: low` for a model whose catalog lists no effort level passed
  « check · clean ✔ » and « Vérifié ✓ ». Every run of it was then refused before its first task
  (NIKA-1800). The Session's check now reports it as a finding, so such bytes are neither shown
  as ready nor saved as clean. What only this machine lacks, such as a seat not installed, stays
  a run-time matter.
- **An empty product is said in the run report.** A run reported « réussi » with « produit ·
  ./journal.md (0 B) » among its green ticks, after moving the three messages it read to
  `traites/`. The report now says when a file it wrote is empty, and how many files the run
  removed beside it, so the person checks the result before running it again.
- **An HTML feed title reads as its words.** A feed title of type `html` (Atom `type="html"`, as
  The Verge publishes) kept its markup: a reading list showed « Bluesky&#8217;s AI tool ». The
  `feed` mode now decodes its entities and drops its tags for the feed's and each item's title; a
  plain title, the summary and the content are unchanged.
- **An identical restatement no longer piles up in the request.** A retry or a restatement sent
  twice joined the request each time: « Original request: » four times, three identical
  corrections, and a judge question large enough to overflow its context. A correction whose
  words equal the request's last ones now leaves the request as it was.
- **An installed trigger whose runs would miss their API keys is never said to run on its own.**
  After the activation card had warned that a scheduled run would not see the person's keys, the
  install still answered « Installé et actif · tournera tout seul ». It now says the trigger is
  started, that every scheduled run fails until it sees the named keys, and how to give them: a
  file of `KEY=value` lines passed to `arm --emit launchd --env-file <file> --write`.
- **An answer the provider interrupted says how to go on.** « the provider interrupted the
  answer: nothing of it was kept or run » came back three times after 2 min 05 s with nothing
  saying whether to retry, rephrase or change the model. It now says the provider ended the
  answer with an error, that nothing was kept or run, and to send the same line again or choose
  another model with `/intelligence`.
- **A GPT-6 call that wrote the prompt cache is priced.** OpenAI reports the input it wrote to
  the prompt cache beside the cache read; Nika read any write as an unknown billed axis, so such
  a call stayed unpriced. The write now counts within the input, at the cache-write rate the
  GPT-6 pages state; on a model whose page lists no write rate, the call stays unpriced.
- **An authoring call whose recalled knowledge exceeds what one request carries is still sent.**
  Bounded inference sends at most 1 MiB and refuses a larger request before any byte leaves, so a
  large knowledge pack lost the whole call and nothing was authored. The document, sketch,
  revision and Plan doors now weigh their opening first: past the capacity less an eighth kept for
  the rounds that follow, the pack's references are shown by their first line from the end of each
  kind's order, the same share of every kind, until it fits. Each says so in its text, and its
  receipt keeps the whole entry's size and digest (`first_line_only`). The request, the card, the
  callable contracts, the language and the component list stay whole, and an opening that fits is
  sent unchanged.
- **An OpenRouter reply is priced again.** Every paid OpenRouter reply read as an unknown cost
  (« cost · UNKNOWN · 1 unpriced call(s) ») because it carries the upstream provider's cost
  beside its own charge, which Nika took for a BYOK bill. A non-BYOK reply now settles at the
  charge it reports, its breakdown read for its shape only: `z-ai/glm-5.3-flash` costs
  $0.000078 for a one-word reply instead of an unknown amount.
- **An unbounded estimate names what bounds it not.** « model output estimate · unbounded: a
  token or iteration bound is missing » stood beside a declared `max_tokens: 4096`, when only
  the number of items a step fans over was unknown. The line now names the tasks that declare
  no `max_tokens`, the tasks that run once per item of a list known only at run time, and a
  composed workflow with an unbounded step.
- **An unpriced call says why.** Every unknown cost read « a route with no price table (a local
  model is unpriced, never free) », said of OpenRouter and Gemini routes, remote and billed. The
  run result now names the reason its frames recorded: no verified tariff for the route, usage
  its provider did not report completely, a subscription seat, rejected usage or a local model.
- **An approval stated before an effect is a gate without an asking verb.** The reader saw a human
  gate only through an asking verb or an « after my approval » shape, so « je veux une validation
  humaine avant d'écrire jobs-<date>.md » or « je valide chaque run avant l'écriture de … »
  stated none, and the « écrire jobs.md » such a gate holds was no write without the word
  « fichier ». A person's approval that a `before` closes now gates the effect named after it,
  and inside a gate a form of writing followed by a file it names is the write. A denied approval
  (« pas de validation avant … ») and a validation step no person performs (« a schema
  validation before writing ») stay none, and a person turning down a proposal (« non, je veux
  plutôt écrire le total dans ./out/total.txt ») bans nothing.
- **`nika arm --emit` no longer arms a beat that cannot run.** `nika arm` reported a beat
  UNREADY (a required input with no binding source, a missing workflow, an unpriced
  model), yet `--emit` printed its unit and `--write` installed it, so the OS would fire
  a run that could only fail. The emission now refuses while any active local beat is
  UNREADY, naming each one with the blockers `nika arm` shows: fix the beat, or suspend
  it with `actif: false`. A beat whose only open question is an unknown (a child
  workflow whose model only the run decides) now reads READY, as `nika arm fire` fires
  it, and `nika arm` still names the unknown.
- **`nika arm --emit --write` retires the units of beats `nika.yaml` no longer arms.** A beat
  removed from the registry, renamed, or moved to the cloud kept its OS unit, which went on
  waking `nika arm fire` for a beat that no longer exists; neither `--write` nor a dry
  emit mentioned it. Now `--write` syncs the units with the registry: each of this
  project's units whose beat is no longer armed is named with the command that unloads it,
  then its files are removed. Loading and unloading stay your gesture, as before. A dry
  `nika arm --emit` lists what `--write` would retire. Another project's unit, and a unit
  without this project's GENERATED mark, are never touched.
- **Two projects that arm a beat of the same name keep their own OS units.**
  `nika arm --emit … --write` named a unit after the beat alone
  (`nika.arm.daily`). A second project's `--write` replaced the first
  project's unit, `nika arm disarm daily --write` in one project deleted the
  other's, and a flagless `--write` could copy another project's env-file
  wrap, so its runs sourced that project's keys. Unit names now carry the
  project's key (`nika.arm.<key>.daily`, the start of the project root's
  fingerprint), and neither the emit nor disarm reads, replaces or removes
  another project's unit. Disarm now prints `launchctl bootout
  gui/$UID/<label>`: the printed path form failed once the plist was gone, so
  the job stayed loaded until logout. Units an older engine wrote are
  reported with the commands that retire them; re-run `nika arm --emit
  <target> --write` in each project. A project whose `.nika/arm/env-file`
  already names another project's file keeps it until you pass `--env-file`.
- **A failed authoring call names the provider's status again.** A dispatched call that fails
  carries its money observations, and the authoring boundary judged that wrapper instead of the
  error inside it: a busy seat's 503, now ended at once instead of re-sent, read only « the
  authoring provider call failed », and an observed admission refusal lost its type. The document
  now says « the authoring provider answered HTTP 503 », so the caller knows a retry may help.
- **The author reads the Session's spend so far with each line.**
  QA gauntlet q09: asked what preparing had cost, the author answered
  « je n'ai accès à aucune mesure » while `/status` said $2.32. Once a
  preparation request was observed, each line the person types now
  reaches the author with the spend as `/status` says it, ahead of the
  run and recovery notes. An agent leading over ACP, whose subscription
  the Session never meters, reads none.
- **« quoi ? » reaches the conversation.** With no recovery to tell, a bare « quoi ? » or
  « hein ? » answered with the last run's fact, « no run yet under this root (no trace in
  `.nika/traces/`) », beside a question the person could not read. It now reaches the
  conversation, which answers what the person did not follow. « what happened? » still reads the
  last run.
- **A bare yes answers the author, never runs the workflow.** « Oui, allez-y, merci. » to the
  author's offer to prepare a change ran the saved workflow and overwrote its report. A line made
  only of a yes, a go-ahead and courtesy now goes to the conversation, which knows what it
  offered.
- **A behaviour defect is said in words.** When a rehearsal of the draft gave a result the request
  does not ask for, and no repair followed, the conversation showed the repair's raw record
  (`{"kind":"behaviour_failed",…}`) under « what stopped it ». It now says what the rehearsal
  showed against what was asked.
- **A choice or decision card no longer hides what Nika just said.** The card covers the
  conversation, so a reply written after your last message, such as why a requested change could
  not be made, was only found by scrolling once the card had closed, and the save decision came
  back looking as if nothing had happened. The card now opens on Nika's words since your last
  message: the whole reply where it has room, otherwise its first row, marked as cut, then its
  latest lines, with Markdown marks read as words. A decision on a proposal shown before your
  last message says `this proposal is unchanged since your last message`; an act you picked on
  the card, such as « Inspect the exact changes », does not count as a message.
- **A recommended ceiling reads as a multiple of one run.** « $0.10 · covers 10 runs like the last
  one » read like a budget for ten runs. It now says « 10 × the last run ($0.0094) », in French
  for French speakers.
- **Naming the ceiling without an amount no longer refuses the line.** « Le plafond, je le donnerai
  au moment de lancer » or « quel est le plafond ? » was refused as an invalid amount, and nothing
  was prepared. Such a line now states no ceiling. A bare « …, budget » after the work and a
  malformed amount (« plafond -1 », « budget NaN ») are still refused.
- **A ceiling offer counts the runs it holds.** The recommended ceiling read « $0.10 · covers at
  least 3 runs like the last one, $0.0015 » for an amount that holds 66 of them. It now says how
  many: « covers 66 runs like the last one, $0.0015 each ».
- **A run's ceiling now reads the same way wherever it appears.** Before a run, the
  Session wrote « ceiling $0.1 (saved with the workflow) » and the line below it wrote « ceiling
  $0.10 ». A ceiling under a cent showed as « $0.00 », which is wrong. Every surface that names a
  ceiling (the run's announcement, the proposal's asked run, the project facts, a run's result,
  `nika check` on a project) now writes the amount as you set it: `$0.10`, `$0.25`, `$0.001`,
  `$2`. Every significant digit is kept, cents show wherever there is a fraction, and no amount is
  rounded to zero.
- **A change said beside a rerun is made first.** A line with a rerun word and an amount was read
  as « rerun the saved workflow under this ceiling » whatever else it asked: « passe l'étape IA du
  commentaire sur Claude Sonnet, et relance avec le même plafond de 0,10 $ » reran the Haiku
  workflow at once and overwrote its report, the model change lost without a word. The shortcut
  now applies only to a line that says nothing but the rerun and its ceiling (« augmente le budget
  à 1 $ et relance », « relance-le, max 0,50 $ »); a line asking a change beside it reaches the
  usual reading, which prepares the change.
- **A choice left to Nika is said in the person's language.** « Laisser Nika choisir » recorded
  « I leave the choice to Nika. » as a French person's words. It now records « Je laisse le choix
  à Nika. » for a person who writes French.
- **A revision's circumstance is judged with its clause.** « Dans rapport.md, pour les doublons,
  mets l'email … » was cut at its commas and refused because « Dans rapport.md » and « pour les
  doublons » were judged as missing requirements of their own, under a « Change: » label the
  person never typed. A short phrase that opens with a preposition and ends at a comma now joins
  the clause it frames, and the refusal no longer quotes the label.
- **A long conversation keeps the person's own words.** When a conversation outgrew the
  model's window, its earlier part was folded into a summary that, by design, is not the
  person's words. The author then had nothing of theirs it could cite: it asked four times to
  restate a request that was still on the screen, and refused to bind an authorization given
  earlier. A summary now comes with the person's own lines it folded, verbatim, each with its
  citation. An old line too long to fit is given by its opening words.
- **A cost ceiling says when a model reasons beyond `max_tokens`.** On a route whose reasoning
  no request bound holds (xAI's Grok models), check said « ✔ COST $0.0123 – $0.0123 worst-case
  output ceiling » and « model output estimate ≤ $0.0123 ». Runs then cost $0.016 to $0.021. The
  line now reads « visible-output ceiling · reasoning of `task` billed beyond it ». The proposal's
  money rows name each such task and model. Run admission already reserved the whole generation.
- **A number that counts or ranks something is no ceiling.** A run line was refused whenever it
  held a number without a currency: « J'ai déposé 3 messages dans inbox/, relance-le stp » and
  « relance-le une 2e fois stp » met « that number has no explicit monetary meaning ». A number
  followed by what it counts (« 3 messages », « 2 fois ») or with an ordinal ending (« 2e »,
  « 3rd ») now states no money; a bare number still asks for its currency.
- **A request to create or copy a workflow is shown before anything is written.** The
  conversation's model could treat a request such as « Crée-moi un nouveau workflow X, identique
  à Y » as the person's consent, and the Session then saved a workflow nobody had seen. A workflow
  the person has not seen is now saved on their earlier words only when those words say save or
  run (« enregistre-le », « puis lance-le », « save it »). Otherwise the proposal waits for their
  yes. Their consent to a proposal already shown is read as before.
- **A CSV workflow is rehearsed.** A rehearsal refused every `nika:convert` step: « task
  parse_perso runs nika:convert, outside any bounded subset a rehearsal vouches for ». So no
  workflow that parses a CSV was ever tried before its first run. The room now runs
  `nika:convert` over its own copy, within the copy bound. Every value a room run builds is
  bounded where it is built.
- **A day a noun names is no money.** « J'ai mis le fichier du 16 de côté. Relance-le. » was
  refused as a ceiling without a currency, in English. A day of the month a noun names (« le
  fichier du 16 ») is now data; a day said after a run verb (« relance-le le 16 ») is still asked,
  since it may say when to run.
- **A day a past participle names is no amount.** « J'ai remis le 17 et mis le 16 de côté.
  Relance. » was refused as a ceiling without a currency. A day after « remis », « mis »,
  « gardé » or « déplacé » names what was moved, as « le fichier du 16 » does; « relance-le le 16 »,
  which may say when to run, is still asked.
- **A day of every month is a monthly schedule.** « Le 1er de chaque mois à 8h », « tous les 15
  du mois à 9h » and « every month on the 1st at 8 am » had no cadence reading: the Session
  refused them as incomplete, and an author bound a daily 8:00 with a date check inside the
  workflow, which lost every missed 1st. They now read as `0 8 1 * *` at the head or the end of
  a request and in a schedule answer. A 29th, 30th or 31st, which some months lack, is asked
  again with the last day of the month offered, never bound.
- **A schedule's declaration says no freshness of its sources.** Landing `nika.yaml` said
  « source freshness not judged: it reads no project file », even for a workflow that reads
  `releves/*.csv`. A set that lands no workflow runs nothing, so its yes now says nothing of
  sources.
- **A declared schedule changes zone, or goes, in the person's words.** Four attempts to move a
  declared schedule from Mauritius to Paris time reached the author, which cannot write
  `nika.yaml`, and « retirez cette planification » was sent to « désactive-le », which removes a
  trigger, never the declaration. « Mettez plutôt l'heure de Paris » now proposes `nika.yaml` with
  that entry's cadence in the new zone, its hour, missed policy and ceiling kept, and the trigger
  is offered again once it lands; « retire la planification » proposes the file without the
  entry. A schedule this Mac fires is deactivated first. « l'heure de Paris » now reads as a zone.
- **A demo asked for by its name is its skeleton.** Only the bare name of the welcome's demo
  (« aggregate-by-key ») reached its exact skeleton: « montre-moi cette démo aggregate-by-key dont
  tu parles sur l'écran d'accueil » went to the conversation, which showed the workflow in the
  chat, and « enregistre et lance » then failed because no request of the person's stood behind
  it. A line that names one demo and only asks to see, try or run it now reaches its skeleton
  with no model: its one open value is asked and its answer proposes the demo. A line that adds a
  path, an address or a number stays the conversation's.
- **A discarded proposal never comes back.** After « Discard » of a revision over a saved
  workflow, the next fix edited the discarded draft, which came back under another name as a
  second workflow beside the saved one. A discard now leaves the saved workflow as it stands as
  the base of the next change (no base when nothing is saved), and the author is told what was
  discarded. The discard is said in the language you write in.
- **A failed step says why.** « proposing the workflow failed · 3 ms » now carries the reason
  the run reported, on one short line.
- **A resume serves back a fan-out over its own `with:` and a group fold.** A `for_each` whose
  collection read the task's own binding (`for_each: { items: "${{ with.ids }}" }`) was never
  stamped, so `--resume` ran it live again: the content a gate had shown changed under the
  answer, and every answer was refused NIKA-SEC-010. The stamp now resolves the collection over
  the item-free `with:`, as the run does. A `${{ group.<name> }}` fold is stamped over its
  declared members, without their durations, which the journal cannot serve back.
- **A fetch that sends says it sends.** A `nika:fetch` task whose method is POST, PUT, PATCH or
  DELETE now reads « sends to the web » wherever the Session says what a task does. That covers
  the gate's « un oui permet … » line. Before, a POST to a webhook read « fetches from the web ».
- **A file the person names alone is found where it is.** A request that names a file by its
  name only (« si la note tarifs-2027.md échoue ») while the file lives in a folder
  (`./notes/tarifs-2027.md`) was refused at every revision as « UNREALIZED PATH », even when the
  workflow read every note through a glob. The observation now locates the one file of that name
  under the project (hidden folders and build outputs aside), and a workflow that reads it where
  it is realizes the name. Two files of that name, or none, leave it unrealized as before.
- **A file the person takes back is no longer owed.** Every file a request named stayed owed for
  the whole conversation: after « ne lis pas profil.md, copie mon profil dans le workflow », each
  workflow that no longer read the file was refused until the person restated everything. Their
  latest words about a file now decide: a later line that stops it from being read or used, or an
  offer they picked that says it is no longer read, withdraws the earlier mention, and naming it
  again asks for it again. A line that only qualifies the reading (« ne lis pas data.csv en
  entier ») or a condition (« si tu ne lis pas … ») withdraws nothing.
- **A folder named in words is held by the rehearsal.** « Les fichiers .txt du dossier inbox »
  named no folder, because only path-shaped words (`./inbox/`) counted. The rehearsal then held
  nothing, and the first run on the empty inbox failed. A folder introduced by its noun
  (« dossier », « folder », « répertoire ») and a bare name is now copied into the rehearsal room
  when the workflow reads under it. « Le dossier client » with no read under `./client/` stays a
  case file. A named folder that is not there is skipped, as the real run finds it.
- **A folder you name as `copies/` is tried before « propre ✔ ».** « Dans le dossier copies/ … »,
  « dans commandes/ … » or « du dossier notes/ » named no folder the rehearsal could hold, so a
  workflow reading those files was proposed as clean without running a step on them, and its
  first real run could fail on its own jq. Such a folder is now copied into the rehearsal room
  where the workflow reads under it (within the room's copy bound, originals untouched), and the
  steps after a model step that declares its answer's shape run on an example of that shape: a
  wrong jq is caught before you are asked to say yes.
- **A proposal over a folder is really rehearsed, and a rehearsal that binds nothing is said.**
  A Session proposal whose request reads a folder (« copies/ », « inbox/ ») was still shown
  « propre ✔ » with no rehearsal: the world observed before the trial refused the folder as no
  regular file, the trial's evidence was refused for having no receipt of the folder itself, and
  the refusal was dropped in silence. The world before a rehearsal now holds each file of a
  folder or a pattern the request reads, and of a destination folder, so the rehearsal is bound
  to the copies it ran on; a source the workflow reads only under (« lis ./inbox ») is held as
  the folder it is, even empty; a file the run writes that the request never named is bound as
  the absence it was in your project. The proof then keeps what the request names and what the
  run wrote: files arriving in or leaving an inbox between runs are no drift. A proposal whose
  trial binds nothing says « No rehearsal bound to these bytes » and why.
- **A schedule in another zone than the Mac's can be armed with launchd.** launchd fires in the
  machine's zone, so `nika arm --emit launchd` refused a cadence such as `TZ=Europe/Paris 0 8 * *
  *` on a Mac set to another zone. The unit now wakes at each machine hour one of the schedule's
  slots can fall on over the coming year (10:00 and 11:00 on a Mauritius Mac for 08:00 Paris,
  summer and winter time). The firer reads every wake in the schedule's own zone: it runs the due
  slot and leaves the other wakes not due, without journaling them.
- **A free « Oui » to the author's approval question is the person's approval.** When the
  author asked « Veux-tu valider chaque envoi avant qu'il parte ? » and the person typed « Oui »,
  that answer bound nothing, so the gate it approved still read as invented and the author asked
  again (six times in one session). Such a yes now states the approval its question names, as a
  picked option already did; a « Non », a qualified no (« oui, sans validation ») or a yes to a
  question that asks no approval states none. Approval questions read their infinitive
  (« valider », « approuver », « confirmer ») and the person glued to the verb that asks them
  (« veux-tu », « dois-je ») or doing them (« par un humain »).
- **A French proposal card says its rehearsal in French.** The card beside a French proposal
  read « répétition · Rehearsed on a copy of your files · nothing ran on the originals · … »,
  while the proposal's own text said it in French. The rehearsal words a host draws now follow
  the person's language as the proposal does.
- **A French card says what runs in French.** A proposal's run-time rows (« writes … », « pauses
  for a human answer », the model estimate), the understanding note and the resume line were
  English on a French card; they now read in French, every path, host, model and amount as
  written.
- **A French session reads its proposals and its runs in French.** A French proposal still
  read « money: $0.25 USD · session default · proposal/Run ceiling · … », « when it runs: » and
  « apply this? (yes applies · …) », and its run « check · … · clean ✔ · ceiling $0.10 (saved
  with the workflow) » and « run observed · exit 0 · succeeded ». A person who writes French now
  reads them in French, every path, amount, identity and workflow byte as written. The identity
  a consent answers is unchanged. The ceiling's source now says « saved beside the workflow »:
  it is kept beside the file, never in it.
- **Asking one more change to a proposal no longer fails the turn.** The revisions of one
  proposal drew on one rehearsal account across every turn, so after a few changes each new one
  failed with « the session runtime failed: the shared turn rehearsal budget admits no further
  world ». A line that answers no open question now starts a fresh account; answering a question
  still continues the one it belongs to, and a suspended proof still waits aside.
- **A run's text result says its last line too.** The run card showed a declared text output
  by its first line. A journal each run appends to read the first run's line after every run, as
  if nothing new had been written. A text of several lines now reads as its first line, its last
  line and how many lines it holds.
- **A workflow that keeps its own state is rehearsed, first day included.** A journal it appends
  to, or yesterday's figure it compares with, was read in the rehearsal room as « read but not
  observed », or refused as an absent input on the first day, so the whole rehearsal was dropped
  and the first real run met the first-day case untried. A path the request names that the
  workflow both reads and writes is now handed to the room as its state: copied as it is, or held
  as the first day's absence, read back after the run, and its write advances the proof.
- **A jq step built from a `const:` value is compiled by `nika check`.** A `nika:jq` expression
  holding `${{ const.NAME }}` was skipped as « built at run time », so a constant set unquoted in
  the program (`${{ const.target_dir }} + "/"`, read by jq as names) checked clean and failed the
  first paid run with « undefined filter or variable ». The check now builds such a program
  exactly as the run does and compiles it; a template only the run can know is still left to the
  run.
- **A judge reads the Foundry offers it weighs, not the whole catalogue.** Every verification
  question carried each offered component with its contract, about 70 KB for fifty components.
  The decision seat could not read the whole-request question, so nothing was judged on the
  standard path. A question now carries in full the components the bytes hold; every other offer
  appears by identity, title and status. Only a localization, which weighs every contract, is
  sent them all. A seat that refuses a question as too large no longer advises a retry that
  would ask it again.
- **A refused key check says what the provider said.** Testing a key the provider refused
  answered with the hint a failed run prints for the command line (« NIKA-INFER-001 …
  `--access <seat>` »). It now reads the provider's own words and its error's kind, the key
  withheld.
- **A launchd unit given an env file passes its keys to the run.** `nika arm --emit launchd
  --env-file <file>` sourced the file through `/bin/sh` without exporting it: the plain
  `KEY=value` lines of a dotenv file never reached the firer, and every scheduled run that needs
  an API key failed. `nika arm fire --env-file <file>` now reads the file itself (plain
  `KEY=value` lines, an optional `export `, comments, blank lines, single or double quotes) and
  fires the beat with those variables set, never printing a value; a file it cannot read, or a
  line that is not `KEY=value`, refuses the fire before anything runs, naming the file and the
  line. The launchd unit hands `--env-file <file>` to the firer in its `ProgramArguments`, with
  no shell wrapper. Units written by an earlier engine are still recognized when they are
  re-emitted or retired.
- **A line typed after a failed run's diagnosis is the person's own message.** When a run the
  person's words authorized failed, Nika's note started a turn the model answered, but the
  proposal it ran still counted as waiting: the person's next line was read as that proposal's
  answer and moved into its reply, so the request ended on the model's own message. Claude Opus
  5.5 refused it (« does not support assistant message prefill ») and the turn failed. Once the
  model has answered such a note, nothing waits on the proposal any more.
- **A change that names the saved workflow's files revises it.** After a workflow was saved,
  « Trie le rapport par ligne. » could be read as new work. It then got an English question about
  the run model and an unrelated, paid `classify` workflow. A line that opens with a change (« trie
  », « ajoute », « retire », « sort »…) and names a file the saved workflow reads or writes, by
  name or without its extension (« le rapport » for `rapport.md`), now revises it. A line that
  states the work again, or asks for a new workflow, still starts one.
- **A judge locates a missing part over each offer's purpose.** On the standard path, every
  revision still overflowed the decision seat on one question: the localization of a missing part,
  which was sent every contract of the catalogue. It is now sent each offer's identity, title,
  purpose and status. The components the bytes hold stay whole.
- **A model's working notes are never shown.** A model that streams its reasoning as text (no
  reasoning field) had its English working notes, turn ids and finding names shown in the
  conversation and in a question card to a French person. The words beside a call are now shown
  only when they are written in the person's language, without what a reasoning tag
  (`</think>`, `</mm:think>`) closes or opens.
- **A model the person named is told how to state it.** A model the person named in words
  (« Grok 4.7 (xAI) ») but stated by the author as typed (`answered`) was refused as words
  « not in the person's line », though they were; Grok 4.7 retried other excerpts of the same
  line again and again. The refusal now says the value was not typed and asks for it as
  `named` with the same words.
- **A newline after a lone reference is written.** `content: "${{ with.d.version }}\n"` wrote
  `3.15.0` without its newline, and `"${{ x }}\n\n"` lost both: a string holding one reference
  and only whitespace around it was read as the reference alone, which keeps the value's type and
  drops the whitespace. Only the reference alone keeps its type now; any other character around
  it, whitespace included, is text. A `for_each` collection is still read whole when a block
  scalar leaves whitespace around its reference.
- **A per-run budget never blocks a proposal.** « … chaque run doit coûter au maximum 0,10 $ :
  budget max 0,10 $ par run » was read as an obligation whose quote spanned the model clause too,
  and the first proposal was blocked twice for a ceiling no task can carry. An obligation whose
  requirement states only the Run ceiling beside the candidate is now carried there. A copy of a
  saved workflow also keeps the ceiling the person set for its next runs in the conversation
  (« Le plafond est de 0,10 $ par run stp », then « identique à … »: the copy ran at $0.25).
- **An approval the person picks is their gate.** The approval laws read only the person's typed
  words, so a gate they picked from a question (« Oui, je valide chaque run avant l'écriture de
  jobs-<date>.md ») was refused as an invented gate, while the Rule of Two required that very gate:
  a job digest that read the person's profile was refused after each of five such questions. The
  laws now read the label of an offer the person picked themselves as their words: the gate it
  asks for stands, and a candidate without it owes the approval. A choice left to Nika or a label
  that denies the approval states none.
- **A proposal's choice takes the words you type beside it, and `cancel` discards the
  proposal.** The card's hint said « or type your reply · cancel drops it », yet a sentence of
  your own came back as « not taken · answer text restored for editing » after Nika had read and
  answered it, and « cancel » or « annuler » were sent to Nika as one more answer, so the only
  way out was to accept a change you did not want. Words Nika reads now stay said: its reply
  shows above the choice, which keeps waiting. `cancel` or `annuler` on its own line discards
  the proposal through the same door as « Discard this proposal », with no model call and
  nothing written, and the hint now says `cancel discards the proposal`. An offer that is itself
  named `cancel` still answers the choice.
- **A proposal of the saved workflow says nothing changed.** The saved workflow proposed again
  byte for byte showed Save, Save and run once, and Discard. It now says nothing changed and how
  to run or activate it, and no proposal waits.
- **The Session no longer crashes when the decision seat skips an oversized question.** After the
  seat had refused one question alone for its context, a later, larger question was skipped
  without a place in the request journal. The next request then settled at the wrong index and
  the terminal app panicked (`batch.rs:121`). Every skipped question is now journaled as never
  sent, in its own place.
- **A question the author asked owns its answer.** After an accidental « annuler », the author
  asked when the journal should be written; « tous les soirs à 20h » then opened the activation
  of the welcome example saved earlier, and its zone, missed-run and ceiling questions. A line
  that answers a question the author asked now reaches the author; with no question waiting, a
  schedule stated for the saved workflow still activates it.
- **A rehearsal says when what it read back came from steps it did not run.** A trial that
  could not run three model steps still read back the file written behind them, « _Aucun article
  sur les 7 derniers jours._ » three times, as if it were the result; the real run wrote 46
  articles. A trial now says that what it read back of a file its unrun steps write never is what
  a run writes.
- **A recommended run ceiling says why, never as a ratio.** The ceiling question of an activation
  said « $0.10 · ~3× the last run, $0.0015 », read as $0.10 being three times $0.0015 (it is
  about 67 times). It now says « covers at least 3 runs like the last one, $0.0015 ».
- **A run whose gate the person refused is never said to have succeeded.** After a « non » at a
  gate, the observation line now says « exit 0 · stopped at your no » / « arrêté à votre « non » »
  instead of « succeeded ». `/status` says « Last run · Done · stopped at your no » / « Dernier
  run · Terminé · arrêté à votre « non » » instead of « le run a réussi »
  (`RunFacts::refused_at_gate`).
- **A refused key reads plainly while a workflow is prepared.** When the provider refused the
  key of an authoring call, the recovery card carried the hint a failed run prints for the
  command line (« --model … --access <seat> »). It now says the provider refused the
  connection's key, with the provider's own words, and to check it with « Test the key » in
  `/intelligence` or choose another connection.
- **A turn whose proposal was refused says so, whatever the reply claims.** After Nika's check had
  refused every draft of a turn, the author's own words reached the person as they were, and could
  read « C'est enregistré et lancé » while nothing was proposed, saved or run. Such a turn now ends
  with « Nothing was proposed, saved or run in this turn: Nika's check refused the draft. » (in
  French in a French conversation), after the author's words. A turn that refused no proposal
  carries no such line.
- **A refused revision says the proposal you saw still waits.** When Nika's check refused a
  revision while an earlier proposal was still waiting, the turn ended with « Nothing was
  proposed, saved or run in this turn », beside a proposal the person could still save. It now
  says « This turn changed nothing: Nika's check refused the revision, and the proposal shown
  before still waits for your answer. » (in French in a French conversation).
- **A rehearsal holds the folders and patterns the request names.** A request naming a folder
  (« inbox/ ») was not rehearsed: the room refused the folder as no regular file, and
  `nika:glob` was outside what a rehearsal runs. The room now copies a named folder with each
  file under it, and makes an empty one empty: the empty day is rehearsed too. It copies a named
  pattern by its matches. A rehearsal runs `nika:glob` and `nika:remove_file` over its own copy. A
  listing that no named folder or pattern holds is refused before any room, as a read is.
- **A rehearsal names a web read as one.** A `nika:fetch` whose answer jq shapes was listed
  among the steps not run as « a jq extraction » (« une extraction jq »), which reads as a jq step;
  it is now « a web read through jq » (« une lecture du réseau filtrée par jq »).
- **A rehearsal is told in French to a person who writes French.** The proposal's rehearsal
  lines (« Rehearsed on a copy of your files … », « tried on the pages observed at … », « not
  run: the model step (…) … ») stayed English under French answers. They now read « Répété sur
  une copie de vos fichiers … », « essayé sur les pages observées à … », « pas lancé : l'étape du
  modèle (…) … ». Names, paths, excerpts and reasons stay as written. The proof keeps the engine's
  own words.
- **A rehearsal tries the day a gated step is skipped.** A workflow could pass its check and
  its rehearsal, then fail its first run with nothing new: a filter after a skipped step read its
  null (« cannot use null as string », « cannot use null as iterable »). After its run, a
  rehearsal now evaluates each such `nika:jq` step on that null, in its bounded jq process. It
  fails with `NIKA-REHEARSAL-SKIPPED` only when the null alone makes the filter fail: the same
  filter passes with a value in its place. The author then guards the step before anything is
  proposed.
- **A rehearsal tries the steps after a model step on an example of its answer.** A trial ran
  no model step, so it never ran the steps after one either. A workflow could pass its check and
  its trial, then fail its first run in a step that read the model's answer, or a date beside it,
  the wrong way. When a model step declares its answer's shape (`schema:`, `returns:`), a second
  pass stands an example of that shape in for the answer, over the room's files, writing nothing.
  A step behind it that fails on the shape (a jq type error, a binding that reads beneath a value)
  fails the trial with `NIKA-REHEARSAL-SHAPE`. A failure of content (an assertion, an empty
  filter) is the example's, never counted. Nothing this pass computes is shown as a result.
- **A file the run removed no longer reads « produced ».** The run result listed every path the
  run was allowed to write as « produced », so the inbox files a run moved to `traites/` read
  « produced · ./inbox/… ». A path that is not there now now reads « removed » when a
  `nika:remove_file` step of the run took it away, « not there now » otherwise; the run's own
  completed writes still read « produced ».
- **A reopened conversation checks its saved workflow again.** After a restart, the workflow
  the conversation saved is checked again on disk, with the same audit the save ran and no model.
  An unchanged clean file now stays « Vérifié » instead of « Vérifié ○ », and a file changed into
  findings says so.
- **A reading that repeats a coverage row is said once, not repaired.** When the author's
  reading of the request covered one span in two rows (« coverage[1].span · This identifier is
  repeated »), the verification started a repair round with the model: 2.7 minutes, then 9.7, on
  a slow model, before anything was judged. Rows for one span or one answer key are now merged,
  their references united, before the reading is checked. Nothing the reading claims is dropped.
- **A candidate refused the same way under a new revision is told it repeats.** The author was
  never told it was repeating itself when the same `candidate_write` came back refused with the
  same findings, because each reply carried a new revision number: Grok 4.7 sent it some sixty
  times. The revision is left out of the comparison, so the repetition and stall notes reach the
  model from the second and third identical call.
- **A request over the model's context window is told apart from other refusals.** A
  provider's documented overflow code (`context_length_exceeded`) used to be dropped, so
  the failure read as a generic HTTP 400. It is now a typed context overflow that names
  the model's window when the catalog knows it. On a wire without such a code (Anthropic),
  a 400 `invalid_request_error` counts as an overflow only when Nika's own estimate of the
  request reaches that window; the provider's wording never decides. The overflow is
  never retried as transient, and 429, 503 and 529 stay transient rejections. An answer
  that stopped because the context window was full (Anthropic
  `model_context_window_exceeded`, Mistral `model_length`) stays cut, as before: never a
  normal end, its tool calls never run. It now also carries a window-full marker, set too
  when a provider reports more input than the model's window holds, so a conversation
  can compact before asking again instead of re-sending the same full window.
- **« je voudrais l'enregistrer et le lancer » saves and runs the proposal shown.** Requests around
  the save and run words are now read by the closed consent grammar, so they no longer reach the
  author as new work: « je voudrais », « pouvez-vous », « can you », « I'd like to », with the
  infinitive verbs « l'enregistrer », « le lancer ». A line that asks anything more keeps its
  route.
- **A request to see the results never starts a run.** Asking to show, display
  or read what a workflow produced (« Pouvez-vous me montrer le contenu des
  trois traductions », « affiche le résultat », « lis-moi », « show me what it
  wrote ») could be heard as a request to run it again: a metered run then
  rewrote the very files the person wanted to read. Such a line now never
  reaches Run, whatever a classifier hears in it, even beside a run verb; the
  conversation answers it from the files or the last trace, and a run still
  takes its own words (« lance-le ») (NIK-234).
- **A refused call names the amount it needs.** A model call whose reservation the stated
  allowance cannot cover was refused with « remaining catalog allowance cannot cover the
  full-context reservation ». It now names the reservation, what it covers (for Grok, its whole
  generation, since a request cannot bound its reasoning), what remains of the allowance, and the
  amount to state: « state at least $3.40 for this work, or choose a model whose call reserves
  less ».
- **A result offer, a decision and a schedule are said once.** After a run, « What would you
  like to adjust in this result? » was printed twice: in the run's words and on its own card. A
  trigger's decision repeated « Install the trigger on this Mac? » under its own heading, and a
  schedule read « every day at 9 · every day at 9 (Mauritius time) ». Each is now said once; words
  the cadence reads differently stay beside it.
- **A resumed run keeps its ceiling.** A run that paused at a gate and was resumed without
  `--max-cost-usd` ran its next leg unbounded (« budget: none journaled »). The run answered in
  the same terminal got its whole ceiling again. Now both continue under what the paused leg's
  ceiling leaves after its spend (`RunFacts::ceiling_left`), and the resumed leg journals that
  budget. A ceiling stated on the resume still stands as given.
- **A run a gate split reports the ceiling it started under.** Its resumed leg records only
  what the first leg left, so the report said « ceiling · $0.212482 » for a run started under
  $0.25, and a ceiling stop compared the whole spend with that remainder.
- **A revision of a budgeted workflow is no longer refused for its ceiling.** After a workflow
  saved with « 0,10 dollar par run » was run, every revision was refused: « the judge finds no
  task performing » the budget. The person's later words for the same ceiling (« le plafond de
  0,10 dollar s'applique au lancement du run ») were not the words beside the candidate, so the
  judge was asked to find a task for a ceiling only the Run applies. Words that say only the
  ceiling of the amount beside the candidate are now carried there, as the first statement was.
- **A run's card shows one task count and one cost.** The card under a finished run said
  « ✔ Ran in 1.7s · 20 tasks · $0.005 » while the run's result beside it said « 9 tasks ran (6
  from cache) · 2 skipped » and « cost · $0.0046 ». A run as small as « <$0.001 » read
  « cost · $0.0004 » there too. The card now counts tasks as the result does: the tasks that
  ran, those answered from cache, then those skipped, failed or never run. It also writes every
  cost on the card at the result's precision (`$0.0046`).
- **A run line with courtesy is still a run.** « lance copy.nika s'il te plaît », « lance-le une
  fois » and « relance-le tout de suite stp » were not read as run lines. They reached the
  conversation instead, and in one case met an English reply. They now run as « lance-le » does.
  « lance-le une fois par jour » still is not a plain run: it asks for a schedule.
- **A run names the items it recovered.** A site check listed `https://example.com`, a host its
  workflow permits, and logged it as down with a code 0: the transport never dials a
  documentation address (RFC 2606/6761), the step recovered the item, and the run report said
  nothing of it. The report now names each item of a list that failed and was recovered, and
  says when the address is one Nika never dials, in the person's language.
- **A run number of no amount is refused in French to a French person.** « that number has no
  explicit monetary meaning … » now reads « ce nombre ne dit pas un montant … » in a French
  conversation; the money record keeps the reason as found.
- **A run refused before it started says why.** A run the engine refused before its first step
  (an access path refused, NIKA-1800, for an effort Gemini does not offer) showed only « exit 3 ·
  refused by the environment »: its reason went to a discarded stderr. The run's lane now keeps
  that reason and shows it as the run's line, never doubling a reason the stream already gave,
  and the conversation hears it with the person's next line instead of guessing a cause.
- **A run says whether each step that sends sent.** The run card named the files a run produced,
  but not whether its webhook or notification went out. Each step that sends beyond the machine
  (a notification, a request other than GET) now reads « sent · `ping` · the step completed »,
  or « not sent · `ping` · its condition was false » (cancelled, failed, served from the cache),
  in the person's language.
- **A run now settles a crashed run's leftovers when it starts.** A run killed before its
  end (SIGKILL, a machine that went down) left the files it created in place as if
  whole, and the original bytes of the files it changed in `.nika/originals/`, where
  nothing ever mentioned them. Each file a run first writes now leaves a small record
  there, with the digest of a created file's bytes after each write. The next `nika run`
  in the project settles a run whose journal shows it dead: a file it created moves to
  `.nika/quarantine/<its run>/` only while its bytes are still the ones it wrote (a file
  you changed since stays where it is), a file of yours it changed stays and its
  original moves to `originals/`, and a `manifest.json` lets `--resume` of that run put
  the moved files back. A run that is still alive, or whose state cannot be proven, is
  never touched. Each action is one line on stderr.
- **A save and its checks are said in the person's language.** A French conversation read
  « applied · wrote », « check · … · clean ✔ », « running … once · ceiling $0.25 », « (the
  proposal still waits …) », the source freshness lines, the ceiling question after « save &
  run » and every withdrawal in English. They are now said in French to a person who writes
  French, and a rehearsal that read no file no longer prints an empty « les fichiers de l'essai
  sont tels qu'il les a vus :  (…) ».
- **A schedule question names its hour in French.** For a cadence with no stated hour (« toutes
  les heures »), the French questions read « il fixe quand tombe the scheduled time » and « Si cette
  machine est éteinte à the scheduled time ». They now say « l'heure prévue ».
- **A schedule stated for the saved workflow opens its activation.** After a save, « déclare-la
  à côté, tous les jours à 8h heure de Maurice », « je voudrais que ce bilan se fasse tout seul
  le 1er de chaque mois à 8h » or « ok tous les jours à 8h du mat alors » now open the activation
  with that cadence, as « active-le » does. They used to reach the author, which tried to write
  the schedule into the workflow and failed after several minutes. A zone the line names
  (« heure de Maurice », « Europe/Paris », « Paris time ») answers the zone question, which is no
  longer asked again. A line that also asks something else, or asks for a new workflow, keeps its
  route.
- **A schedule's time zone shown as a choice is said to be the schedule's.** An author that
  offered the schedule's zone among the choices beside the candidate was told to write it in the
  candidate, which a schedule binding never is, and tried 35 times. The refusal now says to give
  it as the schedule's `timezone` or ask it, and to leave it out of the shown choices.
- **Searching the intelligence list counts its matches, not every model behind it.** Typing a
  name over the list of connections showed « 1 of 657 match » next to about 23 connections. The
  657 counted every model of every connection, which the list keeps out of view until a search
  finds them. Where a search can find such rows, the title now counts the matches alone
  (« 1 match », « 17 matches »). A list that shows every row keeps « 4 of 30 match ».
- **An agent account that cannot write workflows says so before you choose it.** The
  intelligence list offered « copilot · Agent account » with no warning. Once chosen, every
  request failed with « authoring unavailable · seat `copilot` is not infer-grade for
  json_schema ». The list now reads « copilot · Agent account · cannot write workflows here » on
  the connection's row and on its own. The check is the same one applied once a seat is chosen:
  one turn, no implicit tool, an answer checked against a JSON schema, and an observable model.
  Accounts that can write workflows show nothing new.
- **The intelligence list's filled radio marks the choice in use, not the cursor.** Moving
  through the list filled the radio of the row under the cursor, so « › (●) GPT-6 Astra » could
  read as the current choice while Claude was in use. The filled radio now stays on the value
  the list holds: the model in use, the decision seat consulted, or the model requested for
  Run. The cursor alone shows the row `Enter` applies.
- **A schedule's zone, once settled, stays settled.** After a repair of the saved workflow,
  « active-le tous les matins à 7h » asked the zone again. A revision that carries no schedule
  now keeps the one asked for the workflow, zone included. « Choisis pour moi » at the zone
  question takes this machine's zone and goes on; before, the Session demanded `Area/City`
  syntax. A word that names no zone is refused in the person's language.
- **An OS unit left behind by an edited schedule is named and refused.** After a cadence
  change in `nika.yaml` without re-running `nika arm --emit … --write`, the installed unit
  kept waking `nika arm fire` at the old time; the beat answered « not due » every day and
  nothing woke it at the new time, while `nika arm` still reported it READY. Each beat
  unit now carries the revision of the schedule it was written for (the cadence, and for
  launchd the machine zone it was rendered in) and hands it to the firer, which refuses a
  wake from a stale unit with the re-emit that fixes it and fires nothing. `nika arm` lists
  such units under « unités périmées », and also names a unit written by an older engine,
  which carries no revision. Re-run the emit with `--write` once to give existing units
  their revision.
- **An author stuck repeating one failing call is told to change it.** On Gemini 3.1 Pro the
  author copied the note Nika leaves in its history in place of an earlier candidate's document
  and sent it as a new candidate 80 times in 15 minutes, each refused « needs the complete
  document in `source` ». That refusal now names the copied note and the two ways out (the whole
  document, or `candidate_edit`), and a call that returns the same reply three times in a row is
  told that repeating it cannot change the reply. Nothing stops the conversation: no quota.
- **A step whose output is over 1 MiB no longer ends its run's trace.** A fetched 454,763-byte
  JSON page made a 1,180,930-byte `task_completed` frame: its `output` and its `outcome` each
  carry the value, the journal refused the line (« journal line is 1180930 bytes; maximum is
  1048576 ») and the trace stopped there, so the run could not be resumed. A frame over the
  line bound now moves its largest values, one at a time, to `<trace>.values/<sha256>` beside
  the journal until it fits, each field keeping the value's digest, and `spilled_fields` names
  them. The chain commits to each digest, `--resume`, `nika trace show|replay` and the debugger
  read the values back only when their bytes match it, and `nika trace verify` refuses a value
  that is missing or altered. The line bound stays; trace retention removes the values with
  their journal and counts them in its budget.
- **A decision seat that cannot hold a document no longer stops its proposal.** When the decision
  seat's context is too small for the state a verification asks about, every question was sent,
  refused, halved and sent again (about a thousand calls in two live runs) and nothing was ever
  proposed. A question larger than one the seat already refused alone for its context is no
  longer sent, and a verification the seat stopped is judged by the authoring model instead. The
  proposal says which seat could not judge and which model did.
- **A choice gate takes the answer its resume line prints.** A choice gate whose choices spell
  numbers (`choices: ["42", "17"]`) printed `--answer review=42` to resume, then refused that
  very line: `42` was read as a number, not one of its strings, and a shell could not pass the
  JSON string without a second quoting. An `input` or `choice` gate now takes the text typed
  (`'"42"'` still gives its text); a word outside the choices is still refused.
- **A topic is selected by the model, never by keywords.** « les 5 top stories de la front page
  qui parlent d'IA » was written as a fixed keyword regex: it missed AI stories that do not use
  the words and kept stories that only mention them, so one story in five was wrong in every run.
  The author is now told that selecting items by what they are about is a judgment: an `infer`
  step returns the matching identifiers, and a deterministic step keeps them in the source's
  order. A filter on words the person names exactly stays deterministic.
- **A trial names the steps it cannot run behind one it does not run.** A trial said « not
  run: … the model step (select_top) … » and nothing of the steps that read it, which were not
  tried either (the first run failed in one of them). Its words now add « nor what reads them: »
  with those steps, in the workflow's order.
- **A Unicode regex class is found before the run.** `test("\\p{L}")` compiles as jq, then the
  runtime's regex engine refuses it on the first item: a clean check was followed by a failed
  run. The check now names the class and the way out (an explicit set such as
  `[A-Za-zÀ-ÖØ-öø-ÿ]`), so the author repairs it before proposing.
- **The trigger install says when a scheduled run would not see the API keys.** A unit started by
  launchd does not inherit the terminal's environment, so a workflow whose model reads its key
  there (`NIKA_ANTHROPIC_API_KEY`, `ANTHROPIC_API_KEY`…) would fail at every scheduled run.
  « Installer le déclencheur sur ce Mac ? » now names those keys, says the run would fail until
  the project's units load a file that holds them (`arm --emit --env-file`), and recommends
  keeping the schedule declared. Workflows with no model step, with a mock or a local model, or
  whose units already load an environment file naming their keys keep the card as before.
- **A line that sets the ceiling moves it.** « Mets le plafond à 0,001 $ par run et relance »
  was not read as money: only a ceiling phrase attached to the work (« avec un plafond de … »,
  « budget max … ») was, so the run kept the first request's 0,10 $. A verb that sets the work's
  limit (« mets », « remets », « passe », « set », « change », « je veux un budget de … ») now
  states the ceiling, « à » and « to » link it to its amount, and the directive ends before the
  conjunction that opens the next clause. A record's budget (« le budget du client à 500 $ dans
  ./clients.csv ») stays data.
- **A verification no longer reads the same request again.** Every verification in a
  conversation asked its author to read the person's request, then asked for an audit of that
  reading, even when the words had not changed. With Grok 4.7, those calls took 89 % of a
  session's reasoning tokens. The Session now keeps, in memory, the last reading audited as
  covered. The next verification of the same words, answers, model and prompts reuses it with
  no call. The judge still judges every candidate, and a reopened conversation reads and audits
  the request again.
- **A workflow named without its extension is the one run.** « je voulais relancer
  journal-histoire-du-soir » ran the last saved workflow, journal-recopie.nika, twice. A line
  naming exactly one project workflow by its name (a hyphen, an underscore or a digit in it)
  now runs that workflow; two such names, or a plain word, name none.
- **A workflow may read back the file it keeps.** « Il garde le chiffre d'affaires du jour pour le
  comparer au jour suivant » asks for state a next run reads. The output law refused any read of a
  derived output, so every such workflow was refused and the author re-parsed an older report
  instead. An output may now be read back by the workflow that writes it (`nika:read`,
  `nika:grep`, `nika:glob`), and nothing else uses it. A read source that a pure step builds
  addresses from is refused with the way to keep it: compose the base address in the `url:` of the
  fetch that reads each item.
- **A workflow that keeps its own state runs again.** A rehearsed workflow that reads and writes
  the same file (a log it appends to, yesterday's figure it compares with) could run once: every
  later « lance-le » was refused because that file had « appeared » since the rehearsal, and the
  only way out was the command line. The run's own completed write now advances that file in the
  rehearsal's witness, like any file the workflow only writes. An edit by anyone else after the
  run, or of a file the workflow only reads, still withdraws the rehearsal.
- **A workflow that also writes a file you never named runs again.** A site check that appends to
  `statut.log` and keeps its own `etat_echecs.json` could run once: every later « lance-le » was
  refused because `statut.log` had « appeared » since the rehearsal. One written file outside what
  the rehearsal witnessed cancelled the whole advance. Such a file is now left out of the witness
  (nothing witnessed it, so it can never drift), and the files the rehearsal bound advance as
  before. A source edit, a foreign write, or a write outside the project still withdraws it.
- **A written text file ends with a newline.** Translated notes written from a model's answer
  ended on their last character, while their sources ended on a line break. The author is now
  told to end each written text with one line break.
- **« Oui, enregistre » saves.** A plain yes before the save verb (« oui, enregistre »,
  « ok, save it and run it ») and a courtesy after it (« stp », « s'il vous plaît », « please »)
  were not read as the consent they gave, and the proposal waited on. They are now read as the
  closed save, or save-and-run, line they are; a line opened by another word keeps its route to
  the author.
- **« oui, enregistrez ces changements » saves the card it answers.** Under a card whose option
  reads « Enregistrer ces changements », the person's « oui, enregistrez ces changements » was
  not read as a yes: it reached the author, which proposed something else, and the `nika.yaml`
  was never saved. A yes may now name what the card shows: « ces changements », « these
  changes », « ça ».
- **« Oui, activez-le » and « vous pouvez l'activer ? » open the activation.** Only an
  imperative opened it: « oui activez le s'il vous plait » ran the workflow once more instead,
  and « vous pouvez l'activer ? » became a change proposal, so a French person had to learn the
  English keyword « activate ». A yes before the words, a space for the hyphen and the polite
  question forms now open the activation, whose questions still decide everything.
- **A yes to the bytes a file already holds says nothing changed.** The landing said « applied ·
  wrote » for a proposal identical to the saved workflow; it now says « applied · nothing
  changed in … ».
- **A yes with its go-ahead consents to the proposal shown.** « Oui, allez-y », « ok vas-y
  merci » or « yes, go ahead please » under a proposal card now save the bytes shown, as « oui »
  does. They used to reach the author as new words and start a fresh revision. A line that adds
  anything else (« oui mais change le titre ») still goes to the author, and a spending approval
  still takes the strict yes.
- **A cloud inference call is no longer cut at 30 s by a deadline the workflow never declared.**
  An `infer:` task without `timeout:` gave a buffered cloud call a total deadline of 30 s (300 s
  for a local model), so a legitimate answer from a reasoning model or a long prompt failed with
  NIKA-INFER-001 (provider 408) and the Run wrote nothing. Every buffered call without a task
  `timeout:` now gets 600 s, local or cloud: the provider transport's own bound on a connection
  that delivers nothing. A task `timeout:` still sets its own bound, streaming keeps its
  idle-read guard, and the timeout message names the deadline that applied.
- **A call that wrote to the provider's prompt cache keeps its price.** Nika priced a call
  that reported a cache write as unknown. It now prices the written tokens at the catalogue
  row's own cache-write rate (the five-minute one, the only lifetime Nika asks for). A row
  that states no write rate, a declared tariff and an admission tariff still leave such a
  call unknown, as do two write meters that disagree.
- **« annuler » at a question the author asked drops it.**
  Gauntlet wave 3: « cancel », « annuler » or « laisse tomber » typed at
  the author's question reached it as one more answer. The hosts' one
  cancel reader now drops the question with no model call: nothing is
  bound, the author reads in one fact that the person dropped it, and
  asking it again is refused. A cancel word the question itself offers
  (an option « Annuler ») still answers it.
- **The native authoring card's example no longer caps its `infer`
  step at `max_tokens: 600`.** The card's own rule says to write
  `max_tokens` only when the person names a token bound and otherwise to
  leave the default to the compiler, which sizes it once the workflow's
  model is known; its example contradicted the rule, and an author that
  copied the 600 left a model that reasons before it answers no room to
  answer.
- **Carriage returns leave a model's call arguments.** GLM 5.3 Flash slipped carriage returns
  between the words of its calls (`schedule\r_zone`, `Indian\r/Mauritius`) and copied its own
  earlier calls with more of them each time: 115 calls were refused for a key or a time zone that
  did not match. They are removed from every call's arguments before the tool runs and before the
  call is recorded, so the model never reads them back.
- **`nika check` no longer says a run would fail for a model its catalog does not
  list.** The advisory line under MODELS, the oracle's `models_catalog_warnings` and
  the MCP `nika_check` now read: not one of the provider's models in this binary's
  catalog snapshot, a typo or a model newer than the snapshot, and the provider
  decides at run time. It stays a warning, never a refusal.
- **« Augmente le budget du run à 1 dollar » states a ceiling.** The money reader took the word
  after « budget » as the amount, so « le budget du run à 1 dollar » was refused as an invalid
  amount, and an unaccented « a » (« budget du run a 1 dollar ») stopped the reading too. The run a
  ceiling bounds may now be named between the anchor and the amount (« du run », « of the run »,
  « pour ce workflow »), and « a » reads as « à » before an amount.
- **`nika check nika.yaml` judges the beats as `nika arm` and `nika serve`
  do.** It read the project file's shape only, so a beat whose cadence has
  no `TZ=` zone, names an unknown zone, breaks the cron or readable grammar,
  carries a `décalage` other than `hash`, a `jusqu_au` that is not a date or
  a workflow path with a `..` segment passed `check` and was refused only
  when someone armed it. `check` now applies the arming door's own laws
  (the same function `nika arm` uses) and names every law broken, each with
  its beat, its code and its fix; `--json` lists one finding per law.
- **`nika check` names a read that breaks when its producer is skipped.** A task
  that can be skipped (a `when:` that can close, an empty `for_each`, `on_error:
  skip`) hands its readers `null`, and a `with:` binding is evaluated before the
  reader's own `when:`: `u: ${{ tasks.x.output.url }}` guarded by
  `when: ${{ with.u != null }}` failed the run with `NIKA-VAR-001` while `check`
  said « run ready ✔ » with zero hints. `check` now carries a `skipped-read`
  hint for such a read, in a binding or in a `when:`, `for_each:` or verb field
  stepping into a whole bound value that no `when:` rules out as null. The hint
  names the reader, the read, the producer, why it can skip and the repair: bind
  the whole `tasks.x.output`, test it in `when:`, then step into it in the verb.
  Guards that hold once the producer skipped stay silent (`has()`, `!= null &&`,
  the producer's own consent guard repeated), so does a task the skip cancels
  or a verb read its `on_error:` recovers. Advisory: `is_clean` is unchanged,
  and past the 2,000-task analysis cap the miss is stated.
- **The CI file ratchet honours `LOC-EXEMPT`, as the hygiene dashboard does.** A source file
  with a whitelisted marker (codegen, lookup-table, enum-mega) on one of its first 5 lines may
  hold up to 5,000 lines in CI too, where it used to be refused above 2,500. Both gates read one
  file-size policy and state the same verdict for the same file.
- **A Claude 5 model's fixed sampling and adaptive thinking are named at check, not by an
  HTTP 400.** The catalog records that Claude Fable 5.1, Fable 5, Opus 5.5, Opus 5, Sonnet
  5.5, Sonnet 5 and Haiku 5.5 think by default and, on Anthropic's API, refuse a thinking
  budget and any temperature but the default. `nika check` and the run's model admission
  name a `temperature:` (on `infer:` or `agent:`) or a `thinking.budget_tokens` on such a
  seat, a nickname included, with the repair; the Anthropic wire refuses the same request
  before any byte instead of dropping the value. The Session's classification calls send
  no temperature to these models.
- **A Claude Code session over ACP no longer reads as silent while it
  thinks.** Every Claude Code session the engine opens (the authoring and
  `infer:` one-shot profile, and the `agent:` seat) now asks the adapter
  for adaptive thinking with its summary displayed
  (`_meta.claudeCode.options.thinking`), unless a thinking option is
  already named. Recent models otherwise default to signature-only
  thinking whose text is empty, which the adapter never streams, so a long
  thinking turn showed no frame at all until its answer, and could be cut
  as silent. This changes what is displayed, not the model, effort, budget
  or tools; thought frames stay counted, never stored as text, and the
  silence allowance is unchanged. Codex and other adapters send nothing
  new.
- **A `claude-code` call no longer runs with the person's own Claude Code setup.** `--tools ""`
  empties only Claude Code's built-in tools: every MCP server of the person's configuration
  (plugins and claude.ai connectors included), their hooks, plugins and personal CLAUDE.md still
  loaded into each authoring, conversation and decision call through `claude -p`. Measured on
  2.1.283: 180 MCP tools and about 115,000 input tokens for a one-word answer, and an authoring
  call that ran a web-search MCP tool, then stopped at its one-turn limit, which the TUI showed as
  « I couldn't use the authoring model for this part ». The call now also passes
  `--strict-mcp-config` and an empty `--setting-sources`, the isolation the ACP completion profile
  already has: a model or effort set in the person's Claude Code settings no longer applies to
  these calls either, and the CLI's own default for the account answers, as on the ACP route.
- **Claude Sonnet 4.6 and Claude Haiku 4.5 are priced.** The catalogue still offers these two
  legacy models, but they had no admission tariff: the author offered them as « prix non connu
  ici », and no run ceiling could be checked against them. Two rows now read Anthropic's pricing
  page of 2026-10-16 (Sonnet 4.6 at $3 in / $15 out per million, its whole 1M window at standard
  rates; Haiku 4.5 at $1 / $5, a 200K window) and each model's own page for its limits. Claude
  Sonnet 4.6's catalogue limits were also out of date (200K, 8K output): its page says 1M and
  128K.
- **A refused cloud key reads « key refused », and a keyed route's own listing governs its
  offers.** A Session's census used to call every keyed cloud route ready from the key's presence
  alone: a key the provider had revoked was offered, and the offers came from the catalogue. The
  census now sends each keyed OpenAI-compatible route one bounded, authenticated
  `GET {base}/models` with its own key (about 3 s, all routes at once, never an inference): a 200
  lets the listing govern the route's offers, a 401 or 403 reads « key refused » with the
  provider's own words (the key withheld) and the route is not offered, and no answer leaves the
  key configured, not validated.
- **Codex authors again on codex-cli 0.162.** Nika admits Codex as an author only on the codex-cli
  minors where its pre-execution empty-tools profile was measured, and that was 0.160 alone: after
  the CLI updated to 0.162.1, every authoring call refused with « codex-cli 0.162 has no measured
  pre-execution empty-tools profile », so the `codex` account could not write anything. The
  profile is now measured on 0.162.1 (every disabled feature reads `false`, a prompt asking to read
  a file, run a command and search the web ran no tool and never saw the file, a JSON-schema answer
  came back in one turn) and that minor is admitted. Any other unmeasured minor still refuses
  before the prompt.
- **A Codex conversation's approval of Nika's own tool is now recognized.** codex-acp 1.13.1
  announces each MCP call with the server and tool Codex routed it to, then asks Codex's
  approval of it by the call's id alone (`{toolCallId, kind, status}`); Nika read only Claude
  Code's tool name, so it rejected every such ask. Codex 0.156.1 asks one before each Nika tool
  that is not read-only (`candidate_write` among them), and a rejection reaches the model as the
  user cancelling the call, so no Codex-led Session could write a candidate. A turn now keeps
  what each open call was announced as, and an ask by id alone is for that call; Codex's own
  question (`Allow the <server> MCP server to run tool "<tool>"?`) is read when codex-acp cannot
  tie the ask to one. Only an offered tool on Nika's server is allowed, once; an id never
  announced or no longer open, another server, a tool Nika does not offer and a command stay
  rejected. Claude Code's path is unchanged.
- **A Codex-led Session conversation can write its candidate.** Codex 0.156.1 asks before each
  Nika tool that is not read-only, and the ask reaches the client only through its
  `tool_call_mcp_elicitation` feature, which the Codex profile turned off with every other
  feature. Codex then asked through `request_user_input` instead; codex-acp 1.13.1 answers that
  itself with no answer, because Nika advertises no form elicitation, and Codex reads no answer
  as the user cancelling the call. So `candidate_write` was cancelled without Nika ever being
  asked. The conversation profile now keeps that feature on, and the spawn-time read-back
  requires it on, so each ask reaches Nika as `session/request_permission` and Nika allows its
  own tool once. The one-shot completion profile and `codex exec` keep it off.
- **An open name the request already settles is no longer asked.** When a
  request leaves open where a file's name starts (`un payload
  out/notification.json`), a folder another path of the request names
  (`out/report.json`) settles it, and an answer the person gave counts on
  every document door. A name nothing settles is still asked, with its
  readings as the options and the path alone as the default.
- **A request opened or closed by « tout les matins » states its
  schedule.** The compiler's reading of a request took « tous les » and
  « toutes les » as the quantifier of a cadence at the start or at the
  end of a sentence, but not « tout les » or « toute les » as people
  often type them, so « tout les matins à 9h, … » kept no trigger. They
  now read the same way; the singular « tout le matin » (all morning
  long) still opens no schedule.
- **A child grant inside its parent's glob now composes.** A parent granting `fs.read: ./references/**` and a child declaring and reading exactly `./references/preview-shell.html` were refused with `NIKA-COMP-002`, because the parent ∩ child meet kept only grants spelled identically on both sides; repeating the parent's wider glob in the child was the only way through. The meet now keeps a pattern the other side contains, at its narrower spelling, for `fs.read`, `fs.write`, `net.http` and `tools` (`*.example.com` ∩ `api.example.com` is `api.example.com`, `nika:*` ∩ `nika:read` is `nika:read`), in the reading of the matcher each family is enforced with. `nika check` and the run share that one meet, so the child's read is admitted at run too, and a read outside the parent's glob is still refused (`NIKA-COMP-002` at check, `NIKA-SEC-004` at run). A child glob wider than its parent's is cut to the parent's tree, never widened past it. Path grants that leave the workspace (absolute, `~/`, climbing) or name `..` still meet only by their exact spelling, and two globs that overlap without either containing the other still meet to nothing.
- **A workflow that computes its read paths reads the project.** For a workflow reading
  `./releves/*.csv` or `./notes/**` through computed paths, the Session said « fraîcheur des
  sources non jugée : il ne lit aucun fichier du projet ». It now counts the declared read grant,
  and says the freshness of those sources is not judged because they are a pattern.
- **« On continue » shows the waiting proposal whoever prepares.** After switching to an
  intelligence that leads no conversation, « On continue là où on en était » reached the
  compiler: a free, ready proposal was re-authored for 13 minutes into a new one with model
  steps. A line that only asks to go on now shows the waiting proposal again as it stands, at the
  consent door as in the conversation.
- **« On continue » shows the waiting proposal again.** After an intelligence switch, « On
  continue là où on en était » sent a ready proposal back to the author, which wrote it anew for
  minutes and lost part of it. With a proposal waiting, a line that only asks to go on shows the
  same proposal again, as it stands. It is never a consent and never a change.
- **The authoring conventions show a total written alone as a workflow
  writes it.** They said the value is « stated as `alone: true` on its
  write ». `alone` is how the plan states that a file holds one computed
  total by itself; `nika:write` takes no such argument, and no door that
  reads the conventions (the native, sketch and document authors, their
  verifier and the Session's author) states a plan. An author that copied
  the line wrote an argument the check refuses (`NIKA-BUILTIN-001`). The
  conventions now show the write's own form, the total's field as its
  `content` (a json file holds `40`), and that several totals or a csv,
  yaml or toml file are asked about rather than written. The plan's field,
  the compiler's lowering and its refusals are unchanged.
- **The conversation knows about a run refused before it started.** Only a run that ended (exit 0
  or 1, with a readable trace) reached the conversation's author. A run refused before it started
  (exit 2, its cost floor above the ceiling) or around it reached nothing, so « pourquoi le run a
  échoué ? » right after « NIKA-1709 · refusing to start » met « aucun run n'a échoué ». Every
  observed run now rides the person's next line to the author, as a success already did; a failed
  run of the author's own candidate is still repaired at once.
- **`nika:convert` reads semicolon and tab separated CSV, and writes a null as an empty
  cell.** A European spreadsheet export (`nom;montant`, decimals with a comma) was read
  with the comma: it became one column, or failed on its first decimal. The separator
  is now counted on the first line outside quotes: a semicolon or a tab that outnumbers
  the comma is the separator, otherwise the comma stays (RFC 4180). JSON to CSV wrote a
  `null` as the text `null`; it is now an empty cell, as a missing key already was.
  Cells are still strings: convert numbers with `tonumber` before arithmetic or
  ordering.
- **`nika check` refuses a `nika:date` arg its op never reads.** `{ op: now, format: "%Y-%m-%d" }`
  passed the check, because `format` is an arg of `nika:date`, and ran without it: the file got
  `2026-10-15T21:39:15.511348+04:00`, matching neither declared format. Each op now reads only its
  own args (`now`: tz · `add`/`subtract`: base, duration, tz · `format`: input, format, tz ·
  `parse`: input, format · `diff`: start, end, unit), and an arg of another op is refused as « not
  an arg of `nika:date` op `now` » with that op's own args. A templated op is left to the run.
- **`nika:date` documents every op that reads `tz:`.** The tool catalog
  described `tz` as applying to `now` only, so `op: format` looked unable
  to render local time, and workflows sliced ISO strings by hand instead.
  `format` renders its fields in `tz:` and `add`/`subtract` apply calendar
  units there. The description now says so, and adds that an input's own
  offset is not kept when `tz:` is absent.
- **« désactive-le » removes only units the trigger host says it keeps.** When an earlier
  conversation had installed a trigger, the Session built its launchd path in
  `~/Library/LaunchAgents` itself. A host that keeps its units elsewhere was then handed a file it
  never wrote. A host now says where its units are (`TriggerHost::unit_of`, given by the launchd
  host). The declared state of a schedule also asks the host whether its unit is installed.
- **A System One decision seat no longer starts a batch with a request known to fail.** A
  seat of `typesafe/jev-1.13.0` plans each batch inside the capacity the service documents (64k
  tokens per request), splitting a request in halves before it leaves when its exact body would
  exceed it, and never again sends a body as large as one the service refused for capacity. The
  Foundry qualification of a large knowledge recall used to send the whole batch first and only
  shrink after `max_tokens_exceeded` refusals. Other decision models keep their earlier
  behaviour.
- **A `provider/name` decision seat is stated as sending each request once.** The compile
  receipt and the `--decision-model` help said its client kept protocol retries; the provider
  transport no longer re-sends anything, so the receipt now says « its own provider client, one
  attempt per request ».
- **The decision seat's tally counts every call it sent.** A batched decision call (the whole
  request's verification, its parts, Foundry qualification) is answered as a whole, but the
  tally counted only single-question answers: `/status` said « 33 call(s) sent · 7 answered · 0
  without a response ». A batched call that came back now counts as answered, and one the seat
  answered with an HTTP error as without a response.
- **« Choisis toi-même » now settles a setting instead of asking it again.** When the person left a
  value to Nika (« choisis toi-même » to the question for a time zone, « carte blanche pour le
  reste »), the author chose one and the fidelity law refused it as an unauthorized selection,
  so the same question came back (« le compilateur n'accepte pas que je le choisisse à votre
  place »). A delegated selection may now be a value, but only one that no effect carries: a pure
  step (`nika:date`, `nika:jq`, `nika:assert` and the rest of the pure-internal class, the call
  proven pure) or the words of a task. A fetch, a write, a command, any other tool or agent
  configuration, the document's model, permits, run access and secrets still refuse it, and the
  person's own words must still be cited.
- **`/details` says what a conversation built, saved and ran.**
  QA gauntlet q09: after two Saves and three runs it still said « no
  workflow was read in this session yet », because only a compile round
  set what it reads. With no such round, it now names the conversation
  that wrote the workflow, its last revision, the workflow saved, the
  preparation spend and the last run's trace.
- **`nika doctor` names a refused key.** A provider that answers 403 to the route's key now reads
  « 403 · key refused for this route » with the fix to export a key the route accepts, where the
  doctor printed a bare « 403 ».
- **`nika doctor` no longer calls a seat whose login check did not answer
  "not signed in".** A harness seat whose own login command did not answer
  within its deadline is now a warning that says the sign-in is unknown
  and why ("the login check did not answer within 30 s"), and it teaches no
  sign-in. A seat whose login command answered "signed out" still reads
  "not signed in" with its sign-in gesture.
- **A dropped task now debits the tool spend it had already incurred.** A task cut by its
  `timeout:` or by a `fail_fast` sibling debits, once, the cost reported by tool calls that had
  already completed, including a ready MCP reply it never read. The task stays failed, with no
  output, row or checkpoint credit.
- **`nika:edit` refuses an empty `find:` instead of rewriting the whole file.** An empty
  `find:`, such as a templated input left empty, matched between every character: the
  replacement landed between every byte (`price: 10` became `XpXrXiXcXeX…`) and the
  task succeeded. It is now `NIKA-BUILTIN-EDIT-001`, refused before the file is read.
- **`Enter` pressed right after `Esc` in the intelligence list now acts.** Stepping back with
  `Esc` (from the connections to the home list, for instance) armed the guard that ignores a
  second press of the same `Enter`, so an `Enter` pressed within half a second on « Keep
  current » or another row did nothing. Only an `Enter` that sent something starts that guard
  now; after `Esc`, the next `Enter` is your next choice.
- **Every card can be left.** A saved workflow whose files changed since its
  rehearsal showed a mandatory card with one act, « Adapter le workflow aux
  fichiers modifiés »: `Esc`, words of one's own and `/new` all left it in
  place, and only quitting the app did. `Esc` with nothing left to undo now
  sets a decision's card aside, never answering it: the conversation says
  what stays undecided (the workflow stays not run, a paused run stays
  paused, a proposal still waits) and gives the line back. Words typed at
  that recovery card are the person's next line, `/new` begins a new
  conversation the card does not come back over, and a fresh spending
  question keeps its card (its cancel or `Ctrl+C` leaves it). The refused
  run no longer says « restore the original file »: it names what a retry
  as rehearsed would put back, file by file, in place of what each holds
  now (NIK-227).
- **A failed run no longer moves a person's own files into quarantine.** When a run
  failed, `nika run` moved every file a successful `nika:write` or `nika:edit` returned to
  `.nika/quarantine/<run>/`, including a `notes.md` that existed before the run and was
  only edited. The next run then failed to read or edit it. Now only files the run created
  move. A file that existed stays where it is, and its original bytes, copied before the
  run first changed it, wait under `.nika/quarantine/<run>/originals/`. A writer reached
  by `after: failure`, `terminal` or `unwind` keeps its file, since it records the failure.
  The quarantine dir carries a `manifest.json`, and resuming the failed run with
  `--resume` puts the moved files back before it starts. The run says on stderr which
  files moved and where each kept file's original is.
- **A failed run's journal records its quarantine, signed or not.** What the teardown did
  with a failed run's files rode only inside the signed `run_sealed` frame, so a run
  without a signing key journaled nothing about it, and a crash between the moves and the
  seal left only the manifest. A `quarantine_recorded` frame now lands right before the
  seal: the quarantine directory, how many created files moved (and any move that
  failed), how many of your files stayed in place, and the manifest with the sha256 of
  its bytes. A signed run's seal covers it like every earlier line.
- **A feed item dated only by its revision keeps its date.** `mode: feed`
  dropped an Atom entry's `<updated>` and a JSON Feed item's
  `date_modified`, so an item carrying only that date looked undated and
  a filter on today's items dropped it. Each item now carries `updated`
  when the source dates a revision of its own, an RFC 3339 instant like
  `published`; a date filter reads `published // updated`. An RSS item's
  `pubDate` stays its `published` alone.
- **An author knows what a fetched feed returns.** The `nika:fetch`
  contract an authoring model reads named the extraction modes only by
  pointing to another page, so an author filtered a feed's items by the
  feed's own date text and kept nothing. It now shows what each mode
  returns, the feed's shape, and that feed dates are RFC 3339 instants
  with an explicit offset; `nika:date` joins the everyday contracts to
  compute a bound such as today.
- **`nika:fetch` honors its task's `timeout:`.** A fetch whose task declares `timeout: "3m"` gets
  those three minutes for the whole request, as the stdlib states, instead of a fixed 30s that cut
  long API calls (an image generation, a non-streamed inference) and a 30s idle guard that cut a
  server computing before it answers. A fetch without a task `timeout:` keeps 30s, and a timed-out
  fetch names the setting.
- **`nika:fetch` sends a JSON `body:` with `content-type: application/json`.** An object,
  array or other non-string `body:` was serialized as JSON but sent with no content type,
  so a receiver that parses by type read it as empty. It now carries
  `application/json` unless the author's `headers:` already name a content type; a
  string body is still sent as written.
- **`nika:fetch` returns a CSV, text or JSON body as served in its page modes.** The
  default `markdown` mode, like `article` and `text`, read every body as HTML: a
  `text/csv` export came back on one line with markdown escapes (`A\_1`), so a following
  `nika:convert from: csv` produced no rows while the run succeeded, and a `text/plain`
  body lost its line breaks. A body whose `Content-Type` is text other than HTML
  (`text/*`, JSON, XML, CSV, YAML, NDJSON, TOML and their `+json`/`+xml`/`+yaml`
  suffixes) now comes back unchanged, decoded by its BOM or declared charset, else as
  UTF-8, else as windows-1252. HTML, XHTML and untyped bodies keep the extraction.
- **Files the person says they have in a folder are sources.** « J'ai deux fichiers CSV dans le
  dossier releves (a.csv et b.csv) » made each file a write destination of the whole request,
  because « dans » counted wherever it stood. Every revision reading `./releves/a.csv` was refused
  as « UNREALIZED PATH », and one author wrote permits for files that do not exist to pass. A
  path is now a destination only through a connector in its own sentence, in a clause that writes
  there. A clause that says what the person has, or what there is, places existing material.
- **A second Ctrl-C no longer leaves exec children running.** The second
  signal ended `nika run` with `exit(130)`, which runs no destructor, while
  every `exec` child leads a process group of its own that neither the
  signal to the CLI nor a terminal's Ctrl-C reaches: the child kept running
  and writing after the CLI was gone. The abort now ends every process group
  the run still owns first (SIGTERM, a one-second grace, SIGKILL, then a
  bounded reap), never signals a group whose leader the process can no
  longer wait for, and says on stderr how many groups ended and which did
  not confirm their end. Nothing is rolled back: what already ran stays
  done, and an effect in flight has an unknown outcome.
- **Gemini runs, priced at Google's own pages.** A budgeted run with a Gemini model was refused
  (« this provider's wire has no bounded admission settlement »), any other run reported an
  unknown cost, and Gemini 4 Argon, newer than the vendored price snapshot, read as unpriced
  even to `nika check`. The Gemini wire now reserves, sends once and settles each text call at
  its tariff: ten models (Gemini 4 Argon, 3.8 and 3.6 Flash, 3.5 and 3.1 Flash-Lite, 3.1 Pro
  Preview, 3 Flash Preview, 2.5 Pro, 2.5 Flash and Flash-Lite) read the pricing page at peak
  Standard rates, thinking billed with the answer. The picker and `nika check` read the same
  prices, Gemini's snapshot rows included.
- **GPT-6 Sol and GPT-6.1 Sol take `max_completion_tokens`.** OpenAI refused them with « 'max_tokens'
  is not supported with this model » (HTTP 400) on the first message: the catalog knew the
  parameter only for GPT-6 Astra. Both observed models now send `max_completion_tokens`; the rule
  stays exact, with no claim for the rest of the family.
- **Grok runs, priced at the charge xAI reports.** Every xAI model was unpriced at run time: a
  workflow that fetched a page with Grok as its model was refused (« unknown-cost Run supports
  only direct infer and local read/write/jq/assert »), and any other run reported an unknown
  cost, while the model picker showed a price. grok-4.7 and grok-4.3 are now admitted from
  their model pages and settle at the charge each reply reports (`cost_in_usd_ticks`). xAI's
  reasoning is not bounded by `max_tokens` (measured: 1,973 reasoning tokens under a bound of
  40), so a call reserves the model's whole window: a run ceiling needs about $3.40 per
  grok-4.7 call in flight ($2.75 for grok-4.3), while the settled cost stays the real one.
- **`nika guard` no longer refuses plain shell it can read.** Loops,
  `if`, `[ … ]`, `find` without `-exec`, `xargs` with any other command,
  a heredoc script such as `python3 - <<'EOF'` and a command word in a
  variable that cannot make a run (`"$EDITOR" notes.txt`) were refused
  as `guard_unavailable`, blocking agent commands that run no workflow.
  The guard now judges the commands inside them and keeps no opinion
  when none is a `nika run`; `find -exec nika`, `xargs nika` and
  `$N run x` stay visible.
- **A slow login check no longer reads a signed-in seat as signed out.** A
  harness seat's own login command now has 30 s to answer: valid checks
  were measured at 12 s and 25.8 s, and the former 10 s turned them into
  a "not signed in" seat. The harness probe facts now also carry the
  check's answer (`login`): signed in, signed out, or unknown when it did
  not answer in time ("the login check did not answer within 30 s") or
  could not run, so an unanswered check is no longer the same fact as a
  signed-out seat. Admission still requires an answer before it admits a
  seat, and no credential is read.
- **Hints name the program the person runs.** A proposal's hint line said « `nika check x.nika`
  prints them » to a person running `nika-dev`, whose `nika` on the same machine was an older
  release that refused the file. Hints in the Session, the check summary and the refused-run
  story now name the running program (`nika-dev`, `nika`), and a paused run's meaning no longer
  gives a command line: the Session asks its question itself.
- **The Session's hints name the program you run.** `/help` and `/proof` said `nika trace
  verify` while the person ran `nika-dev`. On that Mac, `nika` was an older release that refused
  the trace. Every command those hints name now uses the running program's own name.
- **`Home` takes the conversation back to its first message.** `PgUp` pages through the
  conversation a screen at a time, so a long card such as a full proposal took several presses
  to pass, and `Home` did nothing. With nothing typed in the box, `Home` now goes to the first
  message, `Ctrl+Home` does it even with words typed, and `End` still brings the latest
  messages back. The `?` key list names these keys.
- **An infer answer interrupted by its provider fails as a provider call.** When the provider
  reports it interrupted the generation (`StopReason::Interrupted`), the answer is never the task
  output: the task fails `NIKA-INFER-001`, transient, naming the provider's own word, and the
  task's `retry:` decides. An answer cut at the output cap that parses, even one that satisfies
  `schema:`, still fails `NIKA-INFER-005`.
- **« Inspect the exact changes » shows the lines a revision changes.** For a proposal that
  replaces a saved workflow, it used to print the whole file (« replaces `clean-contacts.nika`
  whole (109 lines …) » followed by all 109 lines), so you had to find the one line that changed.
  It now opens on what changes against the file as it is now, with the size of the change
  (`+1 −1 lines`) and each change shown removed then added among the lines around it. Every byte
  still follows. A file that changed since the proposal was made is said so and not compared.
- **A verifier's unlocated doubt is asked where it is, then held as
  unresolved.** The question whether a step does something the request
  does not ask no longer offers a step that only reads paths the request
  names and writes the output a part it carried asks: the checker's own
  effect inference, with constants resolved, settles such steps, and
  with none left the question is answered with no model call and
  recorded on the attempt (`engine`). The previous check, which let a
  named step pass without resolving its constants, is gone. When the
  verifier rejects the whole request but carries every part, names no
  step, and no trial run of those bytes can decide it, it is asked once
  where its doubt is: a part or step it names is located and repaired
  from as before, and naming nothing holds the workflow as unresolved,
  never ready and never proposed, in words that say nothing was verified
  (`unresolved` on the attempt).
- **Kept defaults, source freshness and the questions left read in French.** For a French speaker,
  « kept the default », the source freshness lines and « 1 more after this one » are now said in
  French; the receipt lines (« applied · wrote », « check · … · clean ✔ ») keep one wording.
- **A development build no longer asks for the keychain password on every run.** A macOS
  keychain grant (« Always Allow ») is bound to the signature of the binary that asked. A
  Developer ID release keeps the same signature from one version to the next, so its grant
  sticks; an ad-hoc build (every `cargo build`, every dev build) is a new identity each time, so
  the password prompt came back on every run. Now only a binary whose signature can keep a grant
  opens the keychain for its run-key; any other takes the explicit `NIKA_RUN_KEY_FILE` pair or
  the 0600 files under `~/.nika/keys`, silently, and runs unsigned when there is none, never
  under a new key. `nika serve` never opens the keychain. A secret key file other users can read
  is refused, as ssh refuses one. `nika doctor` says in one `run-key` line where the key comes
  from, the fingerprint of the key this process would sign with, and why the keychain is or is
  not part of it. A machine whose keychain holds the key a signed release signs with, beside a
  0600 pair dev builds and `nika serve` sign with, has two identities: a release's doctor names
  both keys, and `nika key align`, run from a signed release, gives the 0600 files the keychain
  key and retires the one they held (the journals it sealed stay verifiable). At init, rotate and
  align a signed release records the keychain key's fingerprint and the item's modification date
  in `~/.nika/keys/keychain.json`; a dev build compares through that record only while the item
  is unchanged, and otherwise says it does not know. `nika key init` in a process that does not
  open the keychain refuses to mint a second key beside the keychain's, unless forced. Even a
  signed release now opens the secret keychain item only to sign or to align: `nika key trust`,
  `nika doctor`, the memory fold, a seal grade and a workflow signature read the public item
  alone, and each item is read at most once per process. `nika key init` sees whether the keychain
  holds a key from the items' attributes, never their data. `NIKA_KEYCHAIN=off` still turns the
  keychain off everywhere.
- **Without a decision seat, an authoring call no longer receives every other knowledge entry.**
  With a knowledge release lent and no decision seat selected, the author was shown, beside the
  recalled pack, the descriptor of every other admitted entry and of every embedded template,
  unqualified: 244 to 369 references (102 to 199 KB) per request on the release this build
  embeds, measured over 16 requests. A descriptor is there for a decision seat to judge whether
  its entry serves the request, and without a seat nobody asks it. Nothing is widened then: the
  author reads the recalled pack, and the qualification record says why. With a seat, every
  admitted entry is still asked.
- **A shared stopword no longer recalls knowledge for an authoring call.** The knowledge door's
  text recall (BM25 over the release's rows) read every word of three letters or more, so a row
  sharing only « the », « and » or « with » with the request was recalled and presented whole:
  on the release this build embeds, a ten-word authoring request reached 22 of its 26 examples
  and all 16 skills that way. The recall now reads informative words only, without the
  compiler's shared stopwords. Every row it reaches is still presented whole, with no count or
  byte quota, and the pack names its builder `knowledge-door-v7`.
- **A French article no longer recalls knowledge either.** The knowledge door's text recall also
  reads past the compiler's retrieval lexicon (French articles, pronouns and politeness; English
  request scaffolding such as « please » or « help me »), the words the compile's own retrieval
  already reads past. On the release this build embeds, a French request for a news digest
  recalled four French-titled patterns through « les » alone; it now recalls by its content
  words. The lexicon moved beside the shared stopwords (`nika_compile_reader::text`) unchanged.
- **The Linux jail no longer drops a write grant without a word.** bwrap
  can only bind a path that exists, and a jailed command cannot create its
  own grant. The jail used `--bind-try`, which skipped such a grant
  silently, so a command that had to create the granted file failed far
  from the cause (on Linux only: macOS Seatbelt admits a file that does
  not exist yet). Existing write grants now bind with `--bind` (a source
  that vanishes before the mount fails loudly), and a spawn that leaves
  grants unbound writes one line on stderr naming them with the remedy:
  grant the directory, and make sure it exists before an exec writes into
  it. An absent grant is not refused, since another task of the workflow
  may be the one that creates it.
- **An MCP server's network allowlist now reaches its hosts.** On macOS a
  server declared with `"network": {"allowlist": [...]}` reached no host at
  all and could not even resolve a name: its jail admitted loopback only,
  while `nika mcp approve` announced it as confined like `"allow"`. The
  spawn now starts the loopback egress proxy that the exec allowlist uses,
  points the server at it (`HTTPS_PROXY` and siblings) and fences the jail
  to the proxy's port, so exactly the declared hosts answer. The approval
  note says `through a loopback egress proxy`, and a refused host prints
  one `nika:egress REFUSED` line naming the server's allowlist.
- **Nika's MCP server and client say only the protocol they speak.** The
  server answered an `initialize` asking for MCP 2026-07-28 with that
  revision, and an unknown revision with it too, yet the final 2026-07-28
  text has no handshake and neither half implements its MUSTs; it now
  answers 2025-11-25, the latest handshake revision, as does the
  conversation server, and the 0.94.0 changelog line that said it spoke
  2026-07-28 natively carries the correction. As a client, a `tools/call`
  result whose `resultType` is `input_required` or `task` is a failed call
  instead of an empty success; a reply that follows more than sixteen
  interleaved notifications is read, the wait being bounded by its deadline
  and by 8 MiB of skipped messages; and a call that times out is cancelled
  with `notifications/cancelled` before the server is stopped.
- **`nika mcp --http` holds every connection to a deadline and serves them side by side.**
  A request has 30 s in all to arrive, head and body, and its response 30 s to leave; each
  connection runs on a thread of its own, at most eight at once, the next waiting in the
  listener's backlog, so a slow client holds one connection for a bounded time. A failed accept
  on the server's side pauses the loop briefly.
- **An MCP call the run stops waiting for is cancelled, not left running.**
  When a task's `timeout:` cut an `invoke` of an `mcp:` tool, its call kept
  running on its worker until the 120 s call timeout, holding the server's
  pipe so every other call to that server waited behind it, and the server
  was never told; the operator's Stop (Ctrl-C, SIGTERM, a Session's Stop)
  waited for such a call to finish. The call now carries a stop: one not yet
  written is never sent, and one in flight is cancelled with
  `notifications/cancelled` while its session stays open for the next call.
  Under the operator's Stop the task fails `NIKA-CANCEL-001`, never retried,
  its `error.details.stopped_call` naming `{tool, call_id, outcome}` with
  `outcome` `not_sent` or `unknown` (it was sent, so the server may have
  acted), and the run's settlement lists every such call as `stopped_calls`
  (`{task, tool, call_id, outcome}`), whether its task failed, was skipped
  past or recovered. Other in-flight work still completes at the wave
  boundary. A reply that already arrived now wins over a stop, a server
  request that reuses the call's id is no longer taken for its reply, and a
  stopped call's late reply no longer counts against the next call's byte
  budget.
- **`/meaning` beside an authored proposal reads your request.** Beside a proposal an
  intelligence wrote, `/meaning` printed the money card: no compiler reading stood behind it.
  It now lists, in the language you write in, what Nika kept of your request: your words, each
  value the workflow relies on with its part and where it comes from, what you left to Nika to
  choose and the questions still open. The proposal keeps waiting for your answer.
- **The model question no longer demands `<provider>/<model>`.** On a subscription account (codex,
  claude-code), a request with an AI step asked « Which explicit runtime provider/model should run
  the language steps? » with nothing offered: « choisis toi-même » was taken as typed, and the
  compiler answered « Name the runtime model as <provider>/<model> ». The question now offers the
  configured model this conversation recommends (the saved default, else the newest configured
  one), which Enter takes. A reply that leaves the choice to Nika takes it too, said as Nika's
  choice beside the proposal, and a model named or described in words (« prends Claude Haiku ») is
  read against the configured models whose name carries those words, never invented. A reply that
  designates none of them binds nothing and says which model is offered.
- **A proposal's money line says its ceiling as it was set.**
  « money: $0.1 USD » sat two lines from « ceiling $0.10 »: the Session's
  money lines and project default printed a ceiling with the float's own
  digits. They now read through the one ceiling formatter every other
  surface uses (« $0.10 », « $0.001 », « $2 »).
- **More of the Session's own words in French.** A line the Session could not read now says, in
  a French conversation, « je n'ai pas lu cette ligne comme un travail, et je n'ai pas su ce
  qu'elle veut dire — rien n'a changé ». The same holds at a proposal, a question and a gate. A
  revision's card says « Même workflow que la proposition vue » or « Changé depuis la
  proposition vue », and a model failure says « la conversation n'a pas pu continuer ».
- **A Claude call is priced, so the Run ledger, `--max-cost-usd` and a Session allowance
  count it.** The Anthropic wire never marked a reply's usage complete, so every Claude
  call stayed cost-unknown ("provider did not report usage") and a Session allowance with
  a Claude author was refused. A reply whose usage accounts for every billed meter (input,
  output, cache read and 5-minute cache write, no server tool, standard tier, global
  inference) is now complete: it is priced at the model's admission tariff, else the
  pricing snapshot on Anthropic's own endpoint, and an allowance reserves, sends once and
  settles a Claude text call through the Anthropic wire, the model named exactly, by
  nickname (`anthropic/sonnet`) or through the `claude` alias. A reply missing a meter, or
  a Claude Haiku 5.5 prompt above its admitted 100,000 tokens, stays unknown, never zero;
  under an allowance, a Haiku 5.5 request whose bytes could exceed that tier is refused
  before anything is sent.
- **The key a provider reads is named, and the Nika-scoped variable is the one advised.**
  The probe lists the key variables set for each provider, names only, in the order the
  run reads them; the census and `nika doctor` name the one that supplies the key
  (`NIKA_ANTHROPIC_API_KEY` before `ANTHROPIC_API_KEY`), and two variables set with
  different values earn a doctor warning naming both, compared in memory where the keys
  are read and never printed. A missing key at run or in a conversation now advises
  `NIKA_<ID>_API_KEY`, which no other tool reads (exporting `ANTHROPIC_API_KEY` can move
  another application, such as Claude Code, onto that key), and a conversation shows it
  without the command-line teaching. An API pick naming the `claude` alias
  (`/intelligence 2 claude/<model>`) selects Anthropic.
- **`run.reasoning.effort` reaches a Claude 5 model.** The catalog records the levels this
  engine names (`low`, `high`, `max`) for Claude Fable 5.1, Fable 5, Opus 5.5, Opus 5,
  Sonnet 5.5, Sonnet 5 and Haiku 5.5, so a declared effort on such a model, a nickname
  included, is admitted at check and rides the Anthropic request as
  `output_config.effort` on Anthropic's own endpoint, read back from the bytes sent. An
  earlier Claude model, a thinking budget beside the level or an operator-pointed endpoint
  still refuses before any byte, and `nika check` names the file's refused access as the
  blocker instead of an empty list beside ACCESS READY ✖.
- **`nika check` refuses a `nika:date` zone the run would refuse.**
  `tz: "Paris"` checked clean, then the run failed at its first
  `nika:date` task with `NIKA-BUILTIN-DATE-001 · unknown tz: Paris`. A
  literal `tz:` is now judged against the embedded IANA time-zone
  database the builtin itself reads, and an unknown zone is refused with
  the run's code, naming the zone when one is obvious (`Paris` or
  « L'heure de Paris » → `Europe/Paris`). A templated `tz:` is left to
  the run. The ARGS rung now prints every refused literal value with its
  own code and the checker's wording, where it said « has no `<arg>`
  arg ».
- **`nika:notify` now reaches Slack and Discord webhooks.** A Slack incoming webhook
  (`hooks.slack.com/services/…`) refused the stdlib body `{ message, severity, data? }`
  with HTTP 400 `no_text`, so a project whose last step posts to Slack failed on every
  run. Slack incoming webhooks now get `{ text, mrkdwn: false }` and Discord webhooks
  (`discord.com/api/webhooks/{id}/{token}`) get `{ content, allowed_mentions }`: the
  message, a non-`info` severity and the `data:` context as plain text. Slack's `&`, `<`
  and `>` become entities. Discord's markdown and mention openers are escaped, no
  mention is parsed, and the content is bounded to Discord's 2,000 characters. Every
  other target, including a Slack Workflow Builder trigger, keeps the stdlib body. A
  refused delivery (`NIKA-BUILTIN-NOTIFY-002`) now quotes the receiver's answer, as one
  bounded line with the target's own parts withheld, and carries `details.status_code`.
- **A model offer shows the price its admission uses, or none.** A Session's
  model offer showed the models.dev snapshot's list price, not the price its
  admission uses: GLM 5.3 Flash on OpenRouter read $1.2 per million output
  tokens while its admission reserves $1.6, the peak of its tariff, and a model
  no tariff admits, such as Claude Sonnet 4.6 on an Anthropic key, showed a list
  price all the same. An offer is now priced only from the qualified admission
  tariff of its exact route and endpoint (the reply still settles at
  OpenRouter's reported charge), and a route the admission cannot qualify shows
  no price, never a bare snapshot price. Claude Sonnet 5.5, offered on an
  Anthropic key without an admission tariff, shows no price, is never ranked by
  the strongest-offer rule and is still chosen exactly when named.
- **One « newest » mark per model family, chosen by real recency.** Searching the intelligence
  list for `deepseek` showed « newest DeepSeek » on three rows at once: DeepSeek V4 Pro on the
  DeepSeek API, the dated V4 Flash 0731 on Scaleway and R1 0528 on Hugging Face, whose « 0528 »
  was read as version 528. « newest Grok » stood on Grok 4.20 0309 rather than Grok 4.7. A date
  written in digits (`0528`, `250731`) is now a snapshot date, never a version; a minor version
  reads as a decimal, so 4.7 comes after 4.20; and a family is marked once in a list that spans
  several connections, on its newest model.
- **GPT-6 and GPT-5.6 work with tools.** Any `agent:` task or tool-using author on GPT-6 Astra,
  Sol, 6.1 Sol or Luna, or GPT-5.6 Sol, failed with HTTP 400 « Function tools with
  reasoning_effort are not supported … use /v1/responses »: these models refuse function tools
  on Chat Completions. A request with function tools to OpenAI's own endpoint now rides its
  Responses API (stateless: `store: false`, reasoning items kept encrypted and sent back in
  place), priced at the same verified tariff. A GPT-6 Astra agent that reads a file now answers
  in three turns ($0.01).
- **OpenAI's zero usage meters no longer void a reply's usage.** OpenAI's usage object
  carries audio and predicted-output meters on every text reply, even at zero, and Nika
  read any such key as an unknown axis, so a direct OpenAI reply never had complete
  usage. Zero meters now read as complete; a non-zero one, a cache write or an unknown
  key still leaves the usage incomplete.
- **The free OpenRouter admission names a route OpenRouter still lists.**
  `qwen/qwen3.8-27b:free` has left OpenRouter's catalogue; the declared-free
  admission now names `nvidia/nemotron-3-super-120b-a12b:free`, read from its
  endpoint listing of 2026-10-12: one endpoint at a zero prompt and completion
  price, a 262,144-token window and 235,929 output tokens, for bounded text
  requests only. A run naming the old id is no longer admitted as free.
- **A paid OpenRouter call settles at the charge OpenRouter reports.** A paid
  OpenRouter reply always read as unknown cost, and an admitted paid route would have
  turned its account Uncertain at the first settlement. A non-BYOK reply from
  OpenRouter's own endpoint with complete usage now settles at its `usage.cost`, which
  receipts name as a provider-reported charge; its catalogue tariff only reserves. A
  BYOK reply, a gateway's `cost` or an unknown fee stays unknown. A charge above the
  reservation still settles known, and that route then refuses its next call until its
  catalogue tariff is refreshed.
- **An open-weight model named on another provider is no longer refused as a
  wrong seat.** `nika check` refused `scaleway/llama-3.3-70b-versatile`, or a
  local engine's `meta-llama/…` model, as served only by the one provider whose
  catalogue row lists it. An `org/model` id, or a model the pricing snapshot
  marks open-weight, now has no single owner, whoever lists it; a
  provider-exclusive id pasted on another provider (`groq/grok-4.3`) is still
  refused with its owner's id as the repair.
- **A long conversation no longer overflows an OpenAI-compatible model's window.** Nika asks a
  route for its whole published output capacity, and an OpenAI-compatible route counts the prompt
  and that output against one context window. On OpenRouter's DeepSeek V4 Flash (943,718 output
  tokens in a 1,048,576-token window) every call failed with HTTP 400 once the conversation passed
  about 105,000 tokens: « you requested about 1060562 tokens (… 943718 in the output) », after an
  authoring loop of 30 calls and 13 minutes. The output budget sent is now what the prompt leaves
  of the window, the prompt counted generously from the request's size, less a margin of 1/64 of
  the window. A budget that already fits, and a route with no known window, are sent unchanged.
- **A task with a large output no longer stops the run.** The Session reads a run's frames line by
  line and refused any frame over 1 MiB, ending the child mid-run: a workflow that fetched three
  job boards (one of them 3.4 MB of JSON) failed with « Run cost review refused: Run frame exceeds
  1 MiB; no automatic retry », its effects uncertain. A frame over the cap is now drained without
  being kept and the run goes on to its own end; memory stays bounded. The Session says the step's
  output was neither shown nor kept: the run's trace bounds its lines at the same 1 MiB, so its
  journal stops at that step and the run settles with its evidence lost.
- **The intelligence picker states a model's own context window, and how far a tiered price
  holds.** Claude Haiku 5.5 read "100k context" because its tariff, which prices prompts up
  to 100,000 tokens, stood in for its million-token window. An option's `context_tokens` is
  now the model's window (its provider row when a source is recorded for it, else the
  tariff's, else the list row's), and `ModelFacts` gains `prices_up_to_tokens` when a tariff
  prices only a cheaper part of that window: the TUI reads "from $0.10 in / $0.50 out per
  MTok (≤100k tokens) · 1M context".
- **« active-le alors s'il vous plait » activates the schedule.** Only the bare activation words
  (« active-le », « activate it ») opened the activation; with a courtesy around them the line went
  on as a request and the workflow ran once more. A courtesy or a filler before or after them
  (« alors », « s'il vous plaît », « stp », « please », « now », « ok ») now leaves the same
  activation, and « active-la », « activez-le », « active la répétition » are read too. Anything
  more, such as a cadence, keeps its route.
- **The project file's refusals teach a cadence the arming door reads.**
  A beat with no or an empty `cadence:` was told to write
  `cadence: "dimanche 18h07"`, which `n