anomalyco / anomalyco/opencode
[FEATURE]: `opencode join` — hub-managed node fleet with centralized session discovery
@jlongster is already working on this.
Since Aug 24, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Problem
I run OpenCode servers on a mixed fleet (Windows + WSL, macOS laptop, several Linux hosts, a cloud VPS) and want one web UI that shows and controls sessions across all of them.
Today's multi-server support is client-side federation: every browser maintains its own server list and connects to every server independently. Server identity is tied to a URL, and there is no enrollment, no presence, and no session index shared between clients. A mesh VPN doesn't close the gap — configuration stays O(N x M) (every client device joins the VPN and configures all N servers), and it still provides no shared index, no presence, and no stable application-level identity.
Related: #43727, #43030, #40843, #41152; #19949 / #15752 asked for multi-server-in-one-UI (closed "not planned").
Proposal
An optional hub-and-node control plane on top of standalone servers — centralized discovery, decentralized execution. Standalone opencode serve is unaffected.
flowchart LR
USER("Client Browser"):::NODE
subgraph VPS["Cloud Server"]
HUB(("☁️ Hub Server")):::NODE
GATE(["🔒 Auth"]):::NODE
HUB <--> GATE
end
subgraph LANA["Home"]
NA(["🖥️ PC"]):::NODE
NB(["💻 WSL"]):::NODE
end
subgraph LANB["Office"]
NC(["🗄️ Server"]):::NODE
end
subgraph ROAM["Hotspot"]
ND(["💻 Laptop"]):::NODE
end
NA -->|WebSocket| HUB
NB -->|WebSocket| HUB
NC -->|WebSocket| HUB
ND -->|WebSocket| HUB
GATE <--> USER
style VPS fill:#0001,stroke:gray,stroke-width:1px,stroke-dasharray:6 4,opacity:0.6;
style LANA fill:#0001,stroke:#FA08,stroke-width:1px,stroke-dasharray:6 4;
style LANB fill:#0001,stroke:#AF08,stroke-width:1px,stroke-dasharray:6 4;
style ROAM fill:#0001,stroke:#0AF8,stroke-width:1px,stroke-dasharray:6 4;
classDef NODE fill:#0008,stroke:white,color:white;
- Enrollment:
opencode join <hub-url>(interactive browser approval, or--tokenfor headless). The node gets a permanent ID independent of hostname/IP/URL and authenticates with a node-generated keypair thereafter. - Presence: each node keeps one outbound persistent connection (NAT/firewall-friendly); nodes remain visible while offline.
- Session index: nodes advertise lightweight session metadata via sequenced events with snapshot fallback — same shape as the existing
/sync/history+/global/eventreconciliation. The hub holds an index, never the authoritative session data. - Access: the hub relays authenticated client traffic to nodes over multiplexed streams (nodes themselves authenticate by keypair, not through the client auth layer). Direct client-node transport is a possible later optimization.
Nodes stay authoritative for projects, tools, execution, and session contents. Configuration concentrates to O(N + M): a node enrolls once, a client only needs the hub URL.
Non-goals for now: no session DB migration to the hub, no filesystem sync, no distributed execution, no P2P requirement, no multi-user ACLs committed (the design stays open to them).
Relationship to the experimental control-plane (WorkspaceAdapter)
There is real overlap here, and the indexing/relay parts of this proposal would conflict with it if built independently: control-plane/workspace.ts already proxies requests to remote OpenCode servers (runInWorkspace over a Target.remote { url, headers }), reconciles session events by posting per-session sequence state to /sync/history and following /global/event via SSE with reconnect backoff, and tracks per-workspace connection status.
The scopes differ in ways that look complementary rather than competing. Workspaces are project-scoped — adapters register per projectID and every workspace carries one — while a node is machine-scoped and owns many projects. A workspace's identity is minted by the controlling instance and its remote is addressed by URL, which requires inbound reachability; a node's identity is minted at enrollment, URL-independent, and connects outbound only.
That suggests a concrete compatibility path: the hub becomes the connectivity substrate underneath the existing machinery. An enrolled node surfaces as an ordinary Target.remote — its target() resolving to a hub relay URL plus auth headers — so the existing proxying and /sync/history + /global/event reconciliation are reused rather than duplicated. I'd value maintainer input on whether that layering matches the intended direction of the control-plane work.
Planned first step
I plan to start with node identity + enrollment only: the opencode join flow, a permanent node ID, and keypair authentication — opt-in, with no change to standalone opencode serve or the control-plane code. Presence, session indexing, and relayed access would follow as separate increments built on the sync machinery above.
I have fuller design notes (enrollment token format, re-enrollment/revocation, epoch-based reconciliation, relay multiplexing, trust model, deployment behind an SSO proxy) and will post them if there's interest. Also open to prototyping out of tree first if working code is preferred before an in-tree abstraction.
This proposal and its architecture originate from me. Claude Fable 5 (Anthropic) and ChatGPT Sol 5.6 (OpenAI) assisted with drafting, research, and design review; I have reviewed and take responsibility for all content and claims.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.