A short map of the implementation. Module-level rustdoc inside each file is the source of truth; this document points to the right files for each concern. Only shipped phases (0–3) are documented in any detail.
synapse-core → graph engine. Phases 1–3 implemented (store, indexes, tx).
synapse-agent → typed Goal/Plan/Step/Outcome API. Skeleton; Phase 4.
synapse-cypher → Cypher subset parser. Skeleton; Phase 5.
synapse-proto → gRPC + HTTP wire types. Skeleton; Phase 8.
synapsed → out-of-process daemon binary. Skeleton; Phase 8.
synapse-mcp → MCP server adapter. Skeleton; Phase 8.
synapse-cli → `synapse` CLI. Skeleton; Phase 8/9.
synapse-core is the only crate with code worth reading right now. The
others are intentional empty shells with module-level rustdoc that pins
their scope so later phases land in a known shape.
crates/synapse-core/src/store/
arena.rs— slot-based arena.u32internal index, opaqueNodeId/RelIdhandles that pair a process-uniqueStoreIdwith a packed(generation, index)raw word. Two safety properties matter:- Cross-store rejection. Ids minted by store A cannot operate on
store B even though both reuse low slot indices. Write paths reject
with
StoreError::WrongStore; read paths resolve toNone/ empty iter. - Stale-slot rejection. Generation is bumped with
checked_addon every free; overflow retires the slot rather than reusing it.
- Cross-store rejection. Ids minted by store A cannot operate on
store B even though both reuse low slot indices. Write paths reject
with
tokens.rs— string interner shared by labels, rel types, and prop keys (FxHashMap<SmolStr, Id> + Vec<SmolStr>).record.rs/mod.rs—NodeRecord,RelRecord, the intrusive per-node rel list, and theStoretrait +InMemoryStoreimpl.value.rs— heterogeneousValue(incl.FiniteF64,Vector,Bytes) and theIndexValueprojection used by the property index, with a stablekind_tag-pinned ordering across enum variants.
The kernel is string-keyed end to end on its public surface — token
ids are crate-private and have no public constructor or Deserialize impl,
so a token from store A cannot be smuggled into store B.
The Phase 1 InMemoryStore is not Send + Sync; that bound lands with the
tx layer in Phase 3, which owns its own concurrency model.
crates/synapse-core/src/index/
label.rs— always-on label index,FxHashMap<LabelId, FixedBitSet>over arena slot indexes (not fullNodeIds — the bitset stays dense).scan_labelreconstructs current-generation ids through the arena, so stale slots never leak.btree.rs— opt-in property B-tree index keyed by(LabelId, PropKeyId, IndexValue). Buckets areSmallVec<[NodeId; 1]>so the unique-value-per-key case stays inline. Bucket order is unspecified — read paths sort before equality-comparing (the "sort-before-compare convention" referenced in the tx differential tests).
Index maintenance is synchronous and inline with every mutating store call. Mutations are prevalidate-then-mutate: if any index would reject the op (non-indexable existing value, NaN on a float index, …) the call errors and no state changes. That keeps per-record atomicity inside the kernel without paying for a writeahead log.
crates/synapse-core/src/tx/
The whole module is documented at the top of mod.rs; the summary:
- Isolation. Snapshot isolation. A transaction sees its begin-snapshot
view plus its own writes. Commits are rejected with
TxError::StaleSnapshotif any commit landed betweenbegin()andcommit(); callers retry. - Mechanism. Single-writer / multi-reader. One writer thread owns a
mutable
Inner. On commit it clonesInner, applies the batch, wraps in a newArc, and publishes through anArcSwap<PublishedSnapshot>. Acquisition is a lock-free relaxed pointer load; publish is a single relaxed pointer store. Readers don't block writers and vice versa. - Atomicity layers. Per-record atomicity comes from the Phase 2
prevalidate-then-mutate contract. Per-batch atomicity comes from
writer::try_applyholding the stagedInneron the stack and only publishing the finishedArc<Inner>via oneArcSwap::store— any panic during apply unwinds throughstaged'sDropbefore publish, so readers can never observe a partially-mutated commit. - Writer-thread death surfaces as
WriterDisconnected, not silent progress.
The commit_clone_cost bench measures ~468 µs against a 15 µs gate at
the 10k-node row. M3 ships as internal preview; the record_step
P99 = 50 µs headline budget is suspended until M3.5 (versioned
tombstones with snapshot-filtered indexes) lands. The escalation order is
locked in tx/mod.rs:
ArcSwap<PublishedSnapshot>— already adopted, not the gate-mover.- Per-field
Arc<T>insideInner+Arc::make_mut— useful intermediate, projected ~200–300 µs at the gate row, still over budget. - M3.5 — versioned tombstones is the only design that reaches the gate at 10k+ nodes. Multi-week rewrite touching every storage type.
Multi-op transactions clone Inner twice at present: once on the
tx's first write (for read-your-own-writes), once again in the writer's
staged clone. Until M3.5, that doubling is a tracked follow-up; add a
bench_tx_commit_cost to make it visible.
CommitLog is a bounded in-memory ring (COMMIT_LOG_CAPACITY = 1024).
M6 will frame the same records to a WAL; until then there is no consumer,
so retaining every commit forever is a permanent leak — the ring drops the
oldest entries silently. When M5/M6 wires a consumer, this becomes a
bounded handoff queue with the same shape.
crates/synapse-core/tests/:
index_proptest.rs— invariants on the label + B-tree index layers.tx_proptest.rs— randomized commit / read interleavings.tx_differential.rs— multi-tx histories vs. a model store, with the sort-before-compare convention.tx_stress.rs— long-running write/read mixes.tx_concurrent_snapshot.rs— reader / writer race surfaces.tx_bug_fixes.rs— regression suite for issues found during M3 review.
Fuzz harness under crates/synapse-core/fuzz/ targets the arena's
generation / cross-store id surface.
Phase 4 (M4) — the typed Goal / Plan / Step / Tool / Artifact /
Outcome agent API on top of synapse-core, exposed by synapse-agent.
See the module-level rustdoc in crates/synapse-agent/src/lib.rs for the
intended shape. Subsequent phases (Cypher, persistence, vector recall,
daemon + MCP, polish) are sequenced in the README; the design lives in
internal planning notes that are not part of the public repo.