"Describe tau's three-layer architecture and the load-bearing boundary." tau_ai (provider/model streaming — OpenAI, Anthropic, OpenRouter, HF, Google, Mistral, local); tau_agent (the portable brain — harness, loop, tools, events, sessions; ZERO UI/CLI deps, depends only on tau_ai + Pydantic); tau_coding (the coding app — CLI/TUI, read/write/edit/bash tools). The load-bearing boundary is between tau_agent (reusable brain) and tau_coding (one consumer). A frontend consumes the AgentEvent stream — the brain does not know which app is driving it. This is what makes tau-security possible as a sibling package. course2a::sdd13::recall "Why does reading order matter for tau, and what is the correct order?" tau is explicitly built to be read 'like a book' — ~3000 lines, small enough to read end-to-end. Order: README/architecture docs → harness.py (298 LOC, the public surface) → loop.py (276 LOC, the pure loop) → tools.py (78 LOC, the tool contract) → events.py (134 LOC, the layer contract/seam) → tau_coding/tools.py (1057 LOC, the contract in action). Read harness first because every app builds on it; read tools before the coding tools because it defines the contract every tool satisfies. course2a::sdd13::analysis "What is AgentHarness and what does it own? Why is it the reusable brain?" AgentHarness (harness.py, 298 LOC) is the stateful surface every app builds on. It OWNS: the transcript (list[AgentMessage], exposed as immutable tuple), the steering_queue and follow_up_queue (the HITL/autonomy-gate mechanism), event listeners (subscribe/unsubscribe), and the cancellation signal. It DELEGATES execution to run_agent_loop. It remains independent of CLI, Rich, Textual, session files, coding-agent resource loading. tau-security's SecurityHarness wraps it and inherits the brain unchanged. course2a::sdd13::analysis "What is run_agent_loop and why is it called 'pure'? What is the key error-isolation property?" run_agent_loop (loop.py, 276 LOC) is a pure stateless async generator. It takes the CALLER-OWNED transcript list, a provider, tools, and queue-draining callbacks; yields AgentEvent; appends assistant + tool-result messages to the caller's list. It owns NO state across calls — state is the harness's job. Error isolation: every tool call is wrapped in try/except (the '# noqa: BLE001 - tools are an isolation boundary' comment is explicit). A tool exception is caught and returned as ok=false AgentToolResult — NEVER propagated. One tool's failure does not crash the loop. course2a::sdd13::analysis "Describe the AgentTool design. Why does it make scope enforcement a first-class concern?" AgentTool is a @dataclass(frozen=True, slots=True) — 78 lines. Fields: name, description, input_schema (JSON schema mapping), executor (an async ToolExecutor callable), optional prompt_snippet/prompt_guidelines. execute() is a one-liner: return await self.executor(arguments, signal). frozen=True means the executor cannot be reassigned at runtime (security property — scope enforcement cannot be swapped out). The executor is an ORDINARY async function the author writes — no decorator, no registry, no metaclass. This is WHY scope enforcement hooks in as first-class: in tau-security every tool executor calls assert_in_scope() before any work. The model cannot bypass it — the check is in code, not the system prompt. course2a::sdd13::analysis "List the 14 AgentEvent types and explain why the event stream is 'the contract' between layers." 14 events (events.py, 134 LOC, all Pydantic ConfigDict extra=forbid, discriminated union AgentEvent): AgentStart, AgentEnd, TurnStart, TurnEnd, QueueUpdate, Retry, MessageStart, MessageDelta, ThinkingDelta, MessageEnd, ToolExecutionStart, ToolExecutionUpdate, ToolExecutionEnd, Error. It is THE CONTRACT because: the loop translates provider events into these 14; the harness _notify() fans each to listeners AND yields to the caller; frontends (TUI, security evidence capturer, any async-for consumer) subscribe without the brain knowing. The two security-critical events: ToolExecutionStartEvent (carries ToolCall — what the model requested) and ToolExecutionEndEvent (carries AgentToolResult — what happened). Together they are the complete audit record. course2a::sdd13::recall "How does tau's session model work, and why is append-only JSONL the foundation of tamper-evidence?" Sessions are append-only JSONL. JsonlSessionStorage (40 LOC) opens a file in append mode, writes one entry_to_json_line(entry) per append — entries are NEVER modified or deleted. SessionEntry (114 LOC) is a discriminated union of 9 types: Message, ModelChange, ThinkingLevelChange, Compaction (context summary replacing older entries on replay), BranchSummary, Label, Leaf (active branch pointer), SessionInfo, Custom (namespace'd extension data). Every entry has a parent_id — that is the branching mechanism. Append-only = tamper-evident. tau-security's EvidenceChain uses the SAME property with explicit SHA-256 chaining (_previous_hash starts at 'genesis', each record's hash incorporates the previous; verify_integrity() recomputes and checks the head). course2a::sdd13::analysis "What are the steering and follow-up queues, and why are they the autonomy gate mechanism?" Two deque[AgentMessage] queues owned by AgentHarness. steer(content) queues a message injected MID-RUN after the current tool batch (human corrects a running agent without stopping it). follow_up(content) queues a message injected when the run would otherwise STOP. QueueMode is 'one_at_a_time' (drain one per turn) or 'all'. The loop drains them via callbacks (get_steering_messages, get_follow_up_messages) at the turn boundary. QueueUpdateEvent tells the frontend a steering message is pending. tau-security inherits this unchanged: SecurityHarness.steer() delegates to self._harness.steer(). The autonomy field (advisory/gated/autonomous) + max_turns budget is the security HITL pattern built on the same mechanism. course2a::sdd13::analysis "Describe the tau-security transformation. What changes and what stays the same?" tau-security is a SIBLING package (not a fork) — imports from tau_agent, modifies none of tau's source. SecurityHarness wraps AgentHarness. SAME: the loop, the 14 events, the append-only JSONL sessions, the steering/follow-up queues. DIFFERENT (4 things the app-layer controls): (1) tools — port_scan/http_probe/code_scan/record_finding/generate_report instead of read/write/edit/bash; (2) system prompt — the 6-phase offensive state machine; (3) scope — hard-wired assert_in_scope in every executor (for_bug_bounty/for_ctf/for_appsec); (4) output — tamper-evident evidence chain + triage pipeline + client report. SecurityHarness is ~285 lines, almost all prompt + tool wiring. The brain is inherited. Course thesis: harness is 98.4%, model is 1.6%. course2a::sdd13::application "Where exactly does scope enforcement hook in, and why can't the model bypass it?" In the AgentTool EXECUTOR — the ordinary async function the author writes. Every tau-security tool is a factory (create_port_scan_tool(ctx)) that closes over a ToolContext (Scope + EvidenceChain) and returns an AgentTool whose executor follows 5 steps: (1) define input model, (2) extract target, (3) assert_in_scope(scope, target, tool_name) — THE GATE, (4) execute, (5) capture evidence + return structured result. Out-of-scope → ScopeViolation caught → _blocked() result (ok=false, 'BLOCKED by scope enforcement') — nmap never runs. The model CANNOT bypass it because the check is in CODE in the executor, not in the system prompt. Prompt-level scope is a complement, never a replacement (Course 2A S01). course2a::sdd13::application "Score tau on the 12-module rubric (/60). What is the shape of the score and why is it correct?" 40/60. Strengths (where a teaching skeleton SHOULD be strong): Execution Loop 5 (cleanest in roster, 276 LOC), Tool Design 5 (78-line AgentTool is the exemplar), Documentation 5 (dev-notes phase journals, AGENTS.md, website), Error Handling 4, State 4, Observability 4. Weaknesses (where a security harness MUST add): Memory 2 (per-session JSONL only), Sandboxing 2 (no process/network isolation, bash runs real subprocess), Permission 2 (no action-level gates), Verification 2 (none — coding agent), Security-of-harness 2 (no injection defenses, plaintext creds). Context Mgmt 3. This is the CORRECT shape: maximal clarity on the reusable brain, minimal commitment on domain-varying concerns. The gap between tau (40) and CAI (46) is almost exactly what tau-security adds. course2a::sdd13::analysis "Compare tau to CAI and to the Course 1 reference harnesses. Why is tau the right base for teaching security harness engineering?" tau is the teaching skeleton; CAI is the production system (46/60 — has scope enforcement, CSI meta-harness, evidence buffer, 6 CTF domains). CAI teaches what a finished offensive harness looks like; tau teaches how to BUILD one. Pi (Course 1 DD-01) is the minimal ~1200-LOC reference; tau is Pi's educational clone with the cleaner 3-layer split. aider/opencode are production coding agents — larger, harder to read end-to-end. tau is the right base because: (1) real enough to attack (bash runs real subprocess, plaintext creds); (2) small enough to read entirely (~3000 lines); (3) undefended — every security control is a net-new feature attaching at explicit seams (executor, events, sessions). The lesson: security is concerns attached to a general brain at well-designed extension points, not a different kind of system. course2a::sdd13::analysis