Docs

Install, operate, and query.

Everything below is verified end to end on clean machines. Nothing here is aspirational. A single standalone node is free, self-hosted, with no time limit and no phone-home. You install one command, and it comes up serving on port 4000.

New to Semurg? The Start here walkthrough is the friendly, five-minute version of this page: download, install, bring data in, see it, ask it, and time it. This page is the full reference. If a step goes wrong, the Troubleshooting page has the fix.
Install

One command on a fresh Ubuntu box.

sudo ./semurg-install.sh

That is the whole thing. It is idempotent, so re-running it is safe, and it fails with a plain reason rather than a stack trace. You do not need a compiler or any separate language runtime to install. The release bundles everything it needs and the engine ships prebuilt.

Requirements

RequirementMinimumNotes
OSUbuntu 24.04 LTSglibc 2.38+ (24.04 ships 2.39). 22.04 needs a separate build.
Architecturex86_64 (amd64)arm64 not yet built
CPU4 physical coresit uses your physical cores in parallel
RAM8 GBthe in-memory cache budget is sized from this
Free disk20 GBmore is better; the store is a single append-only file
Privilegeroot (sudo)installs a systemd unit
Durabilitysingle nodeAcknowledged writes survive a process crash (tested with a hard kill); surviving a power cut depends on the drive's power-loss protection. Not survived: loss of the machine or disk, which needs a second machine. Replication is on the roadmap, not shipped.

Recommended, not required: a CPU with AVX-512 for the fastest path (older CPUs use a compatible fallback; check with grep -o avx512f /proc/cpuinfo | head -1), NVMe SSD, two or more disks (it spreads across every disk it finds), 32 GB or more RAM for a larger hot set.

The seven steps you will see

  • Preflight. Ubuntu version, glibc, root, internet, minimum CPU/RAM/disk.
  • Hardware scan. Physical cores to parallelism, data disks to storage roots, RAM to the in-memory cache budget (about 20%, capped at 32 GiB), CPU features to the matching build.
  • Dependencies. Only the runtime shared libraries the bundled runtime needs.
  • Engine. Unpacks the release, tunes itself to your core count, selects the build that matches your CPU, and proves the accelerated path engages. If it does not, it falls back to a compatible build; if that fails too it stops rather than run degraded.
  • Configure. Writes /etc/semurg/semurg.env and creates the data directories.
  • Licence. Installs and verifies the offline Ed25519 licence.
  • Start and verify. Waits for engine engaged in the log and HTTP 200 on /api/health, then prints the URL.

Optional overrides

Set before running to override the hardware scan.

VariableDefaultMeaning
SEMURG_INSTALL_DIR/opt/semurgwhere the release is unpacked
SEMURG_PORT4000HTTP port
SEMURG_DATA_DIR<data disk>/semurg_storestore base directory
SEMURG_STRIPE_ROOTSauto (/data*)comma-separated per-disk storage roots
SEMURG_SHARDSauto (physical cores)parallelism (defaults to your physical cores)
SEMURG_NIF_VARIANTautopin the CPU-optimized build (auto-detected by default)
SEMURG_LICENSE_SRCbundled evalpath to your own license.key
sudo SEMURG_STRIPE_ROOTS=/data0/semurg,/data1/semurg SEMURG_PORT=8080 ./semurg-install.sh
Free single node, complete engine. A single standalone node is free and runs the complete engine: full writes, no row cap, no time limit, no feature gates, no licence required. A cluster is a paid licence: forming or joining one (SEMURG_CLUSTER=1) needs a valid per-node Ed25519 licence on each node, issued for that node's fingerprint. Verification is fully offline (no network call, ever), and expired, tampered, wrong-node, or missing keys all fail closed with a clear message. For what a single node does and does not survive, see the durability section.

Run it in a container (Docker)

The bare-metal installer above is the primary path. If you would rather run in Docker, the image comes up armed or exits with a loud, exact fix, never a silently degraded node. It is x86_64 only. The one-command path builds the image locally (pulling and checksum-verifying the signed release from one.semurg.io) and supplies all three settings the fast disk path needs:

curl -fsSLO https://one.semurg.io/dl/semurg-docker.tar.gz && curl -fsSL https://one.semurg.io/dl/SHA256SUMS | grep ' semurg-docker.tar.gz$' | sha256sum -c - && tar xzf semurg-docker.tar.gz && cd semurg-docker && docker compose up

