Multi-Instance Design: Standalone & Leader/Follower Modes
September 14, 2026 · View on GitHub
Status: EXPERIMENTAL — Leader/follower mode is under active development. Standalone mode is the production default.
Overview
the index supports two operational modes for multi-instance environments where many VS Code windows, Copilot Chat sessions, or Squad agents each spawn their own Index process.
| Mode | Description | Maturity |
|---|---|---|
| Standalone | Each instance is independent — full Index, full I/O, full memory. Default. | Production |
| Leader/Follower | One leader owns Index + disk I/O; followers proxy via HTTP. | Experimental |
Mode Selection
INDEX_SERVER_MODE=standalone → Independent instance (default, production)
INDEX_SERVER_MODE=auto → Attempt leader election; fallback to follower
INDEX_SERVER_MODE=leader → Force leader role (error if port taken)
INDEX_SERVER_MODE=follower → NOT supported by the main entry point — see below
INDEX_SERVER_MODE=followerdoes nothing on the main entry point.src/server/multiInstanceStartup.ts:12-18returns early for any mode that is notleaderorauto, printing[startup] Instance mode=follower (follower mode requires thin-client entry point)to stderr and then running as an ordinary standalone server — it loads the full index, which is the cost the follower role exists to avoid.To actually run a follower, use the thin client entry point (
src/server/thin-client.ts), which is whatMODE=followerwas meant to select. The remaining way to become a follower in-process is to lose anautoelection.This was documented as a supported forced role until #589.
Standalone Mode (Production)
Architecture
---
config:
layout: elk
---
graph TD
subgraph "VS Code Window 1"
Host1[MCP Host<br/>VS Code / Copilot] -->|stdio| MCP1[Index<br/>PID 1001]
end
subgraph "VS Code Window 2"
Host2[MCP Host<br/>VS Code / Copilot] -->|stdio| MCP2[Index<br/>PID 1002]
end
subgraph "VS Code Window 3"
Host3[MCP Host<br/>VS Code / Copilot] -->|stdio| MCP3[Index<br/>PID 1003]
end
MCP1 -->|read/write| Disk[(Shared Disk<br/>instructions/<br/>usage-snapshot.json<br/>.index-version)]
MCP2 -->|read/write| Disk
MCP3 -->|read/write| Disk
style Host1 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Host2 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Host3 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style MCP1 fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
style MCP2 fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
style MCP3 fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
style Disk fill:#4caf50,stroke:#2e7d32,stroke-width:3px,color:#fff
Startup Flow
---
config:
layout: elk
---
flowchart TD
Start([Process Start]) --> ParseConfig[Parse env & CLI args]
ParseConfig --> LoadIndex[Load Index from disk<br/>validate + classify + migrate]
LoadIndex --> StartDash{Dashboard<br/>enabled?}
StartDash -->|yes| Dashboard[Start Express on<br/>INDEX_SERVER_DASHBOARD_PORT]
StartDash -->|no| SkipDash[Skip dashboard]
Dashboard --> StartMCP[Start MCP stdio transport]
SkipDash --> StartMCP
StartMCP --> Ready([Serving MCP tools])
style Start fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style ParseConfig fill:#42a5f5,stroke:#1565c0,stroke-width:2px,color:#fff
style LoadIndex fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Dashboard fill:#00bcd4,stroke:#006064,stroke-width:2px,color:#fff
style StartMCP fill:#ab47bc,stroke:#6a1b9a,stroke-width:2px,color:#fff
style Ready fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
Data Flow — Read Path
---
config:
layout: elk
---
sequenceDiagram
participant Client as MCP Client<br/>(VS Code)
participant Server as Index
participant Index as IndexContext
participant Disk as Disk
Client->>Server: tools/call index_search
Server->>Index: ensureLoaded()
Index->>Index: Check .index-version mtime
alt Cache valid
Index-->>Server: Return cached entries
else Cache stale
Index->>Disk: Read instructions/*.json
Disk-->>Index: JSON entries
Index->>Index: Validate + classify + index
Index-->>Server: Return fresh entries
end
Server->>Server: Filter + score results
Server-->>Client: Search results
Data Flow — Mutation Path
---
config:
layout: elk
---
sequenceDiagram
participant Client as MCP Client
participant Handler as Tool Handler
participant Index as IndexContext
participant Audit as AuditLog
participant Disk as Disk
Client->>Handler: tools/call index_add
Handler->>Handler: Validate schema + body size
Handler->>Index: writeEntry(entry)
Index->>Disk: Atomic write {id}.json
Index->>Disk: Touch .index-version
Index->>Index: Materialize in-memory
Index-->>Handler: Success
Handler->>Audit: logAudit(add, id)
Handler-->>Client: Result
Characteristics
| Property | Behavior |
|---|---|
| Memory | Full Index in every instance (~60-70 MB RSS each) |
| CPU | Full classification + indexing per instance on load |
| Disk I/O | All instances read/write concurrently |
| File locking | None — last-writer-wins on concurrent mutations |
| Consistency | .index-version file + mtime polling for cross-process invalidation |
| Isolation | Complete — each instance independent |
| Failure mode | One crash has no effect on others |
When to Use
- Default for all deployments — proven, simple, no coordination overhead
- 1-5 concurrent instances (typical workload)
- When file locking risk is acceptable (rare concurrent mutations)
- When ~300-400 MB total RSS for 5 instances is acceptable
Leader/Follower Mode (Experimental)
Architecture
---
config:
layout: elk
---
graph TD
subgraph "VS Code Window 1"
Host1[MCP Host] -->|stdio| Leader[Index<br/>LEADER PID 2001]
end
subgraph "VS Code Window 2"
Host2[MCP Host] -->|stdio| Follower1[Index<br/>FOLLOWER PID 2002]
end
subgraph "VS Code Window 3"
Host3[MCP Host] -->|stdio| Follower2[Index<br/>FOLLOWER PID 2003]
end
subgraph "Squad Sub-Agent"
Host4[MCP Host] -->|stdio| ThinClient[thin-client<br/>PID 2004]
end
Leader -->|read/write| Disk[(Shared Disk<br/>instructions/<br/>usage-snapshot.json)]
Follower1 -->|HTTP JSON-RPC| Leader
Follower2 -->|HTTP JSON-RPC| Leader
ThinClient -->|HTTP JSON-RPC| Leader
Leader -->|heartbeat file| StateDir[(data/state/<br/>leader.lock)]
style Host1 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Host2 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Host3 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Host4 fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style Leader fill:#ff9800,stroke:#e65100,stroke-width:3px,color:#fff
style Follower1 fill:#42a5f5,stroke:#1565c0,stroke-width:2px,color:#fff
style Follower2 fill:#42a5f5,stroke:#1565c0,stroke-width:2px,color:#fff
style ThinClient fill:#78909c,stroke:#37474f,stroke-width:2px,color:#fff
style Disk fill:#4caf50,stroke:#2e7d32,stroke-width:3px,color:#fff
style StateDir fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
Election Flow
---
config:
layout: elk
---
flowchart TD
Start([Process Start]) --> ParseConfig[Parse env & CLI args]
ParseConfig --> CheckMode{INDEX_SERVER_MODE?}
CheckMode -->|standalone| Standalone([Standalone path<br/>full startup])
CheckMode -->|auto / leader| TryBind[Attempt bind<br/>INDEX_SERVER_LEADER_PORT]
TryBind -->|listen() OK| BecomeLeader[LEADER ROLE<br/>Write leader.lock]
TryBind -->|EADDRINUSE| HealthCheck[Health check<br/>existing leader]
HealthCheck -->|healthy| BecomeFollower[FOLLOWER ROLE<br/>Proxy to leader]
HealthCheck -->|unhealthy| RetryElection[Wait + retry<br/>election]
RetryElection --> TryBind
BecomeLeader --> LoadIndex[Load Index<br/>Start HTTP transport<br/>Start dashboard]
BecomeLeader --> StartStdio1[Start stdio transport<br/>WITH real handlers]
BecomeFollower --> StartStdio2[Start stdio transport<br/>WITH proxy handlers]
BecomeFollower --> Heartbeat[Start heartbeat<br/>monitor]
CheckMode -->|follower| NotSupported[Log 'requires thin-client<br/>entry point' and continue<br/>as standalone]
NotSupported --> StartStdio1
ThinClient([thin-client entry point]) --> DiscoverLeader[Read leader.lock<br/>Connect to leader]
DiscoverLeader -->|found| BecomeFollower
DiscoverLeader -->|not found| WaitLeader[Wait for leader<br/>with backoff]
WaitLeader --> DiscoverLeader
LoadIndex --> Serving([Serving])
StartStdio1 --> Serving
StartStdio2 --> ServingProxy([Serving via proxy])
Heartbeat --> ServingProxy
style Start fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style Standalone fill:#78909c,stroke:#37474f,stroke-width:2px,color:#fff
style BecomeLeader fill:#ff9800,stroke:#e65100,stroke-width:3px,color:#fff
style BecomeFollower fill:#42a5f5,stroke:#1565c0,stroke-width:2px,color:#fff
style LoadIndex fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Serving fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style ServingProxy fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style Heartbeat fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#fff
Tool Call Flow — Follower Proxy
---
config:
layout: elk
---
sequenceDiagram
participant Client as MCP Client<br/>(VS Code)
participant Follower as Follower<br/>(stdio)
participant Leader as Leader<br/>(HTTP)
participant Index as IndexContext
participant Disk as Disk
Client->>Follower: tools/call index_search
Follower->>Leader: POST /mcp/rpc<br/>{"method":"index_search", "params":{...}}
Leader->>Leader: mcpTransportAuth<br/>(loopback or Bearer)
Leader->>Leader: guardToolInvocation<br/>(declared-tool gate + schema)
Leader->>Index: ensureLoaded() + search
Index->>Disk: Read if stale
Index-->>Leader: Results
Leader-->>Follower: HTTP 200 JSON-RPC response
Follower-->>Client: MCP tool result
Note over Follower,Leader: Follower has NO Index in memory.<br/>All state lives on the leader.
Mutation Flow — Single Writer
---
config:
layout: elk
---
sequenceDiagram
participant F1 as Follower A
participant F2 as Follower B
participant Leader as Leader
participant Index as IndexContext
participant Disk as Disk
par Concurrent mutations
F1->>Leader: POST /mcp/rpc index_add (entry A)
F2->>Leader: POST /mcp/rpc index_add (entry B)
end
Leader->>Index: writeEntry(A)
Index->>Disk: Atomic write A.json
Index->>Disk: Touch .index-version
Leader-->>F1: Success
Leader->>Index: writeEntry(B)
Index->>Disk: Atomic write B.json
Index->>Disk: Touch .index-version
Leader-->>F2: Success
Note over Leader,Disk: Leader serializes all disk writes.<br/>No concurrent file access. No locking needed.
Failover Flow
---
config:
layout: elk
---
sequenceDiagram
participant F1 as Follower 1
participant F2 as Follower 2
participant Leader as Leader
participant Disk as Disk
Note over Leader: Leader crashes (SIGKILL / OOM)
F1->>Leader: Heartbeat
Leader--xF1: Connection refused
F1->>F1: Miss count: 1/3
F1->>Leader: Heartbeat
Leader--xF1: Connection refused
F1->>F1: Miss count: 2/3
F1->>Leader: Heartbeat
Leader--xF1: Connection refused
F1->>F1: Miss count: 3/3 → FAILOVER
F1->>Disk: Check leader.lock PID (signal-0)
Disk-->>F1: PID dead → delete stale lock
F1->>F1: Attempt listen() on INDEX_SERVER_LEADER_PORT
Note over F1: listen() succeeds → PROMOTED TO LEADER
F1->>Disk: Load Index from disk (~300ms)
F1->>Disk: Write new leader.lock
F2->>F1: Heartbeat → discovers new leader
F2->>F1: Resume proxying tool calls
Note over F1,F2: Total failover window: 0.5–7 seconds
Thin Client Architecture
---
config:
layout: elk
---
graph LR
subgraph "MCP Host Process"
Host[VS Code / Copilot<br/>Agent]
end
subgraph "Thin Client Process (~25 MB)"
TC[thin-client.ts<br/>stdin → HTTP → stdout]
end
subgraph "Leader Process (~85 MB)"
HTTP[HTTP Transport<br/>/mcp/rpc]
HTTP --> Registry[Handler Registry]
Registry --> Index[IndexContext]
Index --> Disk[(Disk)]
end
Host -->|stdio<br/>JSON-RPC frames| TC
TC -->|HTTP POST<br/>localhost:9090/mcp/rpc| HTTP
HTTP -->|HTTP response| TC
TC -->|stdio<br/>JSON-RPC response| Host
style Host fill:#607d8b,stroke:#37474f,stroke-width:2px,color:#fff
style TC fill:#78909c,stroke:#37474f,stroke-width:2px,color:#fff
style HTTP fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Registry fill:#ab47bc,stroke:#6a1b9a,stroke-width:2px,color:#fff
style Index fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Disk fill:#4caf50,stroke:#2e7d32,stroke-width:3px,color:#fff
The thin client (src/server/thin-client.ts) is a separate entry point that skips index loading entirely. It reads JSON-RPC frames from stdin and forwards them to the leader's HTTP transport, writing responses back to stdout. This is the lightest-weight follower option.
Leader HTTP transport
Three routes, mounted under /mcp on a bare express app of their own
(src/server/multiInstanceStartup.ts:38) — not on the dashboard app, so
they do not inherit dashboard middleware.
| Route | Auth | Purpose |
|---|---|---|
POST /mcp/rpc | Yes — see below | Tool invocation. The only route that can change state |
GET /mcp/health | No | Liveness for thin clients: status, pid, uptime |
GET /mcp/leader | No | Leader identity: pid, port, role |
POST /mcp/rpc authentication (mcpTransportAuth, #605). Identical in
policy to dashboardAdminAuth, and applied before the JSON body is parsed:
- No
INDEX_SERVER_ADMIN_API_KEYset — loopback callers pass; everyone else gets 403-32001. This matters because a leader bound to0.0.0.0would otherwise expose unauthenticated tool invocation to the network. - Key set — a constant-time
Authorization: Bearer <key>match is required; 401-32001otherwise.
It fails closed: a caller that is neither loopback nor key-bearing is refused.
Request shapes. Two reach this route, both produced by code in this repo, and both pass the same guard:
| Shape | Producer | Result shape |
|---|---|---|
{"method": "<tool>", "params": {...}} | the follower handler proxy | the handler's raw result |
{"method": "tools/call", "params": {"name": "<tool>", "arguments": {...}}} | the thin client, relaying an MCP frame verbatim | the MCP content-array, as stdio produces |
The tools/call form 404'd unconditionally before #605, so the thin-client
bridge was broken for every tool call rather than merely unguarded.
Guards. guardToolInvocation is shared verbatim with the stdio path rather
than copied, so a third transport cannot quietly miss one:
- the declared-tool gate (#592) — a handler with no registry entry is refused;
- handler lookup;
INPUT_SCHEMAvalidation (#581).
Status mapping: -32602 (bad params for a declared tool) → 400;
-32601 → 404, byte-identical whether the tool is undeclared or simply
absent, so the response cannot be used to enumerate hidden handlers.
Configuration
| Env Var | Default | Description |
|---|---|---|
INDEX_SERVER_MODE | standalone | standalone, auto, leader. follower is accepted but not implemented here — see Mode Selection |
INDEX_SERVER_LEADER_PORT | 9090 | TCP port for leader HTTP transport |
INDEX_SERVER_HEARTBEAT_MS | 5000 | Leader heartbeat write interval |
INDEX_SERVER_STALE_THRESHOLD_MS | 15000 | Threshold before follower considers leader dead |
INDEX_SERVER_STATE_DIR | data/state | Location for leader.lock and instance state files |
INDEX_SERVER_LEADER_URL | (discovered) | Explicit leader URL for thin client (overrides discovery) |
INDEX_SERVER_ADMIN_API_KEY | (unset) | When set, POST /mcp/rpc requires Authorization: Bearer <key>. When unset, that route is loopback-only. |
These seven are the complete set.
INDEX_SERVER_LEADER_HOST,INDEX_SERVER_FOLLOWER_HEARTBEAT_MS,_FOLLOWER_HEARTBEAT_MISSES,_FOLLOWER_RETRY_ATTEMPTS,_FOLLOWER_RETRY_BACKOFF_MSand_LEADER_REQUEST_TIMEOUT_MSappear in mcp-index-leader-follower-spec.md and do not exist in the code (#589). Setting any of them does nothing.
Performance Comparison
Benchmark: 10 concurrent Index instances on Windows (32 CPUs, 64 GB RAM, Node v24.3.0).
Resource Summary
---
config:
layout: elk
---
graph LR
subgraph "Standalone (10 instances)"
S_Mem["RSS: 674 MB total<br/>67 MB avg/instance"]
S_CPU["CPU: 4891 ms total<br/>489 ms avg/instance"]
S_IO["Disk writers: 10<br/>File lock risk: HIGH"]
end
subgraph "Leader/Follower (1L + 9F)"
LF_Mem["RSS: 645 MB total<br/>Leader: 85 MB | Follower: 62 MB avg"]
LF_CPU["CPU: 1812 ms total<br/>Leader: 360 ms | Follower: 160 ms avg"]
LF_IO["Disk writers: 1<br/>File lock risk: NONE"]
end
style S_Mem fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#fff
style S_CPU fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#fff
style S_IO fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#fff
style LF_Mem fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style LF_CPU fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
style LF_IO fill:#66bb6a,stroke:#2e7d32,stroke-width:2px,color:#fff
Measured Results
| Metric | Standalone (10) | Leader/Follower (1L+9F) | Delta |
|---|---|---|---|
| Total RSS | 674 MB | 645 MB | -4.2% (savings: 29 MB) |
| Avg RSS per instance | 67 MB | 65 MB | |
| Leader RSS | n/a | 85 MB | |
| Avg follower RSS | n/a | 62 MB | -8% vs standalone |
| Total CPU time | 4,891 ms | 1,812 ms | -63% (moderate improvement) |
| Avg CPU per instance | 489 ms | 181 ms | |
| Disk writers | 10 concurrent | 1 (leader only) | -90% |
| File lock risk | High (10 writers) | None (single writer) | Eliminated |
Key Findings
-
Memory: Small improvement (4.2%) — Followers still load the Node.js runtime (~25-30 MB base), so per-follower savings are ~5 MB. The V8 heap and module graph dominate. Larger Indexs would show proportionally greater savings since only the leader holds entries in memory.
-
CPU: Moderate improvement (63%) — Followers skip index loading, classification, and indexing. The leader does this work once; followers simply proxy. This matters most during startup burst when many instances launch simultaneously.
-
File locking: Major improvement — The single biggest benefit. In standalone mode, 10+ processes racing on
usage-snapshot.json,.index-version, and instruction files risks data loss (last-writer-wins). Leader/follower eliminates this entirely — only the leader touches disk. -
Leader HTTP latency — Sub-millisecond median for proxied calls. 1,800+ req/s throughput in burst. The HTTP proxy adds negligible overhead.
Tradeoff Analysis
---
config:
layout: elk
---
quadrantChart
title Standalone vs Leader/Follower Tradeoffs
x-axis "Low Complexity" --> "High Complexity"
y-axis "Low Benefit" --> "High Benefit"
quadrant-1 "Worth It"
quadrant-2 "Future Value"
quadrant-3 "Avoid"
quadrant-4 "Diminishing Returns"
"File Lock Safety": [0.3, 0.9]
"CPU Savings": [0.4, 0.6]
"Memory Savings": [0.4, 0.25]
"Single Source of Truth": [0.35, 0.75]
"Failover Complexity": [0.8, 0.3]
"Debug Complexity": [0.7, 0.15]
Module Structure
---
config:
layout: elk
---
graph TD
subgraph "Entry Points"
Main[src/server/index-server.ts<br/>Full server]
Thin[src/server/thin-client.ts<br/>Lightweight follower]
end
subgraph "Multi-Instance [EXPERIMENTAL]"
Election[LeaderElection.ts<br/>Port-based election]
Transport[HttpTransport.ts<br/>Express JSON-RPC router]
Client[ThinClient.ts<br/>Stdio↔HTTP bridge]
end
subgraph "Core (unchanged)"
Registry[Handler Registry]
Index[IndexContext]
Handlers[Tool Handlers]
Dashboard[Dashboard Server]
end
Main --> Election
Main -->|leader| Transport
Main -->|leader| Registry
Main -->|leader| Dashboard
Main -->|all modes| Registry
Thin --> Client
Client -->|HTTP| Transport
Election -->|role=leader| Transport
Election -->|role=follower| Client
Transport --> Registry
Registry --> Handlers
Handlers --> Index
style Main fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
style Thin fill:#78909c,stroke:#37474f,stroke-width:2px,color:#fff
style Election fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Transport fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Client fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
style Registry fill:#ab47bc,stroke:#6a1b9a,stroke-width:2px,color:#fff
style Index fill:#ab47bc,stroke:#6a1b9a,stroke-width:2px,color:#fff
style Handlers fill:#ab47bc,stroke:#6a1b9a,stroke-width:2px,color:#fff
style Dashboard fill:#00bcd4,stroke:#006064,stroke-width:2px,color:#fff
File Summary
| File | Lines | Purpose |
|---|---|---|
src/dashboard/server/LeaderElection.ts | ~260 | Lock file + PID election, heartbeat, stale detection |
src/dashboard/server/HttpTransport.ts | ~200 | Express router: /mcp/rpc, /mcp/health, /mcp/leader, plus transport auth and the shared tool-invocation guard |
src/server/toolInvocationGuard.ts | ~90 | Declared-tool gate, handler lookup and input-schema validation, shared by the stdio and HTTP paths |
src/dashboard/server/ThinClient.ts | ~240 | Stdin JSON-RPC → HTTP POST → stdout bridge |
src/server/thin-client.ts | ~30 | Thin client entry point (CLI) |
src/server/index-server.ts | ~900 | Election integration in main startup |
src/config/runtimeConfig.ts | ~4 | Config keys: instanceMode, leaderPort, heartbeatIntervalMs, staleThresholdMs |
Decision Guide
---
config:
layout: elk
---
flowchart TD
Q1{How many MCP<br/>instances?}
Q1 -->|1-3| UseStandalone([Use Standalone<br/>INDEX_SERVER_MODE=standalone])
Q1 -->|4-10| Q2{Concurrent<br/>mutations?}
Q1 -->|10+| UseLF([Consider Leader/Follower<br/>INDEX_SERVER_MODE=auto])
Q2 -->|rare| UseStandalone
Q2 -->|frequent| UseLF
Q3{Squad agents<br/>spawning sub-processes?}
Q1 -->|squad| Q3
Q3 -->|yes| UseLF
Q3 -->|no| UseStandalone
style UseStandalone fill:#2196f3,stroke:#0d47a1,stroke-width:2px,color:#fff
style UseLF fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
| Scenario | Recommendation |
|---|---|
| Single developer, few VS Code windows | Standalone |
| Multiple windows, read-heavy workload | Standalone |
| 10+ windows with Squad agents | Leader/Follower |
| CI/CD or automated tooling | Standalone (ephemeral processes) |
| Shared team instruction index | Leader/Follower (single-writer safety) |
Known Limitations (Experimental)
- No automatic follower-to-leader Index sync — if a follower promotes, it cold-loads the index from disk (~300ms gap)
- Every instance opens a dashboard, including followers. This document
claimed the opposite ("dashboard only on leader") until #589. The dashboard
starts at
src/server/index-server.ts:803, and leader election does not run until:840, so by the time an instance learns it is a follower its dashboard is already listening. Because the port is taken by the leader, the follower port-hops (INDEX_SERVER_DASHBOARD_TRIES, default 10), so a five-instanceautodeployment opens five dashboards on five ports, four of them backed by a proxying follower. SetINDEX_SERVER_DASHBOARD=0on instances you do not want a UI for. Fixing the ordering is a behaviour change and is deliberately not part of the documentation pass. - No request queuing during failover — in-flight calls to a dead leader fail and must be retried by the client
- Windows-specific socket behavior —
SO_REUSEADDRsemantics differ; tested on Windows 10/11 only - No TLS on the HTTP transport — localhost-only by design, but lacks mTLS for defense-in-depth
Related Documents
- Architecture — Overall system architecture
- Leader/Follower Spec — Detailed specification
- Performance Report — Raw benchmark data
- Configuration — All environment variables