Upgrades retire old processes after a grace period, then call rpc.retired. Services live in boot.json. One preopen / with a mount table in the sausage-vfs crate, with guards for in-flight file operations. Draft 7 and our own compatibility check on a fixed subset. Imports register with inventory. Rules for liveness, stop states, routing, locks, activation, endpoints (rpc.endpoint handshake), boot, builds, and a 10 ms scheduling tick.
Sausage
You can see how the sausage is made.
Sausage is a world of small programs ("actors") that you grow by talking to it. Each actor is a WASI program with stdin and stdout. Actors send each other JSON messages, one per line, through a broker. The agent is an actor too: it writes new actors in Rust, builds them, tests them, starts 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/actor/agent/data/config.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
The loop runs nix run --refresh git+https://gitea.surma.technology/surma/sausage -- run my-world.
When the host crashes or an actor sends @broker {"type":"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
never reads stdin, so end of input does not stop the world. It runs until
SIGTERM/SIGINT (clean stop, exit code 0) or a broker exit request (exit
code 75, so the loop starts the newest version). Create the world first
(init), then pass the flag through the loop:
nix run git+https://gitea.surma.technology/surma/sausage#loop -- --headless my-world
In the console:
write an actor "notes" that stores notes, and start it → text to the agent
@notes {"type":"add","text":"milk"} → a JSON message to an actor
@agent hello → text to an actor
/quit
The world folder
The folder is the source of truth. Every actor lives in world/actor/<id>/:
actor.wasm the current version
actor.json installed version, running, subscriptions
source/ the source of the current version
public/ files that the actor publishes
data/ actor state; owner-writable, world-readable via /actor/<id>/data/ (not private)
stderr.log what the actor and the broker write about it
versions/<n>/ every installed version: actor.wasm, source/, meta.json
To add or replace an actor by hand, put an actor.wasm (and its source/,
with the tests in source/tests/) into its folder; the host watches the
world folder and picks the file up once it stops changing. The broker tests
the file before it uses it.
If the tests fail, the old version keeps running, and stderr.log says why.
More
- PROTOCOL.md: messages, the filesystem, the broker,
cargo. Read this to write an actor in any language. sdk/:sausage-actor, the SDK for Rust actors.examples/notes: a small actor with a test.- AGENTS.md: how to work on Sausage itself.