surma 57a47b069e host: process table, shell, coreutils and harness (T8, T9)
T8: sausage:process WIT (spawn, wait, kill), one store per process over the
shared root, /proc/processes, /run, pipeline and cancellation groups, Exec.
brush and 78 uutils commands; the shared cwd-init object sets the cwd from PWD
and keeps numeric exit codes. The corpus matches native bash.

T9: V8 (deno_core) runs pi-durable, pi-ai and chord; SQLite storage in
<dir>/host/harness.sqlite (WAL, synchronous=FULL); file tools on the VFS, bash
through Exec; resumes after SIGKILL.
2026-11-06 16:34:34 +00:00
2026-10-23 02:38:35 +01:00
2026-10-26 12:49:01 +00: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 LLM: the OpenAI Responses API, an OpenAI-compatible Chat Completions API (OpenRouter, Ollama, and most other providers), or the 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 nothing. It writes world/config/agent.yaml, with a comment for each field and an example for each API. Edit it before the first chat: choose the API, the base URL, the model, the key, and the thinking level. Until then, a chat answers with an error that names the missing field. 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.restart, the loop fetches the newest commit and starts it again. The loop sets SAUSAGE_LOOP=1 to enable that RPC; without it, restart returns an error and leaves the host running. Restart answers first, then cleanly stops all processes and exits with code 75. quit (code 0) ends the loop. Set SAUSAGE_FLAKE to run another flake URL instead, for example your fork.

The mediator and all agents NEVER call core.restart without the person's explicit approval each time. This also applies to legacy core.exit, an alias with the same supervisor gate, response-before-exit behavior, and request coalescing. See the protocol for the shutdown contract.

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.restart (requires SAUSAGE_LOOP=1; exit code 75 restarts the loop):

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.yaml             ordered boot services, supervised for crashes (PROTOCOL §8)
config/agent.yaml            the configuration of the agent

Boot services restart on process crashes, not on readiness responses. More than 5 crashes within 10 seconds stops automatic attempts for that service and journals service gave up; an upgrade does not reset that decision. Explicitly stopping or killing a service also leaves it stopped for this host lifetime. See Boot and services.

Rust builds use the SDK in plugins/cargo/sdk/ directly. The host seeds this folder only when it is missing; restarts and host upgrades preserve existing files. SDK edits take effect on the next cargo.build, without a restart. To reset to the host's bundled SDK, back up any edits and remove the whole folder; the next startup or build re-seeds it.

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
16 MiB
Languages
JavaScript 67.5%
Go 11.2%
Rust 11%
C 7%
TypeScript 2.2%
Other 0.9%