Ledger: catching the agent error that scored 0.93 confidence

Role: Product designer, research to prototype · Team: Solo project. I did the research, design and build, using Claude Opus 5 to produce the documents and the code against my direction. · Timeline: August 2026, commits from 24 to 27 August · Year: 2026 · Domain: Healthcare procurement

TL;DR: A concept review layer for medicine procurement: AI agents do the work, humans review it, and autonomy is gated on what each act invented, not on confidence.

A concept review layer for medicine procurement: AI agents do the work, humans review it, and autonomy is gated on what each act invented, not on confidence.

Impact

Summary

A concept review layer for medicine procurement: AI agents do the work, humans review it, and autonomy is gated on what each act invented, not on confidence.

My contribution

Outcome

Key decisions

1. Hide the confidence score and show where each value came from: read, derived or assumed.

Instead of: A confidence column, badge or translated score, the signal the agents already emit.

Because: In the log, the wrong decision scored 0.93 and a glare-damaged photo scored 0.41 and was only hard to read. The reviewer knows pack sizes cold but has no basis to judge a score.

Result: The word confidence appears on no screen. An Inputs column reads 1 assumed on the wrong decision, which a reviewer can check against the document.

2. Gate autonomy on each act with three questions: did you invent an input, who does this land on, what would it cost to undo.

Instead of: Autonomy granted per agent, as the log did, or an authority ladder with spend thresholds.

Because: Per-agent autonomy let a normalisation step invent its divisor and run alone, while a $0 catalogue match waited for a human. No approval hierarchy was ever stated, so a spend ladder would have been an invented org chart.

Result: All 13 records resolve into four verdicts. The bad decision stops because it invented an input, which was knowable at the time.

3. Order the queue by irreversibility, then buyer visibility, then exposure, then age.

Instead of: Sorting by money, the default for a procurement tool.

Because: Sorting by value puts a $448k award that waits until Thursday above a $0 supplier email leaving in twelve minutes.

Result: The console leads with what is about to become permanent. Amount is a filter, never a sort order.

4. Catch the bad decision with three independent triggers that work from the record alone.

Instead of: Reading the log's known_error and how_it_surfaced fields, or relying on a reviewer opening the right record.

Because: Those two fields are the answer key. A design that reads them has been told, not caught anything. Today the error was noticed by accident, two days later.

Result: Two triggers are built. The unread-evidence audit is specified, not built, and labelled that way on screen. Correcting the error re-opens the award it fed.

5. Cut a derived reliability score after it was built.

Instead of: Shipping a ninth console column that scored each decision from checkable facts.

Because: Three independent reviews, told to default to refusing it, all refused. The $448,000 award scored clean on the proposed inputs, so the column would have inverted the queue on the exact case it existed to catch.

Result: Only the one new input survived, as a Rests on column that points at the decision a value came from.

What was unclear at the start

The starting material was one synthetic working day: 13 decisions from seven procurement agents that read supplier documents, match products to a catalogue, normalise prices, check compliance and split awards. One decision was wrong. Nobody had reviewed it, and it had already corrupted a $448,000 award recommendation.

What was open was where the line sits: which acts an agent may take alone, and how a reviewer spots a bad one fast. The log offered confidence scores as the obvious answer. My first move was to test that answer against the data. The wrong decision scored 0.93. A phone photo with glare across the price column scored 0.41 and was merely hard to read.

That narrowed the problem to one sentence: a reviewer cannot tell an agent that checked from an agent that guessed, because confidence is highest in the second case. Autonomy was also granted per agent, not per act, so the error ran unreviewed while a $0 catalogue match waited for a human.

Process

Read the log before designing

I started with the data, not the screens. Reading all 13 records gave six findings, each checkable against the log. Confidence tracks detected ambiguity, not correctness. The reversible flag lies. Exposure sits on the wrong record. Lineage already links decisions and nothing reads it. The agent_question field is empty on exactly the five decisions taken without asking. That absence became a signal.

Define the reviewer, cut the org chart

I defined the reviewer as a procurement operations analyst: deep knowledge of pack sizes, incoterms and lead times, and no basis to judge whether 0.72 is a good score. That asymmetry became the design spine. Writing it down also killed an authority ladder with spend limits. No approval hierarchy was ever given, and designing to an invented one is the failure the product exists to catch.

The reviewer's uneven knowledge, all of it stated as an assumption. Reconstructed from the reviewer definition document.

Gate on the act, not the agent

Fourteen How Might We questions and two Crazy 8s rounds led to the gate: three questions asked of every act. Did it invent an input? Who does it land on? What would it cost to undo? Four verdicts follow: acts alone, acts and tells someone, acts on a hold, or must stop. A first version with four vectors produced mush. The three questions resolved all 13 records.

Explore on artboards, correct against the data

I explored the console on HTML artboards before building. The passes named five row types, retired Confidence and Risk as columns, and replaced them with Inputs, Reach and Undo. Two reversals came from checking the data. An invented reference format gave way to the log's own tender reference. A swap from award to RFQ was undone by counting: award appears four times in the log, RFQ zero.

Make every input legible

Each decision page leads with the verdict, then what changes if it stands, then what it rests on. Every field says whether it was read, derived or assumed, with its source one click away. The reasoning is split in two: why the agent concluded what it did, and what it did not check. Eight review shapes replace one template, because a currency dispute is not a catalogue merge.

Catch the error without the answer key

The log includes fields that mark the wrong decision and describe how it surfaced. I never imported them. Three independent triggers work from the record alone: consequence out of proportion, peer price deviation and unread evidence. The unread-evidence audit is specified, not built, and shown that way on screen. Correcting the error walks the lineage and re-opens the award, which lands as a three-way split, not a dramatic win.

Let the reviewer tailor the agents

A reviewer who can only approve and reject has a workload that grows with the fleet. So autonomy is set per skill, never per agent: the skills are the action types the log shows each agent performing, and each opens on the strictest verdict the gate ever gave it. The orchestrator governs the handoff between agents, where both ends of the failure passed their own gate.

Specify, build, then let review cut

Every surface went through OpenSpec before code: proposal, design, specs, tasks, apply. Each stage of the work had to survive a written artifact before the next inherited it, and that sent work back three times. The biggest cut came after building: three parallel reviews refused a derived reliability score, and I removed it from the console.

Proposal: why → Design: how, and what it will not do → Specs: behaviour as scenarios → Tasks → Apply

Outcome

A live clickable prototype with 17 routes in Next.js and TypeScript, modelling all 13 decisions across 8 review shapes. Nothing calls a model: every behaviour is real UI state or marked specified, not built.

193 tasks specified across 3 OpenSpec changes, 182 applied. The open ones need a conversation with whoever owns the agents.

17 working documents, three of which changed a decision already made.

The prototype makes the argument concrete: a reviewer with no calibration intuition can be given checkable facts instead of a score. It also names the assumption that could break it. The log self-reports an assumed value on 2 of 13 records; I extended that to all 13, and validating it with engineering is the first step.

What I would do differently

What was left out, and why

Open items

What I would do next

  1. Build the unread-evidence audit. It is the only trigger that catches this class of error without a downstream consequence looking suspicious first.
  2. Validate the provenance assumption with whoever owns the extraction agent.
  3. Measure calibration per agent and per field type, and let well-calibrated agents earn a quieter surface.

Highlights

Capabilities

AI product design, Interaction design, Information architecture, Prototyping

Tools

Next.js, TypeScript, Tailwind CSS, React Flow, OpenSpec, Claude