- The cargo target folder is <cache>/cargo-target, next to Wasmtime's cache, which moves to <cache>/wasmtime: Wasmtime's cleanup worker deletes unknown entries in its folder, also Cargo's artifacts. - Builds use the SDK at <cache>/sdk/<content hash>/, so cargo reuses the compiled SDK in every world. /plugins/cargo/sdk/ stays for agents. - Tests run in the release profile and share compiled crates with the release build. - The cargo and cli tests use a persistent cache in CARGO_TARGET_TMPDIR: warm, the cargo suite takes 11 s (was 235 s) and the cli suite 6 s (was 149 s). - nix flake check runs ignored stress tests. AGENTS.md: rules for fast tests.
105 lines
4.1 KiB
Markdown
105 lines
4.1 KiB
Markdown
# 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](https://nixos.org/download) with flakes, and an API token for
|
|
an LLM (OpenAI Responses or Anthropic Messages API). No clone needed:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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):
|
|
|
|
```sh
|
|
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
|
|
|
|
```text
|
|
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](PROTOCOL.md): calls, processes, the filesystem, locks,
|
|
interfaces, and `cargo`. Read this to write a plugin in any language.
|
|
- [DECISIONS.md](DECISIONS.md): why Sausage is the way it is.
|
|
- [`sdk/`](sdk/): `sausage-plugin`, the SDK for Rust plugins.
|
|
- [`examples/`](examples/): small plugins.
|
|
- [AGENTS.md](AGENTS.md): how to work on Sausage itself.
|