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:
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "bench"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
|
||||
[workspace]
|
||||
@@ -1,42 +0,0 @@
|
||||
//! Ping-pong with a target and report the round-trip times.
|
||||
use sausage_actor::{json, messages, reply, send};
|
||||
use std::time::Instant;
|
||||
|
||||
fn main() {
|
||||
let mut inbox = messages();
|
||||
while let Some(message) = inbox.next() {
|
||||
if message.kind() != "bench" {
|
||||
continue;
|
||||
}
|
||||
let target = message.body["target"].as_str().unwrap_or("echo");
|
||||
let n = message.body["n"].as_u64().unwrap_or(1000);
|
||||
if n == 0 {
|
||||
let _ = reply(&message, json!({"type": "error", "reason": "n must be greater than 0"}));
|
||||
continue;
|
||||
}
|
||||
let mut times = Vec::new();
|
||||
let started = Instant::now();
|
||||
for i in 0..n {
|
||||
let t = Instant::now();
|
||||
let id = send(target, json!({"type": "ping", "i": i}));
|
||||
loop {
|
||||
let Some(reply) = inbox.next() else { return };
|
||||
if reply.reply_to.as_deref() == Some(id.as_str()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
times.push(t.elapsed().as_micros() as u64);
|
||||
}
|
||||
let total = started.elapsed().as_secs_f64();
|
||||
times.sort();
|
||||
let p = |q: f64| times[((times.len() as f64 - 1.0) * q) as usize];
|
||||
let _ = reply(
|
||||
&message,
|
||||
json!({
|
||||
"type": "bench_result", "n": n, "total_s": total,
|
||||
"msgs_per_s": (n as f64 / total).round(),
|
||||
"p50_us": p(0.5), "p90_us": p(0.9), "p99_us": p(0.99), "max_us": p(1.0)
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"description": "reject zero benchmark samples",
|
||||
"steps": [
|
||||
{
|
||||
"send": {"type": "bench", "n": 0},
|
||||
"expect": {"type": "error", "reason": "n must be greater than 0"}
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "crasher"
|
||||
version = "0.1.0"
|
||||
@@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "crasher"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[workspace]
|
||||
@@ -1,6 +0,0 @@
|
||||
//! A bad version: it starts, waits 3 seconds (long enough for the smoke
|
||||
//! test), then panics. Used to test the automatic rollback.
|
||||
fn main() {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||
panic!("this version is broken");
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "echo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
|
||||
[workspace]
|
||||
@@ -1,7 +0,0 @@
|
||||
use sausage_actor::{json, messages, reply};
|
||||
|
||||
fn main() {
|
||||
for message in messages() {
|
||||
let _ = reply(&message, json!({"type": "echo", "got": message.body}));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "fetch"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk", features = ["async"] }
|
||||
wstd = "0.6"
|
||||
|
||||
[workspace]
|
||||
@@ -1,85 +0,0 @@
|
||||
//! Fetch URLs over HTTP while answering pings.
|
||||
use sausage_actor::{Inbox, Message, json, me, reply};
|
||||
use wstd::http::{Body, Client, Request};
|
||||
|
||||
fn next_fetch_id() -> Result<u64, String> {
|
||||
let previous = match std::fs::read_to_string("/data/fetch-sequence") {
|
||||
Ok(value) => value.parse::<u64>().map_err(|e| e.to_string())?,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => existing_fetch_id()?,
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
let next = previous
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| "fetch ID space exhausted".to_string())?;
|
||||
std::fs::write("/data/fetch-sequence", next.to_string()).map_err(|e| e.to_string())?;
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
fn existing_fetch_id() -> Result<u64, String> {
|
||||
let entries = match std::fs::read_dir("/public/fetch") {
|
||||
Ok(entries) => entries,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(0),
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
Ok(entries
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|entry| entry.file_name().to_str()?.parse().ok())
|
||||
.max()
|
||||
.unwrap_or(0))
|
||||
}
|
||||
|
||||
async fn fetch(n: u64, message: Message) {
|
||||
let url = message.body["url"].as_str().unwrap_or_default();
|
||||
let started = wstd::time::Instant::now();
|
||||
let result = async {
|
||||
let request = Request::get(url)
|
||||
.body(Body::empty())
|
||||
.map_err(|e| e.to_string())?;
|
||||
let response = Client::new()
|
||||
.send(request)
|
||||
.await
|
||||
.map_err(|e| format!("{e:?}"))?;
|
||||
let status = response.status().as_u16();
|
||||
let bytes = response
|
||||
.into_body()
|
||||
.contents()
|
||||
.await
|
||||
.map_err(|e| format!("{e:?}"))?
|
||||
.to_vec();
|
||||
std::fs::create_dir_all("/public/fetch").map_err(|e| e.to_string())?;
|
||||
let path = format!("/public/fetch/{n}");
|
||||
std::fs::write(path, &bytes).map_err(|e| e.to_string())?;
|
||||
Ok::<_, String>((status, bytes.len()))
|
||||
}
|
||||
.await;
|
||||
let ms = started.elapsed().as_millis();
|
||||
let body = match result {
|
||||
Ok((status, size)) => {
|
||||
let id = me();
|
||||
let actor = if id.is_empty() { "fetch" } else { &id };
|
||||
json!({"type": "fetched", "status": status, "bytes": size, "path": format!("/actor/{actor}/public/fetch/{n}"), "ms": ms})
|
||||
}
|
||||
Err(reason) => json!({"type": "error", "reason": reason, "ms": ms}),
|
||||
};
|
||||
let _ = reply(&message, body);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
wstd::runtime::block_on(async {
|
||||
let inbox = Inbox::start();
|
||||
while let Some(message) = inbox.recv().await {
|
||||
match message.kind() {
|
||||
"fetch" => match next_fetch_id() {
|
||||
Ok(n) => wstd::runtime::spawn(fetch(n, message)).detach(),
|
||||
Err(reason) => {
|
||||
let _ = reply(&message, json!({"type": "error", "reason": reason}));
|
||||
}
|
||||
}
|
||||
"ping" => {
|
||||
let _ = reply(&message, json!({"type": "pong"}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "fstest"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
|
||||
[workspace]
|
||||
@@ -1,38 +0,0 @@
|
||||
//! Filesystem probe. Keeps files open across messages.
|
||||
use sausage_actor::{Value, json, messages, reply};
|
||||
use std::{collections::HashMap, io::Write};
|
||||
|
||||
fn main() {
|
||||
let mut open = HashMap::<String, std::fs::File>::new();
|
||||
for msg in messages() {
|
||||
let b = &msg.body;
|
||||
let path = b["path"].as_str().unwrap_or_default();
|
||||
let r = |result: std::io::Result<Value>| result.unwrap_or_else(|e| json!({"type": "error", "error": e.to_string()}));
|
||||
let response = match msg.kind() {
|
||||
"write" => r(std::fs::write(path, b["text"].as_str().unwrap_or_default()).map(|_| json!({"type": "ok"}))),
|
||||
"open" => r((|| {
|
||||
let mut file = std::fs::File::create(path)?;
|
||||
file.write_all(b["text"].as_str().unwrap_or_default().as_bytes())?;
|
||||
open.insert(path.into(), file);
|
||||
Ok(json!({"type": "ok", "open": true}))
|
||||
})()),
|
||||
"append" => r((|| {
|
||||
let mut file = std::fs::OpenOptions::new().append(true).open(path)?;
|
||||
file.write_all(b["text"].as_str().unwrap_or_default().as_bytes())?;
|
||||
open.insert(path.into(), file);
|
||||
Ok(json!({"type": "ok", "open": true}))
|
||||
})()),
|
||||
"close" => json!({"type": "ok", "closed": open.remove(path).is_some()}),
|
||||
"read" => r(std::fs::read_to_string(path).map(|text| json!({"type": "content", "text": text}))),
|
||||
"list" => r(std::fs::read_dir(path).map(|rd| {
|
||||
let mut names: Vec<String> = rd.filter_map(|e| e.ok()).map(|e| e.file_name().to_string_lossy().to_string()).collect();
|
||||
names.sort();
|
||||
json!({"type": "entries", "names": names})
|
||||
})),
|
||||
"remove" => r(std::fs::remove_file(path).map(|_| json!({"type": "ok"}))),
|
||||
"crash" => panic!("asked to crash"),
|
||||
_ => json!({"type": "error", "error": "unknown message"}),
|
||||
};
|
||||
let _ = reply(&msg, response);
|
||||
}
|
||||
}
|
||||
Generated
+10
@@ -18,6 +18,7 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sausage-actor",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
@@ -39,6 +40,14 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-actor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
@@ -46,6 +55,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Generated
+115
@@ -0,0 +1,115 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "notes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sausage-actor",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sausage-actor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "ticker"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = { version = "0.1", path = "../../sdk" }
|
||||
|
||||
[workspace]
|
||||
@@ -1,13 +0,0 @@
|
||||
//! Send 200 notes to `notes`, 50 ms apart, then stay alive.
|
||||
use sausage_actor::{json, send};
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
for n in 1..=200 {
|
||||
let _ = send("notes", json!({"type": "add", "text": format!("tick {n}")}));
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_secs(3600));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user