Orly — a database where concurrent writers never conflict.
Many writers, one shared graph, no locks and no merge code.
Every multi-agent system reinvents shared memory: a mutex, a queue, or a
single-writer service everyone funnels through. Orly removes the problem rather
than coordinating around it — commutative writes (+=, set-union |=) merge by
construction, so N writers can hit the same key at once and every contribution
lands.
docker run -it --rm ghcr.io/orlyatomics/orly repl(true) effecting { *<['hits']>::(int) += 1; };
(true) effecting { *<['hits']>::(int) += 1; };
*<['hits']>::(int); --> 2
(true) effecting { *<['tags']>::({str}) |= {'blue'}; };
(true) effecting { *<['tags']>::({str}) |= {'green'}; };
*<['tags']>::({str}); --> ["blue", "green"]
No new, no read-modify-write, no lock — += on an absent key folds from the
monoid identity (0 / the empty set), so there is nothing to create first.
Those two writes happen to come from one REPL, which only shows you the
operator. The claim that matters is that they need not be ordered,
coordinated, or from the same client:
examples/agent-swarm/ runs 8 agents extracting
from 40 documents concurrently into one graph, deliberately colliding on hot
keys, and CI checks every contribution against independently-derived ground
truth — 147 provenance records, 105 counters, 76 pairs, zero lost writes.
orly-mcp is an MCP server, so an agent
runtime can use Orly as shared memory without touching the query language:
Two agents given the same pov id share one graph and merge concurrently —
that topology is examples/mcp-agent-duet/,
gated in CI, with a recipe for running it as two live Claude Code sessions.
A non-relational database built around Points of View, causally-ordered merge, and a compiled query language (Orlyscript).
Every value is a fold over its own history, so a point-in-time read is just a query at an earlier version. The clearest showcase is a small parimutuel prediction market ("Polymarket clone"): N traders bet on one market concurrently — zero coordination, not a single bet lost — the implied prices are a read-time fold of the trade log, and the price history is time-travel. Build on it from Python, Go, or TypeScript (browser + Node), all speaking the same WebSocket + JSON protocol.
The concurrency model behind the first two features — POVs, the Tetris merge, sequence-number ordering, and commutative field-call folding — is documented in depth in
docs/architecture.md.
-
Points of View. Optimistic concurrency without locking. Each client makes changes in its own private POV — a small sandbox — which eventually propagates into shared POVs and then into the global POV (the whole database). Field calls (
x += 1) are preferred over field changes (x = x + 1) because they merge commutatively. -
Causal ordering. Merge-conflict resolution defines its "time line" by update causality (per-repo sequence order) rather than clock time. (The 2014 pitch called this the Flux Capacitor; that standalone subsystem was retired in #262 and the ordering now lives in the indy engine.) Internal mechanism; the original pitch also implied a built-in user-facing "time travel" query operator which the engine never grew — but the capability falls out in user-space by encoding a version axis in the key and folding on read, with no engine changes. See the bitcoin, GRC-20, and prediction-market examples.
-
Orlyscript. A high-level, compiled, type-safe, functional query and programming language. Sources are compiled to
.sopackages thatorlyiloads at runtime. Supports inline tests, native compilation, and most of the niceties of a real language rather than just a query DSL. -
Sum types (tagged unions). Typed heterogeneous values —
<| Text(str) | Number(int) | Deleted |>— that construct, store (including sets of differently-tagged variants), and read back like any other value, with exhaustive compile-time-checkedwhenmatching and payload binders (Number(n): n + 1). This lets a heterogeneous event log be folded into its current state in the engine — latest-write-wins, tombstones, formatting — rather than in the driver; see the GRC-20 example. Variants may also be recursive (#103):tree is <| Leaf(int) | Branch(<{.l: tree, .r: tree}>) |>;declares a binary tree, constructed through the type's name (tree.Leaf(1),tree.Branch(<{...}>)) and folded with ordinary recursive functions pluswhen. Recursive values are fully first-class: they compute, compare, live in sets, store and read back (<-/*), and travel to clients over the wire — including mutually-recursive variant groups (#115, #116) — and can be widened to a superset variant withas(#104). -
Single-node first. Fail-over / replication / hundreds of thousands of transactions per second on one node. Sharding was designed for but never built; that part of the original pitch is currently aspirational. The master/slave pair (attach, join-time sync, live replication, failover promotion, bulk-import hand-off) is exercised end-to-end in CI (#367/#501); note that bulk imports are a solo-server operation — a replicating master refuses them (#498).
-
Restart durability. A disk-backed
orlyigives everything durable back after a graceful stop/restart (#435, gated in CI bytests/restart_test.sh): global data, sessions (resume sessionworks across restarts), index-id mappings, and the installed-package registry (packages auto-reinstall; an uninstall stays uninstalled). Graceful shutdown flushes dirty memory layers (#440), drains live client connections (#460), and completes even against an unresponsive replication peer (#461). One deliberate caveat: POVs are ephemeral — un-promoted pov-local updates do not survive a restart, and a pre-restart pov id is refused with a clean error rather than silently resurrected empty (#439); seedocs/PROTOCOL.md.
Docker — the engine itself is Linux-only, so this is also the macOS/Windows
path. The published image is multi-arch (linux/amd64 and linux/arm64), so
Apple Silicon pulls a native image rather than running under emulation. A cold
docker run ... repl answers in ~2s:
docker run --rm -p 8082:8082 ghcr.io/orlyatomics/orlyThat's a solo mem-sim server with the sample, graph, and market example
packages pre-compiled and installable, speaking the
WebSocket + JSON protocol on ws://127.0.0.1:8082/ —
point any client driver (clients/) or the
MCP server at it. orlyc and the source headers are in the
image, so it compiles your own .orly packages too
(docker exec <ctr> orlyc -o /var/lib/orly/packages yourpkg.orly). Extra
docker run ... <flags> pass straight through to orlyi
(#530).
The repl mode shown at the top of this README is the same image
(#538).
From source — system dependencies (Ubuntu 24.04):
sudo apt-get install -y \
build-essential gcc g++ \
uuid-dev libgmp-dev libaio-dev libsnappy-dev \
libreadline-dev libboost-system-dev zlib1g-dev \
bison flex valgrindBuild and test:
make debug # 1707 jobs, ~2-5 min on a recent laptop
make test # runs 188 test binaries
make release # LTO-built production binariesThe three production binaries land in ../out_orly/debug/ (or ../out_orly/release/):
| Binary | Purpose |
|---|---|
orly/orlyc |
Orlyscript compiler |
orly/server/orlyi |
Database server |
orly/client/orly_client |
Interactive client shell |
orlyc compiles incrementally (#312): recompiling an unchanged package into
the same output directory skips codegen and the gcc/link step outright (keyed
on a source-content hash plus the compiler's build stamp; --debug bypasses
the cache). Check-only modes: --syntax-only stops after parsing,
--semantic-only after type-checking — neither writes output — and
--transient-cc keeps only the linked .so, removing the generated C++
intermediates.
Exercise the Orlyscript test suite against compiled .orly programs:
python3 tools/lang_test.py -d orly/data tests/lang_testsA tsan config builds with ThreadSanitizer (-fsanitize=thread) to check the
lock-free / commutative-merge machinery for data races. Build a target and run
it under TSan:
tools/jhm -c tsan orly/indy/context_fold.test # output: ../out_orly/tsan/...
setarch "$(uname -m)" -R ../out_orly/tsan/orly/indy/context_fold.testThe setarch -R (disable ASLR) is required on modern kernels — without it
libtsan aborts at startup with "unexpected memory mapping". CI runs a curated
set of concurrency tests this way in a gating job — the set is clean under
TSan, so the job fails on any un-suppressed race; provably-benign reports are
documented and suppressed in orly/tsan.supp. See
.github/workflows/ci.yml and issues
#177 /
#184.
examples/bitcoin-time-travel/ — time travel + multiverse via key-encoded version
A Bitcoin-flavoured ledger that does historical queries ("balance at block 5") and branched-history multiverse queries ("balance on the fork vs the mainnet at block 7"). The trick: encode the version axis (block height) AND the branch into the key tuple, fold over the height axis on read.
examples/wikipedia-categories/ — same trick, different monoid
A Wikipedia-flavoured demo that does the same fold-over-keyed-versions trick, but with set union as the operator instead of integer addition. Watch the "Programming languages" set grow from {FORTRAN} in 1957 through {...35 entries...} in 2024 — members_at(cat, year) is one four-line Orlyscript function.
Together the first two examples prove the polymorphic-monoid claim: pick the operator (+, |, ++, min, …) and the identity, get historical queries for that domain.
examples/wikipedia-pageviews/ — concurrent += that actually composes
Wikimedia-style hourly pageview counters under contention. 8 concurrent WebSocket sessions all do *<['views', lang, page, hour]>::(int) += n against the same hot keys; the self-check confirms every increment lands (5,938 / 5,938 events across 96 keys, zero lost updates).
Most databases force a tradeoff on this workload:
| Database | Statement | Behavior under N concurrent writers |
|---|---|---|
| Postgres | UPDATE views SET n = n + 1 WHERE page = ? |
Row lock; writers serialize on hot pages |
| Redis | INCR views:<page> |
Atomic, but the server is single-threaded |
| Cassandra | UPDATE views SET n = n + 1 WHERE … |
Counter columns with consistency caveats |
| Orly | *<['views', …, page, hour]>::(int) += n |
Field call; lock-free; all N writers land their increments and the read folds them |
The mechanism: each += emits an {Add, n} mutation at the storage layer instead of resolving to a value at write time, and the read path folds same-mutator runs back into the resolved value. CI runs this as the workload-level integration test for the property; pass DEMO_SCALE=small for the CI-friendly 4-writer / 800-event variant.
examples/agent-swarm/ — multi-agent knowledge-graph extraction without coordination
The shape multi-agent LLM pipelines keep reinventing badly. N independent extractor "agents" each process a disjoint slice of a corpus and stream tags, mentions, and cooccurrences into one shared knowledge graph — with zero coordination. No locks, no per-agent partitioning, no driver-side merge logic. Every tag carries provenance — the agent that asserted it — so a tag three agents independently produce is three records you can read back, not a collapsed string. Combines both commutative-write shapes from the demos above and runs them concurrently:
*<['entity', e]>::({<{.tag: str, .agent: str}>}) |= {<{.tag, .agent}>}— set-union of provenance records per entity (thewikipedia-categoriesoperator, but concurrent and structured — storable record-sets are #90)*<['mention', e, d]>::(int) += 1— per-(entity, doc) counter*<['cooccur', a, b]>::(int) += 1— per-unordered-pair counter
8 agents extract from 40 docs concurrently, with hot entities (Python, OpenAI, Claude, GPT-4, …) deliberately round-robined across agents so they collide on the same keys; the self-check confirms every tag provenance record, mention counter, and cooccurrence counter matches the independently-derived ground truth (147 (tag, agent) records across 25 entities + 105 mention counters + 76 cooccurrence pairs, zero lost extractions). The per-entity rollup annotates each tag with how many distinct agents corroborated it (language×5).
What this would normally require:
| Approach | Problem under N concurrent agents |
|---|---|
| Per-agent local graph + post-hoc merge | Doubles storage; merge logic has to commute by hand |
Read-modify-write per fact (UPDATE … SET tags = tags ∪ {…}) |
Row lock per write; agents serialize on hot entities; provenance needs a separate join table |
| Single-writer service in front of the store | Throughput ceiling = one writer; agents queue |
| Orly | |= and += are field calls; lock-free; all N agents land their contributions and the engine aggregates |
Why it matters: this is exactly the topology AI extraction pipelines have — many agents reading disjoint chunks, emitting overlapping facts into one graph — and every conventional database forces you to invent a coordination protocol on top. Orly gives it to you for free, as a direct consequence of how field calls work. The driver never reads-then-writes; it only ever calls the commutative functions in graph.orly.
And the graph it builds is traversable: cooccurrences are stored as a symmetric <['adj', a, b]> adjacency — a trailing-free prefix scan the sorted index seeks straight to (index-free adjacency) — so bounded k-hop reachability is a reduce over a depth range with a visited set, no new engine primitive. Concurrent multi-writer construction and transitive graph queries in one store, which Neo4j (single-primary writes) and Cayley (a query layer over a store) don't combine.
examples/mcp-agent-duet/ — two MCP agents, one shared graph
agent-swarm lifted to the layer real agent runtimes use: two independent agent processes, each speaking only MCP through its own orly-mcp server instance, build one knowledge graph (same graph.orly schema) in one shared POV, concurrently — the agents share exactly one string, the pov id. The CI-gated verifier checks that corroborated tags keep one provenance record per agent, every counter lands, and a reach() traversal seeded at an entity only agent A wrote arrives at entities only agent B wrote. The README includes the recipe for running the same topology with two live Claude Code sessions (#528).
examples/grc20-pov/ — GRC-20 knowledge graph: event-sourced + concurrent editors + time-travel
A reference impl of Geo / The Graph's GRC-20 knowledge-graph standard. GRC-20 models knowledge as append-only ops (CreateEntity, SetProperty, CreateRelation, DeleteEntity) — exactly the shape Orly stores natively. The op is a typed tagged union (<| Text(str) | Number(int) | Relation(str) | Deleted |>), and every op becomes one |= of an event record (<{.ts: int, .editor: str, .op: ...}>) into a per-(entity, property) history set — a set of variant-bearing records, storable since #90 / #96, so the typed op travels end to end with no string packing. Concurrent editors stream into the same shared POV with no coordination; reads replay the timestamp-sorted log — latest-write-wins, tombstones, value formatting, time-travel — in the engine, via a reduce + an exhaustive when match, not in the driver.
The demo runs three phases over a corpus of six Greek philosophers:
| Phase | Editor | What |
|---|---|---|
| 1 | wiki |
biographical facts (name, born, died) |
| 2 | stanford |
school of thought + student_of relations |
| 3 | both | concurrent race on pythagoras.born — both events survive in history |
After each phase the demo prints a snapshot reconstructed from the event log; the final editorial diff shows per-editor event counts and overlapping entities. This example is the synthesis of the others: time-travel from bitcoin-time-travel and wikipedia-categories, concurrent multi-writer from wikipedia-pageviews and agent-swarm, applied to a published graph standard from another project rather than a synthetic workload — and it owns the GRC-20 op vocabulary and the entire replay in orlyscript, using the sum-type reduce + when fold, with the driver reduced to streaming typed events and reading resolved values.
examples/crdt-text/ — collaborative text editing: the database is the CRDT merge
A Google-Docs-style collaborative editor where Orly's commutative storage is the conflict-free merge — no operational transform, no central reconcile, no lock. It's a Logoot sequence CRDT: every character gets a dense, totally-ordered position id, so the document is an unordered set of (position, char) entries and the visible text is the engine's read-time fold — sort by position, drop tombstoned, concatenate (the same sorted_by + reduce machinery as grc20-pov, over a sequence instead of an event log). Two editors stream inserts and deletes into one shared POV with no coordination and converge; concurrent inserts at the same spot both survive (distinct dense positions, ties broken on (site, clock)), so nothing is lost. The one piece of real CRDT logic — between(p, q), the dense-order position allocation — is ~12 lines in the driver; the merge, convergence, tombstones, and time-travel (version history) are all the engine. The self-check asserts three independent readers render byte-identical text.
All six examples ship two equivalent drivers — Python (./run.sh) and Go (./run-go.sh) — and are smoke-tested in CI on every push.
docs/walkthrough.md — compile an Orlyscript package with orlyc, load it into a running orlyi, and invoke a method on it via orly_client. The full pipeline end to end.
docs/PROTOCOL.md — the WebSocket + JSON client protocol an application uses to talk to a running orlyi (the path the examples/ drivers use): connection, statements, and JSON marshaling.
Client libraries implement that protocol so apps don't hand-roll it: clients/python (orly), clients/go (orly), and clients/ts (@orlyatomics/orly on npm — typed, browser + Node). The examples/ drivers run on them. For AI-agent runtimes, clients/mcp (orly-mcp, npx -y orly-mcp) wraps the TS driver in an MCP server, so any MCP-speaking agent can use a running orlyi as conflict-free shared memory — N agents share a POV id and write concurrently with the engine as the merge (#526).
Interactive orlyscript — clients/repl (orly-repl on npm) is a REPL against a running orlyi: type an expression and see its value, define functions and call them, write through a POV and read back — no package files by hand. Each entry is compiled with orlyc into a synthetic package and installed behind the scenes (#535).
Linux. Verified on Ubuntu 24.04.
x86-64 and aarch64. Both are built and released against: the published
docker image is a multi-arch manifest, each architecture built on its own native
runner and gated by the same smokes — the MCP smoke, an in-container orlyc
compile, and the REPL — run on that architecture's own image, so what is
published is proven to work rather than merely to build.
The port itself needed exactly two changes, which says more about the codebase
than about the port: _mm_prefetch became __builtin_prefetch, and a -msse2
flag that was a no-op on x86-64 was dropped (#548). There is no inline
assembly anywhere in the tree, and the fiber fast path is setjmp/longjmp
bootstrapped from ucontext — both POSIX — so there was no hand-rolled context
switch to port. aarch64 runs make test in CI: 1753 build jobs, 203 test
binaries, zero failures on a native arm runner.
One aarch64-specific defect has been found and fixed, and it is worth knowing
the shape of it: orlyc deadlocked on arm because the compiler hoists the
thread pointer out of a loop, so a fiber that had migrated OS threads read the
previous thread's scheduler and switched onto its stack (#554). It reproduced
only in release builds. The make test job remains informational rather than
a merge gate, and the arm coverage that matters for that class of bug is a
release-build orlyc run (#556).
Earlier releases probably work; not re-tested in the revival pass.
| Component | Version |
|---|---|
| gcc | 13.3.0 |
| C++ standard | -std=c++23 |
| boost (system) | 1.83 — includes boost::beast for the WebSocket surface |
Python (for lang_test.py) |
3.12 |
Go (for the optional Go driver in examples/) |
1.22+ |
Build flags live in root.jhm; per-target overrides in debug.jhm / release.jhm / bootstrap.jhm.
Dormant from 2019 until early 2026. A modernization pass brought the codebase back to building and testing cleanly on a current toolchain — make debug, make test, make release, and the Orlyscript lang_test.py harness all pass on Ubuntu 24.04 + gcc 13 — and a substantial language arc followed: sum types / tagged unions, recursive and mutually-recursive variants (storable and client-transmissible), variant widening, and recursive-return type verification. On the engine side, the LSM merge / disk-compaction subsystem — dormant since the original codebase because of an inverted scheduler deadline — was reactivated and its latent correctness and stability bugs fixed, making writes O(N) instead of O(N²) (#227). See CHANGELOG.md for what's landed and #10 for the original revival status; the open issues track an engine-integrity and test-hardening backlog (latent revival defects plus sanitizer / coverage gaps). Contributions welcome.
See CONTRIBUTING.md for the conventions CI enforces — build/test commands and the TODO(#nnn) comment rule. The build system (jhm) lives in jhm/; the bootstrap path is documented in bootstrap.sh. There's no formal style guide beyond that; match the surrounding code.
Every successful make debug (or any jhm invocation that compiles C/C++) writes a JSON Compilation Database to compile_commands.json at the repo root. clangd, clang-tidy, IWYU, and most C++-aware editors pick it up automatically. The file is regenerated on every build, so it's .gitignored.
README.md © 2010–2026 Atomic Kismet Company. Licensed under Creative Commons Attribution-ShareAlike 4.0 International.