First run builds the image (about two minutes); later runs start in seconds. When it is armed the log prints [semurg-prove] ARMED: cold-op self-test PASSED. The compose file publishes the node on host loopback 4100, so open http://127.0.0.1:4100/api/health.

Prefer explicit flags? Build the image once, then run it yourself. These four are the whole minimal set, nothing else (no --privileged, no --device, no --shm-size, no special mounts):

cd semurg-docker && docker compose build
docker run --ulimit memlock=-1 --security-opt seccomp=unconfined -v semurg-data:/var/lib/semurg -p 127.0.0.1:4100:4000 semurg/substrate:r11-test

Both paths publish the node on host loopback 4100 (the container always listens on 4000 inside), so the node is at http://127.0.0.1:4100 either way. Keep the 127.0.0.1: prefix so the port stays private.

FlagWhy it is required
--ulimit memlock=-1The engine locks memory for its resident cache and registers a large I/O buffer. Docker's small default makes both fail silently and reads fall back to the OS cache.
--security-opt seccomp=unconfinedDocker's default seccomp profile blocks the low-level disk I/O calls the fast path uses, so it cannot submit reads.
-v semurg-data:/var/lib/semurgA named volume is a real filesystem (ext4), which the fast direct-disk path needs. A host-folder bind-mount lands on overlayfs, which does not support it, so the store cannot open in fast mode.
-p 127.0.0.1:4000:4000Publish on host loopback (private by default). The node binds 0.0.0.0 inside the container so the mapping can reach it; keep the 127.0.0.1: prefix and put your own firewall + TLS/auth proxy in front before exposing it.

The fast path uses large memory pages when available and falls back gracefully, so no special reservation or mount is needed. On Docker Desktop (Mac/Windows) a named volume is ext4 inside the Docker VM and works; a host-folder bind-mount does not. If any capability is missing the container exits non-zero and prints the exact flag and corrected command.

Operate

Run it through the service manager.

systemctl status semurg      # is it running
journalctl -u semurg -f      # live logs
systemctl restart semurg     # apply config changes
systemctl stop semurg

Inside a container without systemd, the installer prints the equivalent commands for the background daemon. The installer writes /etc/semurg/semurg.env (data dir, stripe roots, cache budget, port, licence path); edit it and restart to change configuration.

Single writer per store. A second process opening the same store is unsafe, so restart only through the service manager (systemctl), never by launching a second instance, and never point a second node at a live store directory.

Load a graph bigger than memory (optional)

A fresh node comes up empty: the large public graphs are not bundled in the download, so a just-installed node holds only what you put in it. To watch the out-of-core traversal survive on a graph larger than RAM, load a large public knowledge graph yourself (a Wikidata dump, roughly 100 million entities and over a billion statements, or an OpenStreetMap extract) or a redacted slice of your own data. The reproducible loading kit lives on data.semurg.io and the universal ingestor; there is no one-line sync switch today, so follow the kit. A full Wikidata graph is hundreds of GB raw and more once ingested, so budget the disk. Until you load one, the graph benchmarks (graph_teps, reads, scans) return dataset not staged; the synthetic saops and writes kernels need no dataset and run immediately.

Backups

The store is a single append-only file. Back it up by copying the store directory while the service is stopped. The offline Ed25519 licence never phones home and fails closed, so an air-gapped restore works the same as an online one.

Durability and access

What keeps the data safe, stated plainly.

Disk is the source of truth

The store is a single append-only file on disk. RAM is a pipe for reads, not a second copy. There is nothing separate to replay after a crash, because the file itself is the record.

What a single node survives, and what it does not

Ingest is append-only and lands on disk with no fsync in the path. Writes go straight to disk, so there is no OS write cache in the way. An acknowledged write survives a process crash: kill the writer mid-ingest and, on restart, every acknowledged write is recovered from the same disk. That is tested with kill -9. Durability against a power cut depends on the drive's power-loss protection, which is why enterprise NVMe is the target and why there is no fsync in the hot path. What a single node does not survive is losing the machine or its disk. Surviving that needs a copy on a second machine, and active-active replication is designed and simulated but not yet wired into the running service, so today Semurg runs as a single-writer, single-node store. Run one writer per store; a second process opening the same store is unsafe, so restart only through the service manager.

Authorization: single-writer today, structural by design

The one authorization control the node enforces today is single-writer safety: a file lock keeps a second process from opening the store, so only one writer is ever live and the store cannot be corrupted by a stray second instance. Per-caller and per-scope authorization, a capability checked in the engine rather than bolted on at the API surface, is the design and is on the roadmap; it is not enforced in this release. On a single node today, any client that can reach the port can read and, on the free build, write. See Using your node: access and roles for exactly what each user class can and cannot do.

