Human-in-the-Loop AI for BIM: Make It Ask, Not Guess

 Part 4 of a series — a Dynamo tunnel geometry that was already finished, a 39-sheet Excel workbook it had never been wired to, the web viewer built to referee the two, and the Revit add-in at the end of it.

Two AI agents worked this job, and the division of labour between them flipped twice. What made the arrangement work had nothing to do with which one was "the implementer".

The more useful half is the second: a month in, the instruction stopped being "fix it" and became "what should I be explaining to you?"

Who did what — and why the split kept flipping

The role contract was written down up front, in English, before either agent touched the geometry.

Codex is the primary implementer and live artifact operator.
Claude Code is the second reviewer, long-context planner, architecture critic, and document-structure reviewer.

That is a clean separation and it lasted about four instructions.

The handover document that carried it is worth reading in full, because it does something most agent-to-agent briefs do not: it spends more words on what the incoming reviewer must not do than on what it should.

Codex has worked on this tunnel section viewer for weeks and repeatedly failed in the same class of issue: reports say PASS, but the geometry can still be visually or conceptually wrong against the Excel reference.

Claude's job is not to do a broad rebuild. Claude's job is to independently judge whether the current comparison actually proves Excel-reference shape fidelity, or whether it only proves numeric/report PASS.

Then the prohibitions, verbatim:

  • Do not trust PASS reports as final proof.
  • Do not scan the exports directory.
  • Do not open large payloads first.
  • Do not begin with Dynamo graph production.
  • Do not do a broad refactor.
  • Do not delete or move files.
  • Do not edit source until you first produce a short diagnosis and correction plan.

Four of those seven are guards against the same instinct: an agent handed a broken system will start rebuilding it, because rebuilding is easier than diagnosing and produces more visible output per unit of effort. The brief closes that door before it opens.

One more clause is unusual enough to quote on its own, because it removes a question the reviewer is otherwise entitled to ask:

Do not ask about whether trace or numeric labels are authoritative. Numeric Excel parameters are authoritative.

A settled question, pre-settled in writing, so it cannot be re-litigated at 2 a.m. as a way of pausing. And a companion brief adds the tie-breaker that governs everything downstream: "If a report says PASS but the image looks wrong, the image wins."

Flip one: the reviewer becomes the owner

The first instruction of the engagement opened with a sentence that reframed the whole thing:

"You are reviewing a tunnel/NATM section geometry task that Codex has failed to finish reliably for weeks."

So the nominal reviewer arrived into a job that the nominal implementer had already been unable to close. By the fourth instruction, ownership had moved outright, and the wording is worth reading slowly because it closes two escape hatches at once:

"You are now the autonomous owner of this task. Do not wait for Codex to supervise you."
"Do not hand this back to Codex. Do not say 'Codex should verify.' You must complete and self-verify using scripts and generated artifacts."

The first sentence removes the excuse of waiting. The second removes the more insidious one: deferring the verdict to the other agent as a way of never producing a verdict at all. "Codex should verify" is a sentence that sounds like process discipline and functions as an exit. Banning it forced every claim to arrive with a script and an artefact attached.

Flip two: the owner gets reviewed

Six days later it flipped back — the engineer wrote, in effect, "I'm going to have Codex review what you built; tidy up the work and organise the files so Codex can check them." That produced a review pack, and the pack named six specific review points rather than asking for a general opinion:

  1. Does the R2/R3 contract match the intent of the drawing — specifically, what is the basis for the R3 offset being 300?
  2. The directionality of the wall-fitted trim: detail foot −9546 moving to wall −9622, a shift of 76 mm. Is that the right direction?
  3. Drain clamp versus Excel trace dimension — which of the two is authentic?
  4. Where has the raw/production split been left undone?
  5. Are the validator updates unjustified relaxations?
  6. Root-cause fix recommended for the JavaScript validator exit code.

Points 1 and 2 are ordinary engineering questions with numbers attached, which is what makes them answerable. Points 5 and 6 are the ones that matter for anyone building this kind of pipeline.

Point 5 is the review question you have to write down in advance, because no builder asks it of itself: did you make the test easier instead of making the thing correct? Between the previous review and this one, several validators had been updated. Each update had a stated reason. The question is whether the reasons were engineering or convenience, and it is not a question the author of the updates can answer credibly.

Point 6 is the mechanical version of the same disease, and the review pack states the defect precisely in its status section:

44/44 validators PASS — however, some JS validators exit 0 even on failure, so the verdict must be taken from the output JSON's pass field. (The battery script was hardened, but the source was not fixed.)

