FORM NOT VOID, MIND NO CORE

Chapter 8: Growing New Limbs: Capabilities and Execution Environments

2026.08.10

Who executes the code an agent writes? Where does it run? How long does it live? -- These three questions define the architecture of Capabilities.


Pattern Layer

8.1 The Environment x Lifecycle Matrix

The two core dimensions of Capability execution are Environment (where it runs) and Lifecycle (how long it lives).

Environment (execution environment):

  • Shell: the agent executes scripts or binaries directly on the host. Maximum privilege -- access to the filesystem, network, and processes. Its destructive power is equivalent to a user pressing Enter in the terminal.
  • Wasm: executes within a WebAssembly sandbox. Compile-time isolation -- a Wasm module can only access resources exposed through WASI (WebAssembly System Interface) interfaces. No filesystem access (unless explicitly mounted).
  • Docker: executes within a container. Filesystem isolation -- access scope is controlled through volume mounts. Network isolation -- configured via docker network.

The trust cost of the three environments increases progressively: Shell is the most dangerous (highest trust cost required), Wasm is intermediate (compile-time isolation), and Docker is the safest (full container isolation).

Lifecycle (lifecycle):

  • OneShot: executes once, outputs results, process exits. Suitable for: compilation scripts, data migrations, one-time analysis.
  • OnDemand: invoked on demand, creates a new process per invocation. Suitable for: code generation, scheduled tasks, API requests.
  • Persistent: a long-running service that survives across turns and communicates via network or IPC (Inter-Process Communication). Suitable for: background listeners, state-maintaining services.

The 3x3 matrix:

OneShotOnDemandPersistent
ShellExecute a shell script onceStart a shell process on demandLong-running background process (e.g., http server)
WasmExecute a wasm module onceInvoke a wasm function on demandPersistent Wasm instance (limited)
DockerRun a container onceCreate a container on demandLong-running container service

The most commonly used combinations in practice are: Shell/OneShot (everyday scripts), Shell/Persistent (background services), and Docker/OneShot (safely isolated one-shot tasks). The Wasm environment is used less frequently in CodeCoder -- because the toolchain support for the Wasm ecosystem is not yet mature, and the debugging experience for Wasm modules is not as smooth as for Shell scripts.

8.2 The Trust Hierarchy of Three Sandboxes

The choice of Environment determines the trust cost of a Capability.

The Shell environment has the highest trust cost. The Shell environment has access to everything on the host: the filesystem, network, processes, environment variables, and kernel interfaces. A Shell-environment Capability can delete any file, read any environment variable, and connect to any network address.

Consequently, the ceiling rule for the Shell environment is the strictest: it can only reach the session allowlist, never the project allowlist. Any Shell-environment Capability, even after repeated successful executions, cannot be granted cross-session automatic authorization.

The Wasm environment has a moderate trust cost. Wasm modules are sandboxed at compile time -- they can only access resources explicitly exposed through WASI interfaces. No filesystem (unless mounted), no network (unless proxied), no process control.

But Wasm's limitations are also here. Many common agent operations -- reading files, running shell commands -- are unavailable in Wasm unless supported via WASI extension interfaces. Wasm is suitable for pure-computation Capabilities (data transformation, format checking, static analysis), not for Capabilities that need to access external resources.

The Docker environment has the lowest trust cost (i.e., is safest). Docker containers provide full filesystem isolation -- /etc/passwd inside the container is not the host's /etc/passwd. Network isolation -- the container can only access network addresses allowed in its configuration. Process isolation -- processes inside the container cannot see the host's process list.

Docker environments can be promoted to the project allowlist -- because even if the Capability's code contains malicious behavior, it is confined within the container. The container can mount host directories as volumes, but the mounted paths and permissions are declared in the manifest, not controlled by the Capability's code.

EnvironmentIsolation LevelMaximum Trust LevelTypical Use Cases
ShellNo isolationSession allowlistEveryday scripts, compilation, deployment
WasmCompile-time isolationProject allowlistPure computation tasks
DockerContainer isolationProject allowlistUntrusted code, multi-tenant tasks

8.3 Execution Backend Routing

