Norn
Show this page for

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.

For developers

Early product, starting with ERPNext.

I · The supplier

Nile Packaging sends 500 cartons.

A supplier, a purchase order, a delivery and an invoice. Four facts about one order, held in three systems that never talk to each other.

II · The purchase order

PO-0117 lives in ERPNext.

500 cartons, 40×30, at EGP 120. Ordered, approved, waiting for the goods.

III · The goods receipt

The dock counts 400.

GR-0042, in the warehouse system. One pallet short, noted on paper, never told to accounting.

IV · The invoice

The invoice bills 500.

INV-2291.pdf arrives by email. It matches the order. It does not match the dock.

V · The AI agent

An agent tries to pay it.

It reads the invoice, finds the order, and calls pay_invoice. Norn refuses: the facts don’t agree, and nothing moves until they do.

Three papers. One order.

The purchase order is in ERPNext. The delivery note 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.

Delta PackPurchase order
No.
PO-0117
Supplier
Nile Packaging
Date
09 Sep 2026
ItemQtyUnitAmount
Carton 40×30, kraft500120.0060,000.00
Total EGP60,000.00
Approved · ERPNext
Delta Pack · WarehouseDelivery note
No.
GR-0042
Against
PO-0117
Received
21 Sep 2026
ItemOrderedReceived
Carton 40×30, kraft500400

one pallet short — driver to confirm

Nile PackagingInvoice
No.
INV-2291
To
Delta Pack
Ref.
PO-0117
Sent
by email, PDF
ItemQtyUnitAmount
Carton 40×30, kraft500120.0060,000.00
Total EGP60,000.00
Payment due 30 days

The agent tried to pay. Nothing moved.

Permissions know the state of the business, not just who is asking. The refusal changes nothing, says why, and hands the case to a person with the evidence.

refused · rules.three_way_match.quantity_mismatchinvoiced 500 ≠ received 400 · payment only leaves approvednothing paid · ledger head unchangedrouted to Finance · task/finance-review/INV-2291

Why wasn’t INV-2291 paid?

  1. 0001refusedpay_invoice(INV-2291)ap_agent
  2. 0002rulethree-way-match · row quantity_mismatch · invoiced 500 ≠ received 400decision table
  3. 0003receiptGR-0042 · 400 received · one pallet shortwarehouse
  4. 0004orderPO-0117 · 500 ordered · Nile PackagingERPNext
  5. 0005invoiceINV-2291.pdf · line 1 · 500 billedemail

Same answer for a person, an auditor or an agent: it is one call.

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. Five things follow that you cannot bolt on later.

  1. 1

    Agent permissions that know the state.

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

    Today
  2. 2

    Workflows verified before they run.

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

    Today
  3. 3

    Reality against intent.

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

    On sample data
  4. 4

    Changes backtested on the past.

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

    On sample data
  5. 5

    People in the loop, with the evidence.

    The mismatch goes to Finance as a task with the order, the delivery note and the invoice line side by side, the bad line marked.

    Today

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.

Norn is the same idea for a different customer: the distributor in Alexandria, the factory in 10th of Ramadan, the ERPNext partner serving fifty of them. 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.

Your data in your own file Today
Each business’s state and ledger live in one database file it can export, verify and restore, with nothing held back.
Same engine, on Cloudflare or in-country Next
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.
Open, inspectable formats Today
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.

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_invoicerefused · ledger head unchanged
{
  "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"
    }
  }
}

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
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 · two eventswhat commits append
{ "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)" }

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.

flow.tsrejected at compile time
const now = Date.now()     // NORN-FLOW-AMBIENT  9:17
const coin = Math.random() // NORN-FLOW-AMBIENT 10:18

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.

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.

Hash-chained ledger Today
Every commit appends; the head is verified on read. Export and verified restore rebuild a cell from a content-addressed export.
Every write version-checked Today
Compare-and-set, so a stale write is refused, not merged. Backtesting a rule change on history runs on sample data.
Per-agent forks and merges Next
The agent never holds a write to prod.
Point-in-time rollback Next
An agent deletes every customer record; you roll back in one step.
Simulation Next
Run a proposed rule or workflow against the fork, or against history, before it ships.

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.

The model proposes; the rules decide. An agent drafts a match or a payment, and whether it goes through is the decision table’s call. A refusal is a cheap retry: “invoiced 500, received 400, payment only leaves approved” is the whole correction.

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.

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.match procurement.supplier_invoice.approve procurement.supplier_invoice.pay_invoice norn.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
Next
Hosted cells on Cloudflare Durable Objects, rolling out now
Admin UI for definitions, ledger and reviews
External services as tools under the same permissions
More connectors beyond ERPNext, email and warehouse feeds
Installing definitions into a running cell
Per-agent forks, rollback, simulation
Self-hosted cells in-country, on your own servers

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.

Read the developer view

Youssef Tarek Ali, founder