Runtime modes, adapters & resource limits
Foreground daemon, gateway-only, native vs Docker runtime adapters, the daemon supervisor, and resource-limit tuning.
This page explains how Revka runs: the process modes you can launch the single revka binary in, the runtime adapter that decides where agent shell commands actually execute (the host vs. a throwaway Docker container), the supervisor that keeps the daemon’s components alive, and the resource limits you can tune today.
Use this page when you are deciding how to deploy Revka, hardening the sandbox in which agent commands run, or troubleshooting a restart loop or a “Too many open files” error. For installing Revka as a managed OS service (launchd / systemd / OpenRC / Windows Task Scheduler), see Run as a background service. For container images and Compose, see Docker, Compose & one-click PaaS.
Runtime modes
Section titled “Runtime modes”A single binary covers every process mode. Pick the mode that matches your deployment.
| Mode | Command | When to use |
|---|---|---|
| Foreground runtime | revka daemon | Local debugging, short-lived sessions, the full stack in one terminal |
| Gateway only | revka gateway | Webhook endpoint testing, dashboard + REST/WS without channels or cron |
| User service | revka service install && revka service start | Persistent operator-managed runtime |
| Docker / Podman | docker compose up -d | Containerized deployment |
The first two are the focus of this page; the service and container paths are documented in Run as a background service and Docker, Compose & one-click PaaS.
revka daemon — the full autonomous runtime
Section titled “revka daemon — the full autonomous runtime”revka daemon is the production entrypoint. It launches the complete Revka runtime as a foreground process and runs four supervised components together:
- the gateway server (embedded dashboard + REST + SSE + WebSocket),
- all configured channel supervisors (Telegram, Discord, Slack, …),
- the heartbeat monitor, and
- the cron scheduler.
revka daemon # use config defaultsrevka daemon --port 9090 # gateway on port 9090revka daemon --host 127.0.0.1 # localhost only (default)revka daemon --host 0.0.0.0 # all interfaces (requires allow_public_bind = true)| Flag | Meaning |
|---|---|
--port / -p | Overrides gateway.port from config |
--host | Overrides gateway.host from config |
--config-dir (global) | Path to the config directory |
The daemon terminates cleanly on SIGINT or SIGTERM. SIGHUP is explicitly ignored so the process survives an SSH or terminal disconnect — you can start it over SSH and log out without killing it. On Windows, Ctrl+C triggers the same clean shutdown.
revka gateway — gateway only
Section titled “revka gateway — gateway only”revka gateway starts just the embedded React dashboard, REST API, SSE, and WebSocket at http://127.0.0.1:42617 — no channel supervisors, heartbeat, or cron scheduler. Use it for webhook endpoint testing or when you only need the dashboard and API.
revka gatewayrevka gateway start [--host HOST] [--port PORT]revka gateway restartThe default host is 127.0.0.1 and the default port is 42617. See the Gateway API overview for the full endpoint surface.
Runtime adapters
Section titled “Runtime adapters”Process mode decides which subsystems run. The runtime adapter decides where agent shell commands execute. The adapter is selected by runtime.kind in config.toml and is independent of the process mode — you can run revka daemon with either the native or the Docker adapter.
The adapter declares the runtime’s capabilities (shell access, filesystem access, long-running process support, memory budget) and builds the actual command process for each tool invocation.
Native runtime adapter default
Section titled “Native runtime adapter ”The native adapter is the default execution environment. It runs agent commands directly on the host OS (macOS, Linux, Windows, inside Docker, Raspberry Pi) with full shell and filesystem access.
[runtime]kind = "native"This is the default; you can omit the [runtime] section entirely unless you are switching to Docker mode.
| Property | Value |
|---|---|
runtime.kind | "native" |
| Shell | sh -c on Unix, cmd.exe /C on Windows |
| Filesystem access | Full |
| Storage path | ~/.revka (the running user’s home directory) |
| Long-running processes | Supported |
| Memory budget | 0 (unlimited) |
Two cross-platform details matter:
- Shell differences. On Unix, commands run through
sh -c "<command>"; on Windows throughcmd.exe /C "<command>". Commands that assume a POSIX shell will not behave identically on Windows. - Storage path is not configurable in this adapter. It is always
~/.revkaunder the running user’s home directory, resolved from the OS user directories. The native adapter reports a memory budget of0, meaning unlimited — Revka itself imposes no memory cap on host commands.
Docker runtime adapter
Section titled “Docker runtime adapter”The Docker adapter is the sandbox execution environment. It wraps every agent shell command in a fresh docker run --rm invocation against a configurable image, then throws the container away. This gives you a read-only root filesystem, network isolation, and memory/CPU caps per command.
[runtime]kind = "docker"
[runtime.docker]image = "alpine:3.20" # defaultnetwork = "none" # default — full network isolationmemory_limit_mb = 512 # defaultcpu_limit = 1.0 # defaultread_only_rootfs = true # defaultmount_workspace = true # defaultallowed_workspace_roots = [] # default — empty = all paths allowed| Key | Type / default | Meaning |
|---|---|---|
runtime.docker.image | string, "alpine:3.20" | Image used for the sandbox container |
runtime.docker.network | string, "none" | Value passed to docker --network; "none" = no network access |
runtime.docker.memory_limit_mb | int or null, 512 | --memory in MB; null (or 0) = no limit |
runtime.docker.cpu_limit | float or null, 1.0 | --cpus value; null (or 0) = no limit |
runtime.docker.read_only_rootfs | bool, true | Passes --read-only to mount the container root read-only |
runtime.docker.mount_workspace | bool, true | Mounts the host workspace as /workspace:rw |
runtime.docker.allowed_workspace_roots | list of paths, [] | Allowlist of absolute roots; the mount is rejected if the workspace is outside every entry. Empty = all paths allowed |
Each command is launched as roughly:
docker run --rm --init --interactive \ --network none \ --memory 512m \ --cpus 1 \ --read-only \ --volume <host-workspace>:/workspace:rw \ --workdir /workspace \ alpine:3.20 \ sh -c "<command>"Key behaviors and limits:
- No long-running processes. The Docker adapter reports
supports_long_running = false. It is for sandboxing individual shell commands, not for hosting the gateway, heartbeat, or scheduler. Run the daemon itself on the host (or in its own container) and use this adapter only to sandbox the commands the agent issues. - Storage path is
/workspace/.revkawhen the workspace is mounted, otherwise/tmp/.revka. - Memory budget is reported as
memory_limit_mbconverted to bytes, so the agent can adapt its buffering.
Runtime factory
Section titled “Runtime factory”The factory function create_runtime selects the adapter from runtime.kind. Supported values are "native" and "docker". To make failures actionable, it returns explicit errors for anything else:
runtime.kind = "cloudflare"is reserved but not implemented and fails with: “runtime.kind=‘cloudflare’ is not implemented yet. Use runtime.kind=‘native’ for now.”- An empty value fails with: “runtime.kind cannot be empty. Supported values: native, docker”
- Any other value fails with: “Unknown runtime kind ‘<value>’. Supported values: native, docker”
There is no CLI flag for the adapter — it is controlled entirely through config.toml.
Daemon component supervisor
Section titled “Daemon component supervisor”Inside revka daemon, each of the four components (gateway, channels, heartbeat, scheduler) runs in its own task wrapped by a supervisor. If a component exits — whether it crashes with an error or returns cleanly — the supervisor restarts it with exponential backoff.
[reliability]channel_initial_backoff_secs = 1 # default — first retry delay (min 1)channel_max_backoff_secs = 60 # default — cap; must be >= initial| Key | Type / default | Meaning |
|---|---|---|
reliability.channel_initial_backoff_secs | int, 1 | Delay before the first restart attempt. Floored at 1 second |
reliability.channel_max_backoff_secs | int, 60 | Upper bound on the backoff. Effectively raised to the initial value if set lower |
How the backoff progresses:
- A component runs. On any exit, the supervisor records the restart count and the last error in the health snapshot.
- It sleeps for the current backoff, then doubles the delay (capped at
channel_max_backoff_secs) for the next attempt. - A clean exit (the component returned
Ok) is still treated as unexpected — no component is expected to exit while the daemon is up — but it resets the backoff to the initial value, while an error keeps growing it.
Daemon state file
Section titled “Daemon state file”While the daemon runs, it writes a JSON snapshot of all component health every 5 seconds to daemon_state.json, located next to config.toml:
<config_dir>/daemon_state.jsonEach write includes a written_at RFC 3339 timestamp plus the per-component health snapshot. The file serves two purposes:
- External liveness monitoring. Operators can watch this file. A snapshot older than 45 seconds means the daemon has died — that is the freshness threshold Revka itself uses.
- Windows process detection. Because Windows Task Scheduler cannot be queried reliably,
revka service startandrevka service statusread this file (and check the recorded PID is alive) to detect whether a direct-daemon fallback is already running.
# Inspect the latest daemon health snapshotcat ~/.revka/daemon_state.jsonThe state file is also one of the canonical health signals in the operations runbook:
| Signal | Command / file | Expected |
|---|---|---|
| Config validity | revka doctor | No critical errors |
| Channel connectivity | revka channel doctor | Configured channels healthy |
| Runtime summary | revka status | Expected provider / model / channels |
| Daemon heartbeat / state | ~/.revka/daemon_state.json | File updates periodically (< 45s old) |
| Gateway / dashboard | GET http://127.0.0.1:42617/health | 200 OK |
Resource limits & tuning
Section titled “Resource limits & tuning”Revka enforces concrete resource limits in two places today: file-descriptor limits on the managed service, and memory/CPU caps on the Docker runtime adapter. Broader process-wide resource caps are a roadmap item (see below).
File-descriptor (NOFILE) limits
Section titled “File-descriptor (NOFILE) limits”Each MCP server Revka launches consumes file descriptors. With many sidecars and channels running, the default soft limit (256 on launchd) is easily exhausted, producing “Too many open files” errors. The managed service therefore raises the descriptor limit for the daemon process:
| Limit | Value |
|---|---|
Soft NOFILE | 4096 |
Hard NOFILE | 8192 |
These are applied automatically when you install Revka as a service:
- macOS (launchd): the generated
com.revka.daemon.plistsetsSoftResourceLimits/HardResourceLimitsforNumberOfFilesto4096/8192. - Linux (systemd): the user unit sets
LimitNOFILE=4096:8192. - Linux (OpenRC): the init script declares
rc_ulimit="-n 4096".
If you run revka daemon directly (not via the service), raise the limit yourself before launching on Unix:
ulimit -n 4096revka daemonDocker memory & CPU limits
Section titled “Docker memory & CPU limits”When the Docker runtime adapter is active, each sandboxed command is capped by runtime.docker:
[runtime.docker]memory_limit_mb = 512 # passed as docker --memory 512mcpu_limit = 1.0 # passed as docker --cpus 1memory_limit_mbmaps todocker run --memory <mb>m. Setting it tonullor0omits the flag (no memory cap).cpu_limitmaps todocker run --cpus <value>. Setting it tonullor0omits the flag (no CPU cap).
These caps apply per command (each command is its own docker run --rm), not to the long-lived daemon. To cap the whole container in a Compose deployment, use Compose-level resource limits instead — see Docker, Compose & one-click PaaS.
Process-wide resource limits roadmap
Section titled “Process-wide resource limits ”allowed_workspace_roots
Section titled “allowed_workspace_roots”runtime.docker.allowed_workspace_roots is the workspace-mount guard for the Docker adapter. It is an allowlist of absolute directory roots; a workspace path is accepted only if it is inside one of them.
[runtime.docker]mount_workspace = trueallowed_workspace_roots = ["/srv/revka/workspaces", "/home/agent/work"]- Empty list (default): every path is allowed — convenient for development, but it means any workspace path the agent resolves can be mounted read-write into the sandbox.
- Non-empty list: the workspace must start with one of the listed roots, or the mount is refused with an error naming the rejected path.
- The filesystem root (
/) is always refused, regardless of this setting, to prevent mounting the entire host.
This is the main lever for preventing privilege escalation via a crafted workspace path on shared or production hosts. Pair it with read_only_rootfs = true and network = "none" (both defaults) for a tightly confined sandbox.