Skip to content

Kill it. Resume it.Nothing happens twice.

A durable execution runtime for AI agents, in Rust. Every event is written before the runtime acts on it, so a crashed run comes back and finishes from exactly where it stopped.

npm install -g @salvor-run/cli

joseym/salvor on GitHub
agent.tomlagent=d2673e70

Edit it. The next run checkpoints this file and reports the new agent hash. A tool pinned write is never replayed blind: a crash between the record and the result parks the run for a human.

salvor@localfold: salvor-replay (wasm) · model: recorded · v?writes: 0
salvor runtime=loading log=indexeddb fixture=examples/hero/
the runtime loads on first click, or when this pane scrolls into view.

Hard-refresh this page mid-run. The log is in your browser's IndexedDB. Nothing replays.

The whole agent

hello-agent.toml
model = "claude-opus-4-8"
system_prompt = "Answer in one or two sentences."

That's the whole agent.

Two lines is a durable run. Every model call is written before it is made and again when it lands, so the log is the run. Add [[mcp_servers]] when it needs tools, and pin effect_overrides on any tool that touches something outside the process.

Bring it up

Four ways in. Pick the one your build already uses.

npm install -g @salvor-run/cli

prebuilt binary, no Rust toolchain

These four install the CLI binary. The Python and TypeScript clients for the HTTP API are under Pick a door, below.

Pick a door

Same runtime, same log, three surfaces.

Rust library

In-process. The run future is the agent, and it owns its own recovery.

let agent = Agent::builder()
    .model(config, "claude-opus-4-8")
    .system_prompt("Answer in one or two sentences.")
    .build()?;

// start drives the loop; recover() continues a crashed run from the log
match Runtime::new(store).start(&agent, json!({"question": "..."})).await? {
    RunOutcome::Completed { output, .. } => println!("completed: {output}"),
    RunOutcome::Parked { reason, .. } => println!("parked: {reason:?}"),
}

HTTP and SSE

Out of process. Post a run, then read its event stream as it commits. Thin published clients exist for both; neither holds any durability logic of its own.

pip install salvor · npm install @salvor-run/client

from salvor import Client

with Client("http://127.0.0.1:8080") as c:
    agent = c.register_agent(open("agent.toml").read())
    run_id = c.start_run(agent, {"question": "..."})
    for event in c.stream_events(run_id):
        print(event.seq, event.kind)

salvor CLI

The same four commands the terminal above runs, against a real SQLite file.

salvor run --agent agent.toml --input '"a question"'
salvor list
salvor history 0f9c1d2e-6a7b-4c3d-9e8f-1a2b3c4d5e6f
salvor resume 0f9c1d2e-6a7b-4c3d-9e8f-1a2b3c4d5e6f --agent agent.toml

No cure, no pay

Three claims, one gate, and you can run it.

A 109-event control run is recorded, then continued from a fresh store at every one of its 110 prefix boundaries. Every boundary, not a sample.

  • Byte-identical final logEach boundary that completes is asserted equal to the control log, event for event, including every sequence number and recorded timestamp.
  • Zero duplicate writesWrite executions across the whole sweep total exactly the control count, so no boundary re-ran a write the control ran once.
  • A dangling write refusesThe five prefixes that end on a recorded-but-unfinished write refuse with the reconciliation error and execute nothing. A crash mid-write parks for a human rather than guessing.

crates/salvor-runtime/tests/release_gate.rsrelease_gate_kill_at_every_event_boundary_resumes_identically

Reproduce it

The hero fixture is checked in at examples/hero/.

cargo build salvor run --fixture examples/hero

the build puts the fixture's tool server on disk; the run then needs no key and no network