tau_coding_agent — the command and the TUI¶
Cognition × Application
The tau command, and the Textual interface it can launch. This is the only
package that knows what a terminal is.
Distribution ffwf-tau-coding-agent. Its one hard dependency is
ffwf-tau-agent-core; textual and rich are the [tui] extra.
One command, four destinations¶
[tui] split is real rather than aspirational. The Textual app is imported inside _launch_tui and nowhere else, so tau -p and tau --mode rpc run a full turn — extensions, tools and all — in an environment where textual is not installed. Move that import to the top of the file and the headless install stops working, silently, on someone else's machine.| Mode | Entry point | Shape |
|---|---|---|
| Interactive | tau |
Textual TUI in a terminal. |
| Headless print | tau -p "..." |
One turn, prints a transcript, exits. |
| Headless JSON | tau -p --mode json "..." |
One turn, JSONL lifecycle events — the machine-readable equivalent of the TUI's stream. |
| RPC subprocess | tau --mode rpc |
A persistent JSON-RPC 2.0 server over stdio. See RPC. |
tau -p and tau --mode rpc write and resume real sessions. A headless
run shows up in the TUI's picker and can be continued interactively. There is
no separate headless session format.
CLI flags¶
tau --help is the authoritative contract. This table is a snapshot, and a
hand-maintained snapshot will drift — an earlier version of it carried three
outright wrong claims. Treat a disagreement as this table being stale.
| Flag | Short | Notes |
|---|---|---|
--print |
-p |
run one turn headlessly, print, exit |
--mode {text,json,rpc} |
headless output format; rpc does not combine with --print |
|
--model |
-m |
config key, or provider/id shorthand |
--provider |
long-only, matching pi | |
--tools LIST |
-t |
allowlist |
--no-tools |
-nt |
offer the model zero tools, built-in and extension-registered alike. Extensions still load: hooks, commands, injections and subscriptions are untouched |
--exclude-tools LIST |
-xt |
denylist, built-ins only |
--no-builtin-tools |
-nbt |
drop the built-in set; extension-registered tools survive and are still offered |
--extension PATH |
-e |
repeatable, explicit load. Runs even under --no-extensions |
--no-extensions |
-ne |
disable discovery only |
--bus |
declare this run may reach a message bus, so TOUCHES_BUS extensions may load |
|
--ext-config NAME.KEY=VALUE |
repeatable per-extension override; the CLI wins over config.json |
|
--ui-defaults METHOD=ANSWER,... |
headless dialog auto-answers. --print only; without it a headless dialog raises |
|
--system-prompt / --append-system-prompt |
the latter is repeatable | |
--no-context-files |
-nc |
turn off AGENTS.md / CLAUDE.md discovery |
--continue |
-c |
continue the most recent session; use with --print |
--resume |
-r |
open the session picker at TUI startup. Raises under --print, which has no screen to open one on |
--session REF |
resume a specific session by path or filename stem | |
--fork REF |
fork a session into a new one | |
--name |
-n |
session display title |
--no-session |
ephemeral, nothing persisted | |
--thinking {off,minimal,low,medium,high,xhigh} |
requires a reasoning-capable model | |
--store {file,jmfts} |
session backend for this run | |
--session-dir DIR |
file store only. The default differs by mode — ~/.tau/sessions, or a private temp directory under --mode rpc |
|
--import-session PATH / --export-session REF PATH |
JMFTS store transfer, then exit | |
--fun / --no-fun |
pick the startup tagline at random rather than always the same one | |
--verbose |
long-only | |
--help / --version |
-h / -v |
-v is --version, not verbose |
--continue, --resume, --session and --fork are mutually exclusive.
Tool flags are run-level policy, not a per-model override. They ride in
the run config rather than being written into one model entry, because a
mid-session /model switch to a different entry used to hand the tools back —
the denied set under -nt, the un-allowlisted set under -t.
Sessions from the interface¶
Two things that sound alike and are not:
| Picks | Opened by | |
|---|---|---|
SessionPickerModal |
which session to open | --resume, /resume, the command palette |
SessionTreeModal |
a point inside the current conversation | Ctrl+G |
The picker filters fuzzily over name and first and last message, Tab widens
the scope from this directory to all, and a path is elided at the front —
/home/john/Devel… says only that it is under Development, which every row
already said, while the last component names the project.
--resume, /resume and the palette entry are one handler with three
bindings, not three implementations. /resume <ref> names a session directly,
using the same path / id / unique-prefix grammar as --session.
SessionTreeModal browses message, compaction, branch and navigate nodes with
a marker on the active leaf, and hands the chosen node to a follow-up action:
navigate, summarise a branch, summarise with custom instructions, or elide a
span. Fork is not there — /fork is a separate command dispatched through
AgentSession.submit().
The sidebar mounts closed. Ctrl+B opens it and the choice sticks.
What the TUI is actually made of¶
Worth stating because an early design sketched a widgets/ directory of ten
small files, and none of that layout exists.
| Where | What |
|---|---|
chat_widgets.py |
MarkdownLineFormatter, ReasoningRegion, ToolBox, ExchangeBox |
app.py |
the Parley app plus its widgets and modals — ChatSidebar, MessageList, MessageBox, ExtensionStatusBar, LaneStrip, ExtensionPanel, SessionTreeModal and the extension dialogs |
session_picker.py |
SessionPickerModal, which needs only a SessionCatalog and a cwd |
parley.tcss |
every colour in the interface |
Input is a plain TextArea with history navigation and Ctrl+Enter to
submit. There is no @ file reference, no !command bash escape and no tab
completion for paths — none of those were built.
Status is the window header's subtitle: the model name plus one aggregate label. There is no context-window percentage and no session-name indicator anywhere.
The palette is Textual's own, listing τ's commands from get_system_commands.
On theming¶
The palette is Catppuccin Mocha, as real hardcoded values in parley.tcss.
It is not a swappable theme system — there is no themes/ directory and
no second theme in the source. Because every colour lives in the one
stylesheet rather than in per-widget code, changing it is a file swap; that is
a property of where the colours are kept, not a feature that exists yet.
Streaming is throttled to 30 Hz, carried over from Parley and still the right answer for not thrashing a terminal on token-by-token deltas.
Configuration¶
~/.tau/config.json selects the default model and holds per-extension config.
A model entry names its vendor with backend and, optionally, its wire
protocol with api:
{
"default_model": "sonnet",
"models": {
"sonnet": { "model": "claude-sonnet-5", "backend": "anthropic" },
"local-llm": { "model": "…", "backend": "local",
"base_url": "http://127.0.0.1:8080/v1" }
}
}
Wire resolution is: a stated api wins, then the registered vendor's own
protocol, then the historical openai-completions default. So an entry that
worked before τ had more than one protocol builds the same model now.
A stated api τ does not implement raises against the registry rather
than falling through to the OpenAI wire.
See the DevOps Manual for credentials and
the vendor table, and tau_llm for what each field means.