Skip to main content
Version: v0.9.0a2

Quickstart — two nodes federating

5 min hands-onFor first-time operatorsUpdated 2026-05-19

What you'll do

Run two Stigmem nodes on one machine and watch a fact asserted on Node A replicate automatically to Node B. End-to-end smoke test including peer handshake, replication, and audit log inspection.

The fastest path is make demo.

It starts two local nodes, registers them as peers, asserts a fact on node A, waits for pull replication, checks node B, prints recent audit entries, and tears the stack down. Everything below explains what make demo does and how to drive it manually.

Prerequisites

Tool
Version
Notes
Docker
≥ 24
Required.
Docker Compose (v2)
≥ 2.20
Required.
curl + jq
any recent
For inspecting JSON responses.
helm
≥ 3.14
Kubernetes install only — Helm chart is deferred in v0.9.0a2 (see caution below).

No Python installation required — the nodes run in containers.

Memory Garden ACL is opt-in in v0.9.0a10

Default deployments enforce direct garden_id reads and writes, but tenant-wide fact queries, recall ranking, push subscriptions, OIDC permission ceilings, and graph traversal do not filter by garden membership unless stigmem-plugin-memory-garden-acl is installed, registered, and enabled with the relevant STIGMEM_MEMORY_GARDEN_ACL_* flags. Check /v1/doctor for the current memory_garden_acl_filtering state before relying on advanced garden filtering. The endpoint is unauthenticated in v0.9.0a10 and exposes only this coarse ops posture, not garden names, memberships, tenants, or policy subjects.

Step 1 — Clone and start the stack

git clone https://github.com/eidetic-labs/stigmem
cd stigmem
make demo

make demo runs scripts/quickstart-verify.sh. By default it uses the pinned GHCR image from docker-compose.yml for speed. Contributors who need to prove the local working tree can set DEMO_BUILD=1 make demo to force a local image build first.

The quickstart starts two services:

Service
Host port
Role
node-a
8765
First Stigmem node.
node-b
8766
Second Stigmem node — the federation peer.

Local-dev defaults.

The compose file sets STIGMEM_FEDERATION_INSECURE=1 plus STIGMEM_LOCAL_DEV_ALLOW_INSECURE_NON_LOOPBACK=1 because containers address each other with Docker service DNS names instead of localhost. Do not carry those flags into production — use mTLS for any deployed federation.

If ports 8765 / 8766 are busy, the demo script automatically selects free host ports starting at 18765.

Troubleshooting make demo

Ports 8765/8766 are already in use

The containers still listen on port 8765 internally, but the host ports can move. When 8765 or 8766 are busy, make demo automatically selects available host ports starting at 18765 and prints the Node A and Node B URLs it used.

Use those printed URLs for manual curl commands, or set explicit ports before running the demo:

STIGMEM_NODE_A_HOST_PORT=18765 STIGMEM_NODE_B_HOST_PORT=18766 make demo

If a browser or curl command still points at localhost:8765 or localhost:8766, it may be talking to another local process instead of the demo stack.

Docker image pull failures

By default, the demo pulls the pinned GHCR image from docker-compose.yml. Transient network, registry, or local Docker cache failures are usually safe to retry. Contributors validating local changes — or anyone blocked by a published image pull — can build the local image instead:

DEMO_BUILD=1 make demo

Failure before teardown

make demo prints each step before it runs it. When a failure happens before teardown, the first failing step and the Docker error output are the most useful diagnostics.

To keep containers available for inspection, rerun with:

KEEP_UP=1 make demo

Then inspect the stack with:

docker compose ps
docker compose logs node-a node-b

When you are done, clean up with docker compose down -v --remove-orphans.

Step 2 — Verify federation

The demo prints each step as it runs:

Step 0 — docker compose up -d
Step 2 — Inspecting /.well-known/stigmem
Step 3 — Peer handshake (both directions)
Step 4 — Asserting fact on node-a
Step 5 — Waiting 35s for federation pull
Step 6 — Federation audit log on node-b

quickstart smoke test PASSED confirms three things:

Peer declaration accepted

The receiving node verified the signed peer declaration.

Pull replication moved the fact

Node B observed the fact written on Node A within the pull interval.

Audit endpoint readable

The federation audit log returned the expected pull and peer events.

To keep the demo stack running for manual inspection:

KEEP_UP=1 make demo

Step 3 — Manual fact assertion

With KEEP_UP=1, assert another fact on node A:

