Use cases/For developers

Write the rule. Skip the rest.

Mandala is meant to be the whole backend behind your frontend. You describe the application as contracts in DHL: state, actions, what must hold, what can be queried. Mandala gives you the storage, the checks, the history and the API. Your Next.js or Astro site draws the pages. This page is the tour, with what is ready and what is not. There is a section for operators at the end.

Planet Express · Amy’s afternoon

Amy is asked to make deliveries to the Moon need a second signature. In her last job that was a migration, a model change, a controller, a policy, a form and a test for each.

Here it is one line in Ship. The editor underlines her first attempt, because she compared a status to a string. She tries the contract on a throwaway node, watches it refuse the Moon, and asks Hermes to sign the upgrade.

The table, the record page, the API and the history didn’t need touching. They are read off the contract.

Mira · 10:12
Shipping 120 crates of waffle irons for Lumon to the Moon.
Refused · rule 5 of Ship

Nothing goes to the Moon until a second person confirms it. I’ve asked Leela.

state.status == Confirmed or state.destination != "Moon"
Waiting for Leelaexplain · com.planetexpress.delivery
01 · DHL today

A small language for business rules.

A contract is a Markdown file with a namespace, a version and a fenced block of DHL. An aggregate declares typed state. Actions say who is authorized, what must hold, and how state changes. That’s the whole model.

Typed, and strict about it

Text with a length, integers, booleans, identities, enums, references to other records, lists with a capacity.

Compiles to a fingerprint

A contract compiles to a schema and a small deterministic program. Their digest is the artifact id that every record is bound to.

Versions upgrade, explicitly

A new version names the artifact it upgrades from and how records migrate. Upgrades

Merge and durability are declared

Per action: how two offline devices merge, and how strongly a write must be on disk before it is acknowledged.

Tutorial: your first contract · Language reference

com.planetexpress.deliveryv3 · DHL
# Nothing ships to the Moon, or over 200 units,# without a second person confirming (Leela).action Ship()    authorize dhl    validate        state.paid or state.credit_terms        state.crates_checked == state.crates        state.status == Requested or state.status == Confirmed        state.status == Confirmed or state.units <= 200        state.status == Confirmed or state.destination != "Moon"    apply        state.status = Shipped
The whole contract (65 lines)
aggregate Delivery    state        public customer: Text(len=200)        public destination: Text(len=100)        public units: Int = 0        public crates: Int = 0        public crates_checked: Int = 0        public paid: Bool = false        public credit_terms: Bool = false        public requested_by: Identity        public confirmed_by: Identity        public status: Enum(New, Requested, Confirmed, Shipped) = New action Request(customer: Text(len=200), destination: Text(len=100), units: Int, crates: Int, credit_terms: Bool)    authorize dhl    validate        state.status == New        units > 0        crates > 0    apply        state.customer = customer        state.destination = destination        state.units = units        state.crates = crates        state.credit_terms = credit_terms        state.requested_by = context.signer        state.status = Requested action CheckCrate()    authorize dhl    validate        state.status == Requested        state.crates_checked < state.crates    apply        state.crates_checked = state.crates_checked + 1 action Confirm()    authorize dhl    validate        state.status == Requested        context.signer != state.requested_by    apply        state.confirmed_by = context.signer        state.status = Confirmed action Pay()    authorize dhl    validate        state.status != New        state.paid == false    apply        state.paid = true # Nothing ships to the Moon, or over 200 units,# without a second person confirming (Leela).action Ship()    authorize dhl    validate        state.paid or state.credit_terms        state.crates_checked == state.crates        state.status == Requested or state.status == Confirmed        state.status == Confirmed or state.units <= 200        state.status == Confirmed or state.destination != "Moon"    apply        state.status = Shipped

Real DHL, exactly as it runs: this contract compiles. Every action is checked against it before it happens, for people and AI alike.

com.planetexpress.delivery · v3.0.0compiled
source65 lines of DHL
compiles toa schema and a small program
artifact2756b4d993223d59b2c21a4a63b403cce008502c9c882cffb5a07030832acb8e

