Skip to content

[rush] RFC: Rush Daemon (rushd) — server-first re-architecture with thin clients #5863

Description

@bmiddha

1. Summary

Rush is a CLI-first application: @microsoft/rush is a thin launcher that resolves the workspace's
rushVersion, loads the matching @microsoft/rush-lib, and runs the entire engine in-process, once per
invocation
. Every command re-reads configuration, rebuilds the operation graph, re-resolves state, and
throws it all away on exit.

This RFC proposes rushd, a long-lived, per-workspace daemon that owns the engine and keeps build
state warm (config, project graph, operation graph, file watchers, plugins). rush becomes a very thin
client
that forwards the command to the daemon and pumps stdout/stderr/stdin back to the terminal — an
interaction transparent to the user and to scripts. All interesting work happens in the daemon; the
client only owns presentation. There is one daemon per Rush workspace, and it auto-starts on
first use.

The model is directly inspired by Bazel's client/server architecture.
It reuses the watch-mode overhaul (PR #5378) to keep the build graph warm, and the Reporter Overhaul
(#5858) for the presentation-free event contract that flows over the wire. The default flip ships as Rush 6.

2. Context and Motivation

  • Cold start every time. Config parsing, graph construction, and snapshotting are repeated on every
    rush build/rush install/rush update, even when nothing changed.
  • No resident watch state. The watch-mode overhaul makes the operation graph stateful and warm-able, but
    a CLI process cannot keep it resident across invocations.
  • Presentation is fused to the engine. rush-lib writes ANSI/English directly, which blocks non-terminal
    clients (agents, IDEs, web) and makes output hard to adapt for CI/agents.
  • Concurrency is hostile. A second rush command today races or fails ("another rush is running")
    instead of coordinating.
  • Agent/AI ergonomics. Long-running foreground processes (rush start) and verbose output are poor for agents; a daemon
    plus a structured client protocol fixes both.

3. Goals and Non-Goals

Goals

  • A per-workspace daemon that keeps RushConfiguration + operation graph + watchers + plugins warm.
  • A thin client that is byte-for-byte transparent: identical stdout/stderr, exit codes, TTY/color/width
    behavior, stdin, and Ctrl+C semantics as today.
  • Terminal rendering lives in the client wrapper — no SIGWINCH/terminal-control signals sent to the
    daemon; the client owns width/color/resize.
  • Auto-start the daemon on demand; new rush daemon start|stop|restart|status|logs subcommands.
  • The daemon handles rush install / rush update and restarts itself (with socket handoff) when
    they change installed state.
  • Queue-and-wait concurrency: a conflicting command waits rather than failing, with exclusivity
    classes and shared-build merging.
  • Warm-set management: auto-evict idle projects, telemetry-weighted warm selection.
  • A presentation-free protocol enabling additional clients (MCP, web, IDE).
  • An experimental graph CLI to inspect/scope the warm watch graph.
  • Delivered as new split packages merged incrementally into main at 0.x, then a Rush 6 cutover.

Non-Goals

  • Redesigning operation scheduling, parallelism, or cobuild (the daemon observes the existing engine).
  • Multi-workspace or multi-user shared daemons (strictly one per workspace, per user).
  • Remote/networked daemons — transport is local (Unix socket / Windows named pipe), loopback only.
  • Replacing rush-lib — the engine is reused, not rewritten (strangler-fig decomposition).
  • Shipping the web dashboard UX itself (only the wiring to serve/attach it).

4. Proposed Solution

 rush (thin client)                         rushd (daemon, 1 per workspace)
 ─────────────────                          ───────────────────────────────
 parse minimal argv                         WorkspaceSession (warm):
 resolve workspace key  ──socket/pipe──►      RushConfiguration + graph + watcher + plugins
 forward request envelope                    RequestScheduler (exclusivity + queue-and-wait + merge)
 (argv,cwd,env,columns,colorLevel)           CommandRouter (phased vs global, per-request context)
 pump stdout/stderr/stdin  ◄──events/log──   ReloadController (fingerprint, tiers, self-restart/handoff)
 own ALL presentation                        emits presentation-free events (reporter contract, #5858)
 permanent in-process fallback               keeps state warm across invocations

Key architectural decisions:

  • Presentation on the client. The engine emits a structured, presentation-free event stream
    (adopting @rushstack/reporter's event contract, [rush] Rush Reporter Overhaul #5858); the client renders it. The daemon never touches
    terminal-control state.
  • Transparency via a request envelope, not shared TTY. The client sends columns/colorLevel/cwd/env
    per request; the daemon injects these per-request (never process.chdir/mutating global process.env).
  • Queue-and-wait scheduling with SHARED-BUILD / SHARED-READ / EXCLUSIVE classes; install/update
    are EXCLUSIVE and trigger reload tiers.
  • Self-managing lifecycle. After install/update, the daemon sends the client its exit code, then
    drains, restarts a successor, and hands off the socket; version-mismatch also triggers a restart.
  • Warm-set bounding. LRU idle eviction under a memory budget + telemetry-weighted warm selection;
    best-effort, never correctness-affecting.
  • Split packages from day one (protocol / transport / renderer / client-core / daemon), with a separate
    CLI-specific client package
    (@rushstack/rush-cli-client) until cutover, so @microsoft/rush is untouched
    pre-6.

5. Relationship to In-Flight Work

  • Watch-mode overhaul (PR [rush] (BREAKING CHANGE) Overhaul watch-mode to facilitate orchestration #5378) — hard prerequisite. Provides the stateful IOperationGraph
    (setEnabledStates, invalidateOperations, scheduled iterations, terminal-destination management) the
    daemon keeps warm. Its hook churn is @alpha/@beta (not a Rush 5 stable-API break).
  • Reporter Overhaul ([rush] Rush Reporter Overhaul #5858) — reused, not duplicated. Supplies the presentation-free event envelope +
    NDJSON wire adapter + built-in reporters. Its "daemon-aligned major default flip" is the Rush 6 cutover;
    the two efforts share a breaking-change set (ILogger.terminal, RushSession.terminalProvider, plugin-API
    gating) and cut over together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    design proposalA new direction that is complex enough to have open questions but specific enough to implement

    Fields

    No fields configured for Feature.

    Projects

    Status
    Needs triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions