# InfiniBot Federation Satellite Audit
**Date:** 2026-05-26  
**Branch:** feat/opencode-swappable-backend  
**Goal:** Verify satellite-as-thin-client model works end-to-end; identify and close gaps.

---

## Architecture Overview

InfiniBot has a well-designed federation infrastructure in `src/federation/` (43 files). The core model:
- **Master** runs `FederationHub` (WebSocketServer on `:47913/federation`)
- **Satellite** runs `FederationSatellite` (WebSocket client, connects to master)
- **`invokeOnMaster(method, params)`** exists on satellite — proxies any RPC call to master's gateway

The satellite registration flow, heartbeat, compute routing, memory sync, and identity sync are all fully implemented. The main gaps are at the **gateway RPC handler level**: most data handlers use `STATE_DIR` (local SQLite) unconditionally, with no satellite proxy wired in.

---

## Data Source Audit Matrix

### Kanban Tasks + Projects
- **Read path:** `src/gateway/server-methods/databases.ts:58` → `getDatabasesStore(STATE_DIR)` (local SQLite)
- **Write path:** same file, all mutations → local SQLite; `src/tasks/store.ts` also local
- **Current behavior on satellite:** ❌ Local-first — satellite has its own empty DB on first boot
- **Gap to thin-client model:** Satellite sees NO kanban tasks/projects from master; edits don't reach master
- **Fix needed:** In `databases.ts` (and `notes.ts`), detect satellite mode and proxy all `db.*` RPCs via `satellite.invokeOnMaster()`

### Businesses
- **Read path:** `src/gateway/server-methods/business.ts` → local store
- **Write path:** same, no federation check
- **Current behavior on satellite:** ❌ Local-first
- **Gap to thin-client model:** Satellite can't see master's businesses
- **Fix needed:** Proxy `business.*` RPCs to master on satellite

### Clients DB, Leads DB (per-business CRM)
- **Read path:** `src/gateway/server-methods/databases.ts` → `getDatabasesStore(STATE_DIR)` → local SQLite rows with `business_id` scoping
- **Write path:** same
- **Current behavior on satellite:** ❌ Local-first — satellite has no client/lead rows
- **Gap:** All CRM data missing on satellite
- **Fix needed:** Same databases.ts proxy fix covers this (clients/leads are database rows)

### Notes (system-role Notes DB)
- **Read path:** `src/gateway/server-methods/notes.ts:36` → `NoteStore(stateDir)` + `system-role-bridge` → `databases` → local SQLite
- **Write path:** same
- **Current behavior on satellite:** ❌ Local-first
- **Gap:** Notes on master not visible from satellite
- **Fix needed:** `notes.ts` proxy fix or covered by `db.*` proxy since notes use database rows

### Companies, Deals, Contacts (system-role DBs)
- **Read/Write path:** `databases.ts` → local SQLite
- **Current behavior on satellite:** ❌ Local-first
- **Gap:** Same as Notes
- **Fix needed:** Covered by `db.*` proxy fix

### Voice Chat — Audio (LiveKit)
- **Read path:** `src/gateway/server-voice-urls.ts` → reads `config.voice.*` (config written at satellite bootstrap)
- **Write path:** LiveKit stream goes to master's LiveKit server
- **Current behavior on satellite:** ✅ Already master-routed — `satellite.ts:959-1041` auto-wires `livekitUrl`, `kokoroUrl`, `speachesUrl` to master's host on connect
- **Gap:** None for audio
- **Fix needed:** None

### Voice Chat — Transcripts (text log)
- **Read path:** `src/gateway/server-methods/chat.ts:53` → `resolveTranscriptPath()` → local file
- **Write path:** `chat.ts:133` → `fs.appendFileSync(transcriptPath, ...)` → local `~/.infinibot/sessions/*.transcript`
- **Current behavior on satellite:** ❌ Transcripts written only to satellite's local disk
- **Gap:** Voice session transcript not on master; master can pull via `transcript-sync.ts` on demand, but write originates locally
- **Fix needed:** On satellite, after local write, also forward transcript entry to master via `invokeOnMaster('chat.send', params)` — or redirect entire `chat.send` RPC to master when in satellite mode