Read that sequence carefully. "44/44 PASS" and "some of them return success even when they fail" are in the same paragraph. The headline number was, for an unknown subset of those 44, meaningless — and the workaround was to stop trusting the exit code and parse the JSON instead, which fixes the reader without fixing the writer. The pack says so explicitly: the source was not fixed. That is the honest form of a partial fix, and it is the reason point 6 exists as a standing review item rather than a closed ticket.

The same pack carries three gates marked "do not change": no Dynamo execution before the engineer's visual approval, globalPerfect=false stays false, completion may be declared only after Dynamo runtime verification, and the original workbook must never be modified.

What the other side left behind

Codex's own records survived too, and they are not what I expected. Its review log ran on a 15-minute cycle, and the file is 439,440 bytes across 2,545 lines — nearly half the total volume of the entire instruction-document folder. It covers roughly six continuous days.

Counted by heading, it holds 222 entries: 35 narrative sections written in prose at the start, and 187 machine entries after. Of the machine entries, 183 are automated review cycles and four are manual interventions. What happened between entry 35 and entry 36 is the interesting part: the prose collapses into a fixed field template and then repeats, almost verbatim, 158 times.

The template's recurring fields, with how often each appears across the file:

FieldOccurrencesWhat it answers
Viewer gate181Does the viewer render without console errors?
API gate154Do the QA endpoints return ready flags and zero failures?
Freshness gate121Was the report written after the change it validates?
Fresh no-legacy production payload gate119Has a legacy branch crept back into production?
Dynamo readiness gate119Is the handoff readiness report still passing?
Fresh R2 topology gate117The single most-corrected piece of geometry, re-run every cycle
Server liveness107Is the thing being measured actually running?
Claude prompt delivery101Did the instruction reach the other agent at all?
File/report inspection93File mtimes, byte sizes, presence
Current decision82Issue an instruction this cycle, or not

There is no cleverness in any of it. It is a loop that asks the same eight questions every fifteen minutes and writes down the answers whether or not anyone is reading. Two fields carry more weight than the rest.

Freshness gate catches the lie that is hardest to see. A stale report is a JSON file that exists, parses, says pass: true, and was written before the change it supposedly validates. Every human reviewer skims the verdict; only a timestamp check reads the mtime. Later this exact failure is found and named in a review — an overall green status that "only held because those latest reports were stale-green."

Claude prompt delivery is the field nobody plans for. 101 cycles logged whether the instruction had physically arrived, because it sometimes had not: one early entry records that the other agent's window had returned to a login screen, so the prompt could not be pasted or sent. An agent that is not running is indistinguishable, from the outside, from an agent that is thinking.

Here is one steady-state cycle in full, representative of 158 of them:

Codex heartbeat review
— Fresh validator rerun: PASS. validate_r2_100_arc_topology issueCount 0 with frames 66 / sides 132 / exteriorBulgeChecks 132. payload_freshness_contract PASS issueCount 0. Handoff readiness READY_FOR_USER_VISUAL_APPROVAL, technicalReadinessPass true, issueCount 0.
— API gate: the endpoint returns r2_100_topology_ready true, failureCount 0; one sheet's left exterior bulge 530.312337 mm / intrusion 0; right 725.417074 mm / intrusion 0.
— Viewer gate: console error/warn count 0.
Current decision: no work instruction issued this cycle because technical acceptance gates pass. Status remains ready for user visual approval, not globally perfect until the user accepts the visible section.

That last line, repeated across cycle after cycle of unbroken green, is the whole discipline in one sentence. Everything passed, and the loop still declined to call it done. An automated watcher that upgrades its own verdict when the tests stay green for long enough is worse than no watcher, because it converts patience into false confidence. This one had exactly two states available to it — ready for approval and not ready — and no amount of consecutive passes could reach a third.

And an early cycle, before the template existed, shows what the loop was originally built to catch:

Cycle 1 Review. Result: no completion evidence yet.
server.py: last modified 22:09, unchanged after the active task was issued.
— the expected report JSON: missing.
Decision: keep project status as NOT COMPLETE.

Two file-system facts, one verdict. No opinion about whether the work was going well.

What it actually did here
CodexInitial implementation, then watchdog — 15-minute status checks, instruction documents, catching stale reports
ClaudeImplementation owner — geometry solver, viewer, validators, self-analysis of the conversation
CrossedEach reviewed the other's output. Whoever built a thing, the other held the pass

The roles were not fixed and honoured; they were swapped whenever one side jammed. What produced the value was not the role assignment — it was that the builder was never allowed to declare its own work passed. Everything else about the arrangement was improvised. That one rule was not.

What the pipeline actually was

Six stages, each with a file you can open and a number you can count. The table first, then what each row actually contains, because the sizes are the part that surprised me.

