Spike: console /await, DECISIONS.md, SPIKE.md

This commit is contained in:
2026-10-15 05:06:24 +01:00
parent 0be152dda9
commit 56d02befc8
4 changed files with 293 additions and 1 deletions
+107
View File
@@ -0,0 +1,107 @@
# 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).