runy.dev: A modern process management
Managing multiple development processes has always been a pain point for developers. We’ve all been there - juggling terminal windows, tabs, and tmux sessions, trying to keep track of what’s running where. Runy.dev addresses this fundamental problem with an elegant architecture that combines the performance of Rust with the flexibility of TypeScript.
The Problem Space
Before diving into Runy’s architecture, let’s understand the problem it solves:
- Terminal Proliferation: Modern development often requires running multiple services simultaneously - frontend dev servers, backend APIs, databases, message queues, and more
- Context Switching: Constantly switching between terminal windows breaks flow and reduces productivity
- Process Lifecycle Management: Starting, stopping, and restarting services manually is tedious and error-prone
- Log Management: Tracking logs from multiple services across different terminals is challenging
- Team Onboarding: New team members struggle to understand which services to run and how
Runy’s Architectural Approach
Runy takes a unique approach to solving these problems through a carefully designed architecture that separates concerns while maintaining performance and usability.
Core Architecture Components
The architecture consists of three main layers:
┌─────────────────────────────────────────┐
│ Web Dashboard (Browser) │
│ http://localhost:4122 │
└────────────────┬────────────────────────┘
│ HTTP/WebSocket
┌────────────────▼────────────────────────┐
│ Runy Daemon (Rust) │
│ - Process Management Engine │
│ - gRPC Server │
│ - Web Server │
└────────────────┬────────────────────────┘
│ gRPC
┌────────────────▼────────────────────────┐
│ Runy CLI & SDK (TypeScript) │
│ - Command Line Interface │
│ - TypeScript Configuration API │
└─────────────────────────────────────────┘
The Rust Core: Performance Where It Matters
The heart of Runy is written in Rust, and this choice is deliberate. Process management requires:
- Low-level system access: Spawning processes, managing file descriptors, signal handling
- Performance: Minimal overhead when managing multiple processes
- Reliability: The daemon must be stable and handle edge cases gracefully
- Resource efficiency: Low memory footprint even when managing many processes
Rust provides all of these capabilities while ensuring memory safety and preventing common bugs that plague system-level software.
The daemon architecture follows a client-server model:
- Automatic Lifecycle Management: The daemon starts automatically when needed and can run indefinitely
- gRPC Communication: Uses Protocol Buffers for efficient, type-safe communication between CLI and daemon
- Process Supervision: Monitors child processes, captures output, and handles lifecycle events
- Web Server: Serves the dashboard interface and provides real-time updates via WebSockets
TypeScript SDK: Developer-Friendly Configuration
While the core is in Rust, Runy provides a TypeScript SDK for configuration. This design decision offers several advantages:
import { workspace } from "@runy-dev/core";
const ws = workspace(import.meta);
ws.service("api-server", (s) => {
s.autorun(); // Start automatically when workspace loads
s.run(async (ctx) => {
// Complex initialization logic
await setupDatabase();
// Start the actual process
await ctx.process({
alias: "api",
cmd: "node",
args: ["./dist/server.js"],
env: {
PORT: "3000",
NODE_ENV: "development"
}
});
});
});
Benefits of this approach:
- Type Safety: Full TypeScript support with autocompletion in IDEs
- Familiar Syntax: Developers can use JavaScript/TypeScript they already know
- Complex Logic: Support for async operations, conditionals, and programmatic configuration
- Composability: Services can depend on and interact with each other
Web Dashboard: Visual Process Management
The web dashboard at http://localhost:4122 provides a unified view of all running processes:
- Real-time Logs: Stream logs from all services in one place
- Process Control: Start, stop, restart services with a click
- Resource Monitoring: CPU and memory usage for each process
- Workspace Organization: Group related services together
- Search and Filter: Quickly find specific log entries or services
The dashboard uses WebSockets for real-time updates, ensuring you see log output and status changes immediately.
Workspace Organization
Runy introduces the concept of “workspaces” - logical groupings of related services. This maps naturally to how developers think about their projects:
const ws = workspace(import.meta);
// Frontend services
ws.service("frontend", (s) => {
s.run(async (ctx) => {
await ctx.process({
alias: "next",
cmd: "npm",
args: ["run", "dev"],
cwd: "./frontend"
});
});
});
// Backend services
ws.service("backend", (s) => {
s.run(async (ctx) => {
await ctx.process({
alias: "express",
cmd: "npm",
args: ["run", "start"],
cwd: "./backend"
});
});
});
// Infrastructure
ws.service("database", (s) => {
s.run(async (ctx) => {
await ctx.process({
alias: "postgres",
cmd: "docker",
args: ["run", "postgres:14"]
});
});
});
Comparison with Alternatives
vs Terminal Multiplexers (tmux/screen)
While tmux is powerful, it has a steep learning curve and doesn’t provide:
- Visual process management
- Programmatic configuration
- Easy team sharing of configurations
- Integrated log management
vs Process Managers (PM2, Supervisor)
Traditional process managers are often designed for production use and lack:
- Developer-focused UI
- TypeScript configuration
- Workspace organization
- Integrated development workflow
vs Docker Compose
Docker Compose is excellent for containerized services but:
- Requires containerization of all services
- Adds overhead for local development
- Less flexible for mixed environments
- Steeper learning curve for simple use cases
Implementation Details Worth Noting
Process Isolation
Each process runs in its own environment with:
- Separate stdout/stderr capture
- Environment variable isolation
- Working directory management
- Signal handling for clean shutdowns
Log Management
Runy implements a sophisticated log pipeline:
- Capture stdout/stderr from processes
- Timestamp and tag each line
- Buffer for performance
- Stream to connected clients
- Optional persistence for debugging
Error Recovery
The architecture includes several resilience patterns:
- Automatic restart on failure (configurable)
- Graceful degradation when services fail
- Health checks for critical services
- Clean shutdown sequences
Future Architectural Directions
While the current architecture is solid, there are interesting possibilities for evolution:
- Plugin System: Allow extending Runy with custom functionality
- Distributed Mode: Manage processes across multiple machines
- Cloud Integration: Deploy and manage cloud resources
- AI Assistant: Natural language process management
- Performance Profiling: Built-in profiling for managed processes
Open Source Considerations
Currently, the Rust core remains closed source while the SDK and documentation are open. This hybrid model allows:
- Rapid iteration on core functionality
- Community contributions to SDK and integrations
- Potential future open-sourcing of the core
- Commercial sustainability for continued development
Conclusion
Runy’s architecture represents a thoughtful approach to developer tooling. By combining Rust’s performance with TypeScript’s developer experience and a modern web interface, it creates a powerful yet approachable solution to process management.
The separation of concerns - with Rust handling system-level operations, TypeScript providing configuration, and the web dashboard offering visualization - allows each component to excel at its specific role. This architecture not only solves immediate developer pain points but also provides a foundation for future enhancements.
Whether you’re managing a complex microservices architecture locally or just tired of juggling terminal windows, Runy’s architecture offers a glimpse into how modern developer tools can simplify our workflows without sacrificing power or flexibility.