Repository navigation
ThinkWatch Core 0.69.0
This release keeps a model's reasoning intact across API formats, recognises tool calls that a Chat-format model writes into its answer text, reads compressed request bodies and compressed upstream answers, keeps a queued request's connection alive, lets routing rules match the time of day, and tells, for every turn of a session, how full the model's context window was and what filled it.
Upgrade notes
- The control-plane protocol version (
CONTROL_API_VERSION) goes from 44 to 45. ThinkWatch Lite connects only to a core with the same protocol version: ThinkWatch Lite 2026.10.12 includes 0.68.0 (protocol 44) and does not connect to 0.69.0. A server used with it stays on 0.68.0 until the app is updated to a release that includes 0.69.0;sudo twcore upgrade --version 0.68.0 --restartswitches a server back. - The request store's schema is unchanged; nothing is cleared.
- Configuration: a routing rule's
whentakes a new optionaltimecondition (see below). Existing configurations load unchanged. - New message codes:
gw.request.unsupported_encoding,gw.request.bad_encoding,gw.request.decoded_body_over_limit,engine.rule_time_syntax,engine.time_syntax. - Changed in the protocol:
ConditionFieldgainstime.TurnViewgainscontext_window.- New endpoint
GET /sessions/{session_id}/turns/{turn_id}/context(→TurnContext).
Reasoning across API formats. When the gateway converts between formats, a model's reasoning now survives the round trip in the cases that used to lose it:
- A Chat-format upstream's
reasoning_content(DeepSeek, Kimi, GLM and others) is carried to the client in its own format and sent back to that upstream on the next turn, so a conversation with tool calls keeps the reasoning the model requires. Reasoning from a Chat upstream is never sent to an upstream of another vendor, andreasoning_contentis written only for reasoning that came from a Chat upstream, since OpenAI's own API refuses unknown fields. Consecutive assistant items from a Responses client (reasoning, text, tool calls) are folded into one Chat message. - A Gemini model's
thoughtSignatureon afunctionCallpart is read from upstream answers and from client history, carried through Anthropic and Responses clients, and written back on thefunctionCallpart when the next turn reaches Gemini; the skip placeholder is used only when there is no real signature. When a Gemini client receives a streamed answer, a function call carries its signature and a thought part carries its text and signature together. - The cross-format test matrix now covers reasoning for every client and upstream pair.
Tool calls written into the answer text. Some Chat-format models write a tool call into their text instead of the structured tool_calls field. When the request defined tools and the answer has no structured call, the gateway recognises <tool_call>{…}</tool_call>, DeepSeek's own tool-call block markers, GLM's <arg_key>/<arg_value> form and <function=NAME><parameter name="k"> blocks, truncated endings included, and turns each into an ordinary tool call with a generated id. Only names the request defined are converted; bare JSON in prose is never touched. Streaming answers buffer from the opening marker to its close; text before the marker streams as usual. Recovered calls go through tool-call inspection like any other. A Chat client talking to a Chat upstream in the same format is passed through unchanged. A program that embeds the dialect crate and only forwards a stream can keep such text as text with Session::keep_text_calls, so a copy it records matches what its client received.
Compressed bodies. A request body sent with Content-Encoding: gzip, deflate or zstd is decoded on every route before it is read; the size limit applies to the decoded size (gw.request.decoded_body_over_limit), an encoding the gateway does not decode is refused with gw.request.unsupported_encoding, and a body that fails to decode with gw.request.bad_encoding. A compressed upstream answer, whole or streamed, reaches the client decoded. Traffic figures still count the bytes as they crossed the network.
Keepalive while queued. A streamed request waiting for its key's concurrency slot or for room in its rate window now receives the stream's 200 and keepalive comments after 15 seconds, as a request waiting on an upstream already did; a limit that then refuses the request ends the stream with the same error the plain refusal would carry. The no-response timeout still starts only when the upstream request is sent.
Routing by time of day. A rule's when can hold time, one or more windows in the core machine's local time, written as [days ]HH:MM-HH:MM: days are mon…sun or ranges such as mon-fri and fri-mon, omitted for every day; the start is inclusive, the end exclusive, 24:00 allowed; an end earlier than the start is an overnight window counted on the day it starts. A malformed value is refused when the configuration loads and when a rule is saved (engine.rule_time_syntax). The dry run evaluates it at the current time and names the time it saw (fri 17:30) when the condition did not match.
Context window per turn. Every turn of a session reports the context window of the model that answered it, from the model specs known now. GET /sessions/{session_id}/turns/{turn_id}/context estimates, over the stored request body, how many tokens the system prompt, the tool definitions, the earlier history and the last user message took, with the measured input and cache-read tokens beside them, using the same estimator the gateway uses to count tokens locally. When the body is no longer stored, the answer says so instead of estimating.
Downloads
| Platform | Binary | Archive for server installation |
|---|---|---|
| Linux, x86_64 | twcore-x86_64-unknown-linux-gnu |
twcore-x86_64-unknown-linux-gnu.tar.gz |
| Linux, aarch64 | twcore-aarch64-unknown-linux-gnu |
twcore-aarch64-unknown-linux-gnu.tar.gz |
| macOS, Apple silicon | twcore-aarch64-apple-darwin |
— |
| Windows, x64 | twcore-x86_64-pc-windows-msvc.exe |
— |
| Windows, ARM64 | twcore-aarch64-pc-windows-msvc.exe |
— |
Each file is published with a .sha256 file beside it. A Linux archive contains twcore, the systemd unit twcore.service and LICENSE. ThinkWatch Lite includes its own copy of twcore; the files here are for running core separately, such as on a server.
Server installation
On Linux (x86_64 or aarch64), the install script sets up twcore as a systemd service. This installs 0.69.0:
curl -fsSL https://raw.githubusercontent.com/ThinkWatchProject/ThinkWatch-Core/main/scripts/install.sh | sudo sh -s -- --version 0.69.0An installation made with the script switches to 0.69.0 with:
sudo twcore upgrade --version 0.69.0 --restartConfiguration, the remote control port and connecting ThinkWatch Lite are described in docs/server.md.
Verifying a download
A .sha256 file holds the SHA-256 of the file followed by its name. With both files in the current directory, on Linux:
sha256sum -c twcore-x86_64-unknown-linux-gnu.tar.gz.sha256On macOS:
shasum -a 256 -c twcore-aarch64-apple-darwin.sha256On Windows, in PowerShell, the following prints True when the binary matches:
(Get-FileHash .\twcore-x86_64-pc-windows-msvc.exe).Hash -eq (Get-Content .\twcore-x86_64-pc-windows-msvc.exe.sha256).Split()[0]The install script and twcore upgrade check the SHA-256 themselves.
What's Changed
- Recognise tool calls a Chat upstream writes into its answer text by @fylorn in #313
- Keep a streamed request alive while it waits for admission by @fylorn in #317
- Keep Gemini thought signatures that sit on functionCall parts by @fylorn in #315
- Decode compressed request bodies and upstream responses by @fylorn in #319
- Per-turn context window and what fills it by @fylorn in #318
- feat(dialect): round-trip Chat reasoning_content across dialects by @fylorn in #316
- Routing rules can match the local time of day by @fylorn in #314
- v0.69.0 by @fylorn in #320
Full Changelog: v0.68.0...v0.69.0