Applied AI

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

Intake 1 Agent 2 Agent contract 3 Company resolver 4 ERP adapter 5 Dashboard API 6 Intake 1 Agent 2 Agent contract 3 Company resolver 4 ERP adapter 5 Dashboard API 6
How a document reaches the ERP through an agent, and how the dashboard reads the result.

Select a component to read what it does and how it fails.

  1. Intake. An ERP webhook announces a receipt or quotation, and OCR turns it into text. A poor scan gives the agent poor text.
  2. 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.
  3. Agent contract. One versioned contract shared by expense and procurement. Downstream consumers rely on it, so a careless change breaks callers.
  4. Company resolver. Resolves company, currency, warehouse and price list per sales order. Unknown companies get a fallback. Only this layer knows the trigger.
  5. 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.
  6. 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.

Rule 4 of 9

Stack

  • Python
  • ERPNext
  • Frappe REST API
  • Model Context Protocol
  • OCR
  • LLM tool-calling
  • PostgreSQL
  • Docker
  • Traefik
  • Server-sent events
  • Webhooks
  • OAuth2

Specified, not built by me:

  • The ERP adapter for agent tooling, a fork of an open-source adapter: I wrote its specification and the decision record that keeps it company-agnostic, not its code.
  • The stock-reorder agent: I wrote its specification and a multi-company requirements note. Someone else built its original pipeline.

Next