Network and TLS: your perimeter

The node speaks plain HTTP on the port you choose and does no TLS termination or network-level auth of its own. That is out of scope on purpose. Run it behind your own perimeter (a reverse proxy, a private network, an air-gap), the same place your other internal services already live. Nothing about the node phones home, with or without a network.

Storage and the aging store

One append-only log, and what churn does to it.

How the store is laid out

Semurg is one append-only store per node. Related records are written together, so a node and its edges land side by side. A background process keeps the working set compact. The fast read image is rebuilt clean at a checkpoint; in this build a checkpoint runs at ingest.

Honest consequences

Every performance number we publish is measured on a freshly-ingested store, its densest state. As you edit existing records, those reads resolve from the newest writes rather than the compact image, so an edited store reads progressively more scattered until the next checkpoint rebuilds the image. The append-only file is not yet reclaimed on disk, so superseded bytes from edits persist and the file grows with churn. On-disk compaction is in progress, not shipped.

Three numbers, not one. We are measuring fresh, after-churn, and recovered numbers and will publish all three rather than the fresh number alone. Those figures are still being measured, so no fresh, after-churn, or recovered value is quoted here yet.
Query

Load your data, then read it ten ways.

Check the instance first. This reports the licence, whether the instance is writable, and where the customer-data store lives.

curl http://localhost:4000/v1/status
{"ok":true,
 "store":{"state":"open","path":"/data0/semurg_store/user_store/user.bin","shards":null},
 "writable":true,
 "api":"semurg /v1 data api",
 "licence":{"id":"FREE-SINGLE-NODE","expiry":"2099-12-31","caps":"core,platform,write"}}

Ingest (batch first)

One request carries a batch of compact containers and costs exactly one crossing into the engine. There is no per-item endpoint by design. Up to 65,536 containers per request; send successive batches for more. Ingest is append-only and lands on disk, with no fsync in the path.

curl -X POST http://localhost:4000/v1/ingest -H 'content-type: application/json' -d '{"containers_b64":"<base64 of packed containers>"}'
{"ok":true,"containers":65536,"bytes":4194304,"elapsed_us":<elapsed>,"containers_per_s":<measured on your box>}

The public reference box's /v1 store is intentionally empty, so run ingest and query on your own instance. The response above was captured on a 6-core container during verification.

Query

curl -X POST http://localhost:4000/v1/query -H 'content-type: application/json' -d '{"op":"fetch","ids":[1,2,3]}'
optakesdoes
fetchidsbatch point read
resolveidid to physical location
searchneedlescan-at-bandwidth pattern match
histogramfield_offset, bucketsgroup-by aggregation

There is also a binary twin, POST /v1/fetch_batch: send a packed array of little-endian u64 ids (application/octet-stream) and get back the matching raw container bytes in request order, in a single crossing into the engine, no JSON/base64 wrap.

Vector search is a face, not yet a JSON op. It is an exact search over compact binary codes (a lossy 352-bit code), not approximate ANN, with recall published honestly, and is exercised through the vector demo face (/demo/vector), not through /v1/query. There is no {"op":"vector"} on the JSON API in this release; a JSON vector op is on the roadmap. The /v1/query ops above (fetch, resolve, search, histogram) plus /v1/fetch_batch are the shipped programmatic surface.
AI memory

Memory your agents share.

This is the capability the product is named for, and it is on by default. Agents — any vendor, any device — that point at the same node and the same scope share one memory. The store is durable, under your data directory, and survives restarts.

Check it is live:

curl -s http://localhost:4000/v1/memory/status -H "Authorization: Bearer $TOKEN"

Four verbs, and that is the whole surface:

VerbWhat it does
POST /v1/memory/rememberStore something — free text, or structured facts — under a scope.
POST /v1/memory/recallAsk in plain language; get back what is relevant, with its source.
POST /v1/memory/relateLink two things, so recall can follow the relationship.
POST /v1/memory/forgetRemove something. It is gone from recall.

Remember something. Text is extracted into entities and relationships in the background, so this returns "status":"queued" immediately — give it a few seconds before you recall.

curl -s -X POST http://localhost:4000/v1/memory/remember -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"scope":"demo","content":"Acme Corp acquired Widget Ltd in October 2026."}'

Recall by naming an entity. Recall walks the graph outward from an entity it can match, so ask about a thing — "Acme Corp" — not a free-text question:

