a3s-box-sdk 3.0.3

Rust SDK for a3s-box. Includes a programmable CI/CD pipeline API (pipelines as code, one MicroVM kernel per step).
Documentation

a3s-box-sdk

The Rust SDK for a3s-box. Today it provides a programmable CI/CD pipeline API (a3s_box_sdk::pipeline) — a pipeline is a Rust program, not a YAML file, and each step runs in its own MicroVM (one Linux kernel per step, so an untrusted step can't escape to the host or a sibling step). More capabilities will be added over time; the crate is intentionally not limited to CI.

Dependency-free: a thin wrapper over the a3s-box CLI (which owns the box lifecycle and state). Set A3S_BOX if a3s-box is not on PATH.

Pipelines

Warm a base box once (clone + install deps), snapshot it, fork per step. Run steps sequentially (fail-fast) or in parallel (collect-all → a typed report):

use a3s_box_sdk::pipeline::{warm_base, WarmBase, FileCache, Step};

fn main() -> Result<(), a3s_box_sdk::pipeline::PipelineError> {
    let cache = FileCache::new(".ci-cache")?;          // skip a step when its inputs are unchanged
    let base = warm_base(
        WarmBase::new("node:20", "git clone $REPO /w && cd /w && npm ci")   // runs ONCE
            .env("REPO", "https://github.com/me/app")
            .cache(&cache),
    )?;

    // Sequential, fail-fast: a non-zero exit returns Err.
    base.step(Step::new("lint", "cd /w && npm run lint"))?;

    // Parallel, collect-all: each step is an isolated CoW fork; <=4 at a time.
    let report = base.run_parallel(vec![
        Step::new("test",  "cd /w && npm test"),
        Step::new("build", "cd /w && npm run build"),
    ], 4);

    println!("{}", report.to_json());   // {"passed":..,"total_ms":..,"steps":[..]}
    if !report.passed { /* inspect report.failures() */ }
    Ok(())  // `base` drops here -> snapshot auto-removed (or call base.dispose())
}
  • run_parallel is the way to use a3s-box's cheap (~ms) CoW fork at scale — a matrix / evolution-style batch — without hand-rolling threads (every method takes &self).
  • A step reports a metric by printing ::metric <key>=<number> to stdout; it surfaces as StepResult::metrics (the scoring channel for a selection loop).
  • StepResult carries separated stdout/stderr, duration_ms, and cached; Report::to_json() is the machine-readable handoff to an agent/scorer.
  • Step::allow_failure() keeps a non-zero step from failing the run; Step::input(..) adds extra cache-key parts.

The base auto-disposes its snapshot on drop, and each per-step box is removed on every path (including a panic), so a long-running batch doesn't leak.

Why forking is cheap

a3s-box's snapshot restore is copy-on-write: each fork mounts the snapshot's pristine rootfs as a read-only overlay lower with its own upper — near-instant, a few MB per fork, and isolated. So snapshot-per-step fan-out costs almost nothing.

What it hides

CLI footguns verified on a real KVM host, so you don't hit them: run/exec need -- before the command; snapshot restore yields a created box (started before exec); snapshot rm keys on snapshot ID, not name; rm -f of a missing box is a no-op here (idempotent reruns).

Run the example

cargo test -p a3s-box-sdk                                            # offline unit tests
A3S_BOX=/path/to/a3s-box cargo run -p a3s-box-sdk --example pipeline  # live, needs /dev/kvm