AI agents working inside a live ERP
Built an agent platform over an ERP: it began as expense-claim automation and became multi-company procurement and inventory intelligence.
- Role
- Author of the platform and its ERP integration contract
- Period
- 2026 to present
- Status
- Live, with a demo and a production deployment (as of 6 August 2026)
- Outcome
- Two live deployments, a demo and a production one.
01 / Problem
An ERP holds stock, suppliers and purchase orders, yet decisions across companies, currencies and warehouses still needed a person to read and reconcile. It began as a demo of expense-claim automation. The aim then became AI agents doing real work inside the existing ERP: first on expense claims, then on procurement and inventory for two companies with different currencies and warehouse sets. The agents had to act through the ERP's own API, so the ERP stayed the system of record. Results also had to reach people on a live dashboard, signed in through the ERP's own accounts.
02 / System
Select a component to read what it does and how it fails.
- Intake. An ERP webhook announces a receipt or quotation, and OCR turns it into text. A poor scan gives the agent poor text.
- Agent. An LLM with forced tool-calling, so it acts only through named tools. It can still pick the wrong one, so writes share one contract.
- Agent contract. One versioned contract shared by expense and procurement. Downstream consumers rely on it, so a careless change breaks callers.
- Company resolver. Resolves company, currency, warehouse and price list per sales order. Unknown companies get a fallback. Only this layer knows the trigger.
- ERP adapter. A generic ERP adapter for agent tooling, forked from an open-source one. I specified it and did not build it. It never chooses a company.
- Dashboard API. Postgres read models, supplier scoring, demand forecast and purchase-order drafts. Actions are validated and audit-logged, so a wrong one can be traced.
03 / Decisions
Company resolution stays outside the adapter
- Decision
- I kept the adapter generic and put company resolution in the callers that know the business trigger. I wrote the decision down in the adapter's specification.
- Rejected
- Hard-coding the known companies into the adapter's tools.
- Why
- The adapter is a generic ERP adapter. It cannot know why a request was made, so it cannot know which company the request belongs to.
- Cost
- Every procurement caller has to implement company-aware filters itself.
One resolver for many companies
- Decision
- I made the platform resolve company, currency, warehouse and price list per sales order, with a fallback for unregistered companies and a warehouse policy per item.
- Rejected
- Procurement hard-coded to a single company, and a warehouse chosen from a company string.
- Why
- The two companies had different currencies and warehouse sets. A company string does not say which warehouse holds an item.
- Cost
- Every procurement path now has to carry company context from start to finish.
Reuse the pattern for procurement
- Decision
- I pointed the same OCR, tool-calling and ERP layer at procurement, and moved expense and procurement onto one shared contract in a single commit.
- Rejected
- Rebuilding the agent and ERP layer from scratch for procurement.
- Why
- The hard parts, reading documents and acting through the ERP's API, already worked.
- Cost
- Expense and procurement now share a contract, so a change made for one can break the other.
04 / What broke
The history holds no outage, revert or data-loss commit, so I will not invent an incident. The honest boundary is what I left undone: dashboard cross-origin rules moved from open to an explicit allowlist, but hardening them for production stayed as recorded debt.
05 / Outcome
- The ERP integration contract is documented, and downstream consumers rely on it.
- Dashboard sign-in goes through the ERP's own OAuth2, so people use the accounts they already have.
- The platform commits sit under a shared team identity, so my authorship is my own statement, not something git proves.
- I have no usage, uptime or user figures, and I claim none.
06 / The rule I took from this
Keep the adapter generic. Put business rules where the business trigger lives.
Stack
Specified, not built by me:
Next
Working on something similar? Email me about this build.
Related: A social and market signal intelligence backend, An AI voice front desk for dental clinics.
Hiring for this kind of work? Python backend engineer.
Next case study: A production AWS backend on HIPAA-eligible services.