Repository navigation
chore(release): 0.22.2 — status writer + websocket extra + max_output_tokens reserve - #118
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…_tokens reserve ## Summary Patch release, no protocol change. Three unrelated bugs closed in one shipping window: a status field that structurally could not report the failure it existed to report (`backend_reachable` initialised to `None` and never written), a packaging extra that the SDK's own `ImportError` told users to install but that `pyproject.toml` never declared, and a pre-flight that priced at the input rate and so silently under-reserved every real LLM call. None is a wire change — all three are SDK-side. The status and packaging fixes are behaviourally invisible to a healthy configuration; the reserve fix is visible to anyone whose `max_output_tokens` is currently being ignored. ### Fixed - **DEF-STATUS-WRITER-01** — `NullRunStatus.backend_reachable` was initialised to `None` in `__init__` and read in `status()` — and never assigned anywhere in the SDK. The comment above the initialisation claimed the pair was "set in `_authenticate` and updated on every successful / failed backend call thereafter"; no such writer had ever existed. Two silent consequences: the DEGRADED headline was gated on `is False` so it never fired (pinned to `None`), and the MISCONFIGURED arm was gated on `_last_backend_attempt_at is not None` so it was dead for the same reason. Fix: one writer `_note_backend_attempt(ok)` called from the four sites that actually learn whether the backend answered. "Ok" means "the backend answered", NOT "the call succeeded": a 402/403/429 and a 401 are the backend replying, and only a transport-level failure sets it False. Collapsing those would brand a healthy server that correctly refused a call as unreachable and drive DEGRADED off a credential problem. The pair is read under a new lock because gate calls arrive on the tool thread while `_authenticate` runs on the caller's thread. (`b076643`, +100/-8; test files `test_status_backend_reachable_has_a_writer.py`, `test_status_ws_connected_reads_real_attribute.py`) - **DEF-SDK-16 (packaging: declare the `websocket` extra)** — `WebSocketConnection.connect` raises `websockets library not available. Install with: pip install nullrun[websocket]`, but `nullrun` had no `websocket` extra. `[project.optional-dependencies]` declared exactly two — the opentelemetry pair and `dev` — and `websockets` appeared nowhere in `pyproject.toml`. Two declarations, not one: `websocket = ["websockets>=14.0,<17.0"]` and `dev` also gains `websockets` (so `tests/test_ws_push.py` resolves through a declared edge, not through `langchain-core -> langsmith -> websockets` which nobody here controls). A new source pin asserts the general property: every `nullrun[<extra>]` install hint appearing anywhere under `src/` must name an extra `pyproject.toml` actually declares. (`a8cb823`, +24/-1; `tests/test_websocket_extra_is_declared.py`) ### Added - **DEF-MOT-01 (reserve: forward caller-declared `max_output_tokens` to `/gate`)** — per ADR-016 §2.1 the backend prices an output reserve from TWO independent signals (the caller's declared cap and its own per-model catalog cap) and refuses to reserve from either one alone. The SDK's half of that contract is now narrow and specific: when the caller set a cap it is sent, when the caller did not the key is ABSENT (not present-and-null — a null is a value the backend has to defend against, not an honest "I don't know"), and a cap of zero or less is treated as "not declared" rather than silently under-reserving. New ContextVar `_call_max_output_tokens_var`, reader `get_call_max_output_tokens()`, and a `max_output_tokens=` parameter on `set_call_context(...)`. The via-edge box's `token_split()` falls back to the declared cap before the one-token estimate; every step down errs toward charging MORE, never less. (`f095c46`, +251/-9; test file `tests/test_max_output_tokens_reserve.py`) ### Verification | Check | Result | |---|---| | `ruff check src tests` | All checks passed | | `mypy src/nullrun` | Success: no issues found in 39 source files | | `pytest -q` | **1913 passed, 12 skipped** in 156.34s (vs baseline 1849 / 12 — **+64 new tests** from `test_status_backend_reachable_has_a_writer`, `test_status_ws_connected_reads_real_attribute`, `test_websocket_extra_is_declared`, `test_max_output_tokens_reserve`) | | Scratch diff | clean (`dist_local/`, `*.defect*` absent) | | `nullrun.__version__` | `0.22.2` | | Wire-format compatibility | unchanged from 0.22.1 (no protocol change; the `max_output_tokens` key is new-but-OPTIONAL, the backend ignores its absence as before) | ### Commits included ``` b076643 fix(status): backend_reachable was structurally pinned to None a8cb823 fix(packaging): DEF-SDK-16 — declare the `websocket` extra the ImportError names f095c46 feat(reserve): forward caller-declared max_output_tokens to /gate (DEF-MOT-01) e56241b fix errors ```
maltsev-dev
force-pushed
the
release/0.22.2
branch
from
October 16, 2026 16:20
960bb00 to
10839fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Patch release, no protocol change. Three unrelated bugs closed in one
shipping window: a status field that structurally could not report
the failure it existed to report (
backend_reachableinitialised toNoneand never written), a packaging extra that the SDK's ownImportErrortold users to install but thatpyproject.tomlneverdeclared, and a pre-flight that priced at the input rate and so
silently under-reserved every real LLM call. None is a wire change
— all three are SDK-side. The status and packaging fixes are
behaviourally invisible to a healthy configuration; the reserve
fix is visible to anyone whose
max_output_tokensis currentlybeing ignored.
Fixed
DEF-STATUS-WRITER-01 —
NullRunStatus.backend_reachablewas initialised to
Nonein__init__and read instatus()—and never assigned anywhere in the SDK. Two silent consequences:
the DEGRADED headline was gated on
is Falseso it never fired(pinned to
None), and the MISCONFIGURED arm was gated on_last_backend_attempt_at is not Noneso it was dead for thesame reason. Fix: one writer
_note_backend_attempt(ok)calledfrom the four sites that actually learn whether the backend
answered. "Ok" means "the backend answered", NOT "the call
succeeded": a 402/403/429 and a 401 are the backend replying,
and only a transport-level failure sets it False. The pair is
read under a new lock because gate calls arrive on the tool
thread while
_authenticateruns on the caller's thread.(
b076643, +100/-8;tests/test_status_backend_reachable_has_a_writer.py,tests/test_status_ws_connected_reads_real_attribute.py)DEF-SDK-16 (packaging: declare the
websocketextra) —WebSocketConnection.connectraiseswebsockets library not available. Install with: pip install nullrun[websocket], butnullrunhad nowebsocketextra.websocketswas inuv.lockonly as a transitive oflangsmith, so the WS wire test's coverage depended on adependency edge nobody here controls. Now declared with floor
14.0(theadditional_headersspelling replacedextra_headersin the asyncio client in 14.0; an older installwould
TypeErrorat connect time, later and in productiontraffic, instead of at the
ImportErrorthe user was told tofix).
devalso gainswebsocketssotests/test_ws_push.pyresolves through a declared edge. A new source pin asserts
the general property: every
nullrun[<extra>]install hintappearing anywhere under
src/must name an extrapyproject.tomlactually declares. (a8cb823, +24/-1;tests/test_websocket_extra_is_declared.py)Added
max_output_tokensto/gate) — per ADR-016 §2.1 thebackend prices an output reserve from TWO independent signals
(the caller's declared cap and its own per-model catalog cap)
and refuses to reserve from either one alone. The SDK's half
of that contract is now narrow and specific: when the caller
set a cap it is sent, when the caller did not the key is
ABSENT (not present-and-null — a null is a value the backend
has to defend against, not an honest "I don't know"), and a
cap of zero or less is treated as "not declared" rather than
silently under-reserving. New ContextVar
_call_max_output_tokens_var, readerget_call_max_output_tokens(), and amax_output_tokens=parameter on
set_call_context(...). The via-edge box'stoken_split()falls back to the declared cap before theone-token estimate; every step down errs toward charging
MORE, never less. (
f095c46, +251/-9;tests/test_max_output_tokens_reserve.py)Verification
ruff check src testsmypy src/nullrunpytest -qtest_status_backend_reachable_has_a_writer,test_status_ws_connected_reads_real_attribute,test_websocket_extra_is_declared,test_max_output_tokens_reserve)dist_local/,*.defect*absent)nullrun.__version__0.22.2max_output_tokenskey is new-but-OPTIONAL, the backend ignores its absence as before)Commits included