Release notes, v0.14.1
TandemLLM 0.14.1 fixes how Flint's tool calls reach a client and how it answers with thinking off, and moves model code behind adapters. A call's argument names are matched to the offered tool's schema, so Flint's read with path reaches opencode as filePath. A call to a tool the request did not offer goes to the offered tool of its kind. With thinking off, Flint answers without writing its reasoning into the answer. Flint can keep its full layers' keys and values in e4m3, off by default. Each model family now registers itself and declares how it is served, and shared code names no model. The other models write the same text as in 0.14.0 and keep their saved conversations. Flint's fingerprint moves once, so its saved prompt-cache entries from 0.14.0 are rebuilt once. Every change is in CHANGELOG.md, and the PDF below is the manual.
Tool calls
- Argument names are matched to the tool's schema before a call leaves the server (SRV-691), on both paths and in every tool-call form. A name is kept when the schema has it. Otherwise it is matched by case,
_and-, then by a small synonym table: a path for the schema's one path-like parameter,query/pattern/regex,cmd/command,content/text/contents. A rename happens only when exactly one parameter is the target and the call has not set it. A value is never changed, except to take its target's JSON type. A stream renames a key as soon as it is decided, so the streamed call and the parsed one agree. The journal records each rename (ARGS, RENAME). Through opencode's checks at Flint 4.0's card sampling, 8 runs an arm: calls the client refused fell from 4 to 0 under the conductor and from 2 to 0 raw, and the checks passed went from 28 and 29 of 32 to 32 of 32. - A call to a tool the request did not offer (SRV-693) goes to the one offered tool of its kind when its arguments then name that tool's required parameters. A search or a listing with no such tool becomes a quoted
greporlsfor the client's bash tool. A write, edit or delete never becomes a shell command. Anything else goes out as written, and the conductor's C4 asks for it again with the offered list. - The conductor's call checks read the calls as the client receives them, after the renames (SRV-691).
tools/client_smoke.py --sampling cardruns the tool checks at the server's default sampling (SRV-692). The release gate stays greedy.
Flint
- With thinking off, Flint's prompt gets one line before the conversation: "Do not show your reasoning. Reply with the answer, or call a tool when one helps." (SRV-697). Without it, Flint opened its answer with reasoning on 7 of 10 hard asks greedy and 13 of 20 at its card's sampling, with a median of 582 and 600 tokens (600 was the cap). With the line it did so on 0 of 10 and 0 of 20, with a median of 153 and 116 tokens. On tool-eval-bench (4.0, raw path, thinking off, one run each) it scored 75 with the line and 77 without. Thinking on is unchanged.
- Flint's requests keep system and developer messages where they stand, with their own role, since its template renders them in place. The 27B's rule (one system prompt first) is now the default family setting, unchanged for the other models.
FLINT_KV_FP8=1keeps the 14 full layers' keys and values as e4m3 codes with one fp32 scale a (head, token): 14,784 bytes a token instead of 28,672 (SPD-150). This changes the output, so it is off by default. On 4.0 against the exact cache, the last 1,024 tokens of a 32,768-token source file read at KL 0.070, 93.8 % top-1 agreement and perplexity 7.465 against 7.165. At 122,880 tokens the numbers are KL 0.050, 95.7 % and 4.132 against 4.053. Plain decode runs at 79.4 tok/s against 60.6 at 64k, 57.1 against 39.3 at 128k and 36.6 against 23.05 at 256k. Prompt reading is 5 to 12 % slower. docs/flint.md has the table.- To turn it on in the Setup Guide, pick Flint, go to the Tools step and switch on "Long-context cache (FP8)" under Features, then finish Review and Install. The switch shows the cost and the gain, and the memory bar counts the smaller cache.
-
To turn it on from the command line, add
QWEN38_FLINT_KV_FP8=1at the end of~/TandemLLM/run/ops/serve.envand run~/TandemLLM/bin/tandem restart./healththen shows"kv_cache": "e4m3"underweights. To turn it off, delete the line and restart, or switch it off in the Setup Guide. -
tool-eval-bench on Flint's three profiles: bench 2.8.1.dev13 (commit f2e368b), seed 42, temperature 0, one run a row (n=1), the Setup Guide's Flint defaults (one slot, effort medium by default, FP8 cache off), BF16 at a 131,072-token window, 2026-10-22. Score, then the time the row took. Raw rows send
X-Tandem: raw;bench-pkis a test profile we do not ship. Hard mode at n=1 varies a lot between runs, so we claim no ranking between the sizes. docs/flint.md has the chart and models/flint/measurements/2026-10-22-tool-eval-bench.json every row.
| row | EXL3 4.0 | EXL3 6.0 | BF16 |
|---|---|---|---|
| full suite, 69 scenarios | |||
| raw, thinking off | 75 (2 min) | 76 (3.1 min) | 76 (4.7 min) |
| raw, thinking xhigh | 85 (4.7 min) | 86 (11 min) | 86 (21.5 min) |
conductor, bench-pk |
81 (5.1 min) | 86 (8.9 min) | 83 (16.8 min) |
conductor, tools |
83 (4.1 min) | 86 (6.3 min) | 84 (15.3 min) |
| hard mode, 28 scenarios | |||
| raw, thinking off | 36 (1.8 min) | 41 (2 min) | 41 (4.3 min) |
| raw, thinking xhigh | 71 (5.5 min) | 61 (8.6 min) | 62 (19.9 min) |
conductor, bench-pk |
73 (3.5 min) | 61 (5.8 min) | 57 (12.8 min) |
conductor, tools |
80 (2.2 min) | 54 (4.4 min) | 70 (12.8 min) |
Model adapters
- Each family registers itself in its package's
arch.pyand keeps its own knobs inknobs.py(ENG-405). Adding a model edits no shared file. - A family's
ServeSpecsays what the server needs: the tool-call form, how the model opens and turns off its reasoning, and where system messages may stand (ENG-406). The identity note names the model from the profile's card and has no default model. - The tool-call forms (XML, Pythonic, arg-key) sit behind one
ToolFormatinterface inserver/toolformats/, held to one contract bytests/test_toolformats.py(ENG-407).engine/grammar.pyis back to its 0.13 bytes. - A family declares which of its files others build on and which it uses from others, and kernel launch options come through
EngineHooks(ENG-408). A change to a shared file moves exactly the declared dependents' fingerprints. - Community-pack rules live in each base model's
model.yaml, and its profile writer and pack check live in its family (ENG-409). The 27B's builder, drafter stack and side slots moved fromserver/app.pyintoengine/models/qwen3_5/serving.py(ENG-406, ENG-410). - Guards hold this (ENG-404). They check v0.14.0's fingerprints as goldens with only Flint allowed to move, and they freeze
SAME_ROWS. They also check that a change in one package moves only the adapters that hold it, and lint the shared modules for model names. docs/adding-a-model.md says what a family declares.
Tests and CI
- The CPU job on GitHub runs every test file, and a failure fails it (OPS-231). Timing tests bound CPU time against a reference measured in the same run, the two fp32 references bound their error in ulps, and the padded-row check bounds its difference by 2 % of the largest value, a quarter of what padded rows that move the state change. The shared-module lint reads f-strings the same on every Python. The slow-subscriber test gates its reader, and the drain test waits for its signal handler. The gate's fake engine runs in a process of its own. A test server binds before its port is read (
--port 0works), and helpers that need a port up front take one from a locked counter.
Files of the release
- tandemllm-manual-0.14.1.pdf (4529 KiB)