mprocs: The Process Manager That Understands Your Terminal Isn’t Just a Log Stream
Hook
Most developers running multiple processes during local development treat their terminals like dumb log aggregators. They pipe output together, lose context switching between processes, and can’t interact with any of them without killing everything. What if your process manager understood that some of those processes need actual terminal input?
Context
Modern application development rarely involves running a single process. A typical web application might need a backend server, a frontend build watcher, a database, Redis, and maybe a background worker—all running simultaneously during development. The traditional approach splits these across multiple terminal tabs or tmux panes, creating cognitive overhead as you hunt for the right window to restart a crashed service or check its logs.
Tools like concurrently and pm2 attempted to solve this by running processes in parallel and aggregating their output, but they treated every process as a simple command that outputs logs. This works fine for build tools and log-producing services, but breaks down the moment you need to interact with a process—send it input, run an interactive debugger, or use a TUI tool like htop within your orchestration. mprocs fills this gap by providing full pseudo-terminal (PTY) emulation for each process, giving you the parallel execution convenience of concurrently with the interactivity of running each process in its own dedicated terminal.
Technical Insight
The architectural heart of mprocs is its per-process PTY allocation and terminal emulation. Unlike simpler process managers that capture stdout/stderr as byte streams, mprocs spawns each child process with a pseudo-terminal, allowing bidirectional communication and proper terminal behavior. This means applications that check isatty() will behave correctly—your Rails console gets colors, vim works with proper key handling, and interactive prompts function as expected.
Configuration lives in mprocs.yaml files, which can exist at both global (~/.config/mprocs/mprocs.yaml) and project-local levels. Here’s a practical example for a typical full-stack JavaScript application:
procs:
api:
cmd: ["npm", "run", "dev"]
cwd: "./backend"
autostart: true
autorestart: true
frontend:
cmd: ["npm", "run", "dev"]
cwd: "./frontend"
autostart: true
stop: "send-keys"
stop-key-sequence: "ctrl+c"
db:
shell: "docker-compose up postgres"
autostart: false
stop: "SIGTERM"
worker:
shell: $select:
linux: "bundle exec sidekiq"
darwin: "bundle exec sidekiq"
windows: "bundle.bat exec sidekiq"
cwd: "./backend"
env:
REDIS_URL: "redis://localhost:6379/0"
autostart: true
This configuration demonstrates several sophisticated features. The stop field controls termination behavior: SIGTERM sends the signal and waits for graceful shutdown, while send-keys with a stop-key-sequence literally types the key combination into the PTY (essential for processes like Vite that don’t handle signals properly but respond to Ctrl+C). The $select operator enables cross-platform configurations without maintaining separate files.
The autorestart mechanism includes rate limiting to prevent restart loops. If a process exits within one second of starting, mprocs assumes it’s crash-looping and won’t restart it automatically—you’ll need to manually restart via the TUI. For processes that legitimately start and stop quickly but need restarting, this becomes a limitation. The code allows customization of the grace period, but only through recompilation.
The rendering engine uses the tui-rs (now ratatui) crate to build a sophisticated interface with scrollback buffers, mouse support, and keyboard shortcuts. Each process gets its own isolated terminal renderer, and mprocs maintains a focus state machine. Press Ctrl+a followed by a number to jump to that process, or use arrow keys to navigate. Within a focused process, your keystrokes go directly to its PTY—you’re genuinely interacting with it, not mprocs.
The remote control API exposes a Unix socket (typically at /tmp/mprocs.<instance-id>.sock) that accepts JSON commands. This enables integration with other tools:
# Start a specific process from an external script
echo '{"Start": "worker"}' | nc -U /tmp/mprocs.12345.sock
# Query current state
echo '{"List": {}}' | nc -U /tmp/mprocs.12345.sock
This makes mprocs scriptable despite being primarily a TUI application. You could build editor integrations that restart your backend when you save a file, or create shell aliases that manipulate your running development environment without switching windows.
The three-level keymap configuration system (defaults → global config → local config) deserves attention. You can override the default keybindings globally for your personal preferences, then further customize per-project. For instance, if you’re working on a project where you frequently need to restart a specific process, you can bind a custom key sequence in that project’s mprocs.yaml:
keymap:
- keys: ["ctrl+r"]
cmd: restart-current
- keys: ["ctrl+k"]
cmd: kill-current
- keys: ["alt+1"]
cmd: { "focus-proc": "api" }
Gotcha
The one-second restart grace period is hardcoded and non-configurable through YAML. If you have a genuinely fast-failing process that needs immediate retry (perhaps a script that validates configuration and exits quickly when the config is malformed during development), mprocs won’t auto-restart it. You’ll need to manually trigger restarts or modify the source code to adjust this threshold.
More fundamentally, mprocs is a terminal application—it requires an interactive TTY to function. This makes it unsuitable for CI/CD pipelines, Docker containers without TTY allocation, or any headless environment. While the remote control API provides some programmatic access, you can’t use mprocs as a production process supervisor or in automated testing scenarios where you need structured output and exit codes. Tools like concurrently or even simple shell backgrounding with wait are better suited for those contexts. Additionally, there’s no built-in log rotation or size management. If you’re running verbose processes long-term, the scrollback buffers exist only in memory during the session, and if you’re using the experimental log-to-file feature, those files will grow unbounded.
Verdict
Use mprocs if you’re developing locally with multiple long-running processes that you need to monitor and occasionally interact with—think microservices, full-stack applications with separate frontend/backend, or any scenario where you’re tired of juggling terminal tabs and losing process context. It’s particularly valuable when at least one of your processes requires actual terminal interaction (debugging, interactive REPLs, or TUI tools), since that’s where mprocs shines over simpler alternatives. Skip it if you’re orchestrating processes in production (use systemd, Docker Compose, or Kubernetes), building CI pipelines (use concurrently, parallel, or shell built-ins), or working in headless environments. Also skip if you need sophisticated process dependency management, health checks beyond “did it crash,” or you’re running short-lived processes that need rapid restart behavior.