Every action was authorized.
The transaction was still wrong.
An agent creates a vendor. Another changes its bank details. A third approves the invoice. A fourth releases the funds. Every system says yes, because every system is only asked about its own step.
Transaction authority is part of Agent Authority, an add-on deployed in production and available to design partners on request. Agent Authority
The gap is in the handoffs
PwC US set this out in July 2026. Their argument, in summary: agent-driven transactions cross platform boundaries but platform controls do not, and the gap sits in the handoffs — where individually authorized actions chain into risks no single system’s controls are designed to catch.
Their worked example runs across three platforms: a Salesforce agent qualifying leads and generating quotes, handing to an Oracle agent posting revenue recognition, with a Workday agent handling onboarding and compensation. Each platform evaluates the request in front of it, correctly, against the entitlements it knows about. None is positioned to ask what the combination amounts to.
Their recommendation is the part worth sitting with: evaluate segregation of duties across the full transaction chain, rather than permissions within individual systems. That is a different unit of analysis, not a stricter setting of the existing one.
This is not a story about weak controls. In the walkthrough below, the workflow tool runs a genuine segregation-of-duties check, and that check passes. It is satisfied because it is answering a narrower question than the one that matters: whether this approver may approve this invoice, not what else the same authority has already done to the payment on its way here.
Source: PwC US, Agentic AI controls across platforms, 10 July 2026. The argument above is paraphrased, not quoted. Read the original — it is theirs, and it is better first-hand.
PwC has not evaluated, endorsed, or partnered with Rends. Their example is lead-to-revenue across sales, finance and HR; the walkthrough below is procure-to-pay. The shape of the problem carries over; the example is ours.
A worked example, run rather than described
One procure-to-pay transaction across four systems. Each system’s own control is the honest version of what it really enforces — nothing is weakened to produce a denial. The per-payment limit is 10,000 and the payment is 8,400.
| System | Action | Its own rule | Verdict |
|---|---|---|---|
| CRM | create_vendor | vendor record has a legal name and a tax id | PASS |
| ERP | change_beneficiary | editor holds erp.vendor.write | PASS |
| Workflow | approve_invoice | approver ≠ submitter, amount ≤ approval limit | PASS |
| Payment | release_payment | invoice approved, amount ≤ per-payment limit | PASS |
Four passes. Now the same four actions as one chained transaction — this is the ledger the demo prints, not a diagram of one:
#1 create_vendor crm user:mo@acme.example allow 2d490ec949707435…
follows (genesis)
#2 change_beneficiary erp user:dana@acme.example allow 2000f31e0b13cef7…
follows 04c1c25c-1e30-4286-…
#3 approve_invoice workflow user:sam@acme.example allow b3feaaa33de09051…
follows 0a86c058-05f3-4aa6-…
#4 release_payment payment user:dana@acme.example block 6670482a6e9450e5…
follows 17c83acf-e605-430e-…
chain verification: OK (4 entries)Step 2 and step 4 were taken by different agents — one in the ERP, one in the payment system. They share one effective authority: user:dana@acme.example. The same authority that changed where the money goes is participating in sending it.
That is why the denial lands on step 4 and nowhere earlier. Nothing about the release is individually wrong — the invoice is approved, the amount is under the limit, the payer is entitled. It is refused for what happened at step 2.
The run also does the opposite case. Change the beneficiary under a different authority, leave everything else identical, and the release is allowed. A rule that denied both would demonstrate nothing, so the demo exits non-zero unless the first is denied and the second permitted.
What you can run today
The demo, runnable, with its chain hash-linked and verified. Run on 2026-09-13; the output above is that run.
In production, for design partners
These run in the live product for a design partner’s organization once Agent Authority is switched on for it. The public check API takes a transaction id, and our SDKs (2.0.0) send one when you set it.
Transaction identity on the decision. A decision can carry the transaction it belongs to, its position in it, and the decision it follows.
Five stateful conditions over earlier decisions in a transaction — same_transaction, has_action, count, sum, and prior.
A policy that names a transaction it was given no history for errors instead of evaluating to false, so a missing input cannot read as “no match”.
Built, not yet on the API
This exists in the engine and runs in the walkthrough above. No public API evaluates it yet.
Effective authority as its own field, separate from which agent acted, so a rule can be keyed on the authority rather than on the agent.
Authority that can’t be overspent.
LIVE: An agent can’t claim authority it wasn’t given. Rends works out who is accountable on the server, and refuses any request that claims otherwise.
IN PRODUCTION (for design partners): Complete history, or no decision. Rends refuses to evaluate a transaction whose history it can’t read in full.
IN THE RENDS LAB (tested at up to 50 simultaneous operations):
- Budgets hold under pressure. 20 simultaneous $100 payments against a $1,000 budget: exactly 10 reserved. 20 sub-agents booking $500 each against $8,000: exactly 16.
- A new transaction ID doesn’t reset history. A second request about the same vendor still sees the first: 25 of 25 at 50 concurrent.
- Approval is bound to the exact action. Change the amount or the target after approval, and execution is refused.
- Delegation narrows. Sub-agents inherit tighter limits on scope, expiry, depth, budget and purpose, enforced in the database.
Published September 2026.
What is not built
No ERP or CRM integrations. The walkthrough above runs against four mock systems — roughly 180 lines of in-memory code. There is no Salesforce, Oracle, Workday, or connector of any kind, and none is implied. The PwC example names those platforms; our demo does not touch them.
Persistence, and what it does not yet prove. The transaction ledger was in-memory and process-local. As of 18 September 2026 a decision record can carry its transaction: the columns exist, a chained row is append-only in the database, and a row that is part of a transaction must record the effect the engine reached or the write is refused. Measured on that date: 2 rows carry a transaction and 2 carry a decision effect, in govern.agent_actions. Both are ours — a two-step chain we wrote to verify the path, in our own organisation, under an agent we created for that purpose. No customer transaction has been recorded. The hash chain over that table reads 892834d56488b2ff1bfc2bfa756ee3cf.
We previously described the stored record as keeping a hash of the arguments. That was the design and not the data. The column is input_hash, it is supplied by the caller, and on 17 September 2026 we measured that 43 of 59 stored rows did not contain a hash at all — they contained short identifiers the caller had sent. Those rows are now tagged rather than rewritten, because they are the evidence of that defect. The instrument is a ~ '^[0-9a-f]{64}$' count over the live table.
And where it is a hash, it is a hash of a summary. Measured 21 September 2026: 18 of 61 stored rows carry a real SHA-256 in input_hash. Those eighteen are digests of a summary the caller supplies — not of the arguments the engine evaluated. Two consequences, stated rather than left for a reader to find: a cumulative total cannot be recomputed from the stored history, and a caller who summarises loosely produces a record that still verifies cleanly, against the wrong thing. Recomputing the decision from the record is not available to us on this path.
Deciding before the call is two different things, and only one of them stops anything. An escalation halts execution before the tool runs: a permission marked requires_approval holds the orchestrator at awaiting_approval. An allow or a deny is not that — it is returned to the caller as a pre-action decision, and the caller is trusted to honour it. That is the same structural position as any advisory check, which is why we test whether it can be bypassed rather than assuming it cannot. Measured: the hold caught 2 of 4 gated attempts (5–6 September 2026), and it expires with a 30-second sandbox timeout — so an escalation does not imply a human had time to act.
Reading a chain back is a separate claim. A stored chain is only useful if a policy can read it, and a chain that cannot be read must refuse rather than evaluate as an empty one. On 18 September 2026 we wrote the first real transaction through the public API — our own organisation, our own verification agent, not a customer's traffic — and read it back: priors=2, 1:allow → 2:warn. Before that date this had never been exercised against a production row, and we said so here.
This is evaluation, not enforcement. The distributed enforcement path still does not carry a transaction context. Writing and reading a chain did not change that.
No authority graph — still. None of the above derives who holds whose delegation. Effective authority is derived by the server from the verified caller; a request can restate it but cannot choose it. Deriving who holds whose delegation, through which role, at what time, is the hard part and is not attempted here.
Stated this plainly because the gap between the four named enterprise platforms in the framing and four mock systems in our demo is exactly the gap a reader would otherwise assume we had closed.
Related: the evidence plane — what a second party can verify about an enforcement decision without trusting the party that made it.