Enox Federation Protocol

Enox nodes can federate into a decentralized knowledge graph. Each node covers a territory (a set of file paths or knowledge domains). Cross-node queries are resolved lazily at query time — only reaching out to other nodes when a traversal crosses a territory boundary.

Core Design Principle

Within a shard — analysis-time resolution (full materialized graph). Between shards — execution-time resolution (lazy, on-demand, only if needed).

Enox is not just a code graph database. Once federation works, it becomes a general-purpose decentralized federative knowledge database. Code is the first domain; the mechanism is domain-agnostic.

Architecture

┌─────────────────────────────────────────────────┐
│  Browser worker / CLI / MCP                      │
│  ┌───────────────────────────────────────────┐  │
│  │         Query Router                       │  │
│  │   file path → shard → WS connection        │  │
│  └──────┬──────────┬──────────────┬──────────┘  │
│         │          │              │              │
│    ┌────▼───┐ ┌────▼───┐    ┌────▼───┐         │
│    │ Shard A │ │ Shard B │    │ Shard C │         │
│    │ (local) │ │ (local) │    │ (remote)│         │
│    └────────┘ └────────┘    └────────┘         │
└─────────────────────────────────────────────────┘

Local = remote = WebSocket. The difference is only the address: ws://localhost:${port} vs ws://team-b.company.com:${port}.

Key Design Decisions

  1. No phantom nodes, no federation.yaml, no pre-configured shards. Edges store absolute file paths in target semantic IDs. Dangling edge = target not in local graph. Router discovers shards at query time.

  2. Auto-discovery, zero-config. Each grafema server registers itself at startup. Discovery is by file path prefix matching. Shards find each other automatically.

  3. SUBGRAPH as single federation primitive. One command covers all federation query patterns.

  4. Honest gaps. If target shard unavailable → ISSUE node (explicit gap). Optionally fall back to manifest (lightweight export surface). Never pretend the graph is complete when it isn’t.

  5. Datalog stays local. No distributed Datalog. Cross-shard queries are either scatter-gather or traversal-with-frontier, driven by the TypeScript query router.

Auto-Discovery Protocol

Each grafema server knows its root — absolute path of the analysis root. This is its “territory”.