StageArtefactSubstance
Excel dissectionexcel_drawing_registry.json + reference PNGs39 sheets / 33 section sheets / 503 image anchors / 606 drawing items / 103 shapes
Authority manifestsource_authority_manifest_latest.jsonDimension bindings 69/69 mapped, 84 independent trace rows, 6-level authority
Shared schemafull_section_schema_latest.json33 segments × start/end = 66 frames
Web viewer/api/model9 production tabs + 5 Excel reference tabs + audit tab
Dynamo payloadproduction_profile_payload_latest.json63–65 branches, 25,182 polylines, 238,596 points
Revit add-inRole map, part catalogueFull role table over 69 branches + 20 parts

Stage 1, the registry, is 1,677,115 bytes of JSON describing a spreadsheet. It was built by parsing the workbook's package XML directly for xdr: drawing anchors rather than trusting a library's shape list. Its output is not geometry — it is an index saying this image sits at this anchor on this sheet and plays this role. Five image roles were made mandatory per sheet, so that a sheet missing one fails loudly instead of quietly producing a section with a hole in the middle of its evidence.

Stage 3, the shared schema, is the pivot of the whole architecture. 33 sheets become 66 frames because every sheet describes a station interval, not a station: a start section and an end section. Get that wrong and you pair 66 frames into 65 lofts instead of 33 intervals, building a solid across the gap between one sheet's end and the next sheet's start — a solid that corresponds to nothing. The plan document lists that specific error in its stop conditions, by name.

Stage 5, the Dynamo payload, is 35,786,750 bytes. 25,182 polylines is the compact late version; the first run of its predecessor emitted 39,834 world polylines and 298,920 points, and one intermediate reached 70 branches, 65,046 polylines and 444,450 points before the review-only branches were separated out. The count going down over time is the healthy direction — annotation and helper geometry being removed from the production set rather than accumulating in it.

The handoff rule between stages 5 and 6 was one line, and it is deliberately absolute:

Consume branch polylines directly — no proxy boxes, no nearest-point reconstruction, no splines, no fallbacks.

Every item on that ban list is a thing a downstream consumer does when it receives geometry it cannot use: substitute a bounding box, snap to the nearest available point, fit a smooth curve, fall back to a default. Each one produces something that looks plausible on screen and is no longer traceable to a cell in a spreadsheet. Banning them means a bad payload has to fail rather than degrade.

Six grades of authority, one at a time

The single most useful artefact in the project was a manifest that answers, for every dimension and every anchor, one question: where does this number get to come from? Six classes, in strict order.

user_approved_interpretation > numeric_excel > excel_image_topology
  > derived_formula > generated_output > legacy_audit

1. user_approved_interpretation

The highest authority is not a measurement. It is a human decision about what an ambiguous drawing means, recorded as a decision. Exactly one anchor group carries it: r2_r3_endpoint_semantics, covering the roles R2_SOURCE_END and R3_TRIM_ATTACH. Those two endpoints cost more time than anything else in the project (Part 3 has the three-round history), and the reason they sit at the top of the ladder is that no amount of measuring the drawing resolves them — the drawing is consistent with more than one reading, and only a person can say which reading is the design.

The manifest also records that this class was not yet settled: the endpoint semantics are marked AWAITING_USER_VISUAL_APPROVAL until a named checkpoint. A top-authority slot that is explicitly empty is more useful than a filled one that guessed.

2. numeric_excel

Numbers that exist as cells. The manifest does not merely say "from Excel"; it records how the number was obtained, and there are four distinct mechanisms:

  • direct_cell — a single named cell (the R1 arc angle, the road-to-tunnel centre distance).
  • start_end_cells — a pair, because the value differs between the start and end of the interval (lane width, shoulder widths, slopes).
  • label_value_scan — no fixed address; the parser finds a text label and takes the adjacent value. Used where the workbook's layout is not stable across sheets.
  • active_cycle_max / active_cycle_min_positive — a reduction over a matrix of support-cycle rows, taking the longest bolt or the smallest non-zero spacing.

That last mechanism is where a real error hid for two months, and Part 5 covers it: taking the minimum positive spacing out of a cycle matrix gives you a spacing, but not necessarily the spacing you think it is.

3. excel_image_topology

Two anchor groups carry it: utility_drain_topology (utility body, opening, drain, ditch, cover, detail trim face) and barrier_support_topology (barrier, reinforcement, grouting, forepoling). The governing sentence is short and was violated repeatedly anyway:

Embedded images define topology and placement evidence; they are never pasted as production geometry.

There are exactly two admitted exceptions, both flagged in the manifest as image_dimension_constant: the utility cover's length (480 mm) and thickness (60 mm), which appear in the detail drawings and nowhere in any cell. They are marked as image-sourced so that anyone auditing later can see immediately that these two numbers did not come from the spreadsheet.

4. derived_formula

Values computed from higher-authority values. The rule attached to this class is the one that keeps it honest:

