PROTOCOL.md, DECISIONS.md, README.md, AGENTS.md for plugins, processes, and JSON-RPC
This commit is contained in:
+56
-40
@@ -1,55 +1,71 @@
|
||||
# Decisions
|
||||
|
||||
Why Sausage is the way it is. `PROTOCOL.md` and the code own the details;
|
||||
when this file disagrees with them, they win.
|
||||
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
|
||||
|
||||
- **Actors are WASI commands, not components with interfaces.** stdin,
|
||||
stdout, files, HTTP, and sockets are enough, and every language can do
|
||||
them.
|
||||
- **Messages are JSON lines through a broker, fire and forget.** Like
|
||||
Erlang: no acknowledgments, no request/response. A reply is a message with
|
||||
`reply_to`. Sausage is not a reliable message bus.
|
||||
- **The broker accepts only `.wasm` files.** It knows no source language.
|
||||
`cargo` is one compiler actor; others can add more languages.
|
||||
- **The world folder is the source of truth.** Everything about an actor is
|
||||
in `actor/<id>/`. Copy a folder into another world, and it runs there.
|
||||
- **The agent is a normal WASM actor.** It can read and upgrade its own
|
||||
source, and change how it does inference.
|
||||
- **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 `.wasm` files.** It knows no source language.
|
||||
`cargo` is one way to get a `.wasm` file.
|
||||
- **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 in
|
||||
`rpc.retired`.
|
||||
|
||||
## Minimal on purpose
|
||||
|
||||
The harness does only what it needs to keep going. Everything else can be an
|
||||
actor, or a change that the agent makes to itself.
|
||||
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.
|
||||
|
||||
- **The host is not self-modifiable; actors are.** So the host keeps only
|
||||
what an actor cannot do for itself: routing, running instances, versions,
|
||||
tests before an install, and the rollback of a version that crash-loops.
|
||||
Without that rollback, a broken self-upgrade would end the agent.
|
||||
- **No permissions, no limits, no hiding.** Any actor can message, upgrade,
|
||||
or stop any actor, read every folder, and use the network. People who want rules
|
||||
can build them.
|
||||
- **No re-delivery and no state snapshots.** A crash loses the message, and
|
||||
all versions of an actor share `data/` and `public/`. A rollback restores
|
||||
code, not data.
|
||||
- **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
|
||||
`/proc` live, 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.** An earlier version staged every write until close.
|
||||
That broke `tempfile`, git, and SQLite, which expect normal files. Atomic
|
||||
publishing is the usual temporary file + `rename`.
|
||||
- **Test first, then switch.** A new version is tested where it lies. Only
|
||||
a version that passes goes to `versions/`, and the old one runs until
|
||||
then. `versions/` keeps every version that was installed, so a rollback
|
||||
can always go back. A file that fails stays where it was put.
|
||||
- **Tests are JSON transcripts** next to the source. They run on a copy of
|
||||
the actor's state, without network, so a test cannot send real requests.
|
||||
- **`broker` and `cargo` never answer a reply,** so no two actors answer
|
||||
each other forever. The agent starts a turn only for text that is not a
|
||||
reply, for the same reason.
|
||||
- **The SDK is not published.** `cargo` points `sausage-actor = "0.1"` at its
|
||||
own copy, which it also shows in `/actor/cargo/public/sdk/`.
|
||||
- **Plain POSIX files.** Programs like `tempfile`, git, and SQLite expect
|
||||
normal files. Atomic writes use a temporary file and `rename`.
|
||||
- **Tests run under WASI.** `cargo test` on 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.** `cargo` points `sausage-plugin = "0.1"` at
|
||||
its own copy, which it also shows in `/plugins/cargo/sdk/`.
|
||||
|
||||
Reference in New Issue
Block a user