Shard A:  /repo/packages/frontend/*
Shard B:  /repo/packages/api/*
Shard C:  /repo/packages/shared/*

Edge in Shard A:  IMPORTS_FROM → target file: /repo/packages/api/src/client.ts
                                               ^^^^^^^^^^^^^^^^^^^^
                                               This is Shard B's territory

(The paths above are illustrative. Your repo structure will differ.)

Registration

The grafema server at startup writes:

// /tmp/rfdb-shards/{hash}.json
{
  "port": 9201,
  "root": "/repo/packages/api",
  "socket": "/tmp/rfdb-api.sock",
  "pid": 12345,
  "started": "2026-03-17T10:00:00Z"
}

At shutdown — removes the file. Stale files detected by PID check or TTL.

For remote shards: same concept, different registry (DNS-SD, HTTP endpoint, etc.).

Validation

WHO_ARE_YOU
→ { root: "/repo/packages/api", files: 342, nodes: 12847,
    analyzer_version: "0.3.16", analyzed_at: "2026-03-17T09:55:00Z" }

Confirms the shard actually covers the expected path and reports freshness.

Protocol Messages

SUBGRAPH Command

Single federation primitive:

SUBGRAPH
  entries: string[]          # entry point semantic IDs
  direction: forward|backward|both
  edge_types?: string[]      # filter: only CALLS, only DATAFLOW, etc.
  max_depth: number

→ {
    nodes: Node[],
    edges: Edge[],
    frontier: DanglingEdge[]   # exits beyond this shard's territory
  }

Covers all patterns:

  • trace_dataflowSUBGRAPH entries=[x] direction=forward edge_types=[ASSIGNS_TO,PASSES_ARGUMENT,RETURNS]
  • find_calls fan-inSUBGRAPH entries=[matched_functions] direction=backward edge_types=[CALLS]
  • get_contextSUBGRAPH entries=[x] direction=both max_depth=1

Frontier

The key abstraction. When traversal hits a dangling edge (target node not in local store), instead of silently stopping, return it as frontier:

{
  "edgeType": "IMPORTS_FROM",
  "sourceId": "frontend/src/app.ts->apiClient",
  "targetId": "api/src/client.ts->FUNCTION->post",
  "targetFile": "/repo/packages/api/src/client.ts"
}

Router uses targetFile for shard discovery.

Query Patterns

1. Traversal with Frontier (trace_dataflow, trace_alias)

federatedTrace(start, direction, maxDepth):

  1. Shard A — local Datalog trace to boundary
     chain=[userInput → validate → apiClient.post]
     frontier=[{target: "/repo/api/src/client.ts->post"}]

  2. Router groups frontier by target shard
     Shard B: [post]

  3. Shard B — SUBGRAPH from grouped entry points
     (shared visited set — fan-in handled: post and get converge)
     chain=[post → httpClient.request → db.query]
     frontier=[{target: "/repo/db/src/pool.ts->query"}]

  4. Stitch + recurse if new frontier and depth remaining

Critical: Router maintains global visited set across all hops to prevent cycles.

2. Scatter-Gather (find_nodes, find_calls, query_graph)

federatedSearch(query):

  1. Broadcast query to all known shards (parallel)
  2. Each shard executes locally, returns results
  3. Router merges results
  4. Non-monotonic results marked confidence: "partial"

Optimization: use manifests for pre-filtering. If find_nodes(name="redis"), check manifests first — only query shards whose manifest mentions “redis”.

3. Point Lookup (get_context, get_node)

federatedLookup(semanticId):

  1. Extract file path from semantic ID
  2. Discover shard by path prefix
  3. Route to single shard

Federation is Opt-In

Critical: Federation mode must be explicitly enabled. Default behavior is unchanged — single server, no discovery, no cross-shard queries.

Why: Multiple worktrees / CI jobs / dev environments routinely run grafema analyze on the same repo simultaneously. Auto-registering in /tmp/rfdb-shards/ by default would cause conflicts — overlapping territories, stale registrations from parallel workers, cross-contamination between branches.

Activation:

  • grafema server --federate flag enables shard registration and discovery
  • Without --federate, server works exactly as today — isolated, no side effects
  • Config option in .grafema/config.yaml: federation: { enabled: true }
  • MCP/CLI: grafema analyze --federate to analyze + register shard

Worktree safety: Without --federate, worktree-1 through worktree-8 can all run independently on the same codebase without interference, as they do today.

Invariants

INV-0: Federation is opt-in. Default mode = isolated single server, zero side effects.
INV-1: Semantic ID contains absolute file path (discovery depends on it)
INV-2: One file path belongs to exactly one shard (no overlapping territories)
INV-3: Router visited set is global across all hops (termination guarantee)
INV-4: Federation-eligible edge types are an explicit whitelist (completeness)
INV-5: Non-monotonic cross-shard results are marked confidence: partial
INV-6: Cost budget on traversal (fan-out protection)

Design Considerations & Trade-offs

Query completeness

IssueDescriptionMitigation
Non-monotonic queriesNegation/aggregation across shards gives wrong resultsMark as confidence: "partial", require all shards for complete answer
Fan-out explosionO(shards^depth) pathological traversalCost budget, bloom filter on visited, per-shard depth limit

Correctness & freshness

IssueDescriptionMitigation
Cycle terminationCircular imports between shardsGlobal visited set in router
Reachability criteriaWhich edge types to follow cross-shard?Explicit whitelist of federation-eligible edges
Source selection for broadcastfind_nodes without file filter → N connectionsManifest pre-filtering
Version skewStale analysis in one shardManifest timestamp + checksum, staleness warnings

Theoretical Foundations

CALM Theorem (Hellerstein, 2019)

A program has a consistent, coordination-free distributed implementation iff it is expressible in monotonic Datalog.

Implications:

  • Monotonic queries (find, trace, reachability) → coordination-free → safe for federation
  • Non-monotonic queries (negation: “find dead code”, aggregation: “count all nodes”) → require ALL shards → must be marked as partial if not all available

LTQP from the Solid ecosystem is the closest academic analog to this model:

  • Many small sources, unknown before query time
  • Discovery during traversal (follow links)
  • Same problems: termination (cycles), completeness (which links to follow)

Reference: Link Traversal Query Processing Over Decentralized Environments with Structural Assumptions

FedUP (ACM Web Conference 2024)

Source selection is THE bottleneck in SPARQL federation — finding which endpoints to contact. FedUP solves via “quotient summaries”. Enox manifests serve the same role.

Reference: FedUP: Querying Large-Scale Federations of SPARQL Endpoints

Dedalus: Time as First-Class Concern

“Distributed programming is about time, not about space.” (Alvaro et al., Berkeley, 2009)

Version skew between shards is a temporal problem. Shard A analyzed yesterday, Shard B today. Manifest timestamps + checksums make staleness explicit.

Reference: Dedalus: Datalog in Time and Space

Use Cases Beyond Code

The federation mechanism is domain-agnostic. The same protocol works for:

  1. npm/package ecosystem — pre-built graphs on CDN, zero-install deep analysis
  2. Microservices — each team’s service = shard, cross-service impact analysis
  3. Distributed tracing — spans as nodes, causal links as edges, services as shards
  4. SBOM — each package has its own dependency subgraph, federated vulnerability traversal
  5. Multi-language migration — PHP monolith + Node microservices, different analyzers, same query layer
  6. Browser exploration — thin worker routing queries to online Enox servers

Comparison with Semantic Web

Semantic WebEnox Federation
Started with abstraction (“all knowledge”)Starts with concrete domain (code)
Requires ontology alignmentEach shard sovereign, frontier = honest gap
Manual endpoint configurationAuto-discovery, zero-config
SPARQL — complex, slowDatalog — simple, bounded
”Must work perfectly”Graceful degradation (manifest fallback → ISSUE node)