I'm Siddharth Notani — an Applied AI Engineer at Apple. I built this for the Applied AI Engineer role at OpenAI in London.
I built a legal-AI contract-review agent, then broke it on purpose. Here's what failed, why, and the fix.
correct answers on amended contract terms, after one retrieval fix
questions per eval run, generated from a seeded synthetic corpus
predicted failure modes reproduced — a second was my own grader lying, and I caught it
The setup
I reimplemented the workflow archetype behind legal-AI products — retrieval-augmented contract review, the category Harvey defined (OpenAI's Startup Fund backed their seed, and they co-developed a custom case-law model with OpenAI). Contract review is a fair stand-in for enterprise deployment generally: the corpus is messy and versioned, every answer has to be auditable back to a source, and being confidently wrong has legal consequences. My version is the honest first-pass implementation an organisation actually gets into production — BM25 retrieval over clause chunks, an LLM (called through the OpenAI SDK) that must cite the clause it relied on — not a strawman built to fail. I generated a synthetic contract corpus with three traps built in, wrote one programmatic grader per failure mode, and measured the failures a naive implementation of this archetype exhibits — the ones mature platforms have had to engineer away. Every eval ran twice: once against a frontier model, once against the cost-tier model anyone rolling this out at volume will eventually be asked to justify not using.
OpenAI SDK harness · exact model strings pinned in the committed results JSON · 204 questions per run · corpus seed 42 · graders are deterministic string checks, no LLM judges · both models score 98% on 65 control questions, so the failures below aren't noise · every number on this page is rendered at build time from committed results JSON
1 · Stale-amendment retrieval reproduced on both models
When a later amendment supersedes a clause, the agent answers from the original — retrieval surfaces both versions and nothing tells the model which one is law.
This failure survived the frontier model, because it isn't a model failure — it's a context-construction bug. The fix is in the index: apply amendments at ingestion so retrieval only ever sees the effective state of each contract.
- chunks = every clause of every document, indexed independently
+ for each amendment: parse "Clause N ... deleted and replaced",
+ substitute the amended text into the base clause, and index
+ only the effective clauses (agent/index.py, resolve_amendments)2 · Citation grounding did not reproduce — but the eval nearly lied to me
Every contract contains near-duplicate clause pairs differing in one material term — a general liability cap next to a data-protection one — to bait the agent into citing a clause that doesn't support its answer. My first grader scored the cost-tier model at 68% on these (49/72) — an apparent reproduction. It was the grader that was broken: the model cited real, correct clauses in a slightly different format ("[MSA-01 Clause 13] Confidentiality" instead of "MSA-01 Clause 13"), and my exact-match citation resolver scored every one as ungrounded. With citation matching made fair, the true rates are 66/72 (cost-tier) and 68/72 (frontier), and not one of the remaining misses is an ungrounded citation — they're retrieval recall, where the model correctly declined to answer. I'm reporting that instead of shipping the fake failure.
That's the deployment lesson: the eval is part of the system. A grader artifact can manufacture a failure mode as convincingly as the model can, and the first job is telling them apart — which is why every grade in the repo is a deterministic check you can re-run against the committed raw output (python -m evals regrade).
3 · False presence did not reproduce
I expected confident answers about clauses that don't exist, and made the trap as hard as I honestly could: questions presupposing a clause that's absent from the target contract but present, near-verbatim, in other contracts in the corpus — so retrieval returns a plausible clause from the wrong agreement. Both models declined to take the bait on 43 absent-clause questions each (frontier 42/43 — the single miss was an empty response, not a fabricated clause — cost-tier 43/43), without any abstention instruction in the prompt. I'm reporting that rather than weakening the baseline until it broke. Where I'd probe next: multi-hop questions, and corpora where the distractor shares the party names rather than just the clause language.
What I'd do in month one, embedded with an enterprise deploying this
First, sit with their actual traffic and find the five question shapes that matter commercially, because failure modes are properties of the corpus and the questions, not just the model. In an enterprise that means doing it inside their constraints from day one — their data residency, their retention rules, their review process — because a prototype that can't survive security review isn't a prototype, it's a demo. Then build exactly this harness around their workflow: seeded data that encodes their hard cases, one programmatic grader per failure mode, a smoke run cheap enough to run on every prompt change. Fix the worst mode the way I did here — in retrieval and context construction before touching the prompt, because that's where the durable bugs live. Leave the eval running in CI so the next model swap is a measured decision rather than a vibe, and so the number that goes to the risk committee is reproducible by someone who doesn't trust me. Then write the one-pager that lets a general counsel, a security lead and an engineering manager argue about the same numbers instead of their own anecdotes — because in an enterprise, adoption stalls on the stakeholder you didn't bring evidence for.
Synthetic corpus, agent, evals and raw results. Every number here traces to a committed run.