# Diagrams — SDD-13: tau

---

## Diagram 1 — tau's Three-Layer Architecture

```mermaid
flowchart TB
    subgraph TAU_AI["tau_ai — provider/model streaming layer"]
        PROV[ModelProvider<br/>OpenAI · Anthropic · OpenRouter<br/>HF · Google · Mistral · local]
        PEVT[Provider events:<br/>ResponseStart · TextDelta<br/>ThinkingDelta · ResponseEnd<br/>Error · Retry]
        PROV --> PEVT
    end

    subgraph TAU_AGENT["tau_agent — the portable brain (zero UI/CLI deps)"]
        H[AgentHarness<br/>298 LOC<br/>owns transcript + queues + listeners]
        L[run_agent_loop<br/>276 LOC<br/>pure stateless provider/tool loop]
        T[AgentTool<br/>78 LOC<br/>frozen dataclass + async executor]
        E[14 AgentEvents<br/>134 LOC<br/>the layer contract]
        S[Session JSONL<br/>append-only · parent_id branching<br/>CompactionEntry · LeafEntry]
        H -->|delegates| L
        L -->|emits| E
        L -->|calls| T
        L -->|appends to| S
    end

    subgraph TAU_CODING["tau_coding — the coding app"]
        CT[read · write · edit · bash<br/>glob · grep<br/>1,057 LOC of tools]
        TUI[TUI · Textual<br/>CLI · slash commands<br/>skills · on-disk sessions]
        CT --> TUI
    end

    PEVT -->|provider.stream_response| L
    E -->|AgentEvent stream<br/>the contract| TUI

    style TAU_AI fill:#0d1b2a,stroke:#5eead4,color:#e4e4e8
    style TAU_AGENT fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TAU_CODING fill:#1a1015,stroke:#9494a0,color:#e4e4e8
    style H fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style L fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style T fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style E fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style S fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style PROV fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style PEVT fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style CT fill:#1a1015,stroke:#9494a0,color:#e4e4e8
    style TUI fill:#1a1015,stroke:#9494a0,color:#e4e4e8
```

**Reading**: tau's load-bearing design decision is the boundary between `tau_agent` (the portable brain) and `tau_coding` (the coding app). The brain depends only on `tau_ai` and Pydantic — it has zero knowledge of Textual, Rich, local config paths, slash commands, or rendering. A frontend consumes the `AgentEvent` stream. This separation is what makes the brain reusable: `tau_coding` wraps it as a coding agent; `tau_security` wraps the same brain as a security agent. The numbers are the actual source line counts — the entire brain (harness + loop + tools + events) is ~960 lines; the coding tools alone are 1,057. Read the layers top-down: providers emit neutral events, the loop translates them into 14 agent events, the harness fans events to listeners and owns state, the app is one consumer of the stream.

---

## Diagram 2 — The Agent Loop (Provider Stream → Events → Tool Execution → Events)

