108 lines
5.1 KiB
Markdown
108 lines
5.1 KiB
Markdown
# Spike results
|
||
|
||
Date: 2026-10-15. Host: Scout container (8 cores), Rust 1.99, Wasmtime 49.0.3,
|
||
`wstd` 0.6.8. All numbers come from `sausage run` with release builds.
|
||
|
||
## 1. Host, broker, routing, filesystem — works
|
||
|
||
- `sausage init` builds the agent and registers it. `sausage run` starts the
|
||
broker, `cargo`, the console, and every registered actor.
|
||
- Routing, replies (`reply_to`), publish to subscribers, and `undeliverable`
|
||
errors work.
|
||
- **Read-only:** a write to `/actor/<other>/public/...` fails with "Operation
|
||
not permitted". A write to `/actor/<self>/public/...` fails too. A write to
|
||
`/public/...` works.
|
||
- **Commit on close:** while `fs-a` holds `/public/y.txt` open, `fs-b` gets
|
||
"No such file". `fs-a` itself also gets it through a second descriptor. The
|
||
temporary file does not appear in listings. After the close, `fs-b` reads
|
||
the content.
|
||
- **Append:** `append` to an existing file copies the old content. Readers
|
||
see the old version until the close.
|
||
- **Crash with an open file:** the write is discarded, and the old version
|
||
stays.
|
||
- **Escapes:** `/data/../../../../etc/hostname` and
|
||
`/actor/../private/agent/data/config.json` fail.
|
||
- **Hard kill of the host:** after `kill`, no `.sausage-tmp-*` files remained,
|
||
and the next `sausage run` started every actor again.
|
||
|
||
## 2. Async stdin + HTTPS in one actor — works
|
||
|
||
`examples/fetch` (`wstd`) handles `ping` at once while a 2-second HTTPS
|
||
request runs. HTTPS and plain HTTP both work through `wasi:http`, and the host
|
||
does TLS. Bodies go to `/public/fetch/<n>`, and the reply contains the
|
||
global path.
|
||
|
||
One server (`http://neverssl.com/`) never answered. `curl` also hangs on it,
|
||
so the server is the cause. But a request without a timeout keeps its task
|
||
alive, and the actor then did not exit on `shutdown`. The broker killed it
|
||
after 5 s, as designed. Actors should set HTTP timeouts.
|
||
|
||
## 3. Upgrade cycle — works
|
||
|
||
- **No loss during a swap:** `ticker` sent 200 messages to `notes` at 50 ms
|
||
intervals. `notes` was upgraded from v1 to v2 during the stream. The swap
|
||
happened after tick 68. The v1 snapshot has ticks 1–68, and v2 has ticks
|
||
1–200, in order, with no gaps.
|
||
- **Parked message:** `{"type":"crash"}` crashed v1. The broker restarted v1
|
||
and kept the message. After the upgrade to v2 (which handles `crash`), the
|
||
broker delivered it again, and v2 answered `not_crashing`.
|
||
- **Crash loop:** an upgrade to `examples/crasher` passed the smoke test (it
|
||
crashes after 3 s). It crashed 3 times in about 10 s. The broker rolled back
|
||
to v2 with its data and sent `rolled_back` to the console.
|
||
- **Explicit rollback** v2 → v1 restored the v1 snapshot.
|
||
- **Rejections:** a compiler error comes back as `build_failed` with the
|
||
rendered diagnostics. A failing transcript rejects the upgrade, and the old
|
||
version keeps running. Invalid IDs and `..` paths are rejected.
|
||
|
||
## 4. Agent — works with both API shapes
|
||
|
||
- **`gpt-6-luna`** (OpenAI Responses) through the vendors proxy:
|
||
- Asked "which actors exist", it called `broker` `list` and answered in
|
||
about 4 s (2 inferences).
|
||
- Asked for a `counter` actor, it took these steps without help:
|
||
1. It wrote `Cargo.toml`, `src/main.rs`, and `tests/basic.jsonl`.
|
||
2. It built the crate with `cargo` (1.9 s).
|
||
3. It spawned the actor (the tests passed).
|
||
4. It sent increment, increment, get (1, 2, 2).
|
||
5. It read `/actor/counter/public/value.txt`.
|
||
- **Self-upgrade:** asked to change its own prompt, it took these steps:
|
||
1. It copied its source and changed only `prompt.md` (a 3-line diff).
|
||
2. It built the change and upgraded `agent`.
|
||
3. The upgrade stopped it in the middle of the turn. The new version
|
||
resumed the turn from `/data/turn.json` and answered with the new
|
||
signature.
|
||
- **`claude-haiku-4-5`** (Anthropic Messages): the same conversation
|
||
continued, with tool calls.
|
||
|
||
## 5. Measurements
|
||
|
||
| What | Result |
|
||
| --- | --- |
|
||
| Round trip actor → actor → actor | p50 88 µs, p99 172 µs |
|
||
| Sequential round trips | ~10,400 per second |
|
||
| Warm `cargo` build, small actor | 0.5–1.9 s |
|
||
| Warm `cargo` build, agent | 5.5 s |
|
||
| First build of the agent (`init`) | 17 s |
|
||
| Component compile, small actor | ~70 ms |
|
||
| Component compile, agent (870 KB) | ~290 ms |
|
||
| Host start, 8 actors compiled in parallel | ~0.8 s each |
|
||
| Host RSS with 8 running actors | ~117 MB |
|
||
| First release build of `sausage` | ~9 min (Wasmtime) |
|
||
|
||
Caveat: `~/.cargo/config.toml` in this container sets `kache` as the
|
||
`rustc-wrapper`. `kache` caches compiler outputs across projects, so the
|
||
first build of the agent (17 s) is probably faster than on a clean machine.
|
||
|
||
## Next steps I suggest
|
||
|
||
1. A `matrix` actor (WASM, `wstd`, long-polled `/sync`). The agent could write
|
||
it, with you checking the result.
|
||
2. Cache compiled components (`Component::serialize`, `.cwasm` files) to
|
||
remove the compile time at host start.
|
||
3. A network allow-list per actor, for example in a manifest in the source
|
||
folder.
|
||
4. A sandbox for builds (for example `bwrap`), because build scripts run
|
||
natively.
|
||
5. Context compaction for the agent's conversation.
|
||
6. A better console (line editing, history).
|