Development setup¶
The repository is a uv workspace of independently publishable Python packages plus, later, TypeScript parts managed with pnpm. This page gets you to a green test run and explains the checks CI enforces.
Requirements¶
- Python 3.12 (uv installs it if needed)
- uv
- git
Clone and sync¶
--all-packages installs every workspace member in editable mode: tau-core, tau-tui and the three test-fixture distributions under packages/tau-core/tests/fixtures/ (tau-example-tool, tau-example-provider, tau-example-service). --group dev adds pytest, pytest-asyncio, pytest-textual-snapshot and ruff. A separate docs group holds MkDocs Material and mkdocs-static-i18n.
Never pip install into the system Python; everything runs through uv run.
Layout¶
packages/
├── tau-core/ build_agent, persona, tiers, roles, sessions, the tau CLI, tau hub
│ ├── src/tau_core/
│ └── tests/ pytest; fixtures/ holds the three example plugin distributions
└── tau-tui/ the Textual interface
├── src/tau_tui/
└── tests/ pilot tests and SVG snapshots
persona/ persona.md (tracked), user.example.md (tracked), user.md (gitignored)
infra/remote/ Tailscale + OpenSSH + tmux/mosh setup scripts
infra/services/ rendered launchd plist and systemd unit examples
roadmap/ the issue tracker: one markdown file per issue, board.py
docs/ this site, ARCHITECTURE.md, the ADRs
tau.toml · .env.example the repository's own configuration root
Every directory under packages/ is a PyPI distribution named tau-<name> with the flat import name tau_<name>. There is no tau namespace package. Packages depend on each other only through public APIs and declared dependencies: no path hacks, no private imports.
Run tau from the checkout¶
The repository root holds a tau.toml, so it is a configuration root:
Credentials go into .env at the root (gitignored; names in .env.example). Sessions and logs go to ~/.local/share/tau, never into the checkout.
Tests¶
uv run pytest -q # everything, offline
uv run pytest packages/tau-core -q
uv run pytest packages/tau-tui -q # pilot + snapshot tests
uv run pytest -q -k approval # a subset
The suite needs no credentials and no network. tau_core.testing provides the pieces:
| Helper | Use |
|---|---|
FakeModel([...]) |
a scripted Strands model: text replies, tool calls (ToolCall), truncation (Truncated) |
ScriptedApprover([Decision.APPROVED, ...]) |
scripted approval decisions; tier 2 is still never auto-approved |
UnansweredApprover |
never answers, for timeout tests |
test_config(tmp_path, **overrides) |
a throwaway TAU_HOME and TAU_DATA_DIR under a temporary directory, so tests never read your real files |
record_events(bus, *kinds) |
collect agent events from an EventBus |
Rules that tests enforce (and that you must not weaken):
- tier-2 calls always go through an approver; no test auto-approves;
- no model id appears under
packages/*/src(test_repo_rules.py); - the tracked persona files hold no email, address, tailnet name or home path;
- the shipped templates equal the repository's
tau.toml,persona/persona.mdandpersona/user.example.md; - the rendered service files in
infra/services/equal the package templates.
TUI snapshots¶
The TUI has fourteen SVG snapshots under packages/tau-tui/tests/__snapshots__/test_snapshots/ as .raw files (pytest-textual-snapshot 1.0 with syrupy 6). Every snapshot uses the fixed session id 20260929-120000-abcd (the setup screen, which has no session, is checked for paths only), the pinned version 0.0.0 on the welcome card (build_app(version=...), so a release never changes a snapshot), a fixed clock, UTC event times, no motion and no durations, and is taken after the streams stopped; the settings snapshot runs with show_paths=False so no directory of the test machine appears.
Review a regenerated file before committing it; it must never contain a local path, which the test also checks. Behaviour tests read the widgets through text oracles (Transcript.text, EventLog.text, HintBar.text, …) so that behaviour changes do not churn the snapshots.
Lint and format¶
Ruff runs with line-length = 100, target py312, rules E, F, I, UP, B. docs/ and roadmap/ are excluded (the board and the brand renderer predate the workspace).
Build a wheel¶
Each package builds on its own from its pyproject.toml (hatchling). Publishing is described in Releasing.
CI¶
.github/workflows/ci.yml runs on every push to main and on pull requests, on Ubuntu with Python 3.12:
uv sync --all-packages --group dev
uv run ruff check .
uv run ruff format --check .
uv run pytest -q
uv build --package tau-core
uv build --package tau-tui
Make the same six commands pass locally before you push.
The docs site¶
uv sync --group docs
uv run --group docs mkdocs serve # http://127.0.0.1:8000
uv run --group docs mkdocs build --strict # what the deploy runs; no warnings allowed
Pages are Markdown under docs/, the navigation is in mkdocs.yml. --strict fails on a missing page, a broken relative link or a bad snippet path. Links to files outside docs/ (the roadmap, the packages) must be absolute GitHub URLs.
English pages and their Turkish twins¶
The site is bilingual through mkdocs-static-i18n with the suffix layout. page.md is English and the source; page.tr.md next to it is its Turkish twin, served under /tr/. A page without a twin is served in English under /tr/ as well (fallback_to_default), so ADRs, reference pages and anything not translated yet still work there, and a link from a Turkish page to such a page lands on the English one.
- Twins exist for the home page,
getting-started/,guides/, the threat model and three references (CLI,tau.toml, slash commands). ADRs, issues and the other reference pages stay English only. - Change an English page and its twin together. If you cannot translate the change, put
<!-- tr: out of date -->at the top of the twin so the drift is visible until someone updates it. - A twin starts with
<!-- tr twin of <page>.md, translated YYYY-MM-DD -->; update the date when you bring it level with the English page. - Commands, code blocks, paths, config keys, environment variables, CLI output and URLs stay exactly as in the English page, and
<!-- verified: ... -->markers are copied unchanged. - A heading that another page links to by anchor keeps its English anchor in the twin, for example
## 2. Spine'ını deploy et { #2-deploy-your-spine }, so the same link works in both languages. - Nav labels are translated in
mkdocs.yml, under thei18nplugin'snav_translations. A new nav entry needs its Turkish label there, or it shows in English on the Turkish site. - Search indexes both languages (
lang: [en, tr]on thesearchplugin).
Conventions¶
- Language. Code, identifiers, comments, commit messages, issues and docs are English; the docs site adds Turkish twins of its guides (see above). tau's own persona is Turkish by default and mirrors the user; the UI chrome follows
[ui] language. - Commits. Conventional Commits:
type(scope): summary, scopes such ascore,tui,remote,roadmap,docs,cloud,taubar. Small, logical commits; reference the issue id in the body (Refs: 004). - Privacy. Nothing personal in tracked files: no hostnames, tailnet names, IPs, emails, account ids or key fingerprints. Use
my-mac.tailXXXXXX.ts.net,100.x.y.z,SHA256:.... Real values go into the gitignored.env, with the name and an example value in.env.example. - Models. Code asks for a role, never a model id; ids and prices are data (
tau.toml,templates/models.toml,templates/prices.toml). Providers are called directly and tau enforces the budgets itself (ADR 0011);litellmis never added in any form. - Vocabulary.
CONTEXT.mdis the glossary; use its terms exactly. Decisions are in ARCHITECTURE.md and the ADRs; a new decision gets a new ADR. - Issues. Work is tracked in
roadmap/; see Roadmap and issues.