```mermaid
flowchart TB
    START([prompt called]) --> AST[AgentStartEvent]
    AST --> TURN[TurnStartEvent turn=N]
    TURN --> STREAM[async for provider_event in<br/>provider.stream_response]
    STREAM --> TRANSLATE{translate provider event}
    TRANSLATE -->|ResponseStart| MS[MessageStartEvent]
    TRANSLATE -->|TextDelta| MD[MessageDeltaEvent]
    TRANSLATE -->|ThinkingDelta| TD[ThinkingDeltaEvent]
    TRANSLATE -->|Retry| RE[RetryEvent]
    TRANSLATE -->|ResponseEnd| ME[MessageEndEvent<br/>append assistant msg]
    TRANSLATE -->|Error| ERR[ErrorEvent recoverable=false]

    ME --> CHECK{assistant has<br/>tool_calls?}
    CHECK -->|no| DRAIN1[drain steering queue]
    DRAIN1 --> Q1{queued msgs?}
    Q1 -->|yes| TURN
    Q1 -->|no| DRAIN2[drain follow_up queue]
    DRAIN2 --> Q2{queued msgs?}
    Q2 -->|yes| TURN
    Q2 -->|no| STOP

    CHECK -->|yes| EXEC[for each ToolCall:]
    EXEC --> TES[ToolExecutionStartEvent<br/>carries ToolCall]
    TES --> CALL[await tool.execute arguments, signal]
    CALL --> RESULT{result}
    RESULT -->|exception caught| FAIL[AgentToolResult ok=false<br/>isolation boundary — loop never crashes]
    RESULT -->|success| OK[AgentToolResult ok=true]
    FAIL --> APPEND[append ToolResultMessage<br/>to transcript]
    OK --> APPEND
    APPEND --> TEE[ToolExecutionEndEvent<br/>carries AgentToolResult]
    TEE --> NEXT{more tool calls?}
    NEXT -->|yes| EXEC
    NEXT -->|no| TE[TurnEndEvent turn=N]
    TE --> DRAIN3[drain steering queue]
    DRAIN3 --> BUDGET{max_turns reached?}
    BUDGET -->|no| TURN
    BUDGET -->|yes| MAXERR[ErrorEvent recoverable=true<br/>max_turns budget hit]
    MAXERR --> STOP
    STOP([AgentEndEvent])

    style AST fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TURN fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style STREAM fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style MS fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style MD fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style ME fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TES fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TEE fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style CALL fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style FAIL fill:#1a1015,stroke:#9494a0,color:#9494a0
    style OK fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style ERR fill:#1a1015,stroke:#9494a0,color:#9494a0
    style MAXERR fill:#1a1015,stroke:#9494a0,color:#9494a0
    style STOP fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TE fill:#0d1b2a,stroke:#5eead4,color:#5eead4
```

**Reading**: `run_agent_loop` is a pure stateless async generator (276 lines). The caller (the harness) owns the transcript list; the loop appends assistant and tool-result messages to it. Each turn translates the provider's neutral event stream into the 14 typed agent events. The tool-execution path is the load-bearing part for security: every `ToolCall` yields a `ToolExecutionStartEvent` (carrying the requested name and arguments), then `await tool.execute(arguments, signal)`, then a `ToolExecutionEndEvent` (carrying the `AgentToolResult`). Tool exceptions are caught at the loop boundary — the `# noqa: BLE001 - tools are an isolation boundary` comment is explicit: a failing tool returns `ok=false` and never crashes the loop. This is where scope enforcement lives in `tau-security`: the executor calls `assert_in_scope` before any work, and out-of-scope calls return a blocked result that rides the same `ToolExecutionEndEvent` path. The `max_turns` budget is the safety valve. The steering/follow-up queues drain at the turn boundary — that is the human-in-the-loop injection point.

---

## Diagram 3 — The AgentTool Lifecycle (Frozen Dataclass → Schema → Executor → Result)

```mermaid
flowchart LR
    subgraph DEF["1. Definition (author writes a factory)"]
        FACT[create_port_scan_tool ctx<br/>closes over Scope + EvidenceChain]
        FACT --> BUILD[AgentTool name=port_scan<br/>description=...<br/>input_schema=host, ports<br/>executor=execute]
    end

    subgraph MODEL["2. The model sees"]
        SCHEMA[input_schema JSON:<br/>properties.host type string<br/>required host]
        DESC[description string:<br/>Scan ports on an in-scope host...]
    end

    subgraph EXEC["3. The loop calls (per ToolCall)"]
        TES[ToolExecutionStartEvent<br/>carries ToolCall: name + arguments]
        TES --> CALL[await tool.execute arguments, signal]
        CALL --> GATE{executor body}
        GATE --> S1[1. extract target from arguments]
        S1 --> S2[2. assert_in_scope scope, host, port_scan]
        S2 --> SCOPE{in scope?}
        SCOPE -->|no| BLK[return _blocked result<br/>ok=false · never executes nmap]
        SCOPE -->|yes, rate ok| S3[3. execute nmap subprocess]
        S3 --> S4[4. _capture_evidence → EvidenceChain]
        S4 --> S5[5. return _success result<br/>ok=true · evidence_id · finding_id]
    end

    subgraph RESULT["4. The loop receives"]
        BLK --> RES[AgentToolResult<br/>tool_call_id · name · ok<br/>content · data · error · details]
        S5 --> RES
        RES --> TEE[ToolExecutionEndEvent<br/>carries AgentToolResult]
        TEE --> APPEND[append ToolResultMessage<br/>to transcript]
    end

    BUILD --> SCHEMA
    BUILD --> DESC
    BUILD --> CALL

    style DEF fill:#0d1b2a,stroke:#5eead4,color:#e4e4e8
    style MODEL fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style EXEC fill:#0d1b2a,stroke:#5eead4,color:#e4e4e8
    style RESULT fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style FACT fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style BUILD fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style CALL fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style GATE fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style S2 fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style SCOPE fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style BLK fill:#1a1015,stroke:#9494a0,color:#9494a0
    style S3 fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style S4 fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style RES fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TEE fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TES fill:#0d1b2a,stroke:#5eead4,color:#5eead4
```

