surma a090e1fd57 host: review fixes for cargo.build, the test runner, the console, and main; tests for every PROTOCOL.md rule
cargo.build and the runner (review T22R): builds use the network again;
core.unlock and core.notify in order under the runner; quiet test
output; manifest preparation keeps [workspace], target tables, renamed
and optional entries; cargo.rpc.discover; -32004 with data.output;
retention of 10 builds; light, empty test worlds with an in-memory
journal; only CARGO_/RUST_/SAUSAGE_ variables; stdin is null; the runner
through --config. A compile cache that cannot be created is not fatal.
Activation replaces plugin.wasm without a gap.

Console and main: init ships the workspace Cargo.lock and creates the
world atomically; each console command runs in its own task, so ps,
stop, and kill work during a slow call; SAUSAGE_HEADLESS; the terminal
mode is restored; signals during boot; chat text is not taken as a
command.

Tests: 20 more tests for PROTOCOL.md sections 2 to 10; the cargo tests
choose the libtest binary from cargo's JSON output and time out.
2026-10-23 07:47:39 +01:00
2026-10-23 02:38:35 +01:00
2026-10-23 02:38:35 +01:00

Sausage

You can see how the sausage is made.

Sausage is a world of small WASI programs ("plugins") that you grow by talking to it. A plugin is a .wasm file with JSON-RPC methods. Each call to a plugin starts a new process. The agent is a plugin too: it writes new plugins in Rust, builds them, tests them, activates them, and can change its own code.

Start

You need Nix with flakes, and an API token for an LLM (OpenAI Responses or Anthropic Messages API). No clone needed:

nix run git+https://gitea.surma.technology/surma/sausage

This creates a world in ./world the first time (it builds the agent, which takes a few minutes), then starts it with a console. Later runs start the existing world. The two steps on their own:

nix run git+https://gitea.surma.technology/surma/sausage -- init   # creates ./world and builds the agent
nix run git+https://gitea.surma.technology/surma/sausage -- run    # starts the world and a console

init asks for the API shape (openai-responses or anthropic-messages), the base URL, the model, and the token. It reads SAUSAGE_API, SAUSAGE_BASE_URL, SAUSAGE_MODEL, and SAUSAGE_API_TOKEN first. The values go to world/config/agent.json. Both commands take another folder as an argument, for example ... -- run my-world.

To keep a world on the newest version, run it in a loop:

nix run git+https://gitea.surma.technology/surma/sausage#loop               # ./world, created if needed
nix run git+https://gitea.surma.technology/surma/sausage#loop -- my-world   # an existing world

When the host crashes or a plugin calls core.exit, the loop fetches the newest commit and starts it again. quit ends the loop. Set SAUSAGE_FLAKE to run another flake URL instead, for example your fork.

From a clone (git clone https://gitea.surma.technology/surma/sausage), the same commands are nix run, nix run -- init, nix run -- run, and nix run .#loop.

Headless

--headless (or SAUSAGE_HEADLESS=1) turns the console input off. The host runs until SIGTERM or SIGINT (exit code 0) or until a plugin calls core.exit (exit code 75, so the loop starts the newest version):

nix run git+https://gitea.surma.technology/surma/sausage#loop -- --headless my-world

The console

Input Effect
text a message to the agent (agent.chat)
<plugin>.<method> {json} a call. The console prints the result.
ls [path], cat <path> the filesystem as a process sees it, with /proc and /run
methods <plugin> the methods of a plugin, with params and docs
ps the running processes
log [plugin or pid] [-n N] the journal
activate <plugin> <wasm>, revert <plugin> activate a new version, or go back
stop <pid>, kill <pid> stop a process
help, quit

The world folder

plugins/<name>/plugin.wasm   the current version of a plugin
plugins/<name>/source/       its source
state/<plugin>/              the state of a plugin, by convention
state/core/journal.sqlite    the journal: stderr of all processes, and core events
config/boot.json             the services that start with the world
config/agent.json            the configuration of the agent

Each process also sees /proc (the processes and the state of the core), /self (its own folder in /proc), and /run (shared files in memory).

To replace a plugin by hand, copy a .wasm file to its plugin.wasm. That skips the interface checks. revert <plugin> goes back to the version before the last activation.

More

  • PROTOCOL.md: calls, processes, the filesystem, locks, interfaces, and cargo. Read this to write a plugin in any language.
  • DECISIONS.md: why Sausage is the way it is.
  • sdk/: sausage-plugin, the SDK for Rust plugins.
  • examples/: small plugins.
  • AGENTS.md: how to work on Sausage itself.
S
Description
No description provided
Readme
1.4 MiB
Languages
Rust 98.9%
Nix 1.1%