Derived formulas must list their numeric/image dependencies.

A derived value that does not declare its inputs is indistinguishable from a hardcoded constant, which is the exact disease Part 2 was about. Example from the manifest: the tunnel centre is road centre + signed dist_cr_ct, h_cr_ct, with the source cells named. The slab attachment level is road surface + 160, and it was cross-checked against measured values of 160.1 to 160.4.

5. generated_output and 6. legacy_audit

Anything the solver produced, and anything a previous version of the solver produced. Both are authority in the sense that they can be cited, and in no other sense. Their role in the ladder is to be ranked, so that when a generated value disagrees with a cell, the resolution is not a discussion. Legacy branches are kept because deleting your own history is how you lose the ability to explain a change — and allowed nowhere near production.

The exclusion list

The manifest names four authority classes that are forbidden in any production solid group, and the list is broader than the ladder's bottom three:

legacy_audit · review_only · proxy_geometry · default_fallback

default_fallback is the interesting entry. It is not a source of information at all; it is the absence of one, wearing a value. A geometry that came from a default is geometry with no provenance, and the manifest treats that as disqualifying rather than as a minor quality issue.

What the manifest said about itself

The generated manifest reports 69 of 69 dimension bindings mapped, 84 independent trace rows retained, 33 active section sheets, and 5 of 5 required image roles present. It also reports:

Status: FAIL
Issues: 1
  DIMENSION_TRACE_NOT_PASS  issueCount: 528

Full coverage, and a failing status, in the same document. The 528 is a separate problem — dimension trace rows that collapsed to points, covered in Part 3 — and the manifest declines to average it away against the coverage figure. A document that reports 69/69 and FAIL on adjacent lines is doing its job. The temptation in every generated report is to summarise, and summarising is exactly how a specific failure becomes a general "mostly fine".

One honest inconsistency: the master plan document lists the authority order as five levels, folding derived_formula and generated_output together as "generated schema/payload as derived output". The manifest and the builder script both use six. The builder's AUTHORITY_ORDER constant is the one the code actually reads, so six is operative — but the two documents were never reconciled, and I have not verified which was written first.

Why there was a separate web viewer

The web viewer in full. Workbook counters on the left, a tabbed compare surface in the middle, and a generated-geometry audit column on the right — this is why a separate viewer existed at all.

The current web viewer is being used as a controlled mirror/test bed before the same schema is delivered to Dynamo.

Building a browser viewer for geometry that is ultimately destined for Revit looks like a detour. It was the single highest-leverage decision in the project, for four reasons that the planning documents state directly.

1. A comparison surface was needed, and neither target had one

Gate 8 of the master plan requires "Source / Generated / Overlay / Diff plus synchronized controls" — four renderings of the same section on screen simultaneously, with section selection, role display and zoom all locked together. Gate 6 requires a closed-region check across "66 frames / 132 sides, no open/self-intersecting/proxy regions". The Dynamo canvas has no such screen and Revit's does not synchronise against a scanned source image. Building the comparison surface was not a nicety; it was a precondition of two named gates.

2. To put the approval gate in front of the expensive tools

The plan runs fifteen gates, 0 through 14. Their order is the argument:

GateScope
0–1Baseline freeze; source authority manifest
2–3R1/R2/R3 contract (Checkpoint A); code / schema / payload convergence
4–7Independent image calibration; lower assembly rebuild; closed region topology; review datum payload
8–10Viewer review surfaces; false-pass prevention; 2D user approval (Checkpoint B)
11Longitudinal 3D solids (Checkpoint C)
12–13Dynamo runtime; Revit current-payload proof (Checkpoint D)
14Cleanup and final evidence package

Everything through gate 10 is achievable in a browser. Dynamo does not appear until gate 12 and Revit not until gate 13, both behind an explicit precondition of visual approval. The plan also states the rule that makes the ordering enforceable rather than aspirational:

Automated gates may report READY_FOR_USER_VISUAL_APPROVAL. They may not report global perfection before user approval.

And separately, in the gate-2 detail: technicalPass=true does not unlock downstream work; only a recorded user approval flips downstreamAllowed to true. Two variables, deliberately. The machine gets to say "I found nothing wrong". It does not get to say "this is right".

3. Round-trip cost, measured

Regenerating every frame in the viewer takes 147–155 seconds. The June server logs give the scale of the loop this was feeding: 2,119 HTTP requests across 164 server starts in eight days, of which 904 were GET /api/model, mostly full rebuilds. One day contained 76 redraws. At 150 seconds a rebuild, 904 rebuilds is roughly 38 hours of pure regeneration, and any per-cycle cost increase multiplies against that number. That is the whole economic case for the mirror. The limit of the claim, recorded honestly: the viewer's regeneration time is measured; the Dynamo-side comparison time never was, so "it would have been slower" is an inference from file-open cost, not a benchmark.

