This notebook drills the single most valuable habit of the agent era: never accept a fluent answer; verify it against something that cannot flatter you. We practice on three fronts — quantitative claims, citations, and AI-written reviews.
Everything here runs offline. The “agent outputs” are recorded artifacts embedded in the notebook, so the exercises execute in CI, cost nothing, and are identical for every student. The procedures you build are exactly the ones you will run against live agents in your project.
Part (a): Verify claims against the data¶
An agent was given a raw GNSS displacement series and asked to characterize it. Below is its recorded answer. It is well-written, specific, and confident. Your job is to decide which of its quantitative claims survive contact with the data.
agent_answer = """
I analyzed the daily GNSS displacement series you provided.
Summary of findings:
1. The record spans approximately 10 years of daily positions (3,652 days).
2. The station moves with a secular velocity of about 15 mm/yr.
3. There is a coseismic offset near day 1200 of the record, with an
amplitude of roughly 25 mm.
The series also shows a clear annual cycle of a few millimeters, consistent
with hydrological loading. Overall this looks like a typical plate-boundary
station that experienced one significant earthquake during the observation
period.
"""
print(agent_answer)
I analyzed the daily GNSS displacement series you provided.
Summary of findings:
1. The record spans approximately 10 years of daily positions (3,652 days).
2. The station moves with a secular velocity of about 15 mm/yr.
3. There is a coseismic offset near day 1200 of the record, with an
amplitude of roughly 25 mm.
The series also shows a clear annual cycle of a few millimeters, consistent
with hydrological loading. Overall this looks like a typical plate-boundary
station that experienced one significant earthquake during the observation
period.
Three quantitative claims: record length, secular velocity, coseismic offset. The data comes from mlgeo_synth.gnss_series, so we know the ground truth exactly — the station was generated with a velocity of 12 mm/yr and a 25 mm offset at day 1200. But pretend for a moment that we do not have the truth columns (with real data we will not): the honest check is to estimate each claimed quantity from the raw series ourselves and compare.
import numpy as np
from mlgeo_synth import gnss_series
# The same series the agent saw. (Truth: velocity 12 mm/yr, 25 mm offset at day 1200.)
df = gnss_series(n_years=10, velocity_mm_yr=12.0, annual_mm=3.0,
eq_day=1200, coseismic_mm=25.0, postseismic_mm=0.0, seed=7)
disp = df["disp_mm"].to_numpy()
t_days = np.arange(len(df))
print(df[["date", "disp_mm"]].head(3)) date disp_mm
0 2015-01-01 -3.285793
1 2015-01-02 -8.871975
2 2015-01-03 -8.502938
Your turn. Write code that checks each claim from the raw disp_mm series alone. A solid approach for claims 2 and 3, from Chapter 2/3: least-squares fit of a physical model — trend + annual and semi-annual sinusoids + a step function at the known event day — and read the velocity and step amplitude off the coefficients. Then compare each estimate to the agent’s claim with an explicit tolerance.
Work it yourself before opening the check below.
# Worked verification.
# Design matrix: intercept, trend, annual + semiannual sinusoids, step at day 1200.
t_yr = t_days / 365.25
step = (t_days >= 1200).astype(float)
G = np.column_stack([
np.ones_like(t_yr), t_yr,
np.sin(2 * np.pi * t_yr), np.cos(2 * np.pi * t_yr),
np.sin(4 * np.pi * t_yr), np.cos(4 * np.pi * t_yr),
step,
])
coef, *_ = np.linalg.lstsq(G, disp, rcond=None)
est_velocity = coef[1] # mm/yr
est_offset = coef[6] # mm
checks = [
("record length ~= 3652 days", len(df), 3652, 2),
("secular velocity 15 mm/yr", est_velocity, 15.0, 1.0),
("coseismic offset ~25 mm", est_offset, 25.0, 3.0),
]
print(f"{'claim':<32}{'estimate':>10}{'claimed':>10} verdict")
for name, est, claimed, tol in checks:
verdict = "PASS" if abs(est - claimed) <= tol else "FAIL"
print(f"{name:<32}{est:>10.1f}{claimed:>10.1f} {verdict}")claim estimate claimed verdict
record length ~= 3652 days 3652.0 3652.0 PASS
secular velocity 15 mm/yr 12.6 15.0 FAIL
coseismic offset ~25 mm 23.1 25.0 PASS
Two claims check out; the velocity does not. The estimate lands near the true 12 mm/yr, a 25% discrepancy from the claimed 15 — far outside any reasonable tolerance, yet invisible in the prose. The answer around the number was accurate, which is precisely what makes the wrong number dangerous: correct context launders incorrect figures. (Colored noise means the estimate is not exactly 12 either — which is why the check needs a tolerance stated in advance, and why the question is whether the discrepancy exceeds it, not whether the estimate equals the truth.)
Three habits to take away:
- Check every number independently, not just one. Agents are routinely 80% right; the failure is finding out which 20% at review time rather than in a poster session.
- State a tolerance before you check. “Close enough” decided after seeing the numbers is how motivated reasoning gets in.
- Estimate from raw data with your own code. Asking the same agent “are you sure?” is not verification — RLHF-tuned models often apologize and change correct answers under social pressure, in both directions Sharma et al., 2023.
Part (b): Citation checking¶
The same agent drafted a paragraph of related work for a report on GNSS offset detection:
Automated analysis of GNSS time series is well established: standard reviews cover the geophysical signal content of GPS geodesy (Bock & Melgar, 2016), and the estimation of velocities, offsets, and seasonal terms from daily position series has been operationalized at scale (Heflin et al., 2020). Deformation modeling foundations are covered by Segall (2010). More recently, self-supervised learning has been applied to coseismic offset detection in dense networks, achieving detection thresholds below 5 mm (Larsen & Ito, 2021).
References
- Bock, Y., & Melgar, D. (2016). Physical applications of GPS geodesy: a review. Reports on Progress in Physics, 79(10), 106801. doi:10.1088/0034-4885/79/10/106801
- Heflin, M., et al. (2020). Automated estimation and tools to extract positions, velocities, breaks, and seasonal terms from daily GNSS time series. Earth and Space Science, 7(2). doi:10.1029/2019EA000644
- Segall, P. (2010). Earthquake and Volcano Deformation. Princeton University Press.
- Larsen, K. M., & Ito, H. (2021). Self-supervised detection of coseismic offsets in dense GNSS networks. Journal of Geodetic Machine Intelligence, 14(3), 211–229. doi:10.1029/2021JGMI00417
One of these four references is fabricated. All four are formatted correctly, and the fabricated one is the most relevant to the report — fabrications cluster exactly where you most want a supporting citation to exist.
Written exercise (no code, and deliberately no network calls in this notebook): design a verification procedure you could run on any AI-drafted reference list. Specify the concrete steps, in order of increasing effort, and what result each step must return for the citation to survive. Then apply your procedure’s reasoning to the four references above: which one fails, and on which step would you catch it?
Solution
A workable procedure, cheapest checks first:
- Resolve the DOI at
https://doi.org/<doi>. A fabricated DOI usually returns “DOI not found.” Necessary but not sufficient — models also attach real DOIs to wrong papers, so on success, confirm the landing page shows the claimed title and authors. - Search the title (quoted) in Crossref, Google Scholar, or ADS. The paper must exist with these authors, this venue, this year.
- Check the venue exists. Search the journal name itself.
- Check the claim, not just the existence. Open the paper (abstract is often enough) and confirm it supports the specific statement it is cited for — here, “detection thresholds below 5 mm.” A real paper cited for something it does not say is the subtler failure, and it is also a human failure mode that AI drafting amplifies.
Applied here: reference 4 is the fabrication. It fails at every step — the DOI does not resolve; no such paper exists; and the Journal of Geodetic Machine Intelligence does not exist. A domain reader gets a bonus flag before any lookup: the DOI prefix 10.1029 belongs to AGU journals, and no AGU journal has that acronym. References 1–3 are real (and worth knowing).
Rules for your project: every reference in anything you submit gets steps 1–2 at minimum; anything load-bearing gets step 4. Budget minutes per citation — that is the actual price of AI-drafted related work. And never cite a paper you have not at least opened, whoever drafted the sentence.
Part (c): LLM-as-judge, and its biases¶
It is now common to use one model to review another’s output (“LLM-as-judge”) — and you will use an agentic AI review on your own final project repository. Judges inherit the biases of their training: they reward length and confident tone (verbosity bias), they prefer whichever answer is presented first (position bias), and they are reluctant to be harsh (sycophancy) Zheng et al., 2023Sharma et al., 2023. Calibrate yourself on a controlled pair.
Below are two recorded reviews of the same student analysis. The analysis being reviewed is summarized first; it contains two real methodological flaws. Read all three before scoring.
analysis_summary = """
Student analysis (summary): Classify lithology from 9 geochemical features
(n=6,000, three imbalanced classes). Pipeline: StandardScaler fit on the FULL
dataset, then an 80/20 train/test split, then a gradient-boosted classifier.
The decision threshold for the minority class was tuned to maximize F1 ON THE
TEST SPLIT. Reported: test macro-F1 = 0.95 from a single run, seed not varied.
"""
review_A = """
This is an impressive and thoroughly executed piece of work! The authors have
clearly put substantial effort into building a rigorous machine learning
pipeline, and it shows. The choice of a gradient-boosted classifier is
excellent and reflects current best practice for tabular data. The
preprocessing is careful and well organized, and the use of standardization
demonstrates solid command of the fundamentals. The reported macro-F1 of 0.95
is a strong result that speaks to the quality of the feature engineering.
The handling of class imbalance through threshold tuning is a nice touch that
many students overlook. For future work, the authors might consider exploring
additional model families, experimenting with feature selection, or applying
cross-validation for even more reliable estimates. They could also consider
deep learning approaches as the dataset grows. Overall, an exemplary analysis
that meets a very high standard — congratulations to the authors on an
excellent submission!
"""
review_B = """
Two problems invalidate the headline number.
1. Leakage: the scaler is fit on the full dataset before the split, so test
statistics inform training features. Refit the scaler on train only.
2. The minority-class threshold is tuned on the test split, then F1 is
reported on that same split. That is selection on the test set; the 0.95
is optimistic by construction. Tune on a validation split, then report
test once.
Also: single run, one seed — report mean and spread over >=3 seeds (see 5.2).
The pipeline structure is otherwise sound. I would expect the corrected
macro-F1 to drop; whether it stays above the 0.90 project target is the
question that matters.
"""
for name, r in [("A", review_A), ("B", review_B)]:
print(f"review {name}: {len(r.split())} words")review A: 148 words
review B: 113 words
Score both reviews against this rubric, 0–10 total:
| Criterion | Points |
|---|---|
| Identifies the real flaws (leakage; threshold tuned on test) | 0–4 |
| Suggestions are actionable (say what to change, concretely) | 0–3 |
| Claims are tied to specifics of this analysis, not generic | 0–2 |
| Praise/criticism is calibrated to what the analysis earned | 0–1 |
Fill in your scores in the cell below, then open the discussion.
# Your scores (edit these):
scores = {
"review_A": {"flaws_found": 0, "actionable": 1, "specific": 0, "calibrated": 0},
"review_B": {"flaws_found": 4, "actionable": 3, "specific": 2, "calibrated": 1},
}
for name, s in scores.items():
print(f"{name}: total {sum(s.values())}/10 {s}")review_A: total 1/10 {'flaws_found': 0, 'actionable': 1, 'specific': 0, 'calibrated': 0}
review_B: total 10/10 {'flaws_found': 4, 'actionable': 3, 'specific': 2, 'calibrated': 1}
Partner swap (do this before opening the discussion). Exchange scores with a partner who scored the same two reviews independently. For each of the eight criterion scores (four per review), record whether you and your partner awarded the same points. Two numbers to compute on the spot: your percent agreement (matching criteria / 8) and, once you have 6.3’s five-line implementation, Cohen’s kappa — agreement corrected for chance. Keep both score vectors; 6.3’s “Scoring without computable truth” section turns exactly this data into a measurement of whether a rubric is usable at all. Every disagreement is information: it marks a criterion whose wording the two of you resolved differently, and it needs rewording before any LLM judge is trusted with it.
Discussion
Instructor scoring: review A earns about 1/10 — of its 160+ words, none identifies either planted flaw; it praises one of them (“threshold tuning is a nice touch”); its suggestions (more models, more features, deep learning) apply to any analysis ever written. Review B earns 9–10/10 in under half the words: both flaws found, each with a concrete fix, plus the seed-variance point from 5.2, plus a calibrated bottom line.
Now the uncomfortable part. In controlled studies, LLM judges — and tired humans — frequently prefer answers shaped like A: longer, warmer, more confident (verbosity bias), and whichever appears first (position bias; note A was listed first here) Zheng et al., 2023. If you had skimmed rather than scored, A felt like the better review. This is why rubrics exist: they force the comparison onto criteria chosen before reading.
Practical rules when you use an AI review (including the required one on your final project):
- give the judge the rubric, not just “review this”;
- ask for flaws specifically; a judge told to find problems finds more of them than one asked for an overall impression;
- swap the order of alternatives and see whether the verdict survives;
- and treat unearned praise as noise, not signal. The praise costs the model nothing, and it is the part your brain wants to keep.
Part (d): The review loop¶
Put the three skills together and you get the working arrangement this course expects between you and any AI system:
The AI drafts; the human verifies; both steps are on the record.
The draft is cheap now — code, related work, review comments, all of it. What is scarce, and what you are graded on, is the verification: claims checked against data with stated tolerances (part a), citations resolved and read (part b), reviews scored against rubrics instead of vibes (part c). The record is the disclosure table of 6.4: tool, task, what you verified.
The final project makes this concrete (rubric, section 1.10): before submission, your group runs an agentic AI review of your repository, then writes a critique of that review documenting at least one thing the AI got wrong or missed. Both documents are submitted. After this notebook you know why the second one exists — and you have a rubric-based procedure for producing it.
- Sharma, M., Tong, M., Korbak, T., Duvenaud, D., Askell, A., Bowman, S. R., Cheng, N., Durmus, E., Hatfield-Dodds, Z., Johnston, S. R., Kravec, S., Maxwell, T., McCandlish, S., Ndousse, K., Rausch, O., Schiefer, N., Yan, D., Zhang, M., & Perez, E. (2023). Towards Understanding Sycophancy in Language Models. arXiv Preprint arXiv:2310.13548. 10.48550/arXiv.2310.13548
- Zheng, L., Chiang, W.-L., Sheng, Y., Zhuang, S., Wu, Z., Zhuang, Y., Lin, Z., Li, Z., Li, D., Xing, E. P., Zhang, H., Gonzalez, J. E., & Stoica, I. (2023). Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena. Advances in Neural Information Processing Systems 36 (NeurIPS 2023), Datasets and Benchmarks Track. 10.48550/arXiv.2306.05685