Compare commits
1
Commits
world-live
...
8a533f0158
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a533f0158 |
@@ -11,31 +11,43 @@ change its own code.
|
||||
## Start
|
||||
|
||||
You need [Nix](https://nixos.org/download) with flakes, and an API token for
|
||||
an LLM (OpenAI Responses or Anthropic Messages API).
|
||||
an LLM (OpenAI Responses or Anthropic Messages API). No clone needed:
|
||||
|
||||
```sh
|
||||
git clone https://gitea.surma.technology/surma/sausage && cd sausage
|
||||
nix run -- init # creates ./world and builds the agent (a few minutes)
|
||||
nix run -- run # starts the world and a console
|
||||
nix run git+https://gitea.surma.technology/surma/sausage
|
||||
```
|
||||
|
||||
This creates a world in `./world` the first time (it builds the agent, which
|
||||
takes a few minutes) and then starts it with a console. Later runs start the
|
||||
existing world. The two steps also exist on their own:
|
||||
|
||||
```sh
|
||||
nix run git+https://gitea.surma.technology/surma/sausage -- init # create ./world
|
||||
nix run git+https://gitea.surma.technology/surma/sausage -- run # start it
|
||||
```
|
||||
|
||||
`init` asks for the API shape (`openai-responses` or `anthropic-messages`),
|
||||
the base URL, the model, and the token. It reads `SAUSAGE_API`,
|
||||
`SAUSAGE_BASE_URL`, `SAUSAGE_MODEL`, and `SAUSAGE_API_TOKEN` first. The
|
||||
values go to `world/actor/agent/data/config.json`. Both commands take another
|
||||
folder as an argument, for example `nix run -- run my-world`.
|
||||
values go to `world/actor/agent/data/config.json`. `init` and `run` take
|
||||
another folder as an argument, for example `-- run my-world`.
|
||||
|
||||
To keep a world on the newest version, run it in a loop:
|
||||
|
||||
```sh
|
||||
nix run .#loop -- my-world
|
||||
nix run git+https://gitea.surma.technology/surma/sausage#loop # ./world
|
||||
nix run git+https://gitea.surma.technology/surma/sausage#loop -- my-world # an existing world
|
||||
```
|
||||
|
||||
The loop runs `nix run --refresh git+https://gitea.surma.technology/surma/sausage -- run my-world`.
|
||||
The loop runs the newest `sausage` from that URL (`nix run --refresh`).
|
||||
When the host crashes or an actor sends `@broker {"type":"exit"}`, the loop
|
||||
fetches the newest commit and starts it again. `/quit` ends the loop. Set
|
||||
`SAUSAGE_FLAKE` to run another flake URL instead, for example your fork.
|
||||
|
||||
To work on Sausage itself, clone it
|
||||
(`git clone https://gitea.surma.technology/surma/sausage`) and use `nix run`,
|
||||
`nix run .#loop` and `nix develop` in the checkout.
|
||||
|
||||
In the console:
|
||||
|
||||
```text
|
||||
|
||||
+13
-3
@@ -12,11 +12,13 @@ use sausage::{Host, cargo_actor, console};
|
||||
/// The source of the agent actor. `init` builds it like any other actor.
|
||||
static AGENT_SOURCE: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../agent");
|
||||
|
||||
// Without a command, `sausage` runs the world in `./world` and creates it
|
||||
// first (like `init`) if it does not exist yet.
|
||||
#[derive(Parser)]
|
||||
#[command(version, about)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Cmd,
|
||||
command: Option<Cmd>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -112,7 +114,15 @@ async fn run(dir: PathBuf) -> Result<()> {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
match Cli::parse().command {
|
||||
Cmd::Init { dir } => init(dir).await,
|
||||
Cmd::Run { dir } => run(dir).await,
|
||||
Some(Cmd::Init { dir }) => init(dir).await,
|
||||
Some(Cmd::Run { dir }) => run(dir).await,
|
||||
None => {
|
||||
let dir = PathBuf::from("world");
|
||||
if !dir.join("actor").is_dir() {
|
||||
println!("No world in ./world yet, creating it (same as `sausage init`).");
|
||||
init(dir.clone()).await?;
|
||||
}
|
||||
run(dir).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//! Tests of the `sausage` command line.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn empty_dir(label: &str) -> PathBuf {
|
||||
let nonce = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
|
||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("target/test-cli")
|
||||
.join(format!("{label}-{}-{nonce}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
/// Bare `sausage` (as in `nix run <flake>`) without a world starts `init`
|
||||
/// for ./world instead of printing a usage error.
|
||||
#[test]
|
||||
fn bare_command_without_a_world_starts_init() {
|
||||
let dir = empty_dir("bare");
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_sausage"))
|
||||
.current_dir(&dir)
|
||||
.env_remove("SAUSAGE_API")
|
||||
.env_remove("SAUSAGE_BASE_URL")
|
||||
.env_remove("SAUSAGE_MODEL")
|
||||
.env_remove("SAUSAGE_API_TOKEN")
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
.unwrap();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
// With no answers on stdin, init stops at the first required value.
|
||||
assert!(!output.status.success(), "stdout:\n{stdout}\nstderr:\n{stderr}");
|
||||
assert!(stdout.contains("No world in ./world yet"), "stdout:\n{stdout}\nstderr:\n{stderr}");
|
||||
assert!(stderr.contains("SAUSAGE_BASE_URL"), "stdout:\n{stdout}\nstderr:\n{stderr}");
|
||||
assert!(!stderr.contains("Usage:"), "stderr:\n{stderr}");
|
||||
// It asks before it creates anything.
|
||||
assert!(!dir.join("world").exists());
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
meta.mainProgram = "sausage";
|
||||
};
|
||||
# `nix run .#loop -- <world>` runs the newest `sausage run <world>`
|
||||
# (without a folder: bare `sausage`, i.e. ./world, created if needed)
|
||||
# from the public repo, again and again: after a crash or a broker
|
||||
# `exit` request it refreshes the flake and starts the host again,
|
||||
# so the world updates itself. `/quit` (exit code 0) ends the loop.
|
||||
@@ -44,9 +45,10 @@
|
||||
repoUrl = "git+https://gitea.surma.technology/surma/sausage";
|
||||
loop = pkgs.writeShellScriptBin "sausage-loop" ''
|
||||
url="''${SAUSAGE_FLAKE:-${repoUrl}}"
|
||||
if [ $# -eq 0 ]; then set -- world; fi
|
||||
# No folder: bare `sausage` (creates ./world first if needed).
|
||||
if [ $# -gt 0 ]; then set -- run "$@"; fi
|
||||
while true; do
|
||||
nix run --refresh "$url#default" -- run "$@"
|
||||
nix run --refresh "$url#default" -- "$@"
|
||||
code=$?
|
||||
if [ "$code" -eq 0 ]; then exit 0; fi
|
||||
echo "sausage-loop: sausage exited with $code, starting the newest version in 2 s …" >&2
|
||||
|
||||
Reference in New Issue
Block a user