rdsh — a fast, safe Rust launcher for dsh
rdsh is a drop-in fast path for dsh
(the DeepSeek Harness CLI). Instead of a full rewrite, it ports only the hot paths
to Rust and delegates everything else to the original dsh binary — so you get
~98x faster startup and ~1/23rd the memory with zero behavior change.
- Startup median ~0.90ms (original
dsh: ~88ms) - Resident memory ~2.9MB (original: ~66MB), single ~806KB binary, no runtime tree
- Safe by construction: agent loop and profile boot are never reimplemented,
delegation is a verbatim
exec, and every optimization is output-identical
Contents
- Benchmarks
- Install
- Usage
- Replacement mode (run as
dsh) - Using with Smart-DSH
- Web dashboard
- Safety design
- How it got fast
- Project layout
- Contributing
- FAQ
- License
Benchmarks
Measured on Linux x86_64, including before/after comparisons for the optimizations.
| Case | rdsh | Baseline | Factor |
|---|---|---|---|
--version startup (median, n=5) |
~0.90ms | original dsh ~88ms |
~98x |
--version peak RSS |
~2.9MB | original ~66MB | ~1/23 |
| Hook-equivalent peak RSS | ~2.7MB | equivalent Node script ~45MB | ~1/16 |
| search (300 files, ~600k lines) | ~17ms | before ~41ms | ~2.4x |
| tokens (9.6MB text) | ~12ms | before ~35ms | ~2.9x |
| sessions --tokens (20 sessions) | ~0.41s | before ~1.65s | ~4.0x |
| Distribution size | one ~806KB binary | ~508MB Node tree | — |
Reproduce with rdsh bench --n 5 and /usr/bin/time -v. The before/after
binaries were built from HEAD vs. the working tree in a scratch worktree and
their outputs were diffed for equality.
Install
Or build directly: cargo build --release produces target/release/rdsh.
Requires Rust 1.73+ (uses u32::div_ceil, thread::scope); only three
dependencies (clap, serde_json, anyhow), no async runtime, no build scripts.
Usage
dsh-compatible delegation
Native fast commands (no Node startup)
|
rdsh guard: a fast hook command for hooks.json
guard scans stdin (hook JSON or raw text) for deny patterns and blocks on
match: exit code 2 with the reason on stderr, exit 0 otherwise. With --json
it prints {"decision":"block"} / {"decision":"approve"} instead. * in a
pattern matches any string. At ~1ms startup and ~3MB RSS, per-tool-call hook
cost is effectively zero.
|
This follows the dsh hook protocol: exit 2 blocks with a message the model sees, any other failure is non-blocking and only logged.
Replacement mode (run as dsh)
When the binary is invoked under the name dsh, anything that is not an
rdsh-native subcommand is delegated verbatim to the original binary, so
dsh --version, dsh --profile tui, and dsh --help stay byte-identical.
- Original-binary discovery order:
RDSH_ORIG_BIN(legacyDSH_ORIG_BINstill honored) →~/.config/rdsh/origin→ sibling backups (dsh-orig,dsh.orig,dsh.real) →PATH(self excluded) → known npm install paths - Naming follows dsh convention: kebab-case commands/flags like the original
(
dump-config,--from-default-profile), while theDSH_env namespace stays owned by dsh itself — rdsh-private keys live underRDSH_ - One-shot escapes:
RDSH_PASSTHROUGH=1 dsh ...(no slim env),RDSH_DRY_RUN=1 dsh ...(print only) - Name shadowing: a bare
dsh tokensruns the rdsh subcommand; a profile literally namedtokensstill boots viadsh --profile tokens
Using with Smart-DSH
Smart-DSH is a DSH web-profile plugin bundle (mobile UI, Web Push notifications, Esc-to-stop), not a competing binary — it coexists with rdsh. rdsh passes its setup commands through:
|
Co-use notes:
- Ports: the dsh web GUI and
rdsh serveboth default to 3080. Keep 3080 for dsh web (push/remote access) and runrdsh serve --port 38080. dsh-shadowing: withinstall.sh --as-dsh, Smart-DSH helper scripts that locate DSH viadshon PATH resolve to the Rust binary and fail. Run those scripts against the original (dsh-orig ...) or exportDSH_PACKAGE_DIRto the DSH package dir.- Versions: Smart-DSH documents DSH
0.1.2-rc.1;rdsh doctorprints your actual dsh version so mismatches are visible before installing bundles.
Web dashboard
# open http://127.0.0.1:3080/ (localhost only, read-only API)
# if the port is taken (the dsh web GUI also uses 3080), try --port 38080
| API | Purpose |
|---|---|
GET /api/version |
version |
GET /api/doctor |
health check |
POST /api/tokens |
token estimate for {"text"} |
POST /api/prune |
prune {"text","max_tokens"} to budget |
GET /api/bench?n=3 |
startup measurement |
GET /api/sessions?limit=20 |
recent sessions |
GET /api/skills, /api/profiles |
name lists |
Dependency-free (std-only HTTP server plus one embedded HTML file, no CDN, works offline).
Safety design
- The agent loop and profile boot are never reimplemented — delegation only.
- Slim mode only adds environment variables; unknown keys are ignored upstream.
- Launcher error cases from the original (
desktopprofile, mutually exclusive dumps, missing--profile) are reproduced in Rust. - Read paths never write: tokens/search/compact/dump/native APIs touch nothing.
- Instant retreats:
--passthrough,RDSH_PASSTHROUGH=1,./install.sh --restore.
Verification (all executed)
cargo test: 14 unit tests pass (token math, wildcard matcher, arg splitter). The suite caught and fixed one real matcher bug (single-pattern substring).tests/regress.sh: 19 CLI checks pass (every subcommand, error paths, and sandboxeddsh-name delegation against a fake original).- Optimization diffs: old vs. new binary outputs compared byte-for-byte (300-hit search and truncated-max search both identical).
- Live replacement verified on a real machine:
dsh --versionstill delegates, new native commands work under thedshname.
How it got fast
- ASCII fast path for token estimation: pure-ASCII input is one
len/4computation (non-ASCII keeps the exact scan; results identical). - Two-phase search: sequential walk fixes the order, files are grepped in parallel, hits merge back in walk order. Trees under 32 files keep the exact old sequential code path.
- Parallel zstd expansion for
sessions --tokens(same numbers, order kept). - Release profile stays small:
opt-level=z, LTO,strip,panic=abort(~806KB).
Project layout
src/main.rs— CLI definition, dispatch,dsh-name detectionsrc/dsh_args.rs— originallib/bin.js-compatible arg splitter (read-only)src/passthrough.rs— original-binary discovery +execdelegationsrc/slim.rs— slim environment definitionsrc/tokens.rs— token estimation and pruningsrc/search.rs— order-preserving parallel grepsrc/compact.rs— session transcript compactionsrc/inspect.rs— read-only sessions/logs/skills/profiles viewssrc/guard.rs— hooks.json guard commandsrc/serve.rs+src/ui.html— local web dashboardinstall.sh— installer (--as-dshshadow /--restore)tests/regress.sh— CLI regression suite (19 checks)
Contributing
BIN=./target/debug/rdsh
No new dependencies without discussion: binary size and startup time are
features. Behavior changes must extend tests/regress.sh.
FAQ
- Port 3080 is busy? The dsh web GUI uses it too — run
rdsh serve --port 38080. - A profile collides with a subcommand name? Boot it explicitly:
dsh --profile <name>. - Revert the replacement?
./install.sh --restorebrings the original back. - What does
~123tok?mean? Without thezstdCLI the estimate falls back to compressed-bytes/4; the?marks that.
License
MIT — see LICENSE.