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

Open
#5,863 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
20/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Quiet
Tech stack
nodejs, typescript

Research direction

Start with this RFC and the referenced watch-mode overhaul PR #5378 and Reporter Overhaul issue #5858. The proposed entry points are @microsoft/rush, @rushstack/rush-cli-client, and new protocol, transport, renderer, client-core, and daemon packages. Done would require an agreed server-first architecture and a staged implementation that preserves current CLI behavior, but no specific files or tests are named.

Written by the indexing model from the issue text.

Description

design proposal

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, #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 #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 (#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.
Dominant language
TypeScript
Stars
6.5k
Forks
708
Avg merge
5d 7h
Merged PRs (30d)
44

Contributor guide

No contributing guide indexed for this repository

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.

More from microsoft/rushstack

All issues in microsoft/rushstack

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.