The core logic of run_capability is routing to the corresponding execution backend according to the Environment declared in the manifest:

fn run_capability(name: &str, context: &Context) -> Result<Output> {
    // 1. Get the Capability from the Registry
    let cap = context.registry.capabilities.get(name)?;

    // 2. Check permissions
    let perm_key = format!("run_capability:{}", cap.environment());
    context.permission_check(&perm_key)?;

    // 3. Route by Environment
    match cap.environment() {
        Environment::Shell => run_shell(cap, context),
        Environment::Wasm => run_wasm(cap, context),
        Environment::Docker => run_docker(cap, context),
    }
}

Implementation of each execution backend:

fn run_shell(cap: &Capability, context: &Context) -> Result<Output> {
    let entrypoint = &cap.manifest.entry;
    let output = std::process::Command::new("sh")
        .arg(entrypoint)
        .current_dir(cap.dir())
        .output()?;
    Ok(Output::from_process(output))
}

fn run_wasm(cap: &Capability, context: &Context) -> Result<Output> {
    let wasm_path = cap.dir().join(&cap.manifest.entry);
    // Wasm runtime
    let engine = wasmtime::Engine::default();
    let module = wasmtime::Module::from_file(&engine, &wasm_path)?;
    // ... set up WASI interfaces, execute the module
    // Wasm execution is not yet fully implemented (see ADR 0021)
    return Err("Wasm execution not yet supported".into());
}

fn run_docker(cap: &Capability, context: &Context) -> Result<Output> {
    let image = &cap.manifest.docker_image;
    let entrypoint = &cap.manifest.entry;
    let mount_volumes = &cap.manifest.volumes;

    // Build the docker run command
    let mut cmd = std::process::Command::new("docker");
    cmd.args(["run", "--rm"]);
    for vol in mount_volumes {
        cmd.args(["-v", &format!("{}:{}", vol.host, vol.container)]);
    }
    cmd.args([image, entrypoint]);
    let output = cmd.output()?;
    Ok(Output::from_process(output))
}

run_wasm returns an error -- Wasm execution in CodeCoder is not yet fully implemented. See the ADR deep dive for the rationale.


Case Layer

8.4 Capability Manifest Declarative Design

Each Capability has its own subdirectory under capabilities/, containing a manifest.yaml and one or more entry point files.

# capabilities/daily-report/manifest.yaml
name: daily-report
description: "Generate daily code review report"
version: 1

environment: shell
lifecycle: oneshot

entry: report.sh

permissions:
  - run_command:git
  - write_file:daily-report-*

volumes: []    # Docker environment only
docker_image: ""  # Docker environment only

Core fields of the manifest:

  • environment / lifecycle: declares what environment the Capability runs in and how long it lives
  • entry: entry point file path, relative to the Capability directory
  • permissions: additional permissions required to execute this Capability (optional, for refining permission requests)
  • volumes / docker_image: Docker-specific configuration

The declarative design of the manifest allows run_capability to determine -- without executing any code -- what environment this Capability needs, whether additional permissions are required, and how long it needs to run. It functions as a "pre-execution declaration": before triggering actual execution, the system already knows all of the Capability's execution requirements.

8.5 OneShot Example: Daily Report Generation

# capabilities/daily-report/manifest.yaml
name: daily-report
environment: shell
lifecycle: oneshot
entry: report.sh

Contents of report.sh:

#!/bin/sh
# Collect today's git log
git log --since="1 day ago" --format="%h %s" > /tmp/commits.txt
# Show file changes
git diff --stat $(git rev-list --max-parents=0 HEAD)..HEAD

When the agent calls run_capability daily-report, the Shell backend executes report.sh, captures stdout and stderr, and returns the output to the agent. After execution, the process exits with no residual state.

The advantage of OneShot is simplicity -- no state management, no process supervision, no port conflicts. Every execution starts from a clean slate.

8.6 Persistent Example: HTTP Health Check Service

# capabilities/health-check/manifest.yaml
name: health-check
environment: shell
lifecycle: persistent
entry: server.sh

server.sh starts a simple HTTP service that periodically checks system status:

