4.3 KiB
Working on Sausage (v3)
Sausage is a world of WASI programs and durable agents. It is meant to be read and changed by agents, including its own. Keep it that way.
This branch (v3) rebuilds Sausage. The plan and its decisions (D1, D2, …) are in
~/research/sausage-v3/PLAN.md. README.md describes the current build and layout.
The boundary
- The world is the sandbox. Everything inside the world sees and changes the whole world. Do not add inner boundaries, permissions or per-agent views. The VFS is a safety net against mistakes, not a wall against attackers.
- Host calls pierce the sandbox. The only host import beyond standard WASI is the process interface
(spawn, wait, kill). Do not add another one. Everything else uses standard WASI 0.2 interfaces: files
through the VFS,
wasi:http(the host API on its reserved host name, credential injection, signing), andwasi:sockets. - The credentials manager in the host is the one real security boundary. Its managed secrets never enter
the world. Approvals need a passkey. The exceptions are provider API keys in
/etc/sausage/config.yaml. The user keeps these keys inside the world so agents can change their own setup. - Native code is the exception. The only native work for the world is the Rust builder, and it runs in user, mount and network namespaces.
Priorities
- Correct. Every behavior has a test.
- Simple and short. Prefer the obvious code. No abstraction for one caller. No configuration that nobody asked for.
- Readable. A new reader must understand a file without the others. Each module starts with a doc comment that says what it owns.
- Efficient on the paths that run per command and per model turn.
Layout
host/: the host crate (package and binarysausage): the world, the process table, the harness.host/src/exec.rsis the contract between the harness and the process table.vfs/:sausage-vfs, the virtual filesystem for WASI guests and for host code. It knows nothing about Sausage. Host code reads and writes the world only through it.userland/: the default CLIs of a world, built forwasm32-wasip2in the root Cargo workspace. The patched Brush crates live inuserland/brush/source/and share the root lock file.spikes/: accepted prototypes. Read them for evidence, copy from them, do not build on them.
Programs in the world
- Agents write CLIs and servers. That they are WASI binaries is a detail, not a concept to explain.
- The platform is WASI 0.2 components. Rust programs build for
wasm32-wasip2. WASI 0.1 programs run through an adapter.
Checks
nix develop -c cargo test -p sausage -p sausage-vfs
nix develop -c cargo clippy -p sausage -p sausage-vfs --all-targets -- -D warnings
nix develop -c nu userland/bundle.nu
# For one tool, select its package and WASI target explicitly:
nix develop -c cargo build -p git --target wasm32-wasip2 --release
nix build uses separate Crane pipelines for the native host and WASI userland.
Go tools and QuickJS build separately. Nix assembles the bundle without bundle.nu.
Both Nix and the incremental development bundle read the command names from userland/tools.json.
Coreutils, diffutils, procps, and brush use relative symlinks to one binary each.
nix flake check checks the packaged host, its bundle, and the whole host/VFS test suite.
Sandbox-incompatible tests print an explicit skip reason instead of a flake-level filter.
Tests
Host integration tests share one executable:
cargo test -p sausage --test it
cargo test -p sausage --test it -- chain::
Use the required Nix environment and build slot wrapper for these commands.
Add test modules under host/tests/it/ and declare them in it/main.rs.
Use fully qualified child test filters with --exact.
See host/tests/README.md for fixture and isolation details.
Tests must stay fast:
- Only a few tests may wait for a timing (grace periods, deadlines).
- Poll for a condition with a deadline. Do not sleep.
- Do not test unrealistic load, such as many fast cycles.
#[ignore]only disables a test for a short time, with the reason in the attribute.- Tests that build or compile guests use a persistent cache (for example under
env!("CARGO_TARGET_TMPDIR")), so a warm run does not compile again.