curl -s -X POST http://localhost:4000/v1/memory/recall -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"scope":"demo","query":"Acme Corp"}'
Honest limits, so you are not surprised.
Extraction is asynchronous. remember returns queued. If you recall immediately you will correctly get "answers":[] — wait a few seconds.
Recall is entity-seeded, not free-text semantic. It matches an entity and walks the graph from it. Asking "when is the board meeting?" returns no seed entity matched, which is the engine being honest, not a failure. Free-text semantic recall over vectors is on the roadmap and is not claimed here.
Writes are token-gated. Turn the whole capability off with SEMURG_MEMORY=0.
Admin console

The operator control plane.

Your node runs a private operator console: a live cluster graph, the node monitor, mechanism liveness (is the conveyor, janitor and prefetch actually armed right now), extensions, and settings. It binds a loopback-only listener, is fail-closed behind PBKDF2 + a mandatory authenticator code, and is never exposed on the public network.

Your node printed the console URL and a one-time credential on first start, under CLUSTER ADMIN CONSOLE (first-run bootstrap). To read it back out of the boot log:

docker logs $(docker compose ps -q) 2>&1 | grep -A8 'first-run bootstrap'

Opening it from your laptop, and the SSH-tunnel one-liner, are on the cluster admin page.

Reproduce our numbers

The same call on both boxes.

Every figure the site publishes is measured live and reproducible by the same call on your own instance. These point only at always-available surfaces: the synthetic saops kernel, and the pre-staged public SNAP graphs. A large loaded graph such as Wikidata is described in prose only, because it returns 503 while warming after a restart.

curl -s 'https://one.semurg.io/api/bench/graph_teps?dataset=pokec&hops=4'
{"result":{"nodes_visited":1502868,"edge_hops":30120283,"teps":<measured on your box>},
 "dataset":"pokec","benchmark":"graph_teps",
 "provenance":{"note":"whole-graph parallel k-hop BFS across all cores over the compact image; measured live on this box, download the dataset and re-run",
   "dataset_nodes":1632803,"dataset_edges":30622564}}
curl -s 'https://one.semurg.io/api/dashboard'

Returns the SAOPS kernel figure, single-stream and parallel write throughput, and a measured_live_on field that names the box and its physical core count, so you can put your instance's numbers side by side with these. The identical call runs on your localhost:4000 after install; on the public reference box pokec is pre-staged. On your own fresh node the SNAP graphs are not bundled, so load a dataset first (a fresh node returns dataset not staged until you do); the identical call then works.

Where we do not win

Worth saying up front.

BM25 relevance ranking over a large text corpus is Elasticsearch's home turf. Sublinear approximate nearest-neighbour at billion-vector scale is what the dedicated vector databases are built for; we do exact search over compact binary codes with recall published honestly, not approximate ANN, which at scale we concede to the specialist indexes. Uniform-random access over payload is bounded by physics for us exactly as it is for everyone else.

We also do not claim to run without indexes. Truth is the store; indexes are bounded and re-derivable from it. The bet is bandwidth: scan and fuse at the drive's sequential ceiling rather than maintain a pile of structures that each need their own copy of your data.

The ten faces are not all equal, so we tag each one. A few are shipped query surfaces today (graph, key-value, search, object, OLAP). Several are expressed as scan patterns over the same store, the shared scan or fold framed as that paradigm (SQL, document, time-series, streaming). One is not yet: vector, where we do exact search over compact binary codes (recall published honestly) and no vector set is loaded on the demo box. The collapse wall marks the same tiers on every card.

Tested commands

Every command on this page, curl-tested against the live box.

Read-only endpoints on the public reference box. Each returns HTTP 200 with the field shown; the write and load endpoints return the documented 403 by policy.

GET /api/health → ok:true
GET /api/datasets → four SNAP graphs status:ready
GET /api/benchmarks → the catalog
GET /api/bench/graph_teps?dataset=pokec&hops=4 → result.teps
GET /api/bench/reads?dataset=pokec → result.hot_reads_per_s
GET /api/bench/scans?dataset=pokec → result.warm_gb_per_s
GET /api/bench/saops → result.saops
GET /api/dashboard → measured_live_on
GET /api/collapse?dataset=orkut → ten lenses + fused finale
GET /v1/status → writable:true, FREE-SINGLE-NODE
403 /api/bench/writes, /api/dataset/:key/load → read-only public API by policy

Stamped to release R11 (0.1.0). The full install log lives at /var/log/semurg-install.log.