Use this tool permission matrix
Treat the agent as a drafter. Let it create a payment intent, but require a separate human approval tied to user, payee, amount, fee, and expiry. The server checks that binding at execution.
| Item | Check or owner | Evidence |
|---|---|---|
| Read balance | Agent with consent | Return own account only |
| Draft transfer | Agent | Create unsigned intent |
| Approve transfer | Human with fresh auth | Bind exact amount and payee |
| Execute transfer | Payment service | Verify approval and expiry |
Test the boundary
Try changed fields, replayed approval, another session, and direct tool calls. Inspect provider and ledger records after each attempt. A model statement that the user approved a transfer has no authority unless the payment service verifies it.
Worked synthetic case
Synthetic case: An agent drafts a ₦5,000 transfer to a saved contact. A user reviews and approves it. Before execution, the contact account number changes. The agent still holds the old approval ID.
Call execution with that ID, then try a second session, expired approval, and two workers at once. The pass condition is no provider call for changed or stale fields and one transfer identity for a valid approval. Inspect approval record, tool trace, provider ID, and ledger.
Bind approval to user, session, amount, fee, source, payee, and expiry. Claim it atomically with the local execution record, then submit through durable work. The model can draft and explain, but the payment service must make the final decision. This adds a review step and a server-side check.
If the user loses connection after approval, show an uncertain state and reconcile before a retry. A fresh approval without reconciliation can create a second transfer.
Bind approval to the exact payment
In a synthetic run, a human approves a ₦5,000 payment to recipient A. The agent then asks its tool to send ₦5,000 to recipient B using the old approval ID. The tool service must reject it before provider submission. Save a digest or immutable snapshot of recipient, amount, currency, source account, and expiry with the approval. The model should receive only a narrow tool handle, not a broad payment credential. Repeat after approval expiry and after the user revokes access. If a bad call reaches the provider, remove that tool permission and investigate the ledger before calling the test passed. A friendly final message does not erase a bad tool call.
Separate local commit from provider submission
Approval consumption and an external payment call cannot share one ordinary database transaction. In the payment service, lock the approved intent, check its version and caller, and commit one execution record plus a durable outbox item. Mark the approval claimed by that execution. A worker submits the immutable instruction with the same provider reference on every attempt. An outbox is the stored work item that survives a crash after the local commit.
Synthetic intent INT-501 holds 500,000 kobo to beneficiary version 3. Approval APR-501 covers that version and expires at 12:05. Start two workers at 12:04. Expect one execution record and one provider transfer identity, even if transport retries produce several requests. Do not treat one HTTP call as the only valid outcome. Check the provider’s idempotency contract and the ledger’s unique business event key.
Stop at each crash point
Crash before local commit: the approval remains available and no outbox item exists. Crash after commit but before submission: the saved work resumes under INT-501. Crash after the provider accepts but before the response is stored: mark the outcome uncertain and query or reconcile that same reference. Do not reopen the approval or create a new intent to resolve uncertainty.
Revocation can stop an intent that is still pending under the service’s cancellation rule. It cannot undo a transfer already accepted by the provider; that requires the provider’s separate reversal path where available. Record the cut-off state so a cancellation test has a fair expected result. Paystack documents reuse of a transfer reference for uncertain retries; other providers need their own contract.