One purchase order, across ERPNext, the warehouse and an email.
The order, the receipt and the invoice resolve onto one record, and every value remembers where it came from.
Palantir does it for governments. Norn does it for the businesses Palantir will never serve, starting with ERPNext.
The purchase order is in ERPNext. The goods receipt is in the warehouse system. The supplier’s invoice arrived as a PDF by email. No system holds all three, and an AI agent with a payment tool sees only the invoice.
Illustrative worked example. Delta Pack (an Alexandria distributor), Nile Packaging and every number here are fictional sample data. The live demo currently runs a different scenario, a credit hold on a sales order.
Purchase order to Nile Packaging: 500 cartons, 40×30.
PO-0117 · 500 × carton · EGP 60,000Goods receipt at the dock: 400 cartons. One pallet short, noted on paper.
GR-0042 · 400 receivedThe supplier’s invoice, a PDF attachment, billing the full 500.
INV-2291.pdf · 500 × carton · EGP 60,000Reads the invoice, finds a purchase order with the same number, and calls the payment tool.
pay_invoice(INV-2291)Norn turns your systems into one typed model of how the business works. Every change, whether by a person, a webhook or an agent, goes through it, is checked against the rules, and is written down.
The order, the receipt and the invoice resolve onto one record, and every value remembers where it came from.
Rules are decision tables, not code in someone’s head. When the invoice bills more than the dock received, the payment waits.
| Condition | Decision | |
|---|---|---|
| 1 | invoiced = ordered = received | approve |
| 2 | invoiced > received | quantity_mismatch |
| 3 | anything else | review |
The attachment is read, matched against the order and the receipt, and routed to declared steps. Where the facts disagree, a person decides.
Permissions know the state of the business, not just who is asking. A refusal changes nothing, says why, and hands the case to a person with the evidence.
Every decision keeps its causes. Ask why the invoice wasn’t paid and Norn walks back from the refusal to the rule, the receipt, the order, and the line on the PDF they came from.
Each needs data, logic, action and security in one model. Split them across tools and none of these exist.
runs today in the engine, on norn devin the live demo on the playground’s sample data
The agent’s payment call is refused because the invoice doesn’t match the receipt, not because of its role.
A payment workflow with no human gate after a failed match is rejected before it ever runs.
Process mining over your own history shows which steps were skipped, and who skipped them.
Change a rule and see which past cases it would have held, before you switch it on.
The mismatch goes to Finance as a task with the order, the receipt and the invoice line side by side, the bad line highlighted.
Palantir proved the model: data, logic, action and security in one ontology. It is among the most valuable software companies in the world, it works with governments and the largest enterprises, and it is priced and staffed for them. For nearly every other business it is out of reach.
This technology is too important to rent from abroad. Norn is built so a business, or a country, can run it on its own terms. Built in Alexandria, for businesses like ours.
Each business’s state and ledger live in one database file it can export, verify and restore, with nothing held back.
The same kernel runs hosted at the edge or self-hosted on your own servers inside the country. The self-hosted cell daemon is next.
A SQLite file, JSON definitions and a hash-chained ledger export you can read with ordinary tools, not a format only the vendor can open.
An agent sees exactly four things: typed data (objects and links), rules and workflows, permissions, and actions served as MCP tools. There is no hidden glue between them, so what the agent can do is what the model says it can do.
{
"name": "procurement.supplier_invoice.pay_invoice",
"description": "Pay a matched, approved supplier invoice.",
"inputSchema": {
"type": "object",
"properties": { "invoice": { "type": "string" } },
"required": ["invoice"]
},
"annotations": {
"requires": "procurement.SupplierInvoice in [approved]",
"emits": ["transitioned", "refused"]
}
}
{
"isError": true,
"structuredContent": {
"accepted": false,
"refusal": {
"code": "rules.three_way_match.quantity_mismatch",
"message": "the action was refused; nothing was committed",
"explanation": "INV-2291 bills 500 × carton; GR-0042 received 400; payment only leaves [\"approved\"]",
"routed_to": "task/finance-review/INV-2291"
}
}
}
Tool names, codes and ids follow the engine’s conventions; the three-way-match definitions are the worked example on this page, not a shipped package.
Lifecycles are declared state machines: only a declared transition can move a state, and it is enforced at commit. Every commit appends events to a hash-chained ledger: created, transitioned, observed, deleted, timer, refused. Rules trigger on those events.
import { Schema } from "effect"
import { Norn } from "@norn/authoring"
export const SupplierInvoice = Norn.type({
name: "procurement.SupplierInvoice",
properties: Schema.Struct({
number: Schema.String,
purchase_order: Schema.NullOr(Schema.String),
quantity: Norn.decimal(0),
total: Norn.decimal(2),
status: Schema.Literals(["received", "matched", "approved", "paid", "disputed"]),
}),
identity: [{ name: "primary", properties: ["number"] }],
lifecycles: [{
property: "status",
initial: ["received"],
terminal: ["paid"],
transitions: [
{ id: "match", from: ["received"], to: "matched", by: "rules.three_way_match" },
{ id: "dispute", from: ["received", "matched"], to: "disputed" },
{ id: "approve", from: ["matched"], to: "approved", gate: "human:finance" },
{ id: "pay", from: ["approved"], to: "paid" },
],
}],
})
{
"kind": "transitioned",
"object": "procurement.SupplierInvoice/INV-2291",
"transition": "match",
"from": "received",
"to": "matched",
"by": "rules.three_way_match",
"cause": ["GR-0042", "PO-0117"],
"prev": "sha256:(elided)",
"hash": "sha256:(elided)"
}
{
"kind": "refused",
"actor": "ap_agent",
"action": "procurement.supplier_invoice.pay_invoice",
"object": "procurement.SupplierInvoice/INV-2291",
"code": "rules.three_way_match.quantity_mismatch",
"prev": "sha256:(elided)",
"hash": "sha256:(elided)"
}
Abridged. Hashes elided. A write that isn’t a declared transition is refused at commit, whoever asks.
Definitions are pure data. Rules are pure functions over a slice of state and an event, with budgeted expressions: no IO, no clock, no randomness. Side effects leave only as explicit commands, and their results come back as observations that are re-checked before they count.
decide(slice, event) → facts + commands + timers
const now = Date.now() // NORN-FLOW-AMBIENT 9:17
const coin = Math.random() // NORN-FLOW-AMBIENT 10:18
A pure Rust core, a sans-IO kernel compiled to native and to wasm, and one Durable Object per tenant holding that tenant’s data in its own SQLite file. There is no database cluster, queue cluster or fleet of microservices to run, because the cell is its tenant’s database, workflow engine and policy point in one process.
A sans-IO kernel that decides transitions. Same inputs, same result, native or wasm.
runs todayState and ledger together in a single file the tenant can export, verify and restore.
runs todaynorn devThe whole cell on a laptop: scoped reads, ledger, OpenAPI and MCP.
runs todayOne Durable Object per tenant at the edge, timers on the DO alarm. Being deployed now.
rolling outperform.Three rules that keep the substrate small enough to read end to end.An agent works on a fork of the business, a branch of the ledger, never on prod. Its changes merge back only through the same checks; conflicts are explicit, because every write is version-checked, and the agent has to resolve them. A bad merge is reverted by replaying the ledger.
Mostly planned. The ledger, verified restore and version-checked writes run today; forks, merges, rollback and simulation are next. Chips below say which is which.
When the structure lives in the substrate, the model has less to get right. Typed objects, declared states, rules, permissions, and refusals that say exactly what is wrong: the substrate catches what a smaller model would miss, so smaller and cheaper models can do work that otherwise needs the smartest one. A thesis we are building on, not a benchmark claim.
Point an agent at /mcp. It gets your actions, not your database. Calls go through the same authorization, lifecycle checks and ledger as a person’s, and a refusal is a structured tool error that commits nothing.
With authorization, lifecycle checks and typed refusals, plus norn.why for the chain behind any decision.
HTTP, GraphQL, SDK and other MCP services wrapped as Norn tools, agentgateway-style, so the agent’s whole surface is governed by one policy.
{
"mcpServers": {
"norn": {
"type": "http",
"url": "http://127.0.0.1:8787/mcp",
"headers": {
"x-norn-org-id": "org_demo",
"x-norn-environment-id": "dev",
"x-norn-actor": "ap_agent"
}
}
}
}
norn dev — a local cell with scoped reads, ledger and OpenAPInorn.whyThe playground walks a different worked example, a credit hold on a sales order: the hold, the refused agent, the “why?”, and a rule change backtested on six months of history.
Sample data, replayed in the browser. No live connectors, no sign-up. The three-way match on this page is illustrative; the playground does not run it yet.
A walkthrough on your own order flow, no slides. Operators and ERPNext implementation partners first.