Anthillo /blog

Two AIs, One Document: How an Adversarial Review Caught a 45x Error in Our CDR Analysis

Anthillo Team

A hundred-page analysis document, fifth revision, two weeks before the client call — that’s exactly when it’s easiest to miss a bug, because you’re reading your own prose, not the code. So before sending the final version, we didn’t ask a teammate for review — we asked two independent AI models. Working separately, both flagged the same two serious defects.

Context: the pipeline we were reviewing

This is a private LTE450 network serving mostly IoT/SCADA devices — energy meters, controllers, telemetry concentrators — for an energy-sector company. The pipeline pulls binary “charging” files from network gateways, decodes ASN.1/BER records with our own C parser (zero-copy, zero-malloc, ~800 lines of code), loads them into a columnar analytics database, and runs a cascade of aggregations — hourly, daily, weekly/monthly — orchestrated in Apache NiFi. Target volume grows from roughly 14 million records/day today toward roughly 300 million/day over the next few years.

One number worth keeping: our C parser hit about 298k records/s in benchmarking, against 75k for the best alternative (Java with the jasn1 library), and 71k / 54k for the other two variants (C with asn1c, and a hand-rolled Java BER parser). That architectural decision was made on numbers, not gut feel.

Why we ask AI to review our own work

The analysis phase on a project like this produces roughly a 100-page document that goes through several client revisions — five, in our case, over about five months. By the fifth pass over the same text, an engineer reads it differently than the first time: they know it by heart and stop questioning assumptions baked in from version one. Rather than relying only on human peer review (which we also do), before sending the final version to the client we asked two independent models — Claude and Codex — to critically review the same document, with the same instruction: find bugs and inconsistencies. Each got the text separately, with no visibility into the other’s output, and the results were recorded in two independent reports.

The same bug, found twice

Working independently, both AIs landed on the same contradiction: in one place the document described the retention window for the daily aggregate table as 48 hours; elsewhere, as 3 months. It read like a typo, but it fed directly into disk-sizing estimates — a parameter the client’s infrastructure team cares about, since database licensing and storage budget get planned years ahead. Under the 48h assumption, the projected table size by late 2029 came out to roughly 1.7 GB. At the actual 3-month retention, it was roughly 74–80 GB. A 45x error in one direction.

The second defect both models flagged independently was in the MERGE statement that loads batch data. The document’s process description declared idempotency — rerunning the same job shouldn’t change the result. The SQL itself did something else:

merge_daily_aggregates.sql
MERGE INTO cdr_sessions_daily AS t
USING staging_hourly AS s
ON t.session_date = s.session_date
   AND t.apn_id = s.apn_id
WHEN MATCHED THEN UPDATE SET
    -- bug: adds to the existing value instead of overwriting it
    record_count = t.record_count + s.record_count
WHEN NOT MATCHED THEN INSERT (session_date, apn_id, record_count)
VALUES (s.session_date, s.apn_id, s.record_count);

Run once, the result is correct. Run again — after a failure, a manual rerun, or a catch-up job following a file-transfer outage — and the record count doubles. That’s the exact opposite of the property the document claimed the system had.

Takeaway. Two different models, run independently on the same material, with no visibility into each other’s answers, flagged exactly the same two defects. That’s not proof AI “understands” the system — it’s proof that certain classes of bugs (a contradiction between two sections of a document, a stated property that doesn’t match the code) are mechanical enough to be worth handing to a machine before an engineer loses the distance needed to see them.

Codex went deeper: a business-logic bug

Beyond the two shared findings, Codex caught something Claude didn’t flag — a semantic bug in the session-closing condition. The document filtered records with causeForRecClosing = 17 (an exact match against a single value), while the client’s specification implied >= 17 — a range of cause codes, not a single one. The difference wasn’t cosmetic: in the sample data it changed the reported count of normalRelease closures from 4 down to 2. This kind of bug doesn’t show up as a typo or a textual contradiction — you have to cross-reference a business rule against a specific domain specification to catch it. It needed more context than comparing two paragraphs, and only one of the two models caught it.

What stays with the engineer

Neither AI proposed a fix priority or weighed the business risk of each bug against the send-to-client deadline — that’s still a team call. Both reviews needed manual verification: some findings were cosmetic, or came from misreading the document’s context rather than a real defect. Implementing the fixes — switching the MERGE to overwrite semantics, reconciling the retention numbers, correcting the condition to >= 17 with the reasoning written into the document — took the team four days and went through the usual human review before it shipped. The net effect: an adversarial review from two AI models didn’t replace the engineer, but it caught exactly what’s easiest to miss on the fifth pass over your own text — and it caught it before the client did.