* feat: make Persistence interface async
The 14 Persistence proc fields now return Future[...] with
{.async: (raises: []), gcsafe.}, allowing real I/O backends (SQLite,
encrypted file, network) to suspend rather than block the Chronos event
loop the manager runs on.
Propagates through:
- ReliabilityManager.lock: system.Lock -> chronos.AsyncLock. Acquired
across awaits cleanly; matches the single-threaded Chronos worker the
FFI uses. Multi-OS-thread use is now explicitly the caller's
responsibility.
- sds_utils + sds.nim public API procs (wrapOutgoingMessage,
unwrapReceivedMessage, markDependenciesMet, setCallbacks,
resetReliabilityManager, cleanup, ensureChannel, removeChannel, the
getter snapshots, etc.) are now async.
- FFI request handlers in library/sds_thread/... await the new API.
- Tests converted via an asyncTest template that wraps each test body
in an async proc; setup/teardown use waitFor for their single async
call (ensureChannel / cleanup).
Lock scope is preserved exactly: the same call sites that held the
kernel Lock today hold AsyncLock now -- no new locking added.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor: drop asyncSpawn, add asyncSetup/asyncTeardown
Three asyncSpawn usages removed:
- sds.nim startPeriodicTasks: stored the periodic-task futures on
ReliabilityManager (new field `periodicTasks: seq[FutureBase]`) so
cleanup can cancel them on shutdown instead of leaking the loops
against a cleared manager.
- library/sds_thread/sds_thread.nim: fireSync moved BEFORE processing,
then `await SdsThreadRequest.process(...)` instead of asyncSpawn'ing
it. Aligns the worker with the SP-channel + lock assumption that
there are no concurrent requests; caller throughput is unchanged
because the caller only waits for receipt (fireSync), not processing.
- tests TestBus repair callback: replaced asyncSpawn(deliverExcept...)
with an explicit pending-delivery queue drained by `bus.drain()`.
Integration tests no longer rely on `sleepAsync(10ms)` to let
spawned deliveries finish — they await drain instead.
Tests also pick up an asyncSetup/asyncTeardown pair (tests/async_unittest.nim)
so suite fixtures can `await` directly. All `waitFor` in setup/teardown
blocks is gone; only the top-level asyncTest wrapper still uses waitFor
(once, to drive the async proc to completion).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Correctly propagate error hidden by new async move
* Correctly handle future cancellation exceptions, +some housekeeping
* Apply suggestion from @Ivansete-status
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
* Stylistics, async default implication addressed, nph style run
* Remove leaking CancelledFuture from public facing + as a consequence it is tuneled into handling CatchableError everywhere
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
9.1 KiB
nim-sds — Claude Code Guide
Project Overview
nim-sds is a Nim implementation of the Scalable Data Sync (SDS) protocol (spec, IFT LIP-109). SDS achieves end-to-end reliability when consolidating distributed logs in a decentralized manner — participants broadcast messages over a P2P transport, maintain per-channel append-only logs, and use causal ordering to reach consistent state across all nodes.
The library exposes its functionality via a C-compatible FFI so it can be embedded in applications on any platform. Go bindings are maintained in a separate repo: logos-messaging/sds-go-bindings.
Protocol Concepts
Understanding these is essential before modifying any core code.
Message format
Each SDS message carries (sds/message.nim, sds/protobuf.nim):
| Field | Purpose |
|---|---|
message_id |
Globally unique, immutable identifier |
sender_id |
Originating participant |
channel_id |
Communication channel |
lamport_timestamp |
Logical clock for ordering |
causal_history |
IDs of the 2–3 most recent messages the sender has seen (dependencies) |
bloom_filter |
Compact summary of all message IDs the sender has received |
content |
Application payload |
Sending a message (sds/sds_utils.nim → wrapOutgoingMessage)
- Increment the per-channel Lamport timestamp to
max(current_time_ms, timestamp + 1). - Attach causal history from the local log tail.
- Embed the current bloom filter snapshot.
Receiving a message (sds/sds_utils.nim → unwrapReceivedMessage)
- Deduplicate by
message_id. - Check causal dependencies — if any predecessor is missing, buffer the message.
- When all dependencies are met, deliver: insert into the ordered local log (Lamport timestamp, tie-break by ascending
message_id). - Record
message_idin the bloom filter.
Periodic sync
A node periodically broadcasts a message with empty content carrying an updated Lamport timestamp and bloom filter. These sync messages are not persisted and are excluded from causal chains. They help peers detect gaps in their logs.
SDS-R (Repair extension)
Defined in the spec but not yet implemented in this library.
Bloom filter (sds/bloom.nim, sds/rolling_bloom_filter.nim)
Used to compactly summarise which messages a node has received, so peers can identify gaps without exchanging full ID lists. The rolling variant automatically resets when capacity is exceeded.
Repository Layout
sds/ # Core protocol (pure Nim, no FFI)
message.nim # SdsMessage, HistoryEntry, config constants
sds_utils.nim # ReliabilityManager — send/receive/buffer logic
protobuf.nim # Protobuf encode/decode for SdsMessage
protobufutil.nim # Low-level protobuf helpers
bloom.nim # Bloom filter implementation
rolling_bloom_filter.nim # Adaptive rolling bloom filter
library/ # C FFI wrapper around the core
libsds.nim # Exported C-compatible entry points
libsds.h # C header
ffi_types.nim # C-compatible types and return codes
alloc.nim # Memory allocation helpers
sds_thread/ # Per-context Chronos async worker thread
events/ # JSON serialisation for event callbacks
tests/
test_bloom.nim # Bloom filter unit tests
test_reliability.nim # Protocol-level unit tests
sds.nim # Root module — re-exports public API
sds.nimble # Package manifest + build tasks
flake.nix / Makefile # Reproducible cross-platform build system
Key Types
| Type | File | Role |
|---|---|---|
ReliabilityManager |
sds_utils.nim |
Per-channel protocol state: Lamport clock, bloom filter, log, buffers |
ReliabilityConfig |
sds_utils.nim |
Tunable parameters (bloom capacity, history length, resend interval) |
SdsMessage |
message.nim |
Wire message |
HistoryEntry |
message.nim |
message_id + optional retrieval hint |
UnacknowledgedMessage |
message.nim |
Outgoing message with resend counter |
IncomingMessage |
message.nim |
Buffered message waiting on missing dependencies |
FFI API (library/libsds.nim)
The C API wraps ReliabilityManager behind an opaque SdsContext handle:
| Export | Maps to |
|---|---|
SdsNewReliabilityManager |
Create context |
SdsWrapOutgoingMessage |
wrapOutgoingMessage |
SdsUnwrapReceivedMessage |
unwrapReceivedMessage |
SdsMarkDependenciesMet |
Notify buffered-message dependencies satisfied |
SdsSetEventCallback |
Register event handler (JSON payloads) |
SdsSetRetrievalHintProvider |
Register hint-provider callback |
SdsStartPeriodicTasks |
Start periodic sync loop |
SdsCleanupReliabilityManager |
Free context |
SdsResetReliabilityManager |
Reset state without freeing |
Each SdsContext runs a dedicated Chronos async loop on a worker thread; application threads communicate with it via SPSC channels.
Running Tests
nimble test
Nix can also provide the environment if a local Nim install is not available:
nix develop --command nimble test
Code Conventions
- Types: PascalCase (
ReliabilityManager,SdsMessage) - Variables/procs: camelCase
- Public exports: trailing
* - Errors:
Result[T, ReliabilityError]— use.valueOr,.isOk(),.isErr() - Locks:
withLockmacro (RAII); all exported procs are{.gcsafe.} - Constants:
Defaultprefix (e.g.,DefaultBloomFilterCapacity) - Backward compat:
sds/protobuf.nimsupports old and new causal history formats — do not remove the legacy decode path
Building
Nimble is the primary build tool. Desktop library targets:
nimble libsdsDynamicMac # macOS .dylib
nimble libsdsDynamicLinux # Linux .so
nimble libsdsStaticMac # macOS .a
nimble libsdsStaticLinux # Linux .a
Nix (flake.nix) and Make (Makefile) are optional conveniences that wrap Nimble for reproducible and cross-platform (including Android/iOS) builds.
Dependency Management
Nimble dependencies are locked in nimble.lock.
nimble setup -l # local setup
nimble lock # update lock after changing sds.nimble
If using Nix, also recalculate the fixed-output hash in nix/deps.nix after updating nimble.lock (run nix build, copy the expected hash from the error, paste into outputHash).
GitNexus — Code Intelligence
This project is indexed by GitNexus as nim-sds (889 symbols, 1437 relationships, 45 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
If any GitNexus tool warns the index is stale, run
npx gitnexus analyzein terminal first.
Always Do
- MUST run impact analysis before editing any symbol. Before modifying a function, class, or method, run
gitnexus_impact({target: "symbolName", direction: "upstream"})and report the blast radius (direct callers, affected processes, risk level) to the user. - MUST run
gitnexus_detect_changes()before committing to verify your changes only affect expected symbols and execution flows. - MUST warn the user if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use
gitnexus_query({query: "concept"})to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use
gitnexus_context({name: "symbolName"}).
Never Do
- NEVER edit a function, class, or method without first running
gitnexus_impacton it. - NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use
gitnexus_renamewhich understands the call graph. - NEVER commit changes without running
gitnexus_detect_changes()to check affected scope.
Resources
| Resource | Use for |
|---|---|
gitnexus://repo/nim-sds/context |
Codebase overview, check index freshness |
gitnexus://repo/nim-sds/clusters |
All functional areas |
gitnexus://repo/nim-sds/processes |
All execution flows |
gitnexus://repo/nim-sds/process/{name} |
Step-by-step execution trace |
CLI
| Task | Read this skill file |
|---|---|
| Understand architecture / "How does X work?" | .claude/skills/gitnexus/gitnexus-exploring/SKILL.md |
| Blast radius / "What breaks if I change X?" | .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md |
| Trace bugs / "Why is X failing?" | .claude/skills/gitnexus/gitnexus-debugging/SKILL.md |
| Rename / extract / split / refactor | .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md |
| Tools, resources, schema reference | .claude/skills/gitnexus/gitnexus-guide/SKILL.md |
| Index, status, clean, wiki CLI commands | .claude/skills/gitnexus/gitnexus-cli/SKILL.md |