RuntimeAdapter Trait
TheRuntimeAdapter trait abstracts platform differences, enabling the same agent code to run on native, Docker, WASM, edge, and embedded environments.
Source: src/runtime/traits.rs:5-32
Trait Definition
use std::path::{Path, PathBuf};
use tokio::process::Command;
pub trait RuntimeAdapter: Send + Sync {
/// Human-readable runtime name
fn name(&self) -> &str;
/// Whether this runtime supports shell access
fn has_shell_access(&self) -> bool;
/// Whether this runtime supports filesystem access
fn has_filesystem_access(&self) -> bool;
/// Base storage path for this runtime
fn storage_path(&self) -> PathBuf;
/// Whether long-running processes (gateway, heartbeat) are supported
fn supports_long_running(&self) -> bool;
/// Maximum memory budget in bytes (0 = unlimited)
fn memory_budget(&self) -> u64 {
0
}
/// Build a shell command process for this runtime
fn build_shell_command(
&self,
command: &str,
workspace_dir: &Path,
) -> anyhow::Result<Command>;
}
Built-in Runtimes
Fromsrc/runtime/mod.rs:
| Runtime | File | Status | Use Case |
|---|---|---|---|
| Native | native.rs | ✅ Stable | Local development, servers |
| Docker | docker.rs | ✅ Stable | Sandboxed execution |
| WASM | wasm.rs | 🚧 Planned | Edge, browsers |
Native Runtime
Fromsrc/runtime/native.rs:6-31:
pub struct NativeRuntime;
impl NativeRuntime {
pub fn new() -> Self {
Self
}
}
impl RuntimeAdapter for NativeRuntime {
fn name(&self) -> &str {
"native"
}
fn has_shell_access(&self) -> bool {
true
}
fn has_filesystem_access(&self) -> bool {
true
}
fn storage_path(&self) -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".corvus")
}
fn supports_long_running(&self) -> bool {
true
}
fn build_shell_command(
&self,
command: &str,
workspace_dir: &Path,
) -> anyhow::Result<Command> {
let mut cmd = Command::new("sh");
cmd.arg("-c")
.arg(command)
.current_dir(workspace_dir);
Ok(cmd)
}
}
Docker Runtime
Fromsrc/runtime/docker.rs:15-184:
pub struct DockerRuntime {
image: String,
network: String,
memory_limit_mb: Option<u32>,
cpu_limit: Option<f32>,
read_only_rootfs: bool,
mount_workspace: bool,
}
impl DockerRuntime {
pub fn new(config: &DockerRuntimeConfig) -> Self {
Self {
image: config.image.clone(),
network: config.network.clone(),
memory_limit_mb: config.memory_limit_mb,
cpu_limit: config.cpu_limit,
read_only_rootfs: config.read_only_rootfs,
mount_workspace: config.mount_workspace,
}
}
}
impl RuntimeAdapter for DockerRuntime {
fn name(&self) -> &str {
"docker"
}
fn has_shell_access(&self) -> bool {
true
}
fn has_filesystem_access(&self) -> bool {
self.mount_workspace
}
fn storage_path(&self) -> PathBuf {
PathBuf::from("/workspace")
}
fn supports_long_running(&self) -> bool {
false // containers are ephemeral
}
fn memory_budget(&self) -> u64 {
self.memory_limit_mb
.map(|mb| mb as u64 * 1024 * 1024)
.unwrap_or(0)
}
fn build_shell_command(
&self,
command: &str,
workspace_dir: &Path,
) -> anyhow::Result<Command> {
let mut cmd = Command::new("docker");
cmd.arg("run")
.arg("--rm")
.arg("--network").arg(&self.network);
// Resource limits
if let Some(mb) = self.memory_limit_mb {
cmd.arg("--memory").arg(format!("{}m", mb));
}
if let Some(cpu) = self.cpu_limit {
cmd.arg("--cpus").arg(format!("{}", cpu));
}
// Security
if self.read_only_rootfs {
cmd.arg("--read-only");
}
// Mount workspace
if self.mount_workspace {
cmd.arg("-v")
.arg(format!("{}:/workspace", workspace_dir.display()));
}
// Execute command
cmd.arg(&self.image)
.arg("sh")
.arg("-c")
.arg(command);
Ok(cmd)
}
}
Configuration
Native (Default)
[runtime]
kind = "native"
Docker
[runtime]
kind = "docker"
[runtime.docker]
image = "alpine:3.20"
network = "none" # isolated
memory_limit_mb = 512
cpu_limit = 1.0
read_only_rootfs = true # prevent writes to container
mount_workspace = true
Custom Runtime Implementation
use corvus::runtime::RuntimeAdapter;
use std::path::{Path, PathBuf};
use tokio::process::Command;
pub struct EdgeRuntime {
storage: PathBuf,
}
impl RuntimeAdapter for EdgeRuntime {
fn name(&self) -> &str {
"edge"
}
fn has_shell_access(&self) -> bool {
false // no shell on edge
}
fn has_filesystem_access(&self) -> bool {
true // limited KV storage
}
fn storage_path(&self) -> PathBuf {
self.storage.clone()
}
fn supports_long_running(&self) -> bool {
true // edge functions are long-lived
}
fn memory_budget(&self) -> u64 {
128 * 1024 * 1024 // 128MB
}
fn build_shell_command(
&self,
_command: &str,
_workspace_dir: &Path,
) -> anyhow::Result<Command> {
Err(anyhow::anyhow!("Shell not supported on edge runtime"))
}
}
Tool Integration
Tools query runtime capabilities:impl Tool for ShellTool {
async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
if !self.runtime.has_shell_access() {
return Ok(ToolResult {
success: false,
error: Some("Shell not supported in this runtime".into()),
..Default::default()
});
}
let mut cmd = self.runtime.build_shell_command(
command,
&self.security.workspace_dir,
)?;
// Execute...
}
}
Registration
Fromsrc/runtime/mod.rs:41-66:
pub fn create_runtime(config: &RuntimeConfig) -> anyhow::Result<Arc<dyn RuntimeAdapter>> {
match config.kind.as_str() {
"native" => Ok(Arc::new(NativeRuntime::new())),
"docker" => Ok(Arc::new(DockerRuntime::new(&config.docker))),
"wasm" => Err(anyhow::anyhow!("WASM runtime not yet implemented")),
other => Err(anyhow::anyhow!("Unknown runtime kind: {}", other)),
}
}
Best Practices
Use Docker runtime for untrusted code execution
Implement
memory_budget() to enable resource-aware tool selectionAlways validate
has_shell_access() and has_filesystem_access() before using toolsFuture Runtimes
WASM (Planned)
pub struct WasmRuntime {
memory_pages: u32,
}
impl RuntimeAdapter for WasmRuntime {
fn has_shell_access(&self) -> bool { false }
fn has_filesystem_access(&self) -> bool { false }
fn memory_budget(&self) -> u64 { self.memory_pages as u64 * 65536 }
}
Embedded (Raspberry Pi, ESP32)
pub struct EmbeddedRuntime {
flash_size: u64,
}
impl RuntimeAdapter for EmbeddedRuntime {
fn has_shell_access(&self) -> bool { false }
fn storage_path(&self) -> PathBuf { PathBuf::from("/flash") }
fn memory_budget(&self) -> u64 { 512 * 1024 } // 512KB RAM
}