norn
Early product · starting with ERPNext

Data, logic, action and security — in one place.

Palantir does it for governments. Norn does it for the businesses Palantir will never serve, starting with ERPNext.

Show this page for

The agent tried to pay for 500 cartons. Only 400 arrived.

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.

ERPNext

Purchase order to Nile Packaging: 500 cartons, 40×30.

PO-0117 · 500 × carton · EGP 60,000
Warehouse

Goods receipt at the dock: 400 cartons. One pallet short, noted on paper.

GR-0042 · 400 received
Email

The supplier’s invoice, a PDF attachment, billing the full 500.

INV-2291.pdf · 500 × carton · EGP 60,000
AI agent

Reads the invoice, finds a purchase order with the same number, and calls the payment tool.

pay_invoice(INV-2291)
Next: AI agents with write access. Without one model of the business, the agent sees the invoice and never the dock, and the rule that should stop it lives in someone’s head.

One model of the business. Everyone acts through it.

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.

DATA

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.

ERPNextPO-0117 · 500 ordered
WarehouseGR-0042 · 400 received
EmailINV-2291.pdf · 500 billed
PO-0117 · Nile Packaging
procurement.PurchaseOrder · 3 sources
LOGIC

A rule you can read: order, receipt and invoice must agree.

Rules are decision tables, not code in someone’s head. When the invoice bills more than the dock received, the payment waits.

Ordered 500 · Received 400 · Invoiced 500
three-way-match hit policy · first
ConditionDecision
1invoiced = ordered = receivedapprove
2invoiced > receivedquantity_mismatch
3anything elsereview
Invoice INV-2291quantity_mismatch · row 2
ACTION

An invoice arrives by email and becomes a checked workflow.

The attachment is read, matched against the order and the receipt, and routed to declared steps. Where the facts disagree, a person decides.

[email protected] Invoice INV-2291 · 1 attachment (PDF)
  1. Classified · supplier invoiceemail
  2. Extracted · 500 × carton, EGP 60,000checked
  3. Matched · PO-0117 + GR-0042mismatch
  4. Review · FinanceFN
  5. Payment · after reviewwaiting
SECURITY

The agent tried to pay. Refused by the facts, not by its role.

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.

nothing paidledger unchangedrouted to Finance

Ask why. Get the chain, not a guess.

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.

Why wasn’t INV-2291 paid?
  1. THE REFUSAL ap_agent
    pay_invoice refused
    rules.three_way_match.quantity_mismatch
  2. THE RULE
    three-way-match, row quantity_mismatch
    invoiced 500 ≠ received 400
  3. THE RECEIPT Warehouse
    GR-0042: 400 received
    dock note · one pallet short
  4. THE ORDER ERPNext
    PO-0117: 500 ordered
    Purchase Order · Nile Packaging
  5. THE INVOICE Email
    INV-2291.pdf, line 1: 500
    attachment · [email protected]
Same answer for a person, an auditor or an agent: it is one call.

Five things you can’t bolt on later.

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

runs today

Agent permissions that know the state

The agent’s payment call is refused because the invoice doesn’t match the receipt, not because of its role.

SECURITYLOGIC
runs today

Workflows verified before they run

A payment workflow with no human gate after a failed match is rejected before it ever runs.

ACTIONLOGIC
in the live demo

Reality against intent

Process mining over your own history shows which steps were skipped, and who skipped them.

DATALOGIC
in the live demo

Changes backtested on the past

Change a rule and see which past cases it would have held, before you switch it on.

DATALOGICACTION
runs today

People in the loop, with the evidence

The mismatch goes to Finance as a task with the order, the receipt and the invoice line side by side, the bad line highlighted.

ACTIONSECURITYDATA

Why not Palantir?

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.

  • Same idea.One model of the business that people and AI agents act through, with the rules and the permissions in it.
  • Different customer.The distributor in Alexandria, the factory in 10th of Ramadan, the ERPNext partner serving fifty of them.
  • Different shape.A product you install and can run yourself, starting from the ERPNext you already have, not a programme you buy.

Sovereign by design.

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.

runs today

Your data in your own file

Each business’s state and ledger live in one database file it can export, verify and restore, with nothing held back.

next · self-hosted cells

