Skip to content
Expedify
Building Production-Ready AI Agents

Module · M4 · The control layer

The audit row you can defend

Lesson 17 of 21 · 8 min

The run is finished. Six months from now somebody asks why this customer was given what they were given. What do you show them?

If the answer is “the chat”, you have not deployed an agent — you have deployed exposure. The last node in the canvas exists so that the answer is a row on the account, readable by somebody who has never opened the builder.

The end of the run

After the human approves, one node resolves the record and one writes the note. Neither of them decides anything.

Loop BodyCompleteNoYes

Scroll for all 12 steps →

Short, attached to the record, and written by a node — not by the agent describing itself.

what was proposed

In this build
the proposal, in the execution log the row points at

what the check found

In this build
the reviewer’s score, in the note itself

who released it

In this build
the human’s decision, as the option they clicked

what it was about

In this build
the note is attached to the deal, so it is on the account, not in a log

This node was broken for the entire build, and nobody could see it

The first time this node ever executed, it failed. It had been carrying four independent defects for the whole build phase, and not one of them was visible, for a reason worth sitting with: the audit row sits behind the human gate, and no automated run had ever got past the gate.

Everything upstream of this node had been verified many times over. The one node nobody could reach was the one that did not work.

the record id was read from a tool the agent holds — and a tool is never a step, so it is not in scope

Why it stayed hidden
it resolved to an empty string, and was not even reported as a template that had been detected

the query it was reading from had no id column in it

Why it stayed hidden
it would have failed even with the first defect fixed

the obvious repair — have the reviewer carry the id — produced a fabricated identifier

Why it stayed hidden
it passes every shape check and attaches a decision to nothing

the account name was interpolated into SQL as raw text, not as a bound value

Why it stayed hidden
zero rows, reported as success

Precision made it worse, not better. Told “copy the id column”, the model emitted the account name — wrong, and obviously wrong. Told “it is a UUID, 36 characters, NOT the name”, it emitted a perfectly formed UUID that matches no record in the system — wrong, and invisible. Tightening the wording did not improve accuracy; it improved the camouflage. You cannot prompt your way to a fact.

The repair, and why it is structural

The fix is not a better instruction. It is a node: after the gate, a database read takes the search term the reviewer carried, and returns the identifier. The model supplies the argument; the database supplies the fact.

A wrong search term returns zero rows and fails loudly. A wrong identifier fails silently, forever. That asymmetry is the whole reason to route facts through a lookup rather than through a prompt — the same principle as lesson 7, applied one layer further out.

With that step in place the run completed end to end and the note landed on the real account. It is short by design:

the audit note, as written
RESOLUTION EXECUTED. Reviewer score 5/5. Human decision: Approve.
Enough to answer the question, and attached to the record the decision was about.

Keep the payload to short scalars. Interpolating the violations array — or any free agent prose — into the note’s JSON payload breaks the payload: the array arrives as raw JSON inside a quoted string, and prose arrives with quotes and newlines in it. Put the score in the note and leave the narrative in the execution log, which is already keeping it for you.

The lesson under the lesson

The node whose entire job is to prove what happened was the only broken node in the canvas — and it was broken precisely because it was the safest part of the system. The property that made it protected made it untested.

You have not tested the path you cannot reach. Before you ship a control layer, force a run all the way through the gate — approve one yourself — and watch the last node execute. Everything before the gate gets exercised a hundred times while you build. Everything after it gets exercised the first time it matters.