### Sessions + Subagent Spawns
- **Read path:** `src/gateway/server-methods/sessions.ts:176` — tries local first, falls back to federation (✅ partial)
- **Write path:** Session records written to local `~/.infinibot/sessions/`; subagent kanban entry written via local kanban store
- **Current behavior on satellite:** ⚠️ Hybrid — session spawn can route to master via compute-router, but kanban entry is written locally
- **Gap:** Kanban entry for spawned agents doesn't appear on master
- **Fix needed:** Route kanban write through `invokeOnMaster()` (covered by db.* proxy fix); sessions themselves partially work

### File System / Local Directories
- **Read/Write path:** `src/directories/store.ts` — local SQLite (`directories` table in state dir)
- **Current behavior on satellite:** Local-only — directories registered on satellite not visible from master
- **Gap:** Master can't browse satellite's registered file directories
- **Fix needed:** Expose satellite dirs via federation resources endpoint; add `federation.directories.list` RPC that aggregates across nodes

### Skills
- **Current behavior:** ✅ Local on each node (appropriate — skills are code, not data)
- **Gap:** None (by design — each node has its own skill set; master can list remote skills via MCP proxy)

### MCP Server Registry
- **Current behavior:** ✅ Already federated — `src/federation/mcp-proxy.ts` handles cross-node MCP invocation
- **Gap:** None

### Settings (infinibot.json)
- **Current behavior:** ✅ Local on each node (appropriate) + identity sync pushes SOUL.md/password from master
- **Gap:** None (deliberate)

### Memory (MEMORY.md)
- **Current behavior:** ✅ Bidirectional sync — `src/federation/memory-sync.ts` + `file-watcher.ts`
- **Gap:** None

### Identity (SOUL.md, gateway password)
- **Current behavior:** ✅ Master pushes to all satellites via `identity-sync.ts`
- **Gap:** None

---

## UI Audit

### Federation-specific pages
- `ui/src/ui/views/federation.ts` — mesh visualization ✅
- `ui/src/ui/views/federation-settings.ts` — connection config ✅
- `ui/src/ui/views/federation-modals.ts` — setup wizard ✅
- `ui/src/ui/views/federation-node-selector.ts` — node picker ✅
- `ui/src/ui/federation/index.ts` — state controller ✅

### Satellite status visibility
- **Gap:** ❌ No persistent status indicator showing "you are on satellite X → master Y"
- The setup wizard shows satellite mode during configuration, but once configured there's no HUD-level pill
- `federation-settings.ts:111` renders satellite connection status inside the Settings tab only — not visible in normal use
- **Fix needed:** Add satellite status pill to nav/header (additive, unobtrusive)

### UI parity (hidden features on satellite)
- `ui/src/ui/views/setup-wizard.ts:293` — satellite mode check in wizard only, no feature gates elsewhere
- `memory.ts:345` — "pull from federation master" button only appears in satellite mode (additive, correct)
- **Assessment:** UI is substantially identical on master vs satellite ✅ — no hidden feature gates found
- **Gap:** Minor — the satellite-mode pill is missing, but no existing pages are hidden

---

## Prioritized Gap List

| Priority | Gap | Impact | Complexity |
|----------|-----|--------|------------|
| P0 | `databases.ts` reads/writes go to local SQLite, not master | ALL kanban/CRM/notes/projects data missing on satellite | Medium — add proxy wrapper in handlers |
| P1 | Voice transcript written to satellite's local disk only | Voice sessions not searchable/visible from master | Low — forward to master after local write |
| P2 | No satellite status pill in UI nav | User can't tell if connected to master or working offline | Low — additive HTML in navigation.ts |
| P3 | File directories not federation-shared | Master can't browse satellite's local dirs | Medium — new `federation.directories.*` RPC + UI |
| P4 | Subagent kanban write goes to local kanban | Kanban on master missing satellite-spawned tasks | Covered by P0 if db.* is proxied |

---

## What's Already Working ✅

