This deck carries the Day 3 teaching touchpoints: the 09:00 recap, the 90-minute lecture block (09:15 to 10:45), the lab briefing and the 16:00 review. The lecture notes hold the detail; participants read them beforehand.
Thesis of the day, from the lecture: most runtime failures in Stockroom are not "the model was dumb" but "the harness let it". Today the model is held constant and only the harness changes.
Every number on these slides comes from this repository in mock mode; each note says how to reproduce it.
Timetable from docs/INSTRUCTOR_GUIDE.md section 1. Suggested split of the 90 minutes: 10 min recap table and motivating failure; 15 min state machine and failure classes; 10 min schema drift worked example; 25 min the run-guard seam, the prediction checkpoint and the reveal; 15 min what the mechanisms do not guarantee; 10 min MCP and the lab briefing; 5 min questions.
The AWS section (lecture section 6) is descriptive and comes back in the review; do not spend lecture time reading the service list.
09:00 recap, and the map for the day. Ask before revealing the table: for each flag, which column do you expect to move? Then reveal.
Reproduce: STOCKROOM_WEAKNESSES=<flag> make eval, then read "metrics" in reports/eval_results.json (unset the variable afterwards), or the per-flag charts in the Day 3 notebook, which compute each configuration once. test_every_flag_moves_at_least_one_gate_metric() in tests/test_seeded_weaknesses.py keeps the property that every flag moves a gate metric. Mean input tokens are chars/4 estimates, rounded.
Mock scores are upper bounds: the planner simulates failure classes; it is not a model.
Read the query: nobody asked for a restock. The model followed an instruction it found in a tool result.
The user did mention "restock request" in the query, so a guard that looks for the word "restock" would let this through; what the user did not do is name SKU-1001. That observation is the construction lab.
Reproduce: uv run python -c "from stockroom.agent.harness import Harness; from stockroom.config import StockroomConfig; from stockroom.evals.golden import golden_by_id; c = golden_by_id()['G041']; r = Harness(StockroomConfig.mock(weaknesses='injection_unguarded')).run(c.query, case_id=c.id); print([(t.name, t.arguments, t.executed, t.error_code) for t in r.tool_records])"
The 5,000 limit is reject_above in data/restock_rules.json. test_unguarded_injection_is_followed_and_guarded_is_not() pins the unguarded and guarded behaviour.
Exercises 4 and 5 (a sharper search_products description, a redundant-query assertion) are in the last block of part 2.
Exercise numbering follows notebooks/src/day3_building_custom_agent_harness.py; if it is renumbered, follow the exercise checklist cell at the end of the notebook.
Lecture section 2. Everything is in src/stockroom/agent/harness.py. test_happy_path_transitions_and_result_shape() asserts the exact sequence of states on a happy path.
The order of the pre-call checks matters: compaction runs before the budget check, so a bloated context gets one chance to shrink before the guard fires.
Ask: why should a guard trip be a normal outcome rather than an exception? (Because termination_match_rate compares it with the golden case's expected termination.)
Lecture section 3, one row per subsection. Tests that pin each row: test_invalid_arguments_get_structured_error_and_never_reach_the_tool(), test_ambiguous_tool_description_drops_tool_selection(), test_oversized_payload_exhausts_the_token_budget(), test_executor_in_memory_through_harness() (the same restock twice over MCP gives RSR-0001 both times) and test_naive_retry_loops_until_max_steps().
Spend the time on the "seeded as" column: each class has a flag or a scripted case, so every claim on this slide is something participants can switch on and watch.
G043 numbers for the unbounded-retry row, from Day 1: 8 identical get_order_status calls and MAX_STEPS with the flag on, 2 model calls and COMPLETED without it.
Fixing means "the shipped default is clean and the flag still reproduces the failure"; never delete the flagged branch.
Lecture section 3.1. Reproduce: the argument-validation cell of the Day 3 notebook, or
uv run python -c "from stockroom.agent.harness import Harness; from stockroom.config import StockroomConfig; from stockroom.evals.golden import golden_by_id; c = golden_by_id()['G045']; r = Harness(StockroomConfig.mock()).run(c.query, case_id=c.id); print(r.usage.model_calls, r.termination_reason.value, [(t.arguments, t.executed, t.validation_error) for t in r.tool_records])"
It prints 3 model calls, COMPLETED, and the two records. The MCP server validates again with pydantic; two validators are deliberate (lecture section 3.1, note for MCP).
Lecture section 2, last bullet, and docs/DECISIONS.md entries 45 and 47. With no run guards the loop is unchanged and the golden metrics equal the committed baseline.
Tests: test_run_guard_blocks_an_injected_write_and_the_run_continues(), test_run_guard_allows_requested_restocks_and_sees_only_valid_calls(), test_run_guard_can_halt_the_run_with_a_typed_reason().
Blocked calls are counted apart from invalid ones (blocked_call_rate versus invalid_call_rate), so a guard doing its job never looks like a malformed call.
Exercises 1 and 2 of the Day 3 notebook. The success criteria in the check cells: the guard blocks the injected write in G032, G041 and G042 with the flag on, each run still completes, and the requested restocks in G020, G021 and G033 still execute on the default configuration.
Keep the BlockCall message free of the blocked arguments: it goes back into the model's context.
Two minutes. Most rooms predict that answer correctness recovers along with must_not_call. Ask people to commit to a number before the reveal.
Hint if asked: what else does the planted notice ask the assistant to do, besides filing a restock? (Reveal its system prompt.)
Reproduce: the construction lab's before/after metrics table in the Day 3 solutions notebook (notebooks/solutions/Day3_Building_Custom_Agent_Harness.ipynb), which runs each configuration once. Both rates are shares of cases: 0.06 is the 3 of 50 cases with a blocked call (G032, G041, G042), and the 0.04 invalid call rate in every row is G045 and G046, the scripted schema-drift cases.
Name the channel each control covers: the default quarantine handles both the write and the leak because the model never sees the notice; the run guard handles only the write; an output filter would hide the leak but not stop the write. That is why you want layers.
Lecture sections 2 and 3.3 to 3.6. Each limit has a notebook cell that participants run in part 1, and a test that pins it: test_token_budget_bounds_estimates_not_provider_counts(), test_compaction_keeps_only_a_prefix_of_old_results() and test_sanitizer_is_a_pattern_match_with_known_misses_and_false_positives() in tests/test_harness_guards.py.
Reproduce the compaction numbers with the "what compaction loses" cell (threshold lowered to 600 tokens, keep 1 turn): first search returns SKU-1022 to SKU-1026, only SKU-1022 remains. The chunk count comes from the quarantine-limits cell; test_sanitizer_keeps_every_real_policy_chunk_except_the_seeded_one() pins it.
Fix bloat upstream with bounded payloads (DEFAULT_MAX_RESULTS, COMPACT_FIELDS); compaction is the safety net.
Lecture section 4; the SDK details (MCPServer, Client, the asyncio loop on a thread, result normalisation) are pre-reading.
In the notebook, a server built with ambiguous_tool_desc and a clean harness config routes G014 (an order-status question) to search_products.
Common failure in the lab: a server started by hand in another terminal keeps whatever STOCKROOM_WEAKNESSES it was started with.
Matches the lab plan in lecture section 8 and docs/INSTRUCTOR_GUIDE.md section 4 (Day 3).
Start every participant from StockroomConfig.mock() overrides in the notebook rather than exported environment variables, so nothing leaks into later exercises. If the gate fails "for no reason", run uv run stockroom config and look at weaknesses=.
The check cells encode these criteria. Ask participants to rename each results file (for example eval_results_naive_retry.json) before the next run overwrites it; the Day 4 capstone compares against them.
Questions 1, 2, 8, 4 and 6 of lecture section 9. Put two participants' four-row tables side by side and compare guard designs.
Common mistakes to surface (lecture section 10): fixing a weakness by deleting the flagged branch; leaving STOCKROOM_WEAKNESSES exported; testing a guard only on the clean default; reading the token budget as a spending cap.
Bridge. Preview the uncomfortable fact Day 4 starts from: a gate made only of aggregate floors lets two of today's four flags through. Do not say which; the Day 4 capstone has participants rediscover it.
Remind everyone to unset STOCKROOM_WEAKNESSES and stop any MCP server still bound to port 8765.