surma 1617dfc1ee console: one sequential loop; host tests in one binary
The console reads a line, runs it to the end, and prints the result,
with and without a terminal. Notifications print while a command runs.
Ctrl-C during a command stops only the wait. 314 lines (was 480).

All host integration tests are modules of cli/tests/host/, so they run
in one parallel pool, and the probes build once. Warm, the 184 host
tests take 24 s (was about 45 s as seven binaries in sequence), and
cargo test --workspace takes 45 s.
2026-10-23 09:14:26 +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: the first build on a machine takes a few minutes, later worlds reuse the compiled crates), 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.

Compiled code is cached outside the world, in $XDG_CACHE_HOME/sausage/ (~/.cache/sausage/ without the variable). All worlds share it. --cache-dir <folder> changes it for init and run.

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%