This deck carries the Day 2 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 and the citations; participants were asked to read them before the session, so do not read them aloud.
Ground rule, same as Day 1: every number on these slides comes from this repository in mock mode, and each note says how to reproduce it. Nothing here is an industry statistic or a real incident.
Timetable from docs/INSTRUCTOR_GUIDE.md section 1. Suggested split of the 90 minutes: 10 min motivating failure and objectives; 25 min error analysis and calibration including the prediction checkpoint; 10 min bias probes; 30 min spans, the loop and context growth; 10 min what the judge sees and the lab briefing; 5 min questions.
If people did not do the reading, give them five minutes on lecture section 2 now rather than summarising it: the point of error analysis is that they read the material themselves.
09:00 recap. Ask: in yesterday's lab, which number in the metrics table came from a model rather than from a rule? (answer_correctness, through the fake judge.)
Reproduce 0.64 and 0.78: test_ambiguous_tool_description_drops_tool_selection() in tests/test_seeded_weaknesses.py asserts the gap, and the table in section 1 of the Day 3 lecture shows both values; or run STOCKROOM_WEAKNESSES=ambiguous_tool_desc make eval and read "metrics" in reports/eval_results.json.
Read the answer aloud and ask who would ship it. Then: a judge with rubric v1 would put this in the "correct" column of the gate.
The point is not that the fake judge is weak (it is deliberately imperfect so calibration shows movement), but that you only find out by comparing it with human labels.
Reproduce 10 and 20: calibrate(FakeJudge("v1"), items) gives confusion tp=10, fn=0, fp=10, tn=4, so the humans pass tp+fn=10 and the judge passes tp+fp=20. Command in the note on the reveal slide.
Each objective maps to something observable: a printed table, a passing check cell, or an exercise id in the notebook's exercise checklist.
Exercise numbering follows the notebook (notebooks/src/day2_judge_calibration_and_otel_traces.py); if the notebook is renumbered, follow the checklist cell at its end.
Lecture section 1. C23 and C24 are the reason a judge exists at all: no rule-based grader catches them, and they are the two items the v2 fake judge still gets wrong.
The data is in data/judge_calibration/calibration_v1.jsonl; the README in the same folder explains how the items were built.
The sources are pre-reading (lecture section 2, with links). Do not re-teach the papers; connect them to the files: data/judge_calibration/README.md describes the clusters, and src/stockroom/evals/rubrics/answer_correctness_v2.md transcribes them into numbered criteria.
The lab starts the same way on golden runs: with ambiguous_tool_desc on, eight sample cases (G001, G007, G011, G014, G017, G026, G033, G037) give 5 judge passes but only 3 correct tool selections. The observation becomes the metric. Reproduce with the first two cells after setup in the Day 2 notebook.
Run this live. The code is the calibration cell of the Day 2 notebook, and the test is test_calibration_improves_from_v1_to_v2() in tests/test_judge_calibration.py.
Stop before the output appears: the next slide is the prediction checkpoint. Run it in a terminal the room cannot see, or run it after the checkpoint.
v1 passes any answer with at least half of the key facts; v2 requires every key fact and no forbidden content (FakeJudge.grade() in src/stockroom/evals/judge.py).
Give the room two minutes. Collect a few guesses for v1 kappa out loud; people usually anchor on 58% and guess something in the 0.5 range.
Hint if asked: kappa compares observed agreement with the agreement two raters with these pass rates would reach by chance. v1 passes 20 of 24, so chance agreement is already high.
The arithmetic for v1: p_o = 14/24; p_e = (10/24)(20/24) + (14/24)(4/24) = 4/9; kappa = (14/24 − 4/9) / (1 − 4/9) = 0.25.
Reproduce: uv run python -c "from stockroom.evals.calibration import calibrate; from stockroom.evals.golden import load_calibration; from stockroom.evals.judge import FakeJudge; items = load_calibration(); [print(calibrate(FakeJudge(v), items).summary()) for v in ('v1', 'v2')]"
It prints agreement 58.33% / kappa 0.250 and 91.67% / 0.833 with the confusion tables; report.disagreements lists the item ids.
Discussion hook for the review: would you ship v2 as the gating judge, given C23 and C24? In live mode the judge is a model from a different family (Claude agent, Nova judge by default), and these two items are the first thing to check it against.
Lecture section 4. Thresholds are the ones in src/stockroom/evals/calibration.py.
Reproduce: uv run pytest tests/test_judge_calibration.py -k bias_probes (test_bias_probes_report_no_bias_for_fake_judge() asserts biased is False for all three), or the bias-probe cell of the notebook.
Ask: what would you need to see from a live judge before letting it gate the nightly run? More pairs per group, at least; docs/TASKS.md lists adding probe pairs as a follow-up.
Reproduce: the first tracing cell of the Day 2 notebook (configure_tracing(TraceExporter.MEMORY), RunTracer(handle), run G033, print span_tree()). test_span_tree_per_run() in tests/test_tracer.py asserts the shape: one invoke_agent, one chat per model call, one execute_tool per tool call. The in/out numbers are the fake client's chars/4 estimates.
Exporters (memory, console, phoenix, cloudwatch) are pre-reading in lecture section 5.2. Start everyone on memory so nobody is blocked on Phoenix.
Reproduce: uv run python -c "from stockroom.agent.harness import Harness; from stockroom.config import StockroomConfig; from stockroom.evals.golden import golden_by_id; from stockroom.evals.metrics import ToolCallEvaluator; c = golden_by_id()['G043']; r = Harness(StockroomConfig.mock(weaknesses='naive_retry')).run(c.query, case_id=c.id); t = ToolCallEvaluator().from_run(r); print(r.termination_reason.value, len(r.tool_names), t.loops, t.repeated_identical)"
It prints MAX_STEPS 8 1 1. The notebook's loops cell runs the same case with a tracer and asserts that from_spans() gives the same verdict; test_trace_summary_and_evaluator_agree_with_run() pins it.
Exercise 3 asks participants to find this loop by hand from the execute_tool spans.
Reproduce: the context-growth table cell of the Day 2 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; from stockroom.evals.metrics import ToolCallEvaluator; c = golden_by_id()['G049']; [print(r.termination_reason.value, t.context_tokens_first, t.context_tokens_last, t.context_growth_ratio, r.usage.input_tokens) for r in [Harness(StockroomConfig.mock(weaknesses=w)).run(c.query, case_id=c.id) for w in ('', 'oversized_payload')] for t in [ToolCallEvaluator().from_run(r)]]"
test_oversized_payload_exhausts_the_token_budget() in tests/test_seeded_weaknesses.py pins the TOKEN_BUDGET termination. Day 3 shows the harness side: compaction and bounded payloads.
Lecture section 7. Rubrics are versioned files loaded by load_rubric(name, version); JudgeVerdict.rubric_version is recorded on every verdict, so a metric movement can be attributed to a rubric change rather than an agent change.
Gate on the boolean, keep the 0 to 10 score for diagnosing borderline items (lecture section 3.3, pre-read).
Matches the lecture agenda and the notebook's exercises. Everything runs offline in mock mode; Phoenix is optional and the memory exporter is enough for every check cell.
Facilitation notes are in docs/INSTRUCTOR_GUIDE.md section 4 (Day 2). Common blocker: Phoenix not running or exporter not set; the memory path still works.
The check cells enforce the thresholds: Exercise 1 must beat v2 on both agreement and kappa without introducing a bias; Exercise 2 must use an unused id and disagree with v2; Exercise 3 must match the loop finding of ToolCallEvaluator.
Teaching point for the review: the reference solution's rule is fitted to two items and reaches perfect agreement on this set (run the Exercise 1 check cell of notebooks/solutions/Day2_Judge_Calibration_and_OTEL_Traces.ipynb). Ask what that says about evaluating a rubric on the same items you wrote it from: hold items out.
Questions 1, 2, 5 and 6 of lecture section 9, plus one on overfitting the calibration set. Pick three; spend the rest of the hour comparing participants' v3 rules and new calibration items side by side.
Common mistakes to surface if they come up (lecture section 10): reporting agreement without kappa; reading "no bias" from the fake judge; same model family for agent and judge; installing the phoenix and cloudwatch extras together.
Bridge. Each of the four seeded weaknesses moves a specific metric you can now read in a table and in a trace: ambiguous_tool_desc moves tool selection, naive_retry the loop and termination metrics, oversized_payload context growth and tokens, injection_unguarded must_not_call.
Remind people to unset STOCKROOM_WEAKNESSES before they leave; tomorrow starts from the shipped default.