Same engine, on Cloudflare or in-country

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.

runs today

Open, inspectable formats

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.

Unabstracted primitives for agents.

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.

  • DATATyped objects and the links between them, each value with its source.
  • RULES + WORKFLOWSDecision tables and declared steps that fire on events.
  • PERMISSIONSCedar policies evaluated against the state of the facts, not just the role.
  • ACTIONSEvery admitted action, served as an MCP tool with a typed refusal.
tools/list · pay_invoicewhat the agent sees
{
  "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"]
  }
}
tools/call · pay_invoiceresult
{
  "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"
    }
  }
}
Refused by the kernel. The ledger head is unchanged.

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.

States and events are first-class.

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.

supplier-invoice.entity.tsTypeScript · Effect
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" },
    ],
  }],
})
ledger · eventwhat a commit appends
{
  "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)"
}
ledger · eventa refusal is written down too
{
  "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.

A constrained, pure-functional core.

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.

Definitions are pure data.TypeScript with Effect Schema is compiled to canonical IR and never executed. Your module is read as syntax, not imported.
Checkable before it runs. Replayable. Explainable after.The same inputs give the same decision, so a run can be verified ahead of time, replayed from the ledger, and asked “why?”.
flow.tsrejected at compile time
const now = Date.now()     // NORN-FLOW-AMBIENT  9:17
const coin = Math.random() // NORN-FLOW-AMBIENT 10:18
Not admitted. Ambient time and randomness never reach the core.

A very lean stack.

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.

Pure Rust core

A sans-IO kernel that decides transitions. Same inputs, same result, native or wasm.

runs today

One SQLite file per tenant

State and ledger together in a single file the tenant can export, verify and restore.

runs today

Local host: norn dev

The whole cell on a laptop: scoped reads, ledger, OpenAPI and MCP.

runs today

Cloudflare Durable Object host

One Durable Object per tenant at the edge, timers on the DO alarm. Being deployed now.

rolling out
Definitions are data. Rules are pure functions. Effects leave only through perform.Three rules that keep the substrate small enough to read end to end.
Small and inspectable pays three times.Cheaper to run, easy to self-host in-country, and less surface for an agent to get wrong.

Agents never touch prod. Norn works like git.

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.

Runs today

  • Hash-chained ledger — every commit appends; the head is verified on read
  • Export and verified restore — a cell rebuilds from a content-addressed export
  • Every write version-checked — compare-and-set, so a stale write is refused, not merged
  • Backtesting a rule change on history — in the live demo

Leaner agents on a thinner substrate.

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.

The model proposes; the rules decide.An agent drafts a match or a payment. Whether it goes through is the decision table’s call, not the model’s.
A refusal is a cheap retry.“Invoiced 500, received 400, payment only leaves approved” is the whole correction. No prompt gymnastics to recover it.

MCP as the universal tool surface.

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.

runs today

Every Norn action is an MCP tool

With authorization, lifecycle checks and typed refusals, plus norn.why for the chain behind any decision.

next

External services as tools, under the same permissions

HTTP, GraphQL, SDK and other MCP services wrapped as Norn tools, agentgateway-style, so the agent’s whole surface is governed by one policy.

mcp.jsonstreamable HTTP
{
  "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"
      }
    }
  }
}
procurement.supplier_invoice.matchprocurement.supplier_invoice.approveprocurement.supplier_invoice.pay_invoicenorn.why

Early product. Here is exactly where it is.

Runs today

  • norn dev — a local cell with scoped reads, ledger and OpenAPI
  • MCP — admitted actions as tools, plus norn.why
  • Cedar PDP — actions and reads authorized by compiled policy
  • Rules — decision tables in the dev loop
  • why — lineage with matched rows
  • ERPNext — signed webhooks and mapping into Norn types
  • Exact decimals in the IR and runtime values
  • Restore from a verified ledger export
  • Version-checked writes — a stale write is refused

See it run on a real engine, in your browser.

The 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.

Open the live demo

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.

Early product. If you run a business on ERPNext, or implement it for others, I want to talk.

A walkthrough on your own order flow, no slides. Operators and ERPNext implementation partners first.

Youssef Tarek Ali · Founder
Building Norn. Happy to walk you through it.