surma b536beb069
ci / check (push) Successful in 15m52s
YAML configuration, init without a wizard, and openai-chat-completions
- /config/agent.yaml and /config/boot.yaml replace the JSON files, read
  with serde_yaml_ng (also in the agent, inside WASM).
- sausage init asks nothing and reads no SAUSAGE_* variables. It writes
  commented templates with an example for each API, and prints the path
  to edit. Until the agent is configured, agent.chat names the missing
  field. allow_no_token: true permits a local model without a key.
- The agent speaks openai-chat-completions, the format of OpenRouter,
  Ollama, and most providers. base_url follows each SDK: with /v1 for
  the OpenAI shapes, without it for Anthropic.
- Checked against the proxy with glm-5.3-flash: the agent wrote, built,
  and activated a counter plugin in 55 s.
2026-10-23 11:27:48 +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 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, and the key. 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.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.yaml             the services that start with the world
config/agent.yaml            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%