4. Failures become numbers

Viewer verdicts land as JSON that a validator can read, which converts a sentence into a measurement — "the screen looks wrong" becomes "p95 18 px". That difference is what let the pocket-snapping mistake (Loop B below) be detected and reverted the same day rather than argued about. A metric that collapses from 8 to 18 is not a matter of opinion.

The limit, stated

Viewer pass did not mean Revit pass. Polylines judged closed in the viewer were later rejected as self-intersecting by the Revit loft — a defect that survived six weeks of green gates precisely because the viewer draws a self-intersecting polygon as a perfectly ordinary shape. The mirror caught everything it was capable of catching, and its incapacity was invisible from inside it. Part 6 is the bill for that.

Pixels to millimetres — and the discovery that killed the approach

Pixel-to-millimetre calibration: a 25 px grid laid over the left utility detail so raster distances could be converted to real dimensions.

The same calibration on the right detail. It works — until the drawn symbol and the written dimension disagree, at which point the conversion is measuring the wrong thing.

The first month was one question: how do you turn a drawing image into coordinates? The answer was a line detector, and it is worth writing out in full because its limitations are legible from the parameters alone.

  1. Keep only pixels whose R, G and B are all below 90 — a dark mask.
  2. Extract runs of consecutive dark pixels along rows and along columns, accepting runs of 22 px or longer.
  3. Merge adjacent runs across gaps of under 3 px, producing segments with a thickness and a length.
  4. Discard anything shorter than 34 px. Segments of 70 px or more are classified as long lines.

Read those four steps and one consequence follows immediately: it cannot detect curves. Row runs find horizontals; column runs find verticals; a diagonal or an arc produces a scatter of one- and two-pixel runs that never survive the 22 px threshold. The arches — the entire R1/R2/R3 family, which is the actual subject of the drawing — were unreachable by this path from the first line of code. Measured output on one section: 753 × 643 px yielding 74 segments and 69 geometry candidates on the left, 761 × 636 px yielding 72 and 63 on the right. Good numbers for a straight-line extractor, and not one of them is an arc.

Two rulers

Converting pixels to millimetres used two independent methods, deliberately:

  • Four anchors. Four pixel coordinates on the utility body outline, paired with four known Excel dimensions, solving for a transform. Robust, but it fits a rectangle's worth of the image.
  • A single scale from the D300 drain pipe. One circle of known diameter used as a ruler. Cruder, and independent of the first — which is the point. A single calibration method cannot detect its own error.

Gate 4 of the plan constrains this further: "one transform per image, holdout residuals, no per-role fitting". Fitting a separate transform for each part you are checking is the most natural way to make every comparison pass and the most complete way to destroy the comparison's meaning.

The finding that ended the approach

Then the two rulers disagreed with each other, and the disagreement had structure:

The original drawing itself has different horizontal and vertical scales. Anisotropy 5.81 %.
Measure the R1 arch as a circle and the radius is 339 px; measure it against the numeric axis and it is 319.31 px.

Twenty pixels of disagreement on a radius, in a drawing where a pixel is several millimetres. This is not noise, and it is not a bug in the detector. The drawing was not drafted to scale. It was drafted as a symbol — a diagram that communicates arrangement to a human reader who will take the numbers from the annotations, stretched or squeezed to fit its box on the sheet.

Once that is true, measuring the image has a hard ceiling that no amount of better image processing raises. So the principle hardened into two sentences that governed everything afterwards:

Images are evidence of topology and placement. Numbers come from cells.
Silently overwriting a cell value because an image exists is forbidden.

Note the word silently. The rule is not that an image can never win; it is that an image winning must be a recorded decision with a name attached, which is precisely the user_approved_interpretation slot at the top of the authority ladder. The ladder exists because of the 5.81 %.

The turn — making the AI ask

Question 2: the AI points at a specific step block and quotes the Excel variable name alongside it, rather than choosing an interpretation on its own.

Question 4 as the viewer shows it — the slab tail meeting the drain pocket, with the current flat-cut end marked.

The same question against the source drawing: the slab tail P18-P19 boxed, with a note that it reaches the end face. Question, viewer evidence, source evidence — the three arrive together.

Question 5: left and right R2/R3 ends measured and printed side by side (R2 3,959 / R3 4,609 against R2 3,756 / R3 4,414). The asymmetry was found by measurement, not reported by a human.

Question 7: the band where the red and purple offsets converge, boxed on the drawing. The source geometry interferes with itself here — the tool asks rather than silently trimming.

Supporting zoom for the left lower corner, with the arc radii labelled in place.

The matching drain-pocket zoom. Each adjudication question ships with the crop a reviewer needs to answer it in a few seconds.