#!/bin/sh
PORT=${PORT:-8080}
while true; do
    echo -e "HTTP/1.1 200 OK\n\n$(date): system healthy" | nc -l -p $PORT
done

When the agent calls run_capability health-check, the Shell backend launches server.sh as a background process and registers the PID with the RunningServiceTable:

struct RunningService {
    pid: u32,
    capability_name: String,
    started_at: Instant,
    port: Option<u16>,
}

struct RunningServiceTable {
    services: HashMap<String, RunningService>,
}

impl RunningServiceTable {
    fn register(&mut self, cap: &Capability, pid: u32) {
        self.services.insert(cap.name.clone(), RunningService {
            pid,
            capability_name: cap.name.clone(),
            started_at: Instant::now(),
            port: cap.manifest.port,
        });
    }

    fn unregister(&mut self, name: &str) {
        if let Some(service) = self.services.remove(name) {
            // Terminate the process
            std::process::Command::new("kill")
                .arg(service.pid.to_string())
                .spawn().ok();
        }
    }
}

Key design points of Persistent Capabilities:

  • Process supervision: RunningServiceTable records the PID of each Persistent Capability. When the agent exits or the daemon shuts down, all long-running services are terminated via unregister.
  • Crash recovery: When a Capability crashes, the system does not auto-restart it. RunningServiceTable marks its state as Failed instead of spawning a new process automatically. Rationale: a Capability crash may be due to a permanent issue (code bug, configuration error), and automatic restart would cause a crash loop.
  • No cross-session persistence: RunningServiceTable lives in memory and is not persisted to disk. After a daemon restart, Persistent Capabilities need to be restarted.

ADR Deep Dive

Wasm Capability Source-to-Wasm Compilation Deferred (ADR 0021)

ADR 0021 records the state of the Wasm execution backend: a Capability's manifest can declare environment: wasm, but the Wasm backend of run_capability only accepts pre-compiled .wasm or .wat files. The path from Rust source code compilation to a Wasm module is not implemented.

Why was it deferred? Two reasons.

First, the compilation toolchain dependency problem. Compiling from Rust source code to a Wasm module requires toolchains like wasm-pack or wasm-gc, whose installation and version management increase the complexity of the Capability's runtime environment. A Shell script only needs sh -- almost every system has it. A Wasm module needs wasmtime or a similar runtime -- not every system has it.

Second, the source of Capability code. Currently, Capabilities are primarily Shell scripts, because the most natural form of agent-generated code is a Shell script. The translation from Shell script to a Wasm module is not straightforward -- it requires rewriting in Rust or another language. This increases the cognitive cost for the agent when generating Capabilities.

The deferred state of the Wasm execution backend means that environment: wasm is "reserved but not activated" in the current version -- the manifest can declare it, but execution will return a "Wasm runtime not ready" error. This is consistent with the principle of "no silent degradation for isolation": the system will not silently fall back to the Shell environment just because Wasm is unavailable.

Why No Silent Degradation When Docker Is Unavailable

If a Capability declares environment: docker but Docker is not installed on the server, what should the system do?

Option A: Report an error, "Docker is unavailable; the Capability cannot be executed." Option B: Execute in the Shell environment (since the script might be cross-environment).

CodeCoder chooses Option A. Silent degradation is not allowed. The rationale is: declaring environment: docker in the manifest represents the intent of the Capability's author (the agent). If the system silently falls back to the Shell environment when Docker is unavailable, the Capability's trust model is broken -- the user approved "execution in Docker," but it actually ran in Shell.

The consequence of silent degradation is: the Docker environment can be promoted to the project allowlist, while the Shell environment cannot. If the system silently degrades to Shell, a Capability that should only reach the session allowlist would have been granted project allowlist status -- the security gate has been bypassed.

This design decision is consistent with the ceiling rule: environment declarations are part of the security commitment. Declaring environment: docker means "I only need container-level isolation." The system is obligated to ensure this commitment holds, or to refuse execution when it cannot.


The next chapter re-examines the tripartite architecture from a security perspective -- how the self-authoring safety loop prevents the agent from overstepping bounds during self-evolution.