> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

mprocs: The Process Runner That Gives Every Command Its Own Terminal

[ View on GitHub ]

mprocs: The Process Runner That Gives Every Command Its Own Terminal

Hook

Most process runners capture stdout and stderr. mprocs embeds a complete terminal emulator for each command, which means you can run vim inside your webpack watcher and actually use it.

Context

Anyone who's worked on a microservices-oriented project knows the pain: you need to run six different watch commands simultaneously—webpack for the frontend, nodemon for the API server, tsc in watch mode for shared libraries, a database, maybe Redis, and a background worker. You have three bad options. First, open six terminal tabs and manually start everything, losing your mind when you need to restart them all. Second, use something like concurrently which interleaves all output into an unreadable stream where your webpack error is buried between database logs. Third, write a tmux configuration file in a format that looks like line noise and pray you never need to modify it.

mprocs, despite its confusing name (it started as 'mprocs' then became 'dekit' in some documentation), takes a different approach. It's a TUI multiplexer written in Rust that gives each process its own pseudo-terminal and virtual screen buffer. You get a sidebar listing all processes with their states (running, stopped, exit codes), and the main pane shows the selected process's output. But here's what makes it different: because each process runs in a real PTY with terminal emulation, you can run interactive programs. Need to debug why your webpack build is hanging? Switch to that pane and you have full keyboard control. Want to run htop to see what's eating CPU? It just works. This isn't output capture—it's terminal multiplexing purpose-built for development workflows.

Technical Insight

parse procs

spawn with PTY

spawn with PTY

spawn with PTY

stdout/stderr stream

stdout/stderr stream

stdout/stderr stream

virtual screen state

virtual screen state

virtual screen state

render

YAML commands

send-keys

user input

traverse grid

stop signal

SIGTERM / send-keys

YAML Config

Process Manager

PTY Instance 1

PTY Instance 2

PTY Instance N

VT100 Emulator 1

Ring Buffer

VT100 Emulator 2

Ring Buffer

VT100 Emulator N

Ring Buffer

Ratatui TUI Layer

Terminal UI

Sidebar + Screen

TCP Server :8888

Copy Mode

Cursor State

System architecture — auto-generated

The architecture reveals why mprocs can do things concurrently can't. Under the hood, it uses the portable-pty crate to spawn each command in its own pseudo-terminal, exactly like ssh or docker exec does. The stdout/stderr from each PTY gets fed into a vt100 crate instance—a complete VT100 terminal emulator that maintains virtual screen state. When you switch between processes in the UI, you're literally switching between different virtual terminal screens rendered by the ratatui TUI framework.

Here's a practical example. Create a mprocs.yaml configuration:

procs:
  api:
    cmd: "nodemon server.js"
    autostart: true
    autorestart: true
  webpack:
    cmd: "webpack --watch"
    autostart: true
  database:
    cmd: "postgres -D /usr/local/var/postgres"
    stop: "send-keys C-c"
  interactive:
    cmd: "bash"
    autostart: false

Run mprocs and it loads this configuration. The database entry shows something clever: the stop field uses send-keys C-c instead of SIGTERM. When you stop that process, mprocs literally sends Ctrl-C keystrokes to the virtual terminal. This matters for databases that need graceful shutdown sequences—a SIGTERM might not flush WAL files, but their built-in interrupt handlers will.

The remote control API is where things get interesting for automation. Start mprocs with --server 8888 and you can send commands via TCP:

echo 'start-proc: api' | nc localhost 8888
echo 'send-keys: { proc: interactive, keys: "npm test\n" }' | nc localhost 8888

But the real power is --on-init, which runs commands on startup. Create init.yaml:

- add-proc:
    name: dynamic-test
    cmd: "pytest --watch"
- start-proc: dynamic-test
- send-keys:
    proc: interactive
    keys: "echo 'Setup complete'\n"

Then mprocs --on-init init.yaml boots your environment declaratively. This turns mprocs from an interactive tool into an orchestration runtime. You could have a file watcher that sends restart-proc: api commands when code changes, or a CI script that uses send-keys to interact with a running process.

The copy mode implementation is pure modal editor thinking. Press c and you enter a separate interaction mode with its own keymap. The cursor (hjkl for movement, v for selection start) traverses the virtual terminal's character grid—not the raw output stream. You're copying from the rendered screen buffer, which means ANSI escape sequences are already interpreted. Copy a webpack error and you get clean text, not \x1b[31mError\x1b[0m. This is architecturally possible only because mprocs maintains that vt100 screen state.

The $select operator in configuration handles cross-platform differences without templating:

procs:
  build:
    cmd:
      $select:
        windows: "build.bat"
        macos: "./build.sh"
        default: "make build"

This works through serde's untagged enum deserialization—the YAML parser tries each platform key until one matches std::env::consts::OS. It's a clean pattern for any config schema that needs OS-specific values without external variable substitution.

Gotcha

mprocs will betray you the moment you need session persistence. Unlike tmux or screen, there's no detach/reattach. When you close mprocs, all child processes die. If your SSH connection drops or you accidentally close the terminal, your database loses unflushed data and your long-running build is gone. This is by design—mprocs is a process supervisor, not a session manager—but it means you cannot use it for anything that needs to outlive your terminal session. Run it on a remote server and you need something like systemd or Docker to keep mprocs itself alive.

The TCP remote control server has zero authentication. Start mprocs --server 8888 and anyone who can reach that port can execute arbitrary commands in your processes via send-keys. The README doesn't mention binding to localhost or firewall rules. In a development environment this might be acceptable risk, but the same configuration file could easily get deployed to staging where the port is exposed. There's no TLS, no API keys, not even a simple shared secret. It's YAML over raw TCP.

Terminal emulation is strictly VT100. Programs that expect xterm-256color, true color (24-bit), or modern features like bracketed paste will break or render incorrectly. Try running a Rust program with color-eyre error messages and you'll see escape sequences leak through. The vt100 crate is fast and portable, but it's not a complete xterm implementation. Also, the 1-second autorestart debounce is hardcoded. A process that crashes in 1.1 seconds will loop infinitely; a legitimate restart that takes 0.9 seconds gets suppressed. There's no exponential backoff or configurable timing.

Verdict

Use mprocs if you run 3-7 development processes that you actively interact with—think webpack watch modes, test runners, and local service dependencies that you start and stop as a unit. It excels when you need process isolation with the ability to send input to specific commands, and when you're tired of tmux configuration complexity for simple local development. The Procfile support makes it a drop-in Foreman replacement that actually lets you see what's happening. Skip it if you need session persistence for long-running servers (use tmux/systemd), if you're running more than 10 processes (the TUI doesn't scale), or if your workflow is 'start everything and walk away' (concurrently is simpler). Also skip if you need sophisticated process dependencies or if your programs require modern terminal features beyond VT100. The sweet spot is replacing a pile of terminal tabs with something you can control with keyboard shortcuts while still being able to interact with individual processes when debugging.