About a month in, on 14 July, an instruction arrived that was different in kind from the 105 before it:

"We keep repeating the same mistakes and the same instructions. Re-analyse this conversation yourself. And let's work out what details I should be explaining better. 1. Export the whole conversation to TXT. 2. Make a TXT mapping my instructions to your actions. 3. Build explanatory material to support deeper understanding."

Read the second sentence again. Not "fix yourself" but "what should I be explaining?" The engineer did not locate the cause solely on the AI's side, and that framing is what made the exercise produce something. An instruction to "stop making mistakes" produces an apology. An instruction to find out what information was missing produces a list.

Re-reading 106 instructions

Three artefacts came out: the full transcript (506 KB), an instruction-to-action mapping (63 KB), and a briefing document (11 KB). Over 07-03 to 07-14 there were 106 instructions, of which 27 (≈ 25 %) were corrective by keyword search (why / again / no / wrong / mess).

The diagnosis printed under that number mattered more than the number:

Corrections about the validators, the raster pipeline, or the parametric pipeline themselves are almost absent. The problem is not coding capability — it is missing information at the drawing-interpretation stage.

That is an unusual finding to write about yourself, and it is the reason the fix that followed was a vocabulary and an authority table rather than more tests. Re-reading all 106 and re-classifying them by hand produced a breakdown the original briefing did not contain:

TypeCountShareRepresentative
D. Corrective / rejection3735 %"why did you regress", "that's not it", "what did you supposedly fix?"
E. Geometry instruction (new work)1716 %"R3 starts from the point offset from R1"
B. Proceed / continue1413 %"carry on"
A. Policy / authority declaration109 %"Excel numeric cells are the production source of truth"
F. Question109 %"is there a separate Excel dimension for R2 and R3?"
C. Show me the screen98 %"bring it up so I can check"
H. System noise55 %
G. Meta / organisation44 %"I'm going to have Codex review your work"

The original 27 counted only keyword hits; the wider count of 37 also takes in the rejections that contain none of those words — refused completion reports, "the view is a mess" complaints and the rest. Neither number is the correct one; they count different things. Either way, the ratio that matters is D against E: instructions telling me to do it again outnumbered instructions giving me work to do, two to one.

The other striking row is type C, nine instructions, all the same demand in different words: show me the screen, not the report. Nine separate times, the engineer declined to accept a verdict and asked to look at the thing. While the AI announced PASS, the human asked to look. Every one of those nine is a small vote of no confidence in the gate, and in retrospect the votes were correct — the 21 % band defect described in Part 6 was passing every gate in the suite at the time.

Five repeating failure loops

Loop A — the R2 endpoint, three rounds. The single largest loss in the project. The same endpoint was decided three times across versions v6, v7 and v8, and the first two were wrong for two compounding reasons. First, a marked-up image could not be resolved into "a feature point of the original drawing" versus "a marker the viewer itself had drawn in a previous version" — the AI was being shown a picture that contained both, with no convention distinguishing them. Second, one rule was applied to both R2 and R3 when their termination rules are genuinely different in kind: R3 ends at a fixed drawing feature (a purple toe point), while R2 ends at a constructed intersection whose height varies from station to station. Treating a rule as a feature point produces geometry that is right on one sheet and wrong on the other 32. The v6 attempt produced a lining band that self-intersected and was scrapped entirely. (Part 3 has the round-by-round detail.)

Loop B — pocket cell snapping. The instruction was, roughly, "the green part below has to meet the slab and resolve cleanly". This was read as "move the member", so the sump pocket's vertices were snapped to Excel cell coordinates — a defensible-sounding action that treats the spreadsheet as authoritative for a shape whose authority was explicitly the original trace. The right-side raster metric collapsed from p95 8 px to 18 px and the change was reverted. The intent had been "a connection must exist between these two parts", and the drawing already had the connecting segment drawn on it — a separate short member, later named DRAIN_SHAFT. The correct action was to add a part, not to move one. Nothing about the instruction distinguishes those two readings, which is the entire point of the one-sentence format below.

Loop C — views disagreeing with each other. The main panel drew the new geometry. The inset panels still drew the previous generation's concentric offset bands. Full Compare overdrew the old closure on top of the new one. From the engineer's chair there was no way to tell which view was true, and the reasonable response — "which one of these is real?" — consumed a round trip that had nothing to do with tunnels. The cause here was not drawing interpretation at all; it was a missing checklist. When geometry changes, six surfaces must change with it, and nothing enforced that. So the six were written down as a fixed list: main panel, both insets, Full Compare, the partial assembly view, the 3D group, and the cache-buster version string. The last item is not decoration — a browser serving a stale app.js reproduces this exact symptom with no code change involved at all.

