Neotoma

Peer sync

Translation for id is not available yet; showing English source (translated_from_revision=2026-05-12).

Peer sync lets two explicit Neotoma instances exchange selected entity state without a central hub. Each side stores immutable sync-originated observations and the reducer computes snapshots locally, so peers stay independent stores of truth that converge through signed webhooks. Available since v0.12.0.

Use peer sync when:

  • A team operator wants to mirror selected entity types (issues, product feedback, runbook notes) from a personal instance to a shared "central" instance.

  • Two self-hosted instances need to stay in step without a hosted broker.

  • A hosted operator instance wants to receive guest-tagged entities from many personal instances over a signed webhook.

MCP tools

  • add_peer({ peer_id, peer_name, peer_url, direction, entity_types, sync_scope?, auth_method, ... }) — registers a remote instance. Returns a generated shared_secret when auth_method: "shared_secret" and no secret is provided.
  • list_peers() — enumerates registered peers and their last_sync_at.
  • get_peer_status({ peer_id }) — peer snapshot plus remote_health (a /health probe and a semver compatibility verdict, the same gate the neotoma compat CLI uses).
  • remove_peer({ peer_id }) — deactivates a peer.
  • sync_peer({ peer_id, limit? }) — bounded outbound batch (default 200, max 500). Requires NEOTOMA_PUBLIC_BASE_URL and NEOTOMA_LOCAL_PEER_ID.
  • resolve_sync_conflict({ entity_id, strategy, sender_peer_url?, guest_access_token? })prefer_remote re-fetches the remote snapshot; prefer_local retains local state; manual flags the entity for operator review.

HTTP routes

MethodPathPurpose
GET/peers/{peer_id}Snapshot + remote_health.
POST/peersAdd peer.
POST/peers/{peer_id}/syncBounded outbound batch.
POST/sync/webhookInbound signed notification (HMAC X-Neotoma-Sync-Signature-256 or AAuth).
POST/peers/resolve_sync_conflictStrategy-based conflict resolution.

Environment

  • NEOTOMA_PUBLIC_BASE_URL — public base URL of this API (no trailing slash). Used as sender_peer_url on outbound sync so the peer can fetch entity snapshots back.
  • NEOTOMA_LOCAL_PEER_ID — stable id this instance presents as sender_peer_id. Must match the value the peer recorded as their peer_id for you.
  • NEOTOMA_HOSTED_MODE=1 — operator opt-in for hosted / multi-tenant deployments. When set, the inbound webhook handler rejects any sender_peer_url whose hostname resolves to a private, loopback, or link-local address. This blocks a hostile peer from naming http://127.0.0.1 to coerce snapshot fetches against the host's loopback.

Example

Register a peer and run a bounded outbound sync:

# On the personal instance
neotoma peers add \
  --peer-id central \
  --name "Team Central" \
  --url https://central.example.com \
  --types issue,product_feedback \
  --target-user-id <central_user_uuid>

neotoma peers status central
neotoma peers sync central

The personal instance batches up to 200 changed observations (default limit), POSTs them to https://central.example.com/sync/webhook with X-Neotoma-Sync-Signature-256, and writes the resulting last_sync_at watermark back to its own peer_config row. Replays of the same signed payload are deduplicated by idempotency key so a flaky network does not produce duplicate observations.

Selective sync

Set sync_scope: "tagged" on the peer and stamp eligible entities with sync_peers: ["central"]. Only those entities are delivered:

{
  "entity_type": "issue",
  "title": "Reset MCP token after rotation",
  "sync_peers": ["central"]
}

Loop prevention via subscriptions

For steady-state propagation, create a substrate subscription with sync_peer_id: "central". The bridge skips events whose source_peer_id matches the destination, so replicated rows do not bounce back. Replicated writes carry observation_source: "sync" and rank below local sources in the reducer's default tie-breaking.

Full reference

docs/subsystems/peer_sync.md covers the full inbound / outbound paths (sync_webhook_inbound.ts, sync_webhook_outbound.ts, peer_sync_batch.ts, full_sync.ts, conflict_resolver.ts), the semver compatibility rules in src/semver_compat.ts, and the seeded peer_config schema.

See substrate subscriptions, security hardening, API reference, and MCP reference. .