DEV Community

Arsalan Shahid
Arsalan Shahid

Posted on

Your agent asked for approval. Where did the answer go?

An agent drafts a refund decision. A reviewer reads it, changes the amount, adds a line about the customer's contract, and approves. The refund goes out. Everyone moves on.

Six months later somebody asks who approved that refund, and why the amount changed. The agent's draft is in a trace. The final amount is in the payments system. The reviewer's reasoning was a sentence in a chat thread that has since scrolled away, and the fact that a human changed the number at all is not recorded anywhere as a distinct event. The trace shows a call. The database shows a result. Nobody can show the decision.

This is not an unusual failure. It is what happens by default, because the plumbing we have built for AI agents moves work around without recording the
judgment applied to it.

The protocols move tasks, not decisions

The Model Context Protocol connects an agent to tools. A2A lets agents discover one another and exchange messages. Both do their jobs well, and neither is trying to do this one.

A paper by Kang and Diponegoro makes the point precisely. They take five agent interoperability protocols, including MCP and A2A, and score them against six
governance dimensions drawn from organisational theory: membership, deliberation,
voting, dissent preservation, human escalation, and audit or replay. Their
conclusion is that these protocols coordinate tasks but cannot express a governed
community. You cannot state in MCP who is allowed to approve something, how a
dissent is preserved, or when a human must be brought in. Governance, they argue,
is a missing architectural layer above these protocols rather than a feature
inside them.

That matches what we kept running into. We build AI systems for regulated
industries, where a client cannot simply assert that a human was in the loop.
They have to produce the evidence, sometimes years later, to somebody who is paid
to be sceptical. "The logs show a call was made" is not evidence that a person
exercised judgment.

What a decision record has to contain

The temptation is to log an approval as a boolean and move on. Approved: true.
This is where most implementations start, and it is worth being clear about what
it throws away.

An approval and an edit are different events. If a reviewer changed the draft
before approving it, then the thing that went out is not the thing the agent
produced, and the difference is the most interesting part of the record. It is
where the human judgment actually lives.

So the record needs the agent's output as an artefact, and the human's
intervention as an override that carries the diff, the reviewer's rationale, and
a flag for whether the edit refined the agent's intent or replaced it. Refining
and substituting are different signals: one says the agent was roughly right, the
other says it was wrong. Aggregate a few hundred of those and you have an honest
measure of where the agent is failing, which is a byproduct of the audit trail
rather than a separate analytics project.

Rejections and escalations matter too, and for the same reason. A record that
only preserves the decisions that went through preserves the successes and
discards the disagreements, which is precisely backwards from an accountability
point of view.

Why the record has to be verifiable

A decision record that the system producing it can quietly edit is not evidence.
It is a claim.

The fix is old and well understood: sign each record, and chain it by content
hash so each entry commits to the one before it. Change any entry after the fact
and every subsequent link breaks. A verifier can then re-walk the chain and say
whether it is intact, without trusting the system that produced it.

For the strongest form, that chain can be anchored in an external transparency
log. This is what the IETF's SCITT work is for, and it means a relying party can
check a decision happened without asking either party involved. We have an
optional profile for it, and we have been discussing the details on the SCITT
mailing list, where several implementers pointed out things we had wrong. More on
that in a moment, because it is the most useful part of this post.

Sixty seconds

CHAP is our attempt at this layer. It is an open protocol, Apache-2.0, and it
rides on MCP and A2A as transport rather than competing with them.

The Python coordinator has no runtime dependencies, so the fastest way to see
what a decision record looks like is to run one of the worked scenarios straight
from a clone, with nothing installed:

git clone https://github.com/BrightbeamAI/chap
cd chap/scenarios/02-marketing-copy
python3 scenario.py
Enter fullscreen mode Exit fullscreen mode

That scenario runs a small marketing workflow with one drafter and one editor. It
prints the chain, verifies it, tampers with a copy to show the verification
failing at the exact entry, reconstructs a single edit with its diff and
rationale, and reports which kind of override the editor kept making. The whole
thing is deterministic, so you get the same output every time and can read the
script to see exactly what produced it.