Loop D — ambiguous referents. "The shape below." "The green part." On a screen showing a slab notch, a sump pocket, a filter layer and a drain pipe within the same 200-pixel neighbourhood, these phrases have four candidates. The AI guessed, and was wrong about half the time. Half is roughly what you would expect from an informed guess over four candidates with some context, which is another way of saying the context was carrying the entire load and the words were carrying none.

Loop E — review layers mixed into production. The brown clump visible in the 3D view was barrier hatch strokes — annotation, extruded. The minimum clearance envelope, which is a constraint volume and not a member, shared a screen and a colour scheme with the concrete. The engineer had to ask "is this a member or a dimension?" about the AI's own output, which is a question the output should have answered by construction. This is the same disease as Part 3's drain cradle: a drawing contains two categories of line, and rendering them identically transfers the classification burden onto the reader.

A, B and D are missing information; C and E are entirely the AI's fault. The briefing drew that line itself, and drawing it honestly is what made the remedies different: A, B and D got a vocabulary and an authority table, while C and E got a checklist and a layer-separation validator.

The member dictionary — 14 rows

Viewer branchDefinition
R1 / LININGInner lining arch — R1 100° about the arch centre, plus side-wall R2 arc
R3Outer (back-face) lining arch — Excel radius, to the purple toe point
R123_CLOSURE_CHAINSide-wall bottom closing line: R2 end ↔ R3 toe
LINING_SURFACEConcrete thickness band — closed polygon from the R family
UTILITY_BODYConcrete block outline beside the road
UTILITY_OPENINGRectangular opening inside the body
SLAB_SURFACERoad slab, notch included; end face = slab end
ROAD_LAYERS_SURFACEPavement / filter layers above the slab
DRAIN_PIPE / DRAIN_CRADLEPerforated pipe (D300) plus 50 mm cradle
DRAIN_POCKETSump pocket — six-point closed ring, trace is truth
DRAIN_SHAFTSump-to-slab connection: inner notch corner ↔ pocket knee
DRAIN_START / P20 datumSlab-end calibration datum — must not move
FOOT_DRAINToe weep hole (D50) — circle tangent above the R3 toe
BARRIERBarrier / kerb, hatch included (filtered in 3D)

Three of those rows carry a note that is not a definition. DRAIN_POCKET says "trace is truth" — Loop B's scar tissue, written into the dictionary so the same snap cannot be proposed again. DRAIN_START / P20 says "must not move" because it is the calibration datum; moving it silently re-scales every raster comparison downstream of it, turning a verified pass into a meaningless one. And DRAIN_CRADLE is listed alongside the pipe as though it were a solid, which Part 6 later proves it is not — it is a triangle of construction lines. The dictionary was right about 13 of its 14 rows and confidently wrong about one, and it stayed wrong until someone looked at the screen and asked about a yellow triangle.

Under the table sits the line that shows the dictionary's true character: a request to confirm that the terms marked "assumed" match real site vocabulary. That request is still unanswered, so those part names remain unverified.

The arithmetic for why any of this pays: "the shape below is wrong" has four candidates, so a guess is right 25 % of the time and a miss costs a full round trip. A member name reduces the candidates to one. The cost is a single word.

The authority table

The briefing claimed that one table "explains half the misunderstandings in this session": a per-region assignment of primary authority. R1/R2/R3 radii are Excel cells. The R2 endpoint is a rule — the intersection of the body's top outline with the top of the R2 circle — with no dimension anywhere, which is why its z varies from 396 to 612 across stations. The R3 endpoint is a drawing feature point. The slab attachment level is a derived rule, road surface + 160. The pocket ring is the original trace with cell snapping forbidden. The weep hole and clearance are drawing literals. The opening's width and height are an Excel cell in open disagreement with the drawn symbol — at the time the table was written, still awaiting adjudication.

The useful observation is that six different kinds of authority appear in nine rows of one section. A blanket policy — "Excel is the source of truth" — is correct as a default and wrong in four of those nine cases. Worse, which cases are exceptions is not derivable from the drawing. The exception list is knowledge held by the engineer, and no amount of careful reading recovers it.

When you take on a new region, just attach one of "this is a cell / this is as-drawn / this is a rule (what + how much)" and it goes in one pass.

Part 3 has the table in full, with its evidence and status columns.

Seven adjudication questions

The briefing ended with seven questions put back to the engineer — on the opening's cell-versus-symbol conflict, the name of an unidentified block, an unmatched "channel 100×500" marking, the slab tail geometry, the R2 endpoint's permitted range, an unreachable-intersection case, and a ~10 mm pinch present in the original drawing.

