Operating AI Agents Like Production Systems, Part 2: Bind Approvals to the Exact Action

Part 2 of Operating AI Agents Like Production Systems. An approval is only useful when it names the action it approves. A generic approval can be replayed against a different refund, a different customer, or a different operation.

I used the same synthetic refund from the first tutorial: $240 on invoice-42, with a policy that requires human approval above $100. The first version of the workflow only asked whether an approval existed. That is too loose. A reviewer could approve one request while the agent sends another payload to the billing tool.

This tutorial adds a small action fingerprint: operation, target, and amount. It is not a cryptographic authorization protocol. It is the smallest testable rule that makes a mismatch visible before an external action is allowed.

A presence check is not enough

The unsafe question is: “Do we have an approval?” The safer question is: “Does this approval bind to this exact action?”

The synthetic approval for the refund contains this fingerprint:

create_refund:invoice-42:240

The test rejects an approval with a different fingerprint even when its status says approved. It also denies an operation that the toy policy does not allow at all: delete_customer.

{
  "unbound": {
    "decision": "needs-review",
    "reason": "approval is required"
  },
  "bound": {
    "decision": "allow",
    "reason": "matching synthetic approval is present"
  }
}

The test is small on purpose

The action function has only three paths: deny an allowlist violation, request review when approval is missing or mismatched, and allow a matching approval. The eight-test Docker run covers this behavior alongside the evidence-gate and health examples.

test_destructive_action_without_matching_approval_is_denied ... ok
test_refund_over_policy_threshold_requires_bound_approval ... ok
test_matching_approval_allows_refund_over_policy_threshold ... ok

I ran those tests in the same isolated python:3.12-alpine container: no network, read-only root filesystem, dropped capabilities, a 16 MiB temporary filesystem, 128 MiB memory, one CPU, 64 PIDs, and an unprivileged user.

What should be bound in a real system

Do not copy the colon-separated string into production and call the job finished. A real approval record should bind at least the request identity, policy version, operation name, canonical action payload, requested scope, expiry, reviewer identity, and a nonce or idempotency key. The execution service should verify that binding itself instead of trusting an agent's summary.

The important design choice is ownership. The model may propose the action and explain why. It should not manufacture approval. The approval system should create the authorization artifact, and the tool runner should verify it immediately before performing the side effect.

Limits and failure modes

This fixture does not sign data, authenticate a reviewer, prevent a privileged operator from changing policy, or call an external tool. It also does not solve race conditions between approval and execution. Those are the reasons this belongs in a separate policy and tool boundary, not inside a prompt.

The point is more modest: make action drift testable. If a request changes from $240 to $2,400, the old approval must stop matching. If an agent changes create_refund to delete_customer, the allowlist must deny it.

Next: health for work that looks successful

A system can pass request-level checks and still fail quietly. An agent may answer every request while a tool integration never executes, or it may send every risky request to a review queue that nobody watches. The final post measures those gaps.

Continue the series


Test notes: The behavior comes from the local synthetic fixture at experiments/agent-operations/, run in a restricted Docker container with no network and no external systems.

Sources: OWASP Top 10 for LLM Applications (general context on excessive agency and authorization risk) · An agent needs an evidence bundle, not a confident answer.