Skip to content

v0.9.3

Choose a tag to compare

@jmccardle jmccardle released this 22 Aug 21:24
· 1 commit to master since this release

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.catalog fills 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