anomalyco / anomalyco/opencode

[FEATURE]: `opencode join` — hub-managed node fleet with centralized session discovery

Open
#44,760 0 comments 0 reactions 1 assignee View on GitHub

@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 --token for 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/event reconciliation. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.