Core ERP logic - accounting, sales, purchasing, inventory
Project description
Lambda ERP
Open-source ERP you can run through chat — configurable in plain language
Lambda ERP is a working prototype of a simpler ERP: create invoices, check inventory, answer accounting questions, and change reports by asking for what you need in plain language.
https://github.com/user-attachments/assets/1b2749ef-10e7-42f5-9cce-df5628292667
Click Enter Live Demo for a 40-second scripted walkthrough and prompt freely after (rate-limited to ~$50/day of LLM spend across all visitors).
Join the discussion on Discord — report bugs, share prompts that broke (or surprised) the agent, or just see what other early users are building:
This release is not yet production-ready. It's a complete prototype but not vetted enough to run your payroll on it yet.
Why another ERP?
Today, the bulk of an ERP rollout isn't the software license — it's everything that has to happen on top of it before the system actually fits the company. For a small or mid-sized company, the license is typically a few thousand a year, while getting it set up routinely runs $10-50k — many times the annual license spend before anyone has logged in. The system also keeps evolving after go-live — each custom report, country-specific tax rule, or workflow change is its own round of work. Larger systems like Oracle NetSuite and Microsoft Dynamics follow the same shape, and S/4HANA-class projects run into the millions.
ERPs cost what they cost because they're generic platforms that have to be bent into the shape of each company. The work is configuration, workflow design, custom reports, data migration, integrations — language and structure work. It's been slow and manual because software couldn't do it. Until recently.
We think that's about to flip. The bulk of an ERP implementation is text-transformation and configuration that LLMs are now genuinely good at:
- Reading documents. A supplier PDF becomes a Purchase Invoice. A bank statement becomes reconciled journal entries. An onboarding form becomes a Customer record.
- Chart of Accounts design and mapping. Taking a client's legacy accounts, translating to local GAAP, producing the mapping table - pure language-plus-structure work.
- Master data migration. Cleaning, deduplicating, and loading customer/supplier/item masters and opening balances from whatever mess the legacy system coughs up.
- Custom reports and print formats. The bespoke chart, the specific invoice layout, the cash-flow view nobody else has - unbounded client taste, infinite long tail.
- End-user training and ongoing questions. "How do I issue a credit note?" "Why is this balance off?" "What was last quarter's margin by product line?" - natural-language lookups that used to need a help-desk hour.
Each of those is hours of skilled work today. With a capable LLM in the loop, the same task takes seconds of compute and a review pass from someone who knows the business. The software starts tailoring itself to you, instead of the other way around. And because Lambda ERP is open source and self-hosted, the configuration doesn't stop at go-live — the system can keep evolving alongside the company. For implementation partners, the shape of the work shifts: less time spent hand-writing every change, more time spent on the judgment calls — chart-of-accounts design, compliance, change management — that actually need a human.
How it works
┌────────────────────────────┐ ┌────────────────────────────┐
│ React + Vite frontend │◄──►│ FastAPI backend │
│ - Document forms │ │ - Document CRUD API │
│ - Reports / Analytics │ │ - Report endpoints │
│ - Chat (WebSocket) │ │ - Auth (JWT cookie) │
│ - Client-side JS runtime │ │ - WebSocket chat gateway │
│ (Web Worker) for charts │ └────────────┬───────────────┘
└────────────────────────────┘ │
▼
┌──────────────────────────────────────────┐
│ LLM orchestrator │
│ - GPT-5.4 drives the reasoning loop │
│ - Tool-use: document CRUD, search, │
│ reports, aggregations, analytics │
│ - Delegates JS generation to Anthropic │
│ code-specialist sub-agent │
└──────────────────┬───────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ lambda_erp/ (pure Python, no framework) │
│ - Document base class + lifecycle │
│ - General Ledger (double-entry) │
│ - Stock Ledger (moving average) │
│ - Tax calculation engine │
│ - Pluggable DB backend (SQLite / Postgres)│
└────────────────────────────────────────────┘
Key design choices:
- Chat-first, not chat-bolted-on. The chat isn't a copilot sidebar - it's the primary way to interact with the system. Every document type, every report, every master record is reachable from tool-use.
- One shape for every document. Invoices, sales orders, stock entries, payments - all share a single
Documentbase class and the same three-state lifecycle (Draft → Submitted → Cancelled) withon_submit/on_cancelhooks. The LLM learns the pattern once and drives every doctype the same way. Leading open-source and commercial ERPs have per-model action verbs spread across 150+ core models; each one is a separate tool the model has to get right. - Metadata-driven UI, shared with the LLM. A single React form component renders every doctype from
frontend/src/lib/doctypes.ts. The schema the model reasons over and the schema the user sees are literally the same file. Adding a field is two lines - one in the Python class, one in the config - not a new module with hand-written views and inheritance overlays. - Two-model orchestration. A planner model handles reasoning and tool-use. When it needs to generate code for a custom report, it delegates to a code-specialist sub-agent. This keeps each model doing what it's best at and keeps latency down on simple turns.
- Semantic datasets, not free SQL. The LLM can't write raw SQL; it calls whitelisted semantic datasets (
sales_invoices,purchase_invoices,ar_open_items,stock_balances, etc.) with whitelisted filters and group-bys. This makes the system auditable without sacrificing flexibility. - Client-side analytics runtime. Custom report JS executes in a sandboxed Web Worker in the user's browser. The server never runs untrusted JS. Charts are persisted as portable specs, not screenshots.
- Double-entry invariant enforced. Every submitted document that touches the GL must balance to zero. The engine adds round-off entries for rounding gaps and refuses to post imbalanced vouchers.
- One deployment per customer, simple to operate. Lambda ERP is built to be self-hosted by a single company for its own books - not as a multi-tenant SaaS. One FastAPI process, one database, one VPS is enough. If you want a hosted offering, we'll ship a dedicated instance per customer.
Tech stack at a glance
| Layer | What |
|---|---|
| Backend business logic | Pure Python, no framework (lambda_erp/) |
| Web API | FastAPI + Pydantic |
| Storage | SQLite in the prototype; Postgres planned for production |
| Frontend | React + Vite + TypeScript + Tailwind + Recharts |
| Chat transport | WebSocket |
| LLM orchestrator | OpenAI (configurable) |
| Code specialist | Anthropic (configurable) |
| Auth | JWT httponly cookie, three roles + demo |
What works today
- Full sales and purchase cycles (Quotation → Sales Order → Delivery Note → Sales Invoice → Payment Entry, and the buying equivalents)
- Returns / credit notes / debit notes with proper GL and stock reversal
- Moving-average stock ledger with negative-stock protection
- Double-entry General Ledger with cancellation reversal
- Preset reports: Trial Balance, Profit & Loss, Balance Sheet, General Ledger, AR/AP Aging, Stock Balance
- Custom analytics drafts via chat (persisted, shareable, editable)
- Server-side aggregation tool for in-chat factual answers across large datasets
- PDF / image attachment → add invoices, create quotations, etc. all directly by adding them in the chat
- Auth with admin/manager/viewer roles plus a public demo mode
- Full test suite that exercises every cycle against an in-memory SQLite
What's still todo (Suggestions welcome)
- MCP integration for supplier/customer communication (quotes, orders, confirmations)
- Multi-currency beyond the simplified current handling
- Workflows / approval chains
- Serial & batch tracking
- Manufacturing (BOM, work orders)
- HR / Payroll beyond the journal-entry workaround
- Regional compliance packs (GST, VAT returns, etc.) - see below
- PDF report creation directly inside the chat
Why now
Four things had to be true for this to work, and they all became true in the last ~18 months:
- LLMs can reliably call tools. A year ago, models would hallucinate tool calls, mangle JSON, or drift after 2–3 steps. Today's frontier models can run an 8-step reasoning loop over a real tool inventory without falling off.
- Costs collapsed. Generating a custom report via a code-specialist sub-agent is cents of compute. Even keeping a human reviewer fully in the loop, the marginal cost of "one more report" or "one more dashboard" drops by orders of magnitude — which means companies actually ask for them, instead of living with the defaults.
- Structured output + function calling are first-class. We can constrain the LLM's outputs to valid tool-call schemas, safe SQL parameters, and typed JSON - which is what makes an AI-native ERP even conceivable as a safe thing to run.
- Greenfield is finally cheaper than retrofit. Twenty-year ERP codebases have hundreds of bespoke models and thousands of hand-written forms - teaching an LLM to drive that reliably means curating a custom tool layer over every quirk. Starting from scratch around one Document lifecycle and a metadata-driven UI is now cheaper than retrofitting an existing platform.
Why open source
Every company configuring an ERP runs into the same problems: local tax rules, common workflow patterns, industry-specific accounting quirks. Most of that knowledge isn't a competitive advantage — it's the same ground being re-covered separately at every implementation, by every team, in slightly different ways.
We want Lambda ERP to be where that knowledge lives in public. The base system is MIT-licensed, and we're organizing the repo so that community contributions - a German VAT pack, a U.S. sales-tax-by-state module, an industry template for professional services - can slot in under docs/ and be picked up by the LLM as reference material. The goal is a true community where running an ERP gets cheaper, faster, and more flexible for everyone, not just the implementer.
Get it running
Docker (one command)
You need Docker and Compose v2. The canonical installs:
- Mac / Windows: install Docker Desktop — ships
docker composev2 built in. - Linux / WSL: Docker's own one-liner installs both the engine and the Compose plugin:
curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER && newgrp docker
(Ubuntu'sdocker.ioapt package and Snap'sdockeromit Compose; use the script above instead.)
Then, from the repo root:
cp .env.example .env # add your OPENAI_API_KEY and ANTHROPIC_API_KEY (for custom analytics)
docker compose up --build
First boot takes ~2-3 minutes — the container runs a 3-year historical simulator to populate realistic demo data. Watch the logs; you'll see monthly [sim] progress lines and, when it's done, a clear banner:
======================================================
Lambda ERP is READY — open http://127.0.0.1:8000 in your browser
======================================================
Hitting the URL before that banner will look like the page "hangs" — uvicorn isn't listening yet, so requests queue at the TCP layer until bootstrap finishes. Wait for the banner, then open the URL.
Use
127.0.0.1, notlocalhost. On WSL2 + Docker Desktop, browsers occasionally stall for minutes on WebSocket upgrades tolocalhosteven when HTTP works fine.127.0.0.1has never been observed to misbehave.
The container serves both the UI and the API at the same origin - there's no separate frontend port in Docker mode. You'll land on the login page with register-your-first-admin enabled; create an account and that account becomes the admin for your instance.
If instead you want the hosted-demo experience (a shared public_manager account and the "Enter Live Demo" button), add LAMBDA_ERP_ENABLE_PUBLIC_DEMO=1 to your .env.
State persists to a named Docker volume, so subsequent docker compose up starts in seconds. docker compose down -v wipes the volume to force a fresh re-seed.
Local dev
Requires Python 3.10+ and Node 18+.
# Backend
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
uvicorn api.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev
Open http://localhost:5173. Vite proxies /api/* to the backend.
Environment
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-... # optional, used for the code-specialist sub-agent
ANTHROPIC_CODE_MODEL=claude-opus-4-7 # optional, default shown
Chat needs OPENAI_API_KEY. Custom-report code generation uses ANTHROPIC_API_KEY when set; otherwise it falls back and the chat will tell you it can't generate reports.
Run the validation suite
source .venv/bin/activate
python -m tests.test_erp_validation
Runs a full cycle in-memory: setup, sales cycle, purchase cycle, returns, submits, cancels, payments, trial balance. No credentials, no network, ~2 seconds.
Repository layout
lambda_erp/ # Pure Python business logic (no framework)
accounting/ # GL, journal entries, invoices, payments
selling/ # Quotations, sales orders
buying/ # Purchase orders
stock/ # Stock ledger, stock entries
controllers/ # Tax engine
api/ # FastAPI + WebSocket chat
routers/ # REST endpoints
chat.py # LLM orchestrator, tool schemas, reasoning loop
frontend/ # React + Vite app
tests/ # Validation / regression suite
docs/agents/ # Invariants, gotchas, design decisions (LLM-readable)
docs/agents/ is worth a read if you're going to contribute - it captures the invariants the code assumes but doesn't always enforce, plus the landmines that have bitten us.
Building a customer deployment on top of the core
Lambda ERP is one deployment per customer (see the design choices above). A
customer that needs core business-logic changes should not fork this
repo. Instead, create a separate private repo that depends on this one as
the core and overrides behavior at defined seams. Core fixes then arrive via a
version bump, not a merge into a diverging fork. Full plan and rationale:
docs/core-extension-architecture.md.
Customer repo layout
acme-erp/
pyproject.toml # depends on lambda-erp (git / path / PyPI)
acme/
plugin.py # register() — wires backend overrides + hooks
sales_invoice.py # e.g. class AcmeSalesInvoice(SalesInvoice): ...
frontend/
package.json # depends on @lambda-development/erp-core
tailwind.config.ts # scans @lambda-development/erp-core dist + adds its preset
src/
plugin.ts # registers frontend overrides (doctypes/routes/nav/branding)
main.tsx # import plugin + styles, then bootstrap()
config/ # branding, enabled features, base currency, OAuth
deploy/ # Dockerfile, env/secrets
Override core logic (replace) — subclass + register
# acme/sales_invoice.py
from lambda_erp.accounting.sales_invoice import SalesInvoice
class AcmeSalesInvoice(SalesInvoice):
def _get_gl_entries(self):
gl = super()._get_gl_entries()
# customer-specific posting
return gl
Registering it makes every loader path (create/load/update/submit/cancel) and
document conversions use the subclass.
Add behavior (don't replace) — lifecycle hooks
from lambda_erp.hooks import register_hook
register_hook("Sales Invoice:after_submit", push_to_external_system)
Events are "<DocType>:{before,after}_{save,submit,cancel}". before_* run
inside the document's transaction (a raise aborts and rolls back — use for
guards/validation); after_* run post-commit (the voucher is durable — use
for side-effects/integrations).
Wire it up
# acme/plugin.py
from api.services import register_doctype, register_converter
from lambda_erp.hooks import register_hook
from .sales_invoice import AcmeSalesInvoice
def register():
register_doctype("Sales Invoice", AcmeSalesInvoice)
register_hook("Sales Invoice:after_submit", push_to_external_system)
# register_converter(source, target, fn) # only to replace conversion *logic*
Point the deployment at it with LAMBDA_ERP_PLUGINS=acme (comma-separated for
several). On startup the core imports each module and calls register(). Unset
= the core runs unchanged.
Frontend overrides — the @lambda-development/erp-core library
The frontend ships as a library. The customer app depends on it, registers its overrides in a plugin module, then boots the shared app shell. The seams mirror the backend: add/replace doctypes, routes, nav, whole components, branding, and the API base — without editing core files.
// frontend/src/plugin.ts — runs before bootstrap()
import {
registerDoctype, registerRoute, registerNavGroup, registerComponent,
configureBranding, configureApiBase,
} from "@lambda-development/erp-core";
import AcmeDashboard from "./acme-dashboard";
configureApiBase(import.meta.env.VITE_API_BASE ?? "/api");
configureBranding({ appName: "Acme ERP", tokens: { "--brand": "260 80% 55%" } });
registerDoctype({ slug: "service-ticket", label: "Service Ticket", /* …schema… */ });
registerNavGroup({ label: "Service", icon: null, items: [{ label: "Tickets", path: "/app/service-ticket" }] });
registerRoute({ path: "reports/sla", element: <SlaReport /> }); // under the app shell
registerComponent("Dashboard", AcmeDashboard); // swap a core component
// frontend/src/main.tsx
import "./plugin"; // register overrides first
import "@lambda-development/erp-core/styles.css"; // base tokens + layers (your Tailwind processes it)
import "./acme.css"; // optional: override :root tokens, add utilities
import { bootstrap } from "@lambda-development/erp-core";
bootstrap(); // builds routes AFTER registration, then mounts
Styling follows the "consumer scans source" model — your app runs Tailwind and the library provides the tokens and preset:
// frontend/tailwind.config.ts
import erpPreset from "@lambda-development/erp-core/tailwind-preset";
export default {
content: ["./src/**/*.{ts,tsx}", "./node_modules/@lambda-development/erp-core/dist/**/*.js"],
presets: [erpPreset],
};
Rebrand by overriding the :root CSS variables (--brand, --surface, --text,
…) — at runtime via configureBranding({ tokens }) or statically in your own CSS.
Wire up overrides: registry registration at runtime (above) covers most
cases. For a build-time whole-module swap, point a Vite resolve.alias at your
replacement file.
Rules
- Don't edit core files — override at a seam. If what you need to change isn't a seam yet, add the seam to the core (a PR here), then override from the customer repo.
- Keep branding / feature toggles / base currency / auth config in
config/env, not code. - Bump the core version to pull fixes; never copy core code in.
Both the backend seams (document classes, lifecycle hooks, converters, plugin
loading) and the frontend seams (doctype/route/nav/component registries,
branding, configurable API base, Tailwind preset) are implemented. The backend
also builds a clean pip wheel and the frontend a @lambda-development/erp-core npm library
— see docs/packaging-distribution-plan.md
for the publish path.
Contributing
This is early. The project needs:
- Country compliance packs (tax rules, invoice formats, mandatory fields)
- Industry templates (services, retail, light manufacturing, SaaS)
- More preset reports
- A Postgres storage adapter (the current SQLite layer is fine for local evaluation but will need to be swapped for real multi-user write loads)
- Better observability around token spend per turn
- Native messenger integration, for WhatsApp, Telegram, etc.
PRs welcome. File an issue first if it's a big change, or drop by our Discord to discuss ideas.
License
MIT. See LICENSE for the full text.
Changelog
Release notes live in CHANGELOG.md. Releases are tagged
vX.Y.Z and published in lockstep to PyPI (lambda-erp) and npm
(@lambda-development/erp-core).
Status
Version 0. Working prototype that implements the vision. Fine for demos, internal tools, and hacking. Not yet ready to handle your company's actual books - run it alongside your real ERP if you want to kick the tires.
If you try it, we'd love to know what broke.
Trademarks and affiliations
Lambda ERP and lambda.dev are product and trade names of TORUS INVESTMENTS AG. It is not affiliated with, endorsed by, or sponsored by OpenAI, Anthropic, SAP, Oracle, Microsoft, or any other company named in this repository. SAP, Business One, S/4HANA, Oracle, NetSuite, Microsoft, Dynamics, OpenAI, GPT, Anthropic, and Claude are trademarks of their respective owners and are referenced here only for descriptive and comparative purposes (nominative fair use). We interoperate with OpenAI and Anthropic APIs as a customer like anyone else; you supply your own API keys.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lambda_erp-0.1.0.tar.gz.
File metadata
- Download URL: lambda_erp-0.1.0.tar.gz
- Upload date:
- Size: 170.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22b8632c4d2ff69da2129b5eb3487599f97d3e5f96feff987387635db4ffce76
|
|
| MD5 |
2c2bf176d7c87515b49eb8661aa9b6e2
|
|
| BLAKE2b-256 |
148932e455581ae3eb86ccdff4550dcb39859e86b967a0b224299bf2d907d0e0
|
Provenance
The following attestation bundles were made for lambda_erp-0.1.0.tar.gz:
Publisher:
release.yml on lambdadevelopment/lambda-erp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lambda_erp-0.1.0.tar.gz -
Subject digest:
22b8632c4d2ff69da2129b5eb3487599f97d3e5f96feff987387635db4ffce76 - Sigstore transparency entry: 1635399104
- Sigstore integration time:
-
Permalink:
lambdadevelopment/lambda-erp@098d9051d8cebbafc040d85e2657c02ad5fb13e7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lambdadevelopment
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@098d9051d8cebbafc040d85e2657c02ad5fb13e7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file lambda_erp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: lambda_erp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 194.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
72fd40fb0fe27201ed9e999c3be9756c9b6699f82f21f4a09116ed1b9c1686ca
|
|
| MD5 |
e26b78fecad9156d62027457b8646a0a
|
|
| BLAKE2b-256 |
c8ca8e59f0e4789a28701751d614b68ddecfc53e5ee7e6016dc992d455b162f9
|
Provenance
The following attestation bundles were made for lambda_erp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on lambdadevelopment/lambda-erp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lambda_erp-0.1.0-py3-none-any.whl -
Subject digest:
72fd40fb0fe27201ed9e999c3be9756c9b6699f82f21f4a09116ed1b9c1686ca - Sigstore transparency entry: 1635399114
- Sigstore integration time:
-
Permalink:
lambdadevelopment/lambda-erp@098d9051d8cebbafc040d85e2657c02ad5fb13e7 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lambdadevelopment
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@098d9051d8cebbafc040d85e2657c02ad5fb13e7 -
Trigger Event:
push
-
Statement type: