Skip to content

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.

A single binary covers every process mode. Pick the mode that matches your deployment.

ModeCommandWhen to use
Foreground runtimerevka daemonLocal debugging, short-lived sessions, the full stack in one terminal
Gateway onlyrevka gatewayWebhook endpoint testing, dashboard + REST/WS without channels or cron
User servicerevka service install && revka service startPersistent operator-managed runtime
Docker / Podmandocker compose up -dContainerized 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.
Terminal window
revka daemon # use config defaults
revka daemon --port 9090 # gateway on port 9090
revka daemon --host 127.0.0.1 # localhost only (default)
revka daemon --host 0.0.0.0 # all interfaces (requires allow_public_bind = true)
FlagMeaning
--port / -pOverrides gateway.port from config
--hostOverrides 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 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.

Terminal window
revka gateway
revka gateway start [--host HOST] [--port PORT]
revka gateway restart

The default host is 127.0.0.1 and the default port is 42617. See the Gateway API overview for the full endpoint surface.

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.

PropertyValue
runtime.kind"native"
Shellsh -c on Unix, cmd.exe /C on Windows
Filesystem accessFull
Storage path~/.revka (the running user’s home directory)
Long-running processesSupported
Memory budget0 (unlimited)

Two cross-platform details matter:

  • Shell differences. On Unix, commands run through sh -c "<command>"; on Windows through cmd.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 ~/.revka under the running user’s home directory, resolved from the OS user directories. The native adapter reports a memory budget of 0, meaning unlimited — Revka itself imposes no memory cap on host commands.

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" # default
network = "none" # default — full network isolation
memory_limit_mb = 512 # default
cpu_limit = 1.0 # default
read_only_rootfs = true # default
mount_workspace = true # default
allowed_workspace_roots = [] # default — empty = all paths allowed
KeyType / defaultMeaning
runtime.docker.imagestring, "alpine:3.20"Image used for the sandbox container
runtime.docker.networkstring, "none"Value passed to docker --network; "none" = no network access
runtime.docker.memory_limit_mbint or null, 512--memory in MB; null (or 0) = no limit
runtime.docker.cpu_limitfloat or null, 1.0--cpus value; null (or 0) = no limit
runtime.docker.read_only_rootfsbool, truePasses --read-only to mount the container root read-only
runtime.docker.mount_workspacebool, trueMounts the host workspace as /workspace:rw
runtime.docker.allowed_workspace_rootslist 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:

Terminal window
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/.revka when the workspace is mounted, otherwise /tmp/.revka.
  • Memory budget is reported as memory_limit_mb converted to bytes, so the agent can adapt its buffering.

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.

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
KeyType / defaultMeaning
reliability.channel_initial_backoff_secsint, 1Delay before the first restart attempt. Floored at 1 second
reliability.channel_max_backoff_secsint, 60Upper bound on the backoff. Effectively raised to the initial value if set lower

How the backoff progresses:

  1. A component runs. On any exit, the supervisor records the restart count and the last error in the health snapshot.
  2. It sleeps for the current backoff, then doubles the delay (capped at channel_max_backoff_secs) for the next attempt.
  3. 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.

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.json

Each 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 start and revka service status read this file (and check the recorded PID is alive) to detect whether a direct-daemon fallback is already running.
Terminal window
# Inspect the latest daemon health snapshot
cat ~/.revka/daemon_state.json

The state file is also one of the canonical health signals in the operations runbook:

SignalCommand / fileExpected
Config validityrevka doctorNo critical errors
Channel connectivityrevka channel doctorConfigured channels healthy
Runtime summaryrevka statusExpected provider / model / channels
Daemon heartbeat / state~/.revka/daemon_state.jsonFile updates periodically (< 45s old)
Gateway / dashboardGET http://127.0.0.1:42617/health200 OK

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).

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:

LimitValue
Soft NOFILE4096
Hard NOFILE8192

These are applied automatically when you install Revka as a service:

  • macOS (launchd): the generated com.revka.daemon.plist sets SoftResourceLimits / HardResourceLimits for NumberOfFiles to 4096 / 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:

Terminal window
ulimit -n 4096
revka daemon

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 512m
cpu_limit = 1.0 # passed as docker --cpus 1
  • memory_limit_mb maps to docker run --memory <mb>m. Setting it to null or 0 omits the flag (no memory cap).
  • cpu_limit maps to docker run --cpus <value>. Setting it to null or 0 omits 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 ”

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 = true
allowed_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.