[rush] RFC: Rush Daemon (rushd) — server-first re-architecture with thin clients
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
- Domain
- build-system, cli, developer-experience, tooling
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
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
rushcommand 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, andCtrl+Csemantics 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|logssubcommands. - The daemon handles
rush install/rush updateand 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
mainat0.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 (neverprocess.chdir/mutating globalprocess.env). - Queue-and-wait scheduling with
SHARED-BUILD/SHARED-READ/EXCLUSIVEclasses;install/update
areEXCLUSIVEand 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/rushis 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
- 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.
More from microsoft/rushstack
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
All issues in microsoft/rushstack
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
ontola/atomic-server#1625 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
melgarafael/DeskcommCRM#1451 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
-
bug via-triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bot:ai-assisted component:compact-js status:untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
midnightntwrk/midnight-sdk#403 ·