curl -s -X POST "${NODE_A:-http://localhost:8765}/v1/facts" \
-H 'Content-Type: application/json' \
-d '{
"entity": "user:alice",
"relation": "memory:prefers",
"value": {"type": "string", "v": "dark mode"},
"source": "agent:settings",
"confidence": 1.0,
"scope": "company"
}' | jq '{id, entity, relation, value, scope}'

Record the id from the response — you'll use it to confirm replication.

Step 4 — Verify cross-node replication

Node B pulls facts from Node A on the background pull interval (default 30 s):

sleep 35
curl -s "${NODE_B:-http://localhost:8766}/v1/facts?entity=user:alice&scope=company" \
-H 'Content-Type: application/json' | jq '.facts[] | {id, entity, value, scope, source_node}'

The fact asserted on Node A (localhost:8765) should appear on Node B (localhost:8766) with a source_node field pointing to Node A's node_id.

Pull interval

The default pull interval is 30 s (STIGMEM_FEDERATION_PULL_INTERVAL_S). Lower it in docker-compose.yml for testing.

Step 5 — Federation audit log

Every pull event and peer action is recorded:

curl -s "${NODE_B:-http://localhost:8766}/v1/federation/audit" | jq '.entries[-3:]'

Makefile targets

Target
Use for
What it does
make demo
smoke
End-to-end two-node smoke test: start, peer, assert, replicate, audit, tear down.
DEMO_BUILD=1 make demo
contributors
Same demo, but force-build the local working tree image.
KEEP_UP=1 make demo
inspection
Leave the stack running after the demo for manual inspection.
make demo-attack
security
Malicious-peer rejection demo: unauthorized scope write and source forgery.
DEMO_ATTACK_TRANSCRIPT=/tmp/demo-attack.json make demo-attack
audit
Same malicious-peer demo, plus a small JSON transcript with no secrets or private keys.

For ad hoc compose work, use docker compose up -d and docker compose down -v.

Malicious-Peer Rejection Demo

make demo-attack runs the focused malicious-peer acceptance gate without leaving a demo cluster running. It validates two rejection paths:

Unauthorized scope write

A peer attempts to write into a scope outside its allowed scopes.

Source-forged public write

A peer claims another source identity when writing a public-scope fact.

Expected result:

Exit success

The command exits successfully.

Focused gate passed

Output shows the focused pytest gate passed.

Rejection recorded

The rejected attempts are recorded as rejection / audit outcomes — not accepted replicated facts.

To save a machine-readable summary:

DEMO_ATTACK_TRANSCRIPT=/tmp/demo-attack.json make demo-attack
jq . /tmp/demo-attack.json

The transcript records the gate command, exit code, elapsed time, and expected scenario outcomes. It does not contain secrets or private keys.

Helm / Kubernetes is deferred in v0.9.0a10

The Helm chart has been moved to experimental/deploy-helm/ per ADR-002. It remains buildable but is unsupported until the ADR-008 reintroduction gates pass. The supported v0.9.0a10 deployment surface is Docker Compose (above).

Key generation

Generate Ed25519 keypairs with python3 infra/soak/keys.py. Keys must be base64url-encoded without padding (=).

Peer registration internals

The demo bootstraps local-only admin-federation keys inside each fresh container, registers each direction by running the CLI, then approves each pending peer with its public-key fingerprint:

stigmem federation register-peer \
--local-url http://node-a:8765 \
--remote-url http://node-b:8765 \
--scopes company,public \
--api-key "$NODE_B_ADMIN_KEY"

The receiving node fetches the sender's /.well-known/stigmem, verifies the federation public key, and stores a pending peer record. The script approves the peer after matching the SHA-256 fingerprint of the advertised public key. HTTP 409 means a prior run already registered that peer; the demo clears volumes before starting so the handshake is deterministic.

Environment overrides

Variable
Default
Purpose
STIGMEM_NODE_A_HOST_PORT
first free ≥ 18765
Node A host port for the demo script.
STIGMEM_NODE_B_HOST_PORT
next free after node A
Node B host port for the demo script.
PULL_WAIT_S
35
Seconds to wait before checking node B for replicated facts.
KEEP_UP
0
Set to 1 to leave containers running after the demo.
DEMO_BUILD
0
Set to 1 to force docker compose up --build -d.

Idempotency

Safe to re-run.

The script starts by running docker compose down -v --remove-orphans so stale peer registrations, test facts, and volumes do not affect the next handshake.

Teardown

docker compose down -v # stop and delete all data volumes

What's next