Non-interactive Zig reimplementation of pi.
Built for AI agents, not humans.
Think: "pi, but JSON-first, non-interactive only, and way faster"
Single-shot AI CLI with tool-calling, goal mode, and sessions — designed for agents.
# Single-shot chat (JSON by default)
tau "List the files in src/"
# Tool-calling loop (agents use this)
tau --tools bash,read "Use tools to analyze the codebase"
# Goal mode: autonomous work until complete
tau --session myproject "/goal add a --version flag and verify it builds"
# Check goal status
tau --session myproject "/goal status"👉 JSON output by default — add --mode text for human-readable output
👉 Semantic exit codes — scripts and agents handle failures deterministically
👉 Sessions + goal mode — persistent state across invocations
Zero to your first response in five steps. Every command below is copy-pasteable.
- Zig 0.16.0 — the exact version tau is built against
curlon yourPATH— tau shells out to it for HTTPgit— to clone the repo
zig version # should print 0.16.0
curl --version | head -1git clone https://github.com/javimosch/tau.git
cd tau
zig build # compiles to ./zig-out/bin/tau(Optional) put tau on your PATH so you can call it as tau instead of ./zig-out/bin/tau:
export PATH="$PWD/zig-out/bin:$PATH"tau ships with a built-in key for its default xiaomi provider, so the first run works out of the box. To use your own key or a different provider, export the matching environment variable:
# Default provider (xiaomi)
export TAU_API_KEY="your-key-here"
# …or pick another provider's key
export OPENAI_API_KEY="sk-..." # use with --model openai/gpt-4o-mini
export DEEPSEEK_API_KEY="..." # use with --model deepseek/deepseek-chatKey resolution order: --api-key flag → provider env var → TAU_API_KEY → built-in key. See Configuration for the full provider table and config file.
# Human-readable answer
./zig-out/bin/tau --mode text "Say hello in one short sentence."
# Same call, default JSON envelope (what agents consume)
./zig-out/bin/tau "Say hello in one short sentence."You should see a one-line greeting (text mode) or a {"version":...,"content":"...","done":true} envelope (JSON mode). The process exits 0 on success — see Exit Codes for the rest.
Let the model use tools to inspect this very repo:
./zig-out/bin/tau --mode text --tools bash,read,ls "List the files in src/ and summarize what tau does."That's it — you're running tau. Next steps:
- Verify your build:
zig build test(unit tests) and./scripts/smoke.sh(offline smoke tests) - Go autonomous:
tau --session demo "/goal add a --version flag and verify it builds" - Keep reading: CLI Usage Examples · Configuration · Troubleshooting
AI agents need deterministic, predictable interfaces:
- Interactive CLIs → agents can't use TUIs or prompts
- Inconsistent output → parsing text responses is fragile
- No state persistence → every invocation starts from scratch
- No autonomous workflows → agents need humans to drive each step
Without tau, agents struggle to:
- Execute single-shot commands reliably
- Use tools in a loop with proper feedback
- Maintain context across multiple invocations
- Work autonomously toward complex objectives
tau gives agents what they need:
- Non-interactive only — no TUI, no prompts, no hidden retries
- JSON by default — deterministic structured output
- Tool-calling loop — agents can use bash, read, write, edit, ls, grep, find
- Goal mode — autonomous work until objective is complete
- Structured output —
--schemaconstrains model to JSON Schema - Agent context —
--scan-agents,--load-agents-md,--auto-agents-mdfor AGENTS.md awareness - Skills autodiscovery —
tau skills list|search|loadfor 113+ skills from~/.agents/skills/ - Sessions — persistent conversation + goal state
- Semantic exit codes —
0success,80invalid arg,82missing field,105timeout,106auth failed,110internal
With tau:
- Execute single-shot commands with
tau "prompt"— JSON output by default - Use tools with
--tools <csv>— model calls tools, results fed back automatically - Set goals with
/goal <objective>— agent works autonomously until complete - Persist state with
--session <name>— resume conversations and goals later - Compact context automatically — LLM summarization when history grows too large
# Build
zig build
# Binary: zig-out/bin/tau
# Single-shot chat (JSON by default)
./zig-out/bin/tau "What is Zig?"
# Tool-calling (model uses tools automatically)
./zig-out/bin/tau --tools bash,read "Analyze the project structure"
# Goal mode: autonomous work
./zig-out/bin/tau --session work "/goal refactor the config module"
# Check goal status
./zig-out/bin/tau --session work "/goal status"
# Human-readable output
./zig-out/bin/tau --mode text "Explain this error"
# Streaming (text mode only, no tools)
./zig-out/bin/tau --stream "Count to 10"| Instead of... | You do... |
|---|---|
| Interactive pi sessions | tau "prompt" — single-shot, no TUI |
| Parsing text responses | JSON by default, --mode text for readable |
| Manual tool execution | --tools <csv> — model calls tools automatically |
| Starting from scratch every time | --session <name> — persistent conversation |
What this means day-to-day:
- No interactive prompts — just pass the prompt and get JSON back
- No output parsing — structured JSON every time
- No manual tool loops — the agent handles tool calling automatically
- No context loss — sessions persist conversation and goal state
💡 Important: tau is JSON-first by default. Add
--mode textfor human-readable output.
- 🔍 Deterministic — JSON output by default, semantic exit codes, no hidden behavior
- 🛠️ Tool-calling — Built-in tools: bash, read, write, edit, ls, grep, find
- 🎯 Goal mode —
/goal <objective>→ autonomous work until complete - 💾 Sessions —
--session <name>→ persistent conversation + goal state - 🧠 Context compaction — Auto-summarization when history exceeds threshold
- ⚔️ Author↔Critic loop —
--role author|critic|coordinator|nonefor adversarial self-review - 🚁 Fleet orchestration —
tau fleet run|status|list|logs|cancelfor multi-agent coordination - 🚨 Predictable errors — Standard exit codes:
80invalid arg,82missing field,105timeout,106auth failed,110internal - 📡 Streaming — Real SSE token-by-token streaming (chat mode only)
# Agent workflow: single-shot → tools → goal → session → author/critic → fleet
tau "prompt" # JSON response
tau --tools bash,read "task" # Tool-calling loop
tau --session s "/goal obj" # Goal mode with persistence
tau --session s "/goal status" # Check progress
tau --role critic "review X" # Adversarial review (read-only)
tau fleet run --goal "ship X" # Multi-agent dispatchtau gives agents a deterministic, non-interactive AI CLI:
- 🎯 Single-shot only — No TUI, no prompts, no interactive mode
- 📦 JSON by default — Structured output for scripts and agents
- 🛠️ Tool-calling loop — 7 built-in tools with automatic execution
- 🎯 Goal mode — Autonomous work until objective is complete
- 💾 Sessions — Persistent conversation + goal state across invocations
- 🧠 Context compaction — LLM summarization at configurable threshold
- 🚨 Semantic exit codes — Deterministic error handling
- 📡 Streaming — Real SSE token-by-token output
- ⚔️ Author↔Critic loop — Adversarial self-review (author + critic roles)
- 🚁 Fleet orchestration — Multi-agent work breakdown and dispatch
# Single-shot chat (JSON by default)
tau "What is Zig?"
tau --model openai/gpt-4o-mini "Explain this error"
# Tool-calling
tau --tools bash "Run: echo hello"
tau --tools read,write "Read file.txt and modify it"
tau --tools ls,grep "List src/ and search for TODO"
# Goal mode
tau --session project "/goal add tests for the config module"
tau --session project "/goal status"
tau --session project "/goal pause"
tau --session project "/goal resume"
# Sessions
tau --session work1 "Remember: the build uses zig 0.16"
tau --session work1 "What zig version?"
# Author↔Critic loop
tau --role author --tools bash,write "Implement feature X"
tau --role critic --tools read,grep "Review the implementation"
# Fleet orchestration
tau fleet run --goal "add OAuth and write tests"
tau fleet status <id>
tau fleet list
tau fleet logs <id>
tau fleet cancel <id>
# Human-readable output
tau --mode text "Explain this in simple terms"
# Streaming (chat mode only)
tau --stream "Count to 10 slowly"tau is a non-interactive, agent-first reimplementation of pi in Zig:
- Single-shot only — No interactive TUI, no REPL, no prompts
- JSON-first — Default output mode for machine consumption
- Tool-calling loop — Model calls tools, results fed back until final answer
- Goal mode — Autonomous work with
/goal <objective>syntax - Sessions — Persistent state in
~/.config/tau/sessions/<name>.json - Context compaction — LLM summarization at configurable threshold
Built-in tools (all JSON-schema'd for the model):
| Tool | Purpose |
|---|---|
bash |
Execute shell commands |
read |
Read file contents |
write |
Write files |
edit |
Edit files (old_string → new_string) |
ls |
List directory contents |
grep |
Search files with regex |
find |
Find files by pattern |
Tools are allowlisted/denylisted via --tools <csv> / --exclude-tools <csv> / --no-tools.
Goal mode enables autonomous work:
tau --session myproject "/goal add a --version flag and verify it builds"- Model works in a tool-calling loop until it outputs
<GOAL_MET> - Bounded by
--goal-max-iterations(default: 50) - Goal state persists in sessions
- Subcommands:
/goal status,/goal pause,/goal resume,/goal clear,/goal complete
Sessions persist conversation + goal state:
tau --session work1 "Remember: the build uses zig 0.16"
tau --session work1 "What zig version?" # Recalls from history- Stored in
~/.config/tau/sessions/<name>.json - Includes full message history + goal state
- Supports goal mode across invocations
Auto-compact when history grows too large:
- Trigger: estimated tokens >
--compact-threshold(default: 0.5) of context window - Action: LLM summarizes older history, keeps recent tail verbatim
- Configurable via
--compact-threshold,--compact-keep-recent,--no-compact
Adversarial self-review via two complementary roles. Each turn runs the same agentic tool loop with a role-specific system directive and exit sentinel:
# Author: write/update code+tests, declare READY when done
tau --role author --tools bash,read,write,edit,ls,grep,find --session proj-1 \
"Build feature X and run the test suite."
# Critic: read-only audit, emit APPROVED or BLOCKED with concrete defects
tau --role critic --tools read,grep,find,ls --session proj-1 \
"Audit the spec against the code in src/."
# Combine by chaining two sessions in your harness:
# author -> (loop until <READY_FOR_REVIEW>) -> critic -> (loop until <APPROVED>) -> doneSentinels (one token per line, on its own):
| Role | Sentinel | Meaning |
|---|---|---|
| author | <READY_FOR_REVIEW> |
Work complete, request critic review |
| critic | <APPROVED> |
Spec satisfied — done |
| critic | <BLOCKED> |
Defects found; describe concretely for the next author pass |
The Author↔Critic primitive lives in src/loop.zig as AuthorCriticSpec + runAuthorCritic; it composes two agent.run() calls per iteration with different cfg.role and tool allowlists. See .agents/skills/tau-maintenance/SKILL.md for the full contract.
A fleet is a goal + a work breakdown (set of work items) + a controller. A single coordinator LLM turn decomposes the goal into items with depends_on and acceptance; the controller then dispatches one tau worker per item (currently sequential, re-invoking tau --role author per item).
# Plan + dispatch a fleet (coordinator produces the work breakdown)
tau fleet run --goal "add OAuth login, persist sessions, and write tests"
# Pre-supply work items with dependencies (skip coordinator LLM call)
tau fleet run --items '{"items":[{"id":"a","title":"...","scope":"...","deliverables":"...","acceptance":"...","depends_on":[]}]}' --goal "custom plan"
# Plan with a model override
tau fleet run --coordinator-model openai/gpt-4o-mini --goal "ship the redesign"
# Inspect
tau fleet list # list active fleets
tau fleet status <id> # full manifest (spec + per-item status)
tau fleet logs <id> # per-worker session hint
tau fleet cancel <id> # mark cancelledManifests persist to ~/.config/tau/fleets/<id>.json; workers persist per-role sessions to ~/.config/tau/sessions/<fleet-id>-<item-id>.json. The coordinator retries up to 3 times when all items fail to parse. Items with failed dependencies are marked .blocked and counted as failures in the final tally.
Worker session naming: each worker is spawned as tau --role author --session <fleet-id>-<item-id>, so its session file is ~/.config/tau/sessions/<fleet-id>-<item-id>.json. Use tau fleet logs <id> to see the per-worker session names, then inspect them with tau --session <fleet-id>-<item-id> "/goal status".
v0.4 known issues (see .agents/skills/tau-maintenance/SKILL.md for the full gap list):
- Workers run sequentially and report
status: runninguntil the harness collects their results. cancelis in-memory for v0 (does not yet persistglobal_status: cancelled).topoSortcycles surface as{"code":110,"message":"toposort failed: Cycle"}.
| Mode | Command | Output |
|---|---|---|
| json (default) | tau "prompt" |
{"version","model","content","done":true} |
| text | tau --mode text "prompt" |
Plain text on stdout |
| stream | tau --stream "prompt" |
SSE token-by-token (chat mode only) |
Streaming output:
- Text mode: raw token deltas
- JSON mode: NDJSON
{"chunk":..,"done":false}→ final{"model":..,"done":true}
| Code | Meaning |
|---|---|
0 |
Success |
80 |
Invalid argument |
82 |
Missing required field |
105 |
Connection timeout |
106 |
Auth failed |
110 |
Internal error |
111 |
Unimplemented |
Optional config at ~/.config/tau/config.json:
{
"provider": "openai",
"model": "gpt-4o-mini",
"mode": "json",
"stream": true,
"auto_compact": true,
"compact_threshold": 0.5
}CLI flags override config file values.
| Provider | Endpoint | Env var(s) | Default model |
|---|---|---|---|
xiaomi (default) |
token-plan-ams.xiaomimimo.com/v1/chat/completions |
TAU_API_KEY, XIAOMI_API_KEY |
mimo-v2.5 |
openai |
api.openai.com/v1/chat/completions |
OPENAI_API_KEY |
gpt-4o-mini |
deepseek |
api.deepseek.com/v1/chat/completions |
DEEPSEEK_API_KEY |
deepseek-chat |
opencode-go |
opencode.ai/zen/go/v1/chat/completions |
OPENCODE_API_KEY |
deepseek-v4-flash |
Shorthand: --model openai/gpt-4o-mini resolves provider + model in one flag.
Key resolution: --api-key → provider env var → TAU_API_KEY → provider builtin key.
# Build
zig build
# Binary: zig-out/bin/tau
# Run tests
zig build test
# Smoke tests (offline)
./scripts/smoke.sh
# Smoke tests (with network, requires API key)
./scripts/smoke.sh --netRequires Zig 0.16.0 and curl on PATH.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Empty response with no args | Expected behavior (shows help) | Use tau --help or provide a prompt |
| Auth failed (exit 106) | No API key | Set TAU_API_KEY or provider-specific env var |
| Timeout (exit 105) | Request too slow | Increase --timeout-ms (default: 120000) |
| Tool not found | Tool name mismatch | Check tool name in tool_calls |
| Layer | Technology |
|---|---|
| Runtime | Zig 0.16.0 |
| HTTP | curl via std.process.run |
| JSON | Custom escape/unescape + field extraction |
| Tools | Built-in: bash, read, write, edit, ls, grep, find |
| Storage | ~/.config/tau/config.json, ~/.config/tau/sessions/ |
| Output | JSON by default, text mode available |
| Exit codes | Semantic (Square-style) |
tau is designed to be lightweight — Zig's zero-overhead and compiled binary nature result in minimal resource usage compared to Node.js-based agents.
| Operation | Max RSS | User CPU | Sys CPU | Wall Time |
|---|---|---|---|---|
| Single-shot chat | ~13.6 MB | 0.04s | 0.00s | 2.0s |
| Tool-calling (bash) | ~13.8 MB | 0.09s | 0.01s | 3.8s |
| Session create | ~13.6 MB | 0.05s | 0.00s | 1.7s |
| Session recall | ~13.8 MB | 0.06s | 0.01s | 1.9s |
| Startup (--help) | ~5.4 MB | 0.00s | 0.00s | <0.01s |
Measured with /usr/bin/time on x86_64 Linux, xiaomi mimo-v2.5 model. Wall time includes network latency.
| CLI | Runtime | Typical RSS | Notes |
|---|---|---|---|
| tau | Zig (compiled) | ~13 MB | Zero-overhead, single binary |
| pi | Python | ~50-100 MB | Python runtime overhead |
| opencode | Node.js | ~100-200 MB | V8 + TypeScript runtime |
| devin | Node.js | ~100-200 MB | Electron/V8 overhead |
Why tau is lighter:
- No runtime VM — Zig compiles to native machine code
- Single binary — No node_modules, no virtualenv, no runtime dependencies
- Minimal memory footprint — ~13 MB vs 50-200 MB for interpreted runtimes
- Fast startup — ~4.6 MB RSS at idle, instant CLI help
Run the resource benchmark script:
./scripts/benchmark-resources.shOutputs CSV with max RSS (KB), user CPU time, system CPU time, and wall time for each operation.
Run the resource benchmark script:
./scripts/benchmark-resources.shOutputs CSV with max RSS (KB), user CPU time, system CPU time, and wall time for each operation.
| Capability | State |
|---|---|
| CLI argument parsing | ✅ done |
| Provider abstraction (xiaomi / openai / deepseek) | ✅ done |
Output modes text / json |
✅ done |
Streaming (--stream, real SSE token-by-token) |
✅ done |
@file inclusion, system prompt |
✅ done |
| Proper JSON request escaping + response decoding | ✅ done |
| Semantic exit codes | ✅ done |
| Built-in tools (read/write/edit/bash/ls/grep/find) | ✅ done |
| Tool-calling loop (schemas sent + tool execution) | ✅ done |
Config file (~/.config/tau/config.json) |
✅ done |
Session persistence (--session <name>) |
✅ done |
Goal mode (/goal + status/pause/resume/clear/complete, --tokens N soft budget) |
✅ done |
| Auto context compaction (LLM summarization at threshold) | ✅ done |
Author↔Critic loop (--role author|critic|coordinator, <READY_FOR_REVIEW> / <APPROVED> / <BLOCKED> sentinels) |
✅ done |
Fleet orchestration (tau fleet run|status|list|logs|cancel, manifests at ~/.config/tau/fleets/) |
✅ v0 |
ACP server (tau acp start|stop|status|serve, JSON-RPC over stdio) |
✅ done |
MIT — Javier Leandro Arancibia