Skip to content
ChannelDesk
Docs · Architecture

Four services, one NX monorepo.

architecture.txt
           ┌──────────────────────────────────────────┐
           │           Angular PWA (dashboard)        │
           │   /home (rooms) · /flows · /sessions     │
           └────────────┬─────────────────▲───────────┘
                        │ HTTPS REST       │ socket.io
                        ▼                  │ (live)
            ┌──────────────────────────────┴───────────┐
            │       NestJS orchestrator (main.js)       │
            │  ┌──────────────┐  ┌──────────────────┐   │
            │  │ SessionSpawn │  │ Flow Engine v2   │   │
            │  │   Service    │  │ (BullMQ + cron)  │   │
            │  └──────┬───────┘  └────────┬─────────┘   │
            │         │   ┌────────────────────────┐    │
            │         │   │ SSH fleet · MCP tools  │    │
            │         │   └────────────────────────┘    │
            └─────────┼───────────────────┬─────────────┘
                      │                   │
                      ▼                   ▼
              claude-code               PostgreSQL
              subprocess              (entities, flows,
                  │                    settings, sessions)
                  │ stdio MCP
                  ▼
        ┌─────────────────────────┐
        │  channel-server (Bun)   │  ← spawned per session
        │  reply tool · perms     │
        └─────────────────────────┘
                  │
                  ├── flow-tools         (mcp)
                  ├── cluster-tools      (mcp)
                  ├── dashboard-tools    (mcp)
                  ├── quick-action-tools (mcp)
                  └── unifi-tools        (mcp)

channel-server (Bun)

A per-session MCP relay, spawned alongside every claude-code subprocess. It bridges the running Claude session and the ChannelDesk hub: exposes a reply tool so the agent can talk back to whichever channel triggered it, and a permission relay that surfaces tool-permission prompts in the UI rather than stdin. Built on Bun + the MCP SDK.

orchestrator (NestJS)

Houses the brains. It's responsible for:

  • SessionSpawnerService — spawns claude-code subprocesses, wires per-session MCPs, streams stdout.
  • Flow Engine v2 — BullMQ-backed async pipeline; cron, webhook and event triggers via the FlowEventBus. Uses @nestjs/schedule.
  • SettingsService — persisted user + global settings, model allow-list validation.
  • FleetModule — SSH host registry, key store, session manager. Sync + command adapters per integration (UniFi, Hue, custom).
  • WebSocket gateway — Socket.IO, streams session output and device state to the UI.

MCP tool servers

Out-of-process MCP servers, one binary each, all under packages/. Spawned by the orchestrator and wired into Claude sessions on demand:

  • flow-tools — read/run/inspect flows from inside a session
  • cluster-tools — query and act on the running k8s cluster
  • dashboard-tools — push updates and notifications to the UI
  • quick-action-tools — pre-defined action shortcuts the agent can invoke
  • unifi-tools — UniFi network operations

frontend (Angular PWA)

Installable, offline-capable control desk. APP_INITIALIZER blocks bootstrap until settings are loaded — no more hardcoded fallback labels. Service worker registered in app.config.ts. The /home route is room-centric; every control uses optimistic updates.

registry (PostgreSQL)

Every entity — devices, rooms, flows, sessions, settings — lives in Postgres. The registry is the source of truth; sync adapters reconcile it with the real world. Crash-safe by design.

How a message flows

  1. A trigger fires — chat message in the dashboard, cron tick, SSH event, or webhook.
  2. The orchestrator picks the matching flow and calls SessionSpawnerService.spawnForFlow(), which forks a claude-code subprocess.
  3. A channel-server Bun MCP is spawned alongside the session, exposing the reply tool and a permission relay back to the hub.
  4. Additional tool MCPs (flow-tools, cluster-tools, dashboard-tools, quick-action-tools, unifi-tools) are wired in based on the flow's manifest.
  5. Claude streams tokens → Socket.IO → any subscribed dashboard client. Device actions go through the capability layer; state lands in Postgres; the UI updates optimistically.
  6. The session's final reply is delivered through the channel-server's reply tool back to the inbound channel.

See the remote control page for how resumable sessions and offline-first UI cooperate when your network drops out.

In pixels

The diagram above as it actually renders on a running install — one shot of the orchestrator looking at itself, one shot of a live session streaming back through the channel-server.

/cluster/overview · the orchestrator's view of itself
ChannelDesk Cluster overview — service map across namespaces (channeldesk, ci, immich, monitoring) with pod counts and ingress hosts
/sessions/… · the channel-server's reply tool in action
ChannelDesk Session — Claude Code stdout streamed over Socket.IO into the dashboard, with rich markdown output