0004 · Config roots, tau.toml as data, and persona privacy¶
- Status: accepted (issue 004; refined by issue 005)
- Date: 2026-09-29
Context¶
tau runs from a repository checkout today and from an installed package on the hub later. It needs a place for static configuration, a separate place for state it writes, a rule that model ids are data rather than code, and a way to keep facts about the user out of a repository that may become public.
Decision¶
- Two roots:
TAU_HOME: configuration (tau.toml,.env,persona/,tools/). Default: the current directory when it holds atau.toml(a checkout), else~/.tau.TAU_DATA_DIR: mutable state (sessions, hub state, logs). Default~/.local/share/tau. Never inside the repository.tau.tomlis data: role → provider + model id (+ optional fallback), the loop guard, the session backend, hub ports. The class that reads it isConfig(tau_core.config); the glossary reserves Settings for runtime settings stored in D1 (issue 019). Code asks for a role, never a model id; a test fails if a model id appears underpackages/*/src..envinTAU_HOMEis loaded with python-dotenv and never overrides variables that are already set;.env.examplelists every name with example values.- Persona:
persona/persona.md(tau's character, no personal facts) andpersona/user.example.md(a fake example) are tracked;persona/user.mdis gitignored and holds the real facts about the user. Without it the prompt says so and addresses the user as "siz".
Refinement (issue 005): the persona at runtime¶
- Fixed prompt layout:
persona.md, then## Kullanıcıwithuser.md(or the sentence saying there is none and that the address is "siz"), then## Şu anwith the dynamic context. The headings are Turkish because the model reads them andpersona.mdrefers to those sections by name. - Rebuilt before every model call, not once per turn: a
BeforeModelCallEventhook setsagent.system_prompt, which Strands re-reads on each call. A tool loop therefore sees a persona edit or a later time within the same turn. - Change detection by
stat, not a file watcher: each model call compares the(st_mtime_ns, st_size)stamp of both files and re-reads only on a change. Twostatcalls per model call cost nothing, need no thread or platform API (FSEvents on the Mac, inotify on the RPi5) and cannot miss an event. The trade-off, an edit that keeps both size and mtime, is accepted. - HTML comments are stripped before the text reaches the model. Files can carry notes for
humans; in particular the copy instructions in
user.example.mdwould otherwise tell the model, aftercptouser.md, that its facts are only an example. - Strict first, lenient later: a missing, empty or unreadable
persona.mdon the first read is aConfigError, andbuild_agentreads eagerly, so tau fails at startup. Later the last good version is kept (an editor truncating the file mid-save must not strip tau of its character for one call). A missing or emptyuser.mdis a legitimate state (the user may delete it) and falls back to "siz"; an unreadable one keeps the last good version. - Dynamic context: always Europe/Istanbul (a naive clock is Istanbul time; without tzdata
a fixed UTC+3 is used, since Turkey has had no DST since 2016). A value no layer provides
reads
bilinmiyorand an empty node list readsyok, so the model can tell "not wired yet" from "nothing there";persona.mdtells it never to guess an unknown value. - Context sources (
ContextSource, passed asbuild_agent(context_sources=...)) are how later layers add live values (today's spend in L2, live nodes in L5). They run before every model call, so they return cached values and do no network round trip. Results are normalized; unknown keys are ignored and a failing source is skipped. - Problems are logged once until they clear (the terminal shows warnings), so a broken file or source never floods the chat.
persona.mdholds no facts about the user; a test checks the tracked persona files for emails, addresses, tailnet names and home paths, and thatpersona/user.mdis ignored.
Consequences¶
- Tests build a throwaway
TAU_HOME/TAU_DATA_DIRunder a temporary directory (tau_core.testing.test_config) and never read the developer's real files; tests of the tracked persona readuser.example.md, neveruser.md. - Moving the hub to another machine means copying
TAU_HOME(includingpersona/user.mdand.envover SSH);TAU_DATA_DIRmoves with the session backend. - Changing tau's character is a text edit with no deploy; a bad edit is caught at the next start, not in the middle of a conversation.
Amended (2026-09-29, issue 062)¶
- Prompt headings are English. The layout is
persona.md, then## Userwithuser.md(or "No user file (persona/user.md). Address the user formally."), then## Nowwith the dynamic context. This supersedes the reasoning above that the headings are Turkish because the model reads them: everything the model reads is English, andpersona.mdrefers to the sections by their English names ("User","Now"). The persona body itself may be in any language; it is Turkish by default and tells tau to mirror the user's language. - Dynamic-context labels and markers are English:
Date and time,Profile,Host role,Live nodes,Budget today; a value no layer provides readsunknownand an empty node list readsnone. Weekday names are English. Profiles arehome/travel/sport. - The formal address in the "no user file" sentence is a persona instruction ("formally"), not a Turkish pronoun in the scaffolding; the persona body decides how that reads in its language.
- The system prompt is also built as content blocks (
build_system_content) with a cache point after the persona and user sections, so the dynamic## Nowblock is the only part that changes between calls.