If you would rather wire it into something, the coordinator and the framework
bridges are published:

pip install chap-coordinator
pip install chap-langgraph        # or chap-pydantic-ai, chap-llama-index, chap-ag2, chap-google-adk
Enter fullscreen mode Exit fullscreen mode
npm install @brightbeamai/chap-coordinator
npx -y @brightbeamai/chap-coordinator-mcp   # expose CHAP as MCP tools
Enter fullscreen mode Exit fullscreen mode

What we got wrong

This is the part worth reading if you are building anything similar, because
these were not obvious to us and most of them came from other people.

Adapters must not infer decisions. Our first bridges tried to be helpful.
If a framework's human-input hook returned an empty string, that looked like
assent, so the bridge recorded an approval. It also joined the reviewer as a
human participant regardless of what identity the caller supplied. Both are
unforgivable in a record whose entire purpose is attributing a decision to a
person. An adapter that guesses is manufacturing evidence. Now an explicit
decision is required, unknown identity schemes are rejected rather than assumed
to be human, and a bot cannot be recorded as a human approver.

An override must diff against the artefact under review. Ours originally
took the "before" value from the caller. That let a reviewer record an override
against a document that was never actually reviewed, which makes the diff a
fiction while leaving the rationale and the signature perfectly valid. The base
now always comes from the coordinator's own record of what was sent for review.

Reading a log should not change it. For a while, calling the audit read
method appended an entry to the audit chain, which meant inspecting the log
altered it, and so did verifying it. Obvious in hindsight, invisible in practice
until somebody looked.

Two implementations will disagree about bytes. We have a TypeScript
coordinator and a Python one, and they hash records identically, except that
Python sorts object keys by code point and the JSON canonicalisation standard
sorts by UTF-16 code unit. For any key outside the basic multilingual plane the
two produce different canonical bytes, and therefore different hashes for the
same object, which quietly breaks cross-implementation verification. If you have
two implementations of anything hash-based, test them against each other with
awkward input.

Integrity checks cannot see a record that was never written. This one came
from another implementer on the SCITT list, who has operated a public transparency
log and found four cases of it. Chaining and signing prove that the records you
hold are intact. They say nothing about a record that failed to be created, and a
chain of signed links reads identically whether an approval never happened or
happened and was lost. For a human oversight trail that distinction is the whole
ballgame. The fix is ordering rather than cleverness: admit the record first, and
make the decision's success conditional on it.

Where this is going

CHAP is at 0.2.10, with two reference implementations that answer the same
conformance suite identically, eleven composable profiles, bridges for the five common agent frameworks, and a first external integration from a team building an execution authority layer, which is currently experimental.

The recent releases have been almost entirely security and audit hardening driven by outside review, which is the argument for doing this in the open. Every item in the section above was either found by somebody else or found by us because somebody else asked a sharp question.

If you are building agents that take consequential actions, the questions worth asking are the same whether or not you use any of this. Can you produce the record of a single human decision without disclosing the others? Does your override bind to the exact artefact it overrode? Can you tell the difference between no approval and a lost approval?

If you want to pull at any of it, the spec, the scenarios, and the conformance harness are at github.com/BrightbeamAI/chap, and issues are genuinely welcome, including the ones that tell us we have this
wrong.

Top comments (1)

Collapse
 
mads_hansen_27b33ebfee4c9 profile image
Mads Hansen

The “integrity cannot detect omission” point is the one I wish more audit designs started with.

A useful extension is to make the action consume a short-lived decision capability that is issued only after the record is durably admitted. The capability should bind the artefact digest, reviewer identity, decision type, policy version, expiry, and a single-use operation ID. The executor atomically marks that operation consumed before committing the external side effect.

Then the failure matrix becomes testable: admitted/no action, action/no admitted record (must be impossible), duplicate delivery, crash between consume and commit, stale approval after the artefact changes, and replay against a different tenant or amount. Reconciliation can flag admitted-but-unconsumed decisions without mistaking them for completed work.

Hash chains prove what exists. Receipt-before-action plus single-use binding makes missing or replayed decisions much harder to hide.