v0.9.3
v0.9.3 - three vendors, context files that arrive, and --resume
0.9.2 asked what a stranger's first hour looks like. This release is the
answer, plus two things nobody planned: tau's own system prompt had never
reached a model, and every model was being served over the OpenAI wire
whatever it declared.
Vendors
- tau speaks three wire protocols now: openai-completions (unchanged, still
the default), anthropic-messages, and google-generative-ai. The two vendor
SDKs are optional extras — ffwf-tau-llm[anthropic] and ffwf-tau-llm[google]
— imported lazily on first request, so a plain install pulls neither. - tau dispatches on Model.api. It used to construct OpenAICompletionsProvider
unconditionally and read model.api not at all, so a model declaring a
protocol tau has never implemented was served over the completions wire in
silence. An unknown api now raises, naming what is registered. - Model.api, AssistantMessage.api and .provider widen to str. The last was
pinned to "openai", so a legal Model naming any other vendor raised a
validation error when the vendor was copied onto the message. - The Google vendor registers as
gemini, matching the backend value config
entries already carry. - Reasoning signatures are namespaced by vendor, and the OpenAI writer
REFUSES a foreign one rather than forwarding a token another vendor will
reject. A Gemini 3 function-call signature is replayed on every
reasoning_replay setting including "off" — it is protocol, not
chain-of-thought, and omitting it is a 400. - Three capability questions were settled by measurement against the live
API rather than by reading. Records are in docs/probe-results/.
The system prompt and project context files
- BEFORE THIS RELEASE NEITHER HAD EVER REACHED A MODEL on the TUI or
headless path. The backend passed a config key straight through and never
called the loader behind it. - Discovery now walks the agent dir, then every ancestor of cwd, root-most
first, one file per directory, first match among AGENTS.override.md,
AGENTS.md, AGENTS.MD, CLAUDE.md, CLAUDE.MD. CLAUDE.md was not a name tau
knew at all. A worktree nested in its own repo shadows the repo's copy. - Context files COMPOSE with system_prompt instead of being switched off by
it. --no-context-files / -nc turns discovery off. - A found-but-unreadable file raises, naming the path. Every block is
wrapped in <project_instructions path="..."> so a prompt cannot carry
instructions whose origin it does not state. - A real coding-agent prompt ships, and tau_default_config.json no longer
carries a system_prompt key.
Sessions
- --resume works. It had been rejected in both modes since it was added,
with help text saying "TUI only" and an error blaming headless mode. - A session picker, with fuzzy filter and a Tab that widens the scope from
this directory to all. --resume, /resume and the command palette are one
handler with three bindings. - The sidebar starts closed.
- The branchOf lane tag is gone. Three of its four consumers used it to ask
"does this belong to the conversation being looked at", which is ancestry
from the cursor — so a three-way fork returned three mutually exclusive
alternatives as one conversation.
Backends that are not quite OpenAI-shaped
- Model.stream (default true) — a gateway with no SSE was unreachable
through any config path. - Model.request_timeout — 300s read and 10s connect were fixed at client
construction with no override anywhere. - context_window and max_tokens are reachable; they were hardcoded at 128000
and 4096 for every model in existence. python -m tau_llm.catalogfills a config entry from models.dev. Nothing
is vendored; --base-url is required and never guessed.- Four failures that used to arrive unattributable: a tool call with no name
was built and executed (now raises, naming the call id, model and base
URL); an error could carry no content at all; a keepalive frame crashed
the turn; the timeout was unreachable.
SDK
- create_agent_session takes no_tools ("all" | "builtin" | None) and
no_context_files. Passing tools= and no_tools= together raises.
JMFTS store
- Stop using a character count as a proxy for a token window; ask the
server. 1800 characters of base64 is about 1350 tokens against a 512-token
limit, so dense content took the "short enough to embed whole" path and
was refused.
Packaging and release
- Python 3.11, 3.12, 3.13 and 3.14 are measured, not claimed.
- dist/ is gitignored.
- The missing-SDK error hint named tau-llm[google], which does not resolve —
tau imports as tau_llm and publishes as ffwf-tau-llm. Fixed, with a test
that reads the four distribution names out of the pyproject.toml files.
Upgrade notes
- SessionLog implementors: lane= is gone from the Protocol and every shipped
store. resolve_cursor is "last entry wins" again. One contract test is
inverted — a store must not reintroduce a cursor filter. - The default system prompt changed, and system_prompt is no longer in the
shipped config. - A CLAUDE.md or AGENTS.md above your working directory is read now, on
every run, all the way to /. -nc turns discovery off. - A gateway that omits function.name now raises on the first call instead of
burning up to max_turns. - A model config naming an api tau does not implement now raises.
- create_agent_session(tools=..., no_tools=...) now raises.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com