**Reading**: `AgentTool` is a `@dataclass(frozen=True, slots=True)` — 78 lines total. The `frozen` property means the executor cannot be reassigned at runtime (a security property: scope enforcement, once wired in, cannot be swapped out). The lifecycle has four stages. (1) The author writes a factory function that closes over shared context (the `Scope` and `EvidenceChain` in `tau-security`) and returns an `AgentTool`. (2) The model sees only the `description` and `input_schema` — it does not see the executor. (3) The loop calls `await tool.execute(arguments, signal)` per `ToolCall`, bracketed by `ToolExecutionStartEvent` and `ToolExecutionEndEvent`. The executor body is entirely author-controlled — this is why scope enforcement is a first-class concern rather than a framework hook: the author writes `assert_in_scope` as the second line of every security tool's executor, and the model cannot bypass it because the check is in code, not the prompt. (4) The result is a structured `AgentToolResult` (Pydantic, `extra="forbid"`) that flows back through the event and into the transcript. Blocked calls and successful calls ride the same path — the only difference is `ok=false` and a `BLOCKED` content string.

---

## Diagram 4 — The Event Stream Contract (Provider → Loop → Harness → Frontend)

```mermaid
flowchart TB
    subgraph PROVIDER["tau_ai — provider layer"]
        P[ModelProvider.stream_response]
        P --> PE1[ProviderResponseStartEvent]
        P --> PE2[ProviderTextDeltaEvent]
        P --> PE3[ProviderThinkingDeltaEvent]
        P --> PE4[ProviderRetryEvent]
        P --> PE5[ProviderResponseEndEvent]
        P --> PE6[ProviderErrorEvent]
    end

    subgraph LOOP["tau_agent/loop.py — translates"]
        PE1 --> AE1[MessageStartEvent]
        PE2 --> AE2[MessageDeltaEvent]
        PE3 --> AE3[ThinkingDeltaEvent]
        PE4 --> AE4[RetryEvent]
        PE5 --> AE5[MessageEndEvent]
        PE6 --> AE6[ErrorEvent]
        AE7[ToolExecutionStartEvent]
        AE8[ToolExecutionEndEvent]
        AE9[TurnStartEvent]
        AE10[TurnEndEvent]
        AE11[AgentStartEvent]
        AE12[AgentEndEvent]
        AE13[QueueUpdateEvent]
        AE14[ToolExecutionUpdateEvent]
    end

    subgraph HARN["tau_agent/harness.py — fans out"]
        NOTIFY[_notify event<br/>for listener in _listeners:<br/>await listener event]
        YIELD[yield event to caller<br/>of harness.prompt]
    end

    subgraph FRONTENDS["consumers — the brain knows none of these"]
        TUI[tau_coding TUI<br/>renders deltas to terminal]
        SEC[tau_security listener<br/>captures ToolExecutionEndEvent<br/>→ EvidenceChain]
        CLI[any async-for consumer<br/>async for event in harness.prompt x]
    end

    AE1 --> NOTIFY
    AE2 --> NOTIFY
    AE7 --> NOTIFY
    AE8 --> NOTIFY
    AE13 --> NOTIFY
    NOTIFY --> YIELD
    YIELD --> TUI
    YIELD --> SEC
    YIELD --> CLI

    style PROVIDER fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style LOOP fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style HARN fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style FRONTENDS fill:#1a1015,stroke:#9494a0,color:#e4e4e8
    style P fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style NOTIFY fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style YIELD fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style TUI fill:#1a1015,stroke:#9494a0,color:#9494a0
    style SEC fill:#1a1015,stroke:#5eead4,color:#5eead4
    style CLI fill:#1a1015,stroke:#9494a0,color:#9494a0
    style AE8 fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style AE13 fill:#0d1b2a,stroke:#5eead4,color:#5eead4
```

