- The serve loop ends the process after EOF or exit_after_response,
also with background tasks or listeners.
- One schema generator per direction for the whole document, so types
with the same name get distinct names.
- The agent gives the complete error (code, message, data) to the
model, writes SKIP only after a successful request, caps tool output
at 64 KiB, writes each history line in one write, and skips a damaged
line.
- Endpoints skip blank and non-RPC lines. client_rs does not panic.
- #[must_use] on builders and the lock guard; #[schemars] attributes
and duplicate hooks are compile errors; raw identifiers; -32000 for
local SDK errors; x-sausage-params only for methods with params.
Activation path (review T6R): a broken plugin or a stray file in
/plugins blocks no activation; candidates replace their own entries;
discovery has a 10 s deadline and its process is stopped after the
answer; an unchanged hash keeps plugin.prev.wasm; activations run in
their own task; the watcher is not recursive; no duplicate services;
rpc.retired waits for live processes of its version; reads and hashes
on blocking threads.
Guest I/O and per-call paths: a stdin write error is EOF, stdout and
stderr are drained before the status; stderr is lossy UTF-8 and goes
to the journal from its own reader; compiled components are freed;
shutdown starts no new processes; lock queues drop stopping processes;
compile before the admission gate; CommandPre per component;
core.unlock in order; null errors are successes; the journal thread
survives SQLite errors.
sausage init builds the agent with cargo.build; sausage without a
command creates ./world. The console reads plain lines without a
terminal. PROTOCOL.md and DECISIONS.md describe the decided rules. The
flake tests the whole workspace and checks the packaged cargo.build.
Holds the history lock for a turn, 600 s grace period, interim texts as
core.notify. Tests with a fake LLM in the real core. The SDK tests of
/proc, /self, /run, and detached notifications now run.
- cargo.build (native): one source copy, tests under WASI, release
build to /state/cargo/builds/<n>/, process groups killed on cancel.
The host writes the SDK copy to /plugins/cargo/sdk/ at start.
- sausage test-runner: one instance per test, in parallel, with its own
mount table (empty world, package folder at its path); answers
core.lock, core.unlock, core.notify, core.log.
- The console: rustyline, notifications while typing, ls/cat through the
process view, methods, ps, log, activate, revert, stop, kill.
- core.notify on stdout is handled in order, before the response.
- /proc is a core layer: core/ (PROTOCOL.md, graph.json, graph.dot,
locks.json, plugins/<name>/discover.json and client.rs) and one folder
per process (info.json, log, and its /self files).
- Locks are released only after the task ended, the store dropped, and
in-flight file operations returned.
- core.activate and core.revert with copies, discovery, compatibility
checks of imports and consumers, groups, force. An admission gate
makes the snapshot of old processes complete.
- Graph of discover documents by hash, refreshed by a file watcher.
- Upgrades: grace periods (x-sausage-grace), stop, service restarts,
rpc.retired.
- SDK plugins in the real core (cli/tests/sdk.rs).
The Nix package, the dev shell, and the test check set CC_wasm32_wasip2
and AR_wasm32_wasip2 to the clang of pkgsCross.wasi32 (wasi-libc), so
build scripts with the cc crate work, for example rusqlite with bundled
SQLite. The runtime-toolchain check links C into a probe with the
packaged host and an empty PATH.
A stopped actor gets the new version installed, keeps its mailbox and does
not run main until the next start. Running actors still restart on the new
version. The upgraded and rolled_back answers say whether the actor runs.
The host watches the whole world folder recursively with the notify crate
(any writer counts, .sausage-tmp-* skipped, ~100 ms coalescing per path).
A new or changed actor/<id>/actor.wasm (or new folder actor/<id>) is
booted as at start once quiet: new = spawn (tested first), changed =
upgrade. The broker `scan` request is gone; the boot pass stays, plus a
full pass when the OS watcher overflows.
New broker requests watch/unwatch {path} (saved in actor.json `watches`)
deliver fs_changed {path, op: write|create|remove|rename, to?} from `fs`,
not routed, dropped when the mailbox is full; overflow sends every watcher
one fs_changed {path:"/actor", op:"rescan"}.
Card #91 (multi-host step 1).
- cli: the subcommand is optional; without one, sausage runs ./world and
runs init first when ./world is not a world yet (test: cli/tests/cli.rs).
So `nix run git+https://gitea.surma.technology/surma/sausage` works.
- flake: .#loop without a folder runs bare sausage.
- README: mention the bare command and the bare loop.
{"type":"remove","actor"} shuts the actor down normally, drops its
mailbox, supervisor and actor.json entry (so also its subscriptions and
tap), and deletes /actor/<id>/. Native actors cannot be removed.
Re-applied from card-1-broker-remove (3c91747) onto card-2-broker-tap.
Tapped copies {type:tapped, message:{id,from,to,reply_to,body}, ts} are
delivered directly (never tapped themselves), dropped when the tapper's
mailbox is full, and the tap is saved in actor.json like subscriptions.
File operations under /actor/<id>/fs/ become messages from `fs` to <id>
(fs_read, fs_write, fs_mkdir, fs_remove, fs_rename) with a 10 s timeout.
vfs.rs wraps the wasi:filesystem implementation of wasmtime-wasi.
- broker: `{"type":"exit"}` answers `exiting`; `sausage run` then stops all
actors and exits with code 75.
- flake: apps `loop` / `loop-internal` run `nix run --refresh git+ssh://.../surma/sausage -- run <world>`
in a loop; exit code 0 (/quit) ends the loop, anything else restarts.
- PROTOCOL.md, README.md, test in cli/tests/host.rs.
Not compiled: written by the Sausage agent, which cannot build the native host.
Staging every write until close broke tempfile, git, and SQLite, which
expect normal files. Actors that need an atomic publish write a temporary
file and rename it; the SDK has write_atomic for that.
Host:
- test first: a new version is tested where it lies; only an installed
version goes to versions/, and instances run from there
- data/ and public/ are shared by all versions; no snapshots; rollback
restores code only
- no re-delivery, status.json, list, remove/archive, smoke test, data/
hiding, or stdin timeout; rollback needs a version
- supervisor.rs split out; one place for installing a version
- init only writes the agent folder; run installs it like any actor
- console: @actor, text to the agent, /help, /quit
Agent:
- every text goes out as a broadcast; 5-line prompt; 5 tools that describe
themselves; turn.json only marks an active turn
Docs: PROTOCOL.md, README.md, DECISIONS.md, AGENTS.md rewritten.
Examples: only notes and notes-v2.
- one live instance per actor: check after the lock; dropping an instance kills it
- state snapshots keep target/ and .git/; only source copies skip them
- one shutdown deadline; output during shutdown is still routed
- deliveries route stdout meanwhile; a stuck stdin kills the instance
(no partial lines) and the sender gets undeliverable; control requests
are not blocked by a stuck delivery
- Host::stop aborts running broker and cargo requests first
- compile a version before installing it; roll back if it does not start
- unique archive names; a failed archive keeps the actor manageable
- commit on close: truncate without create; directory renames
- candidates see SAUSAGE_VERSION and the existing state of a new actor
- the host publishes PROTOCOL.md at /actor/broker/public/
- actor/<id>/ holds actor.wasm, actor.json, source/, public/, data/, versions/
- the broker boots by scanning actor/; a changed actor.wasm is an upgrade,
a rejected one stays in versions/ with a reason and the files come back
- /actor hides every data/ folder
- subscriptions live in the subscriber's actor.json
- cargo installs the SDK and points sausage-actor at it; no build limits
- PROTOCOL.md
Imported from the spike branch, with:
- a library crate (Host API) for the CLI and the tests
- JSON test scenarios for candidates
- commit on close only for new or truncated files
- a Nix flake: nix run -- init, nix run -- run