1. **WebSocket federation handshake** — satellite connects, registers, heartbeats, reconnects
2. **Voice audio routing** — LiveKit/Kokoro/Speaches URLs auto-pointed to master on connect
3. **Memory sync** — MEMORY.md bidirectional
4. **Identity sync** — SOUL.md + gateway password pushed from master
5. **Compute routing** — agents can be spawned on remote nodes
6. **On-demand transcript pull** — `transcript-sync.ts` lets any node request a transcript from its owner
7. **MCP proxy** — cross-node MCP invocation works
8. **`invokeOnMaster()` mechanism** — satellite can proxy ANY RPC call to master; just needs to be wired into handlers
9. **UI pages** — federation mesh, settings, node selector all exist
10. **mDNS auto-discovery** — satellite can find master without manual URL entry

---

## Recommended Implementation Sequence

### Phase 2A — Core data proxy (P0)
Create `src/federation/satellite-context.ts` — a thin helper that:
- Reads `loadConfig().federation.mode` 
- Returns `getFederationSatellite()` when in satellite mode
- Exposes `proxyToMaster(method, params)` convenience function

Then modify `src/gateway/server-methods/databases.ts`:
- At the top of each handler, check satellite mode
- If satellite and connected to master → forward via `invokeOnMaster(method, params)` and return master's response
- If satellite but disconnected → serve from local cache (read) or queue write
- Covers: kanban, projects, businesses, clients, leads, notes, contacts, deals

### Phase 2B — Voice transcript proxy (P1)
In `src/gateway/server-methods/chat.ts`:
- After local transcript write, if satellite mode: call `satellite.invokeOnMaster('chat.send', params)` 
- This writes the same transcript entry on master
- Avoid double-processing: use a `x-federation-relay: true` header/param to prevent infinite loop

### Phase 2C — UI satellite status pill (P2)
In `ui/src/ui/navigation.ts`:
- Read `federation.config.get` on load
- If `mode === "satellite"` and connected: render small "Satellite: <nodeName> → <masterName>" pill in header
- Style: muted, non-blocking, positioned at top-right of nav bar

### Phase 2D — Directory federation (P3)
New RPC `federation.directories.list` (in `src/gateway/server-methods/federation.ts`):
- On master: aggregates local dirs + invokes `directories.list` on each connected satellite
- Returns unified list with `sourceNode` and `nodeName` fields
- No schema changes needed — purely additive

---

## Files I Can Modify (In Lane)

**New files (safe):**
- `src/federation/satellite-context.ts`
- `src/federation/routing.ts` (if needed)
- `ui/src/ui/views/federation-status-pill.ts`
- Any new test files

**Existing files I can modify:**
- `src/gateway/server-methods/databases.ts` — not in any agent's lane
- `src/gateway/server-methods/notes.ts` — not in any agent's lane
- `src/gateway/server-methods/chat.ts` — only federation-routing aspect
- `src/gateway/server-methods/federation.ts` — my lane
- `src/gateway/server-methods/federation-resources.ts` — my lane
- `ui/src/ui/views/federation*.ts` — my lane
- `ui/src/ui/navigation.ts` — additive only

**Cannot touch (other agents' lanes):**
- `src/databases/store.ts` (kanban agent)
- `src/databases/kanban-store-ops.ts` (kanban agent)
- `src/tasks/store.ts` (kanban agent)
- `src/gateway/server-methods/kanban.ts` (kanban agent)
- `ui/src/ui/views/agent-activity*.ts` (activity dashboard agent)
- `src/discipline/*` (discipline agent)
- `ui/src/ui/views/workflows.ts`, `controllers/workflows.ts` (kanban agent)

---

## Blocked Items (Need Coordination)

1. **Kanban store sync schema** — `src/databases/store.ts` is owned by the kanban agent. If full bidirectional kanban sync is desired (not just proxy), a schema change would be needed to track `sourceNode` on each row. For now, proxy approach avoids touching store.ts entirely.

2. **Sessions persistence on master** — `src/tasks/store.ts` (kanban agent lane) handles session-to-task linking. Full master persistence would need coordination.
