Agentic ERP Apps Need Transaction Boundaries
By wGrow Project Team ·
Oracle’s Fusion Applications AI announcements describe agents acting on Fusion business objects such as purchase requisitions, approval chains, journal entries, and audit logs [S1]. This is the correct move, and it’s worth saying plainly before the criticism starts. The alternative — an LLM with a broad set of API tools and a system prompt that says “follow company policy” — is a fast way to corrupt a general ledger. If an agent creates a purchase order, updates inventory, and then crashes before it emails the vendor, you don’t need a smarter retry loop. You need a deterministic rollback protocol that doesn’t care how smart the agent was.
Tool Calling Is Not a Transaction Boundary
Embedding agents at the business object layer is structurally sound because Fusion’s business objects already carry state, versioning, and workflow status. The agent operates inside a system that has opinions about what a valid state transition looks like. Compare that to the naive integration pattern: give the agent a purchasing API, an inventory API, and a notifications API, and trust the model to sequence them correctly under failure.
Here’s the gap. LLMs are probabilistic text generators wrapped in a tool-calling loop. Core enterprise systems run on ACID guarantees — atomicity, consistency, isolation, durability — because a half-finished purchase order is worse than no purchase order at all. An agent executing a five-step workflow via tool calls has no native concept of a transaction boundary. Each tool call is its own commit. If step three fails, the agent doesn’t roll back steps one and two. It either retries step three (creating duplicates), skips ahead (leaving state inconsistent), or apologizes in natural language while the database sits in a state nobody designed for. None of those are acceptable outcomes in a ledger.
Phantom Records and Distributed System Scars

We solved a version of this problem before, and it had nothing to do with AI.
Back in 2016, I worked on a marine logistics procurement integration. The design was simple on paper: write a purchase record to our local database, then fire an API payload to an external vendor system to confirm the order downstream. Two systems, one workflow, no shared transaction. A standard integration pattern for that era — nothing exotic.
The failure mode showed up within the first few weeks of production traffic. The vendor API would occasionally time out — sometimes their load, sometimes ours — after our local commit had already gone through. So we’d have a purchase record sitting in our system with no confirmed counterpart on the vendor side. The procurement ledger drifted out of sync with actual fulfillment. Someone would eventually notice a phantom order parked in “confirmed” status with no shipment ever arriving, and reconciling it meant manually cross-checking against vendor paperwork. It wasn’t a rare edge case. It was a predictable consequence of committing local state before an external system had confirmed its side.
The fix wasn’t cleverer retry logic on the API call. It was separating “locally committed” from “externally confirmed” into two distinct states, with an explicit reconciliation job that swept for orphaned records and either re-fired the vendor call or flagged the record for a human.
Map that directly onto an LLM agent. An agent calling external tools is functionally an unreliable third-party API — except this one can also misinterpret what “confirm the order” even means. It deserves the same defensive posture we built for that 2016 vendor integration, not a lighter one just because it’s dressed up as intelligence.
Defining the Agentic Commit and Compensating Action
The architectural requirement follows from that: agents must not have implicit commit rights to core tables. A workflow needs an explicit answer to three questions before it ships. Where does the agentic outcome start? Where does it commit? Where does it become irreversible?
Those are three different points, and conflating them is the actual bug. “Agent proposes a purchase order” is not the same event as “purchase order is committed to the ledger,” which is not the same event as “vendor has been notified and the order can no longer be silently cancelled.” Each one needs its own state and its own gate.
This is where compensating actions come in — a concept distributed systems engineers have used for decades under names like saga patterns, and it applies here without much translation. If an agent successfully updates an inventory record but fails to generate the downstream shipping manifest, the system needs a hardcoded function that reverses the inventory update. Not the agent reasoning its way to “I should probably undo that.” A deterministic function, written by an engineer, tested like any other rollback path, that runs regardless of what the agent’s next token would have said. Letting an LLM improvise its own rollback logic on the fly is the same mistake as trusting it with the commit in the first place — you’ve just moved the improvisation from the happy path to the failure path.
None of this is free. Defining a compensating action for every failure mode is slower and more tedious than pointing an agent at a REST API and hoping the retries sort themselves out. It only pays off when the underlying business objects are well-modeled enough to have unambiguous state transitions in the first place — which is exactly the condition Fusion’s business object layer satisfies, and one that doesn’t hold for every workflow an enterprise might want to automate.
Wrapping Agents at WaterDoctor

We run a version of this at WaterDoctor. Field maintenance schedules get triggered off incoming sensor data — a filtration unit’s readings drift out of range, and the system has to decide whether that warrants a technician visit.
The agent doesn’t own the schedule. It proposes a state transition: “this unit should move from monitoring to scheduled-maintenance, assign technician X, target window Y.” That proposal gets handed to a deterministic state machine that validates it against the actual constraints — does technician X exist and have capacity, is the unit already in a maintenance window, does the proposed transition even make sense from the unit’s current status. The state machine executes the transition or rejects it. The agent never writes to the work order table directly.
This matters because the agent does occasionally hallucinate on the input side: a technician ID that doesn’t exist, a unit reference that’s stale. When that happens, the state machine’s validation step catches it and rejects the proposal outright — no maintenance window gets created against bad input, and no partial write sits around waiting for someone to stumble across it in a report. The agent is a proposal generator. The state machine is the only thing with commit rights.
Audit Trails Tie to the Business Object
Bring this back to Fusion. Auditing an agent’s prompt history or raw token output is close to useless for financial or operational compliance. A transcript tells you what the model said. It doesn’t tell you what changed, when, or under whose authority.
Audit trails need to be tied to the explicit version of the business object the agent modified — the journal entry or purchase requisition record itself, not a model transcript describing it. And the approval gate has to sit strictly before the commit, not after the agent has already made a recommendation that a human rubber-stamps five minutes later without re-checking the underlying data. “Approved” needs to mean the human saw the actual state change before it happened, not that they read a paragraph describing it.
The Takeaway
The interesting part of agentic ERP isn’t the agent. It’s the state machine wrapped around it. Oracle putting agents inside Fusion’s business object layer is a bet that the workflow engine, not the model, should own commit rights — and that bet is the right one, even if it makes the build slower than wiring an agent straight to an API. Anyone building agentic workflows against a ledger, a work order table, or a master data record should start from the same assumption: the agent is an unreliable subroutine with a start boundary, a commit boundary, and a compensating action defined for every failure between them. Skip that step and what you’ve built is a demo, not a system.