Contents TandemLLM Docs
v0.14.1
Star58

Release notes, v0.14.1

TandemLLM 0.14.1 on GitHub, published 2026-10-22. The changelog chapter of this version: Changelog summary; the full file: CHANGELOG.md.

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 grep or ls for 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 card runs 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=1 keeps 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=1 at the end of ~/TandemLLM/run/ops/serve.env and run ~/TandemLLM/bin/tandem restart. /health then shows "kv_cache": "e4m3" under weights. 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-pk is 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.py and keeps its own knobs in knobs.py (ENG-405). Adding a model edits no shared file.
  • A family's ServeSpec says 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 ToolFormat interface in server/toolformats/, held to one contract by tests/test_toolformats.py (ENG-407). engine/grammar.py is 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 from server/app.py into engine/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 0 works), and helpers that need a port up front take one from a locked counter.

Files of the release