What matters here is only their shape, because the shape is the transferable part: none of the seven says "I don't know", each leads with how far the work got and how the ambiguity is being handled in the meantime, most offer exactly two options so the answer fits in one word, every one carries evidence images (thirteen across the seven), and not one of them blocks the work — each carries an interim treatment so the pipeline keeps running while the answer is pending. All seven were answered the same night. Three of them nonetheless left something open: two came back with a construction rule instead of the name that had been asked for, and a third closed the authority question while opening a reconciliation that was never given. Part 8 takes the seven apart individually, answer by answer, including what each answer did and did not settle.

One-sentence instructions and a markup convention

[which member]'s [which property], to/into [which target] + (authority: cell / picture / rule)
Actual instructionWhy it went that way
Good"the current endpoint is probably right for r3's termination. change r2's termination, refer to the image"Members named separately — done in one pass
Good"I said the R2 endpoint has to come beside the top of the [utility body]"Member, property and reference target all present — this settled v8
Bad"the detail below is a mess"Member, property and target all omitted
Bad"why is the geometry going wrong?"Which geometry is missing
Bad"the green part below. it has to meet the slab and resolve cleanly"Member given by colour, but "meet" is undetermined between move and connect → Loop B

The third failure is the instructive one, and it is the reason the format has an authority slot rather than just a member slot. The member was identified — by colour, but unambiguously — and the instruction still went wrong, because the verb was ambiguous. "Meet" can mean move this until it touches that or add whatever connects them, and those two readings produce different geometry from identical words. Naming the member removes the referent ambiguity and leaves the action ambiguity untouched. That is why one word of authority has to travel with the member name.

The markup convention is three lines, and each line exists because its absence caused a specific loss:

  1. One word saying whether the mark refers to the original drawing or to something the viewer drew. (Loop A, round one.)
  2. Fixed colour meanings — red = wrong, blue = target position, green = reference. The colours had been varying between rounds, so their meaning was being re-inferred every time.
  3. If two members are entangled in one mark, write the member name on the arrow.

Did it actually work?

INDEPENDENT_RASTER_MISMATCH — visual approval blocked. The tool refusing to pass its own output.

A second refusal with a different reason: EVIDENCE_NOT_INDEPENDENT. The block distinguishes 'the shape is wrong' from 'the evidence does not count'.

Confirmed: the seven questions unblocked things overnight. Answers came back the night the briefing went out, and the v9 re-contract ran before dawn.

  • Q2: the 150 block is embedded in the lining and its start sits above the R2 end → settled as "R2 terminates at the grouting level"
  • Q5: the R2 endpoint's variation is normal; no limit check needed
  • Q6: resolved automatically by Q2's level decision — hard failures demonstrated gone across 1,452 variation cases
  • Q7: the same change made the band width monotonic, 300.0 → 488.9 (left) and 300.0 → 548.5 (right), and the pinch disappeared

One answer cascaded through Q5, Q6 and Q7. More than a month of round trips resolved in a single night — not because anyone worked harder, but because the question was finally constructed so that a one-line answer could land on it.

Not verifiable: whether the correction rate actually fell. Three separate reasons, and none of them is repairable after the fact:

  • There is no follow-up measurement. The 106 and the 27 came from a transcript extraction on 14 July that was never repeated. No comparison target exists.
  • The session log is in different units. 49 session-level records, not instruction-level. 24 of the 49 (≈ 49 %) name an incident or defect in the title, and it is tempting to set that beside the 25 % and announce a doubling. That comparison is invalid; the denominators are different objects.
  • The session log is self-narrated and biased by design. 39 of 49 (80 %) contain failure vocabulary — not because failures rose but because the log exists to record what went wrong and how it was caught. And 32 of the 49 fall on a single day, so it does not form a time series either.

What can be said is a change in character, not rate. Through mid-July the mistakes were "misread the drawing". By late July, in the add-in stretch, they were "reported something as done that was not" — patches that never applied, gates that passed without checking the instruction's actual condition. That is not obviously an improvement. It is a different failure mode, and it needed a different countermeasure, which is why the verification doctrine described in Part 6 exists.

One more honest note. The adjudication table is still empty. The answers were given verbally and reached v9, but the document's "answer" column was never filled in, and two of the seven are still recorded as blocked — with a third unresolved on the substance. Getting an answer and pinning it into the document turned out to be two different jobs, and only the first one got done.

What's next

The next part goes down a level: the raster gate that turns "looks about right" into a number — and then spends most of its life proving that its own failures were not what they looked like.


Tunnel automation series — nine parts.
← Previous: Dimension Lines vs Real Edges: 9 Cases an AI Got Wrong
Next: Validation Gates That Always Pass: Add a Negative Control (not published yet)
Start of the series: Dynamo Built the Whole Tunnel and Never Read the Excel File

댓글

이 블로그의 인기 게시물

Structural Analysis Workflow with Dynamo and Robot

Dynamo with the Gemini Vision API test(Nano Banana)