3.5 KiB
3.5 KiB
Decisions
Why Sausage is the way it is. PROTOCOL.md and the code own the details.
When this file disagrees with them, they win.
The shape
- JSON-RPC, not component interfaces. Every language can read and write lines of JSON, and models know JSON-RPC from LSP and MCP. A call has a result, like an LLM tool call.
- A process per call. WASI has no threads, so processes give parallelism. A new version applies at the next call, and old processes retire after a grace period.
- Attached by default. A process does not outlive its caller by
accident. A long-running process must ask for
detach. - Services in one file. Only the core starts and restarts them, so upgrades and singletons need no logic in plugins.
- Files to inspect, calls to act. This keeps the core API small, and the agent's file tools are enough to explore the world.
- The core accepts only
.wasmfiles. It knows no source language.cargois one way to get a.wasmfile. - The world folder is the source of truth. Copy a plugin folder into another world, and it works there.
- The agent is a normal plugin. It can read and upgrade its own source, and change how it does inference.
Interfaces
- Generated imports and interface checks, without version numbers. A model that translates a description by hand makes errors. A generator does not. The check catches the most common error in a world that changes itself: a change to one plugin breaks another one.
- A small schema subset. With Draft 7 and a fixed subset, our own check can decide every case. It never has to guess.
- Docs are part of the interface. Other agents work from them, so the SDK requires a doc comment on each method, type, and field.
- The check covers calls, not state. Old and new versions share
/state. A plugin keeps its state format compatible, or migrates it inrpc.retired.
Minimal on purpose
The core does only what a plugin cannot do for itself. Everything else can be a plugin, or a change that the agent makes to itself.
- No permissions, no limits, no hiding. Any process can call, upgrade, or stop any plugin, read and write every file, and use the network. The checks protect against mistakes, not against plugins. Locks are advisory.
- Locks are in the core. A plugin cannot see that another process died, so it cannot release that process's lock. The core can.
- Our own VFS, in its own crate. It works on Linux and macOS, it makes
/proclive, and plugin-served files need it later. Mounts now, overlays later. - TCP endpoints for now. They need no host code. VFS pipes can replace them later behind the same SDK API.
- Simple scheduling for now. An epoch tick every 10 milliseconds keeps the core responsive and makes kills reliable.
- The agent is dumb. It has a short prompt, a few tools, an append-only history that it sends in full, and no compaction. When the context is too large, it stops sending the oldest turns, so it never gets stuck.
Small choices
- Plain POSIX files. Programs like
tempfile, git, and SQLite expect normal files. Atomic writes use a temporary file andrename. - Tests run under WASI.
cargo teston the host cannot find failures that occur only in WASI, for example a missing WASI function or a panic at start. The test runner uses the same Wasmtime and the same mounts as the core. - The SDK is not published.
cargopointssausage-plugin = "0.1"at its own copy, which it also shows in/plugins/cargo/sdk/.