The four rings
How much CX do you have to take on?
Most systems make you take all of themselves. You want a config format and you get a runtime; you want to parse a file and you link a server. CX is built in four rings, and a ring answers one question: how much of CX do you have to take on to get the thing you want?
| Ring | What it is | What it cannot do |
|---|---|---|
| 0 — Data | the format: values, identity, schema, conversions | execute anything — there is no evaluator in Ring 0 |
| 1 — Code | execution: programs, queries, capabilities | open a socket, start a server, or reach a database |
| 2 — Platform | the store, history, the wire, services | be embedded — you reach it over a network |
| 3 — Ecosystem | distribution, registries, the marketplace, bindings | (nothing structural — it is the outermost ring) |
The order matters in one direction only. A ring may use the rings below it and never the ones above. Ring 0 uses nothing of CX at all. That single rule is why the small rings stay small: no amount of growth in the platform can reach down and make the data format heavier.
The boundary people get wrong. The XAP host — the thing that runs a feature — is Ring 2. The XAP marketplace — the thing you discover and install a feature from — is Ring 3. Running is platform; distributing is ecosystem.
Ring 0 — data
Ring 0 is CX as a data format. It parses the full grammar, gives every value an identity, validates against a schema, and converts to and from JSON, XML, YAML, TOML, and Markdown. It has no evaluator, and its only dependency is the host language's standard library.
That last sentence is the product. A Ring-0 consumer can embed CX in a place where running someone else's code is not acceptable — parsing untrusted input, a build step, a sandboxed client — and the guarantee is structural rather than a promise to be careful. There is nothing to disable.
[order id=1001 status=:paid
[line sku='CX-100' qty=2 unit=19.99]
[line sku='CX-220' qty=1 unit=4.50]]
A program is data too. CX is homoiconic, so a Ring-0 consumer
reads [?for …] as an inert element tree the way an XML parser
reads XSLT — it can hash it, diff it, convert it, and hand it on,
without any means of running it. Identity therefore belongs to
Ring 0: a definition hashes to the same bytes everywhere, whether
or not the reader could execute it.
What you can do with only Ring 0: parse and emit; convert between formats losslessly; canonicalize; hash, compare, and diff; validate against a schema; embed in another language through the bindings. See data, identity, surfaces.
Ring 1 — code
Ring 1 adds execution and nothing else. The evaluator, CXPath,
the directive set, quasiquotation, purity checking, capability
enforcement, fmt/lint, the language server. It adds no
servers, no protocol stacks, no databases — a Ring-1 deployment
still cannot open a socket.
Effects are deny-by-default. A Ring-1 program that wants to read a file, dial a host, or read the clock has to be granted that capability on the command line, and a denial names the exact flag that would allow it. So \"adds execution\" does not mean \"adds reach\": the reach is a separate, explicit decision at every run. See capabilities.
$ cx report.cx # pure: computes, touches nothing
$ cx --allow-read report.cx # now it may read files
$ cx --allow-common report.cx # the common working set
What you can do with only Ring 1: evaluate programs; query with CXPath; transform and generate; run the formatter, linter, and language server; call the stdlib packs whose own imports stay inside Rings 0–1. See code, tour-programs.
Ring 2 — platform
Ring 2 is the operating platform: the store and its journal, the XSP session layer and its profiles, the HTTP surface, the XAP host, the fabric, database drivers, session/authz/identity services. This is where data outlives a process and where more than one party is involved.
Ring 2 is reached over a network, not embedded. That is the boundary that keeps Rings 0 and 1 small and auditable: the store engine itself imports Ring 0 only and stays evaluator-free, so running a platform does not drag a server into every program that merely reads data.
What you can do with only Ring 2: persist and query; keep history you can audit and replay; serve HTTP and XSP; host XAP features; operate the whole thing. See store, operations.
Ring 3 — ecosystem
Ring 3 is everything around CX: the registry, the marketplace, feature distribution, native clients, and the packaging of the language bindings. It is the ring most under construction, and this guide says which parts are shipped and which are named.
What you can do with only Ring 3: seal, publish, and install features; consume packages; reach CX from Go, Python, Rust, or V. See xap-distribution, bindings.
The import contract
The rings are not a convention — they are enforced. The contract is one rule per ring:
Ring 0 MUST import nothing internal.
Ring 1 MAY import Ring 0.
Ring 2 MAY import Rings 0-1.
Ring 3 MAY import Rings 0-2.
A component may depend on rings below its own without stacking through the ones between — the shape is a directed graph, not a staircase. What no component may ever do is reach upward.
This is the promise that makes the earlier sentences true. \"Ring 0 cannot execute anything\" is not a statement about intent; it is a consequence of Ring 0 importing no evaluator, checked by the build. \"Ring 1 cannot open a socket\" is the same kind of fact.
Where to go from here
Take the smallest ring that answers your question. You can always move outward later — nothing you wrote against a lower ring has to change, because the lower ring is unchanged by the higher one. choosing is the router: a task table and four reader paths (evaluator, data-format adopter, application author, platform operator), each ending in something you can do. If you just want to try CX first, start at quickstart — it runs entirely inside Ring 0 before it shows you anything that executes.