The run behind the terminal on salvor.run, checked in so you can produce the same events on your own disk, in your own SQLite file.
It is deliberately the smallest agent that still shows the whole behaviour: one model call decides to record a claim, one tool call records it, one more model call closes the run out. Ten events, exactly one write.
# the CLI, however you like it:
npm install -g @salvor-run/cli # or: cargo install salvor-cli
# or, from a checkout of this repository:
cargo build
salvor run --fixture examples/heroThe commands on this page call the binary as salvor, which is right once one
of the install routes above has put it on your PATH. cargo build alone
does not do that: from a checkout with no global install, run
./target/debug/salvor in place of every salvor below. See
examples/README.md for how the other examples name this
same distinction with a SALVOR_BIN variable.
No API key and no network. --fixture reads this directory's agent.toml and
input.json, starts the recorded model in model.json on a local port, and
points the agent at it.
0 RunStarted agent sha256:… input {"item":"ss-waratah"}
1 NowObserved 2026-07-27 20:55:44Z
2 ModelCallRequested request sha256:…
3 ModelCallCompleted usage in 24 out 41
4 ToolCallRequested save_claim [Write] input {"item":"ss-waratah"}
5 ToolCallCompleted output {"content":[{"text":"claim recorded: ss-waratah",…
6 NowObserved 2026-07-27 20:55:44Z
7 ModelCallRequested request sha256:…
8 ModelCallCompleted usage in 96 out 38
9 RunCompleted output "1 claim recorded for ss-waratah."
Read it back at any time with salvor history <run-id>.
Two things to note. Event 4 records the tool call before the tool runs,
which is what the next section depends on. Events 1 and 6 are the driver's
clock observations, one per loop iteration: a replay has to see the same
now() the first run recorded, so the reading is logged rather than read from
the ambient clock.
save_claim appends one line to a claims file per real execution, so the file
itself is the proof. Count the lines before and after.
export SALVOR_HERO_CLAIMS=/tmp/claims.txt
rm -f /tmp/claims.txt
salvor run --fixture examples/hero &
kill -9 $!
wc -l /tmp/claims.txt # 0 or 1, depending where it died
salvor resume <run-id> --agent examples/hero/agent.toml
wc -l /tmp/claims.txt # still 1. never 2.A resume replays the committed events as state and re-executes none of them. If the kill landed between event 4 and event 5, where the write was recorded but its result never was, the resume refuses rather than guessing, because the log cannot tell it whether that write landed:
Run … needs reconciliation and cannot be resumed automatically.
A write tool call was recorded but never completed, so it may or may not have taken effect.
The recorded intent:
seq: 4
tool: save_claim
effect: Write
…
There is no --force. Both honest paths start with you checking
/tmp/claims.txt yourself. If the write landed, record what the tool returned
so replay never re-runs it:
salvor resolve <run-id> --output '{"content":[{"type":"text","text":"claim recorded: ss-waratah"}]}'If it did not land and still needs to happen, perform it yourself first, then record the result the same way. There is no automatic retry for a write.
This is the same rule the release gate asserts at every one of the run's event
boundaries, in
crates/salvor-runtime/tests/release_gate.rs.
The same agent.toml drives a real run. Leave SALVOR_HERO_BASE_URL unset so
the endpoint is the public one, and export a key:
export ANTHROPIC_API_KEY=sk-ant-...
salvor run --agent examples/hero/agent.toml --input @examples/hero/input.jsonThe model will phrase its confirmation differently, and the token counts will not match the recording. The event sequence, the commit-before-act ordering and the write-ahead rule are all the same runtime doing the same thing.
| File | What it is |
|---|---|
agent.toml |
The agent. Two modes, switched by SALVOR_HERO_BASE_URL. |
input.json |
The run input. |
model.json |
The recorded model conversation, keyed by message count so it is replay-safe across a kill. |