**Reading**: The 14 typed events are the contract that makes tau's brain portable. The provider layer (`tau_ai`) emits provider-neutral events; the loop translates each into one of the 14 agent events; the harness's `_notify` fans every event to registered listeners (sync or async) and also yields it to the caller of `harness.prompt()`. Three different consumers can subscribe to the same brain without the brain knowing any of them exists: the `tau_coding` TUI renders deltas to the terminal; a `tau-security` listener inspects `ToolExecutionEndEvent` instances and captures evidence; any caller can `async for event in harness.prompt(...)` and react. The two events that matter most for security are `ToolExecutionStartEvent` (the `ToolCall` — what the model requested) and `ToolExecutionEndEvent` (the `AgentToolResult` — what happened). Together they are the complete audit record of every action the agent took. `QueueUpdateEvent` (steering/follow-up tuples) is the autonomy-gate event made visible to the UI. Every event is `ConfigDict(extra="forbid")` — no schema drift, no accidental fields.

---

## Diagram 5 — tau vs tau_coding vs tau_security (Three Siblings, One Brain)

```mermaid
flowchart TB
    BRAIN[AgentHarness + run_agent_loop<br/>+ AgentTool + 14 events + Session JSONL<br/>tau_agent · ~960 LOC · zero UI deps]

    BRAIN --> CODING
    BRAIN --> SECURITY

    subgraph CODING["tau_coding — the coding app"]
        CTOOLS[tools: read · write · edit<br/>bash · glob · grep]
        CPROMPT[system prompt:<br/>coding assistant]
        CSPROMPT[scope: none]
        CSES[on-disk session:<br/>conversation history]
        COUT[output: code changes]
        CTUI[TUI · CLI · slash commands<br/>skills]
    end

    subgraph SECURITY["tau_security — the security app"]
        STOOLS[tools: port_scan · http_probe<br/>code_scan · record_finding<br/>generate_report]
        SPROMPT[system prompt:<br/>offensive state machine<br/>Recon→Hypothesis→Exploit<br/>→Evidence→Triage→Report]
        SSCOPE[scope: hard-wired<br/>assert_in_scope in every executor<br/>for_bug_bounty · for_ctf · for_appsec]
        SEVID[evidence: tamper-evident<br/>SHA-256 hash chain<br/>from ToolExecutionEndEvent]
        STRIAGE[triage: candidate→confirmed<br/>dedup · FP filter · confidence]
        SOUT[output: client-ready report<br/>JSON or HTML]
        SAUTO[autonomy: advisory · gated<br/>autonomous + max_turns budget]
    end

    style BRAIN fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style CODING fill:#1a1015,stroke:#9494a0,color:#e4e4e8
    style SECURITY fill:#1a1015,stroke:#5eead4,color:#e4e4e8
    style CTOOLS fill:#1a1015,stroke:#9494a0,color:#9494a0
    style STOOLS fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style SSCOPE fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style SEVID fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style SPROMPT fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style CPROMPT fill:#1a1015,stroke:#9494a0,color:#9494a0
    style SOUT fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style COUT fill:#1a1015,stroke:#9494a0,color:#9494a0
    style SAUTO fill:#0d1b2a,stroke:#5eead4,color:#5eead4
    style STRIAGE fill:#0d1b2a,stroke:#5eead4,color:#5eead4
```

**Reading**: `tau-security` is a sibling package to `tau_coding`, not a fork. Both wrap the same `AgentHarness` brain — the same loop, the same 14 events, the same append-only JSONL sessions, the same steering/follow-up queues. What differs is the four things any app-layer wrapper controls: the tools (coding's read/write/edit/bash vs security's port_scan/http_probe/code_scan/record_finding/generate_report), the system prompt (coding assistant vs the six-phase offensive state machine), the scope (none vs hard-wired `assert_in_scope` in every executor), and the output layer (code changes vs a tamper-evident evidence chain + triage pipeline + client-ready report). The `SecurityHarness` class is ~285 lines, almost all of which is the security system prompt and the tool wiring — the brain is inherited. This diagram is the course thesis made visible: the harness is 98.4% of the system, the model is 1.6%, and the security transformation localizes to the tools, the prompt, and an event listener — not the brain. Build a security harness by swapping tools, not by forking the loop.
