Trim the harness to what it needs to keep going
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.
This commit is contained in:
+57
-48
@@ -1,7 +1,7 @@
|
||||
# The Sausage protocol
|
||||
|
||||
This document is the contract between the host and the actors. An actor in
|
||||
any language that follows it works in Sausage. The host publishes this file at
|
||||
This is the contract between the host and the actors. An actor in any
|
||||
language that follows it works in Sausage. The host publishes this file at
|
||||
`/actor/broker/public/PROTOCOL.md`, so every actor can read it.
|
||||
|
||||
## Actors
|
||||
@@ -34,19 +34,21 @@ A line on **stdout**:
|
||||
```
|
||||
|
||||
- `body` is required. By convention it is an object with a `"type"`.
|
||||
Text for people is `{"type": "text", "text": "..."}`.
|
||||
- `to` is the recipient. Without `to`, the broker publishes the message to
|
||||
every subscriber of the sender.
|
||||
- `id` is optional. If the actor sets it, the message ID is `"<sender>/<id>"`.
|
||||
Otherwise, the broker assigns `m-<n>`. The broker always sets `from`.
|
||||
- `reply_to` marks a reply. There is no request/response: a reply is a new
|
||||
message whose `reply_to` is the ID of the message it answers.
|
||||
- Delivery is at most once. Each actor has a mailbox of 1,000 messages. The
|
||||
mailbox keeps messages while the actor restarts or is upgraded.
|
||||
- If the broker cannot deliver a message, the sender gets
|
||||
- `reply_to` marks a reply: its value is the ID of the message it answers.
|
||||
There is no request/response. A reply is a new message.
|
||||
- **Never answer a reply,** or two actors can answer each other forever.
|
||||
`broker` and `cargo` ignore messages that have a `reply_to`.
|
||||
- Messages are fire and forget: delivery is at most once. Each actor has a
|
||||
mailbox of 1,000 messages, which keeps messages while the actor restarts
|
||||
or is upgraded. If the broker cannot deliver a message, the sender gets
|
||||
`{"type": "undeliverable", "reason": "...", "to": "..."}` from `broker`.
|
||||
- An actor must keep reading stdin. If a message waits 30 seconds for the
|
||||
actor to read it, the broker kills and restarts the actor, and the sender
|
||||
gets `undeliverable`.
|
||||
- An actor must keep reading stdin. If the broker must stop or upgrade an
|
||||
actor while a message waits for it to read stdin, it kills the actor.
|
||||
|
||||
Before the broker stops an instance, it sends
|
||||
`{"id": "broker/shutdown", "from": "broker", "body": {"type": "shutdown"}}`
|
||||
@@ -59,13 +61,13 @@ Each instance sees three folders:
|
||||
|
||||
| Path | Access | Contents |
|
||||
| --- | --- | --- |
|
||||
| `/data` | read-write | private state of the actor |
|
||||
| `/public` | read-write | files that the actor publishes |
|
||||
| `/data` | read-write | the actor's state |
|
||||
| `/public` | read-write | the files that the actor publishes |
|
||||
| `/actor` | read-only | the `actor/` folder of the world |
|
||||
|
||||
`/actor/<id>/` contains `actor.wasm`, `actor.json`, `source/`, `public/`,
|
||||
`status.json`, `stderr.log`, and `versions/<n>/` of every actor. The `data/`
|
||||
folders (and the data snapshots in `versions/`) are hidden.
|
||||
`/actor/<id>/` holds everything about an actor: `actor.wasm`, `actor.json`
|
||||
(installed version, running, subscriptions), `source/`, `public/`, `data/`,
|
||||
`stderr.log`, and `versions/<n>/`. `ls /actor` lists all actors.
|
||||
|
||||
A new or truncated file becomes visible when the writer closes it. Before
|
||||
that, readers see the old file or no file. If the instance crashes, the write
|
||||
@@ -77,31 +79,34 @@ message.
|
||||
|
||||
## The broker
|
||||
|
||||
Requests to `broker` (the reply has the same `reply_to`):
|
||||
Requests to `broker`. The answer is a message whose `reply_to` is the ID of
|
||||
the request: the request's `type` in past tense (`spawned`, `upgraded`,
|
||||
`rolled_back`, `stopped`, `started`, `subscribed`, `unsubscribed`,
|
||||
`scanned`), or `{"type": "error", "reason": "..."}`.
|
||||
|
||||
| `type` | Fields | Effect |
|
||||
| --- | --- | --- |
|
||||
| `spawn` | `actor`, `wasm`, `source`?, `built_by`? | test, then start a new actor |
|
||||
| `upgrade` | `actor`, `wasm`, `source`?, `built_by`? | test, then swap the version |
|
||||
| `rollback` | `actor`, `version`? | go back to an older version and its data |
|
||||
| `rollback` | `actor`, `version` | go back to an older version |
|
||||
| `stop`, `start` | `actor` | stop or start an actor |
|
||||
| `remove` | `actor` | move the actor's folder to `archive/` |
|
||||
| `subscribe`, `unsubscribe` | `actor` | receive (or not) what `actor` publishes |
|
||||
| `scan` | | boot new or changed `actor.wasm` files |
|
||||
| `list` | | all actors and their state |
|
||||
|
||||
Paths: `/actor/...`, `/public/...` (the sender's), or `/data/...` (the
|
||||
sender's).
|
||||
Paths are `/actor/...`, or the sender's `/public/...` or `/data/...`.
|
||||
|
||||
**Versions.** `versions/<n>/` keeps every version that the broker saw:
|
||||
`actor.wasm`, `source/`, and `meta.json`. A version that the broker refused
|
||||
has `"rejected"` in its `meta.json`.
|
||||
**Versions.** A new version (from `spawn`, `upgrade`, or a changed
|
||||
`actor.wasm` on disk) is tested first, while the old version keeps running.
|
||||
Only when its tests pass does the broker copy it to `versions/<n>/`
|
||||
(`actor.wasm`, `source/`, `meta.json`) and switch to it. So `versions/` holds
|
||||
exactly the versions that were installed, and instances always run from
|
||||
there. If the tests fail, nothing changes, and the sender gets an `error`
|
||||
with the reason. All versions share the actor's `data/` and `public/`: a
|
||||
new version must read the data of the old one.
|
||||
|
||||
**Tests.** Before a spawn or an upgrade, the broker runs the candidate on a
|
||||
copy of the actor's `/data` and `/public` (empty for a new actor), without
|
||||
HTTP. The candidate sees its future `SAUSAGE_VERSION` and
|
||||
`SAUSAGE_CANDIDATE=1`. The tests are JSON
|
||||
files in `source/tests/*.json`:
|
||||
**Tests.** The broker runs the candidate on a copy of the actor's `data/`
|
||||
and `public/`, without HTTP. The tests are JSON files in
|
||||
`<source>/tests/*.json`, one scenario per file:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -113,25 +118,26 @@ files in `source/tests/*.json`:
|
||||
}
|
||||
```
|
||||
|
||||
An `expect` entry matches when it is a subset of the reply body. A step can
|
||||
also have `from` (the sender, default `test`) and `to` (the expected
|
||||
recipient). Each file runs in a fresh instance. Without test files, the
|
||||
candidate must stay up for 2 seconds.
|
||||
Each step sends `send` (from `from`, default `test`), then reads the next
|
||||
line that the candidate writes for each `expect` entry, in order, within 10
|
||||
seconds. An entry matches when it is a subset of the message body. With
|
||||
`to`, the message must also go to that recipient. Each file runs in a fresh
|
||||
instance. A version without test files passes.
|
||||
|
||||
**Swap.** The broker holds the mailbox, stops the old instance, saves a
|
||||
snapshot of `/data` and `/public` in the old version's folder, installs the new
|
||||
files, and starts the new instance.
|
||||
**Swap.** The broker holds the mailbox, stops the old instance, makes the
|
||||
top-level `actor.wasm` and `source/` a copy of the new version, and starts
|
||||
it. A rollback does the same with the older version.
|
||||
|
||||
**Crashes.** The broker restarts a crashed actor (0.5 s, then doubling, at
|
||||
most 30 s). It keeps the message that the actor got just before the crash,
|
||||
and delivers it again after the next upgrade. A new version that crashes 3
|
||||
times within 30 seconds after an upgrade goes back to the previous version,
|
||||
with its data. The actor that asked for the upgrade gets `rolled_back`.
|
||||
**Crashes.** The broker restarts a crashed actor after 0.5 s, then doubles
|
||||
the delay up to 30 s. A new version that crashes 3 times within 30 seconds
|
||||
after an upgrade goes back to the previous version. The actor that asked for
|
||||
the upgrade gets `{"type": "rolled_back", "from", "to", "reason", "last_exit"}`.
|
||||
|
||||
**The folder is the truth.** At start, and on `scan`, the broker boots every
|
||||
`actor/<id>/actor.wasm`. If the file differs from the installed version, the
|
||||
broker copies it to `versions/<n>/` and upgrades to it. If the upgrade fails,
|
||||
the installed files come back.
|
||||
`actor/<id>/actor.wasm`. If the file differs from the installed version, it
|
||||
is a new version, tested with the tests in `actor/<id>/source/`. If its
|
||||
tests fail, the file stays where it is, the reason goes to `stderr.log`, and
|
||||
the next scan tries again.
|
||||
|
||||
## The `cargo` actor
|
||||
|
||||
@@ -139,7 +145,10 @@ the installed files come back.
|
||||
{"type": "build", "source": "/actor/agent/public/drafts/notes"}
|
||||
```
|
||||
|
||||
The reply is `{"type": "built", "wasm": "/actor/cargo/public/builds/<n>/<name>.wasm", ...}`
|
||||
or `{"type": "build_failed", "errors": "..."}`. A Rust actor depends on the SDK
|
||||
with `sausage-actor = "0.1"`. `cargo` points that dependency at the SDK of the
|
||||
host.
|
||||
The reply is `{"type": "built", "wasm": "/actor/cargo/public/builds/<n>/<name>.wasm", "built_by": "cargo", ...}`
|
||||
or `{"type": "build_failed", "errors": "..."}`. Pass `wasm`, `source`, and
|
||||
`built_by` on to `spawn` or `upgrade`.
|
||||
|
||||
A Rust actor depends on the SDK with `sausage-actor = "0.1"`. `cargo` points
|
||||
that dependency at the SDK of the host, which it publishes at
|
||||
`/actor/cargo/public/sdk/`.
|
||||
|
||||
Reference in New Issue
Block a user