The real fingerprint of this contract. Change one character of a rule and it changes. Every record says which fingerprint decided it, so “which rules were in force?” has an exact answer.

02 · Language server and Workbench today

An editor that knows the language.

The language server lives in the same program as everything else, so the diagnostics you see are the compiler’s own. The Workbench, inside the Data page, is an editor with those diagnostics and completion, and a button to try a contract before it touches anything real.

The compiler, on a status compared to a stringreal output
validation error: invalid eq types at line 68:9          state.status == "Confirmed" or state.units <= 200

An enum is not a string. The mistake is caught when you write it, not when a record goes wrong.

mandala lsp

The language server over stdio, for desktop editors. A VS Code extension exists in the repository and is not yet published.

Try it on a throwaway node

List steps and queries; each step runs in a separate process and shows the new state or the refusal.

Install on approval

Installing a contract is an approval. Its actions and queries then become tools your accomplice can ask for, under the same checks.

Installing restarts Mandala today; pending work is durable and resumes. Installing without a restart is coming. The Workbench

Workbench · steps to tryJSON
{ "steps": [  { "action": "Request", "subject": "lumon-0412", "args": { "customer": "Lumon Industries", "destination": "Moon", "units": 120, "crates": 2, "credit_terms": true } },  { "action": "CheckCrate", "subject": "lumon-0412", "args": {} },  { "action": "CheckCrate", "subject": "lumon-0412", "args": {} },  { "action": "Ship", "subject": "lumon-0412", "args": {} }] }
Request✓ accepted
CheckCrate✓ accepted
CheckCrate✓ accepted
Ship✗ refused: bound for the Moon, not confirmed

The steps are in the Workbench’s real format, for the contract above. Each runs on a throwaway node, so nothing touches real records. The last one is refused by the Moon line, as it should be.

03 · DHL-Q

Queries are part of the contract.

DHL-Q is a pipe language: a source, then one stage per line. A query is declared next to the rules, so what can be read is known, checked and kept ready before the first record exists.

Filter, sort, page

where · sort · take · drop

today
Shape

select · let · explode

today
Join and group

Across contracts, with totals.

today
Text search

Ranked, over the fields you name.

today
Vectors

vsearch, exact, over a declared index.

coming
Graph and time

traverse · shortest · asof · latest

coming

The coming modes are built on a development branch and not in a release. What they look like · DHL-Q reference

std.business.payableexcerpt
query Open(owner: Identity, after: Text(len=100))    returns List<PayableRow>    api authenticated    dhlq    std.business.payable    | where owner == $owner and ref > $after    | sort ref    | take 100    | select subject as id, ref, supplier, number, amount_minor, currency, issued_on, due_on, status, source, digest, note, payment_reference

Real, from the standard library: an owner’s open payables, a page at a time. api authenticated exposes it through the generated API; without it a query stays internal.

04 · The API your frontend calls today

Three interfaces from one contract.

Every contract is served as REST with an OpenAPI description, as GraphQL with queries and mutations, and as gRPC. Operations are internal unless the contract says api public or api authenticated. Your frontend submits the same governed actions and queries as everyone else, and holds no business rule of its own.

The caller signs

A command carries the caller’s signed assertion. The API never signs on anyone’s behalf.

Retries are safe

A command has an idempotency key. Sent twice, it is done once and returns the original receipt.

Refusals have receipts too

A refused command is recorded, with the reason, and changes nothing.

A storefront does this today

In the repository, an Astro shop runs its catalogue, cart, quote and checkout on seven contracts through this API, in a preview. What it proves, and what it doesn’t

Not yet: SDKs for Next.js and Astro, typed clients generated from contracts, and sign-in for your application’s own users (OAuth, email and password) are on the roadmap. Today you call the API yourself, and an outside application talks to the App server, not to the Workspace. The full status table

Describe a contract’s interfacesshell
$ dhl-engine compile contracts/std/business/payable.dhl
$ dhl-engine describe contracts/std/business/payable.dhl ./payable-api

compile prints the artifact id. describe writes openapi.json, schema.graphql and api.proto.

RequestDoes
POST /v1/{tenant}/{namespace}/actions/{Action}Submits a signed command
POST /v1/{tenant}/{namespace}/queries/{Query}Runs a declared query
POST /v1/{tenant}/{namespace}/graphqlThe same, as GraphQL
05 · Connectors and MCP today

Reaching the outside world, with limits.

Rules can’t open a socket, on purpose. Talking to other services is the job of connectors, which declare the access they need and whether each effect waits for approval. Today connectors serve your accomplice and Mandala’s background jobs. Using any connector from a contract is where this is going, and is not built.

The connector SDK

One library for the parts every integration repeats: credentials and OAuth, HTTP with timeouts, backoff and rate limits, durable polling, webhooks checked and admitted once, a policy with revisions, a Settings page and tool descriptions.

Feeds, written on it

RSS and Atom, built from scratch on the SDK as its first full example. PostHog’s transport was moved onto it with the same behaviour.

The MCP catalogue

Add tools from remote MCP servers from a catalogue in Settings. You review each tool: off, read, or ask every time, with a daily ceiling per server.

Connectors todayGoogleTelegramWhatsAppGitHubCloudflareMeta AdsFacebook & InstagramPostHogAhrefsApple HealthFeedsAny MCP server

Coming: Microsoft 365, IMAP and CalDAV, Slack, Shopify, Stripe and accounting packages. Effects declared by a contract itself are built in the engine for two kinds, email and PDF documents, with a durable outbox and signed receipts; they are not yet switched on in Mandala. Webhooks, SMS and payments as effects are designed.

06 · For operators

One program. Nothing beside it.

Mandala is one executable run as one service. Storage is inside it: no database server, no queue, no cache, no container platform. The data is one directory.

todayWorkspace server

A person’s Mandala, always on: the workspace and the accomplice on a machine that stays up while laptops sleep. Linux with systemd; 2 cores and 2 GB free at least, 4 GB comfortable.

engine today · packaged edition comingApp server

The DHL engine without the workspace: it runs a business’s contracts, serves their generated API, and agrees every write among one or three voters. Today it is the dhl-engine command; a packaged edition is on its way.

The server console.

dhl-engine tui is the App server’s terminal console. It is read-mostly, works over SSH, and never edits storage.

1 Overview

Voters, leaders, lag, disk, recent errors.

2 Contracts

Installed artifacts, versions, status.

3 Tenants

Identities and delegations.

4 Activity

Certified commands, live, with refusals and reasons.

5 Logs

Live tail, by level.

6 Connect

Trusted nodes and connectivity.

Back up · stopped, the simplest wayshell
$ sudo systemctl stop mandala
$ tar -C ~ -czf /backups/mandala-$(date +%F).tar.gz .mandala
$ sudo systemctl start mandala

Everything is in the data directory. Or copy from a filesystem snapshot with no downtime. A second node is not a backup: a deletion syncs too. Backups and restore

Signed updates, with rollback

Off, ask me, or automatic in a night window. A release is accepted only if signed by your release key. The previous binary is kept and put back after three failed starts or if health doesn’t answer in 180 seconds. Updates

Restarts are safe

Queued events, approvals and messages to send are written down before they are acted on.

More for operators in the docs: requirements, the data directory, security, monitoring and troubleshooting. “Add a server”, where Mandala sets itself up on a VPS from Settings, is built and not yet released.

You write what must be true. The rest is derived, and it can’t drift.

Is DHL open source?

The licence is not published yet. The language reference and the standard library’s contracts are in the docs.

Can Mandala be my whole backend?

That is the goal. Data, rules, the API and history are there today. Sign-in for your users, SDKs, file delivery and public rate limits are not, so today it fits applications whose users are few and known. The status table has every line.

What is it written in?

Rust, with a C++ storage engine linked into the same program. Contracts run as deterministic WebAssembly.

Where does it fall short?

It is young, the ecosystem is small, and there is no SQL. Mandala vs web frameworks, vs backend services and vs traditional databases say where the usual tools are the better choice.

Who it’s forSee them together

Read the docs. Then write a rule.

Coming really soon The six principles