Skip to main content

salvor_replay/
effect.rs

1//! Side-effect classification for tool calls.
2
3use serde::{Deserialize, Serialize};
4
5/// How a tool call may be retried and how it behaves on replay.
6///
7/// Effect classification does not change one thing: on replay, **any completed
8/// recorded call is read from the log and never re-executed, regardless of its
9/// effect class**. A completion is immutable history.
10///
11/// Effect classification governs calls that were **not** completed, the gap
12/// between an intent recorded in the log and a completion that never arrived:
13///
14/// - [`Effect::Read`] calls re-execute freely.
15/// - [`Effect::Idempotent`] calls retry with the same idempotency key.
16/// - [`Effect::Write`] calls are never automatically retried and surface for
17///   human reconciliation.
18///
19/// Serializes lowercase (`"read"`, `"idempotent"`, `"write"`), because the
20/// wire form is part of the durable event contract.
21///
22/// This type lives in `salvor-core` because events reference it;
23/// `salvor-tools` consumes it when it defines tool contracts.
24#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum Effect {
27    /// A call with no side effects. If it was not completed, it re-executes
28    /// freely, because running it again observes state without changing it.
29    Read,
30    /// A call safe to repeat under the same idempotency key. If it was not
31    /// completed, it retries with that same key, so the provider collapses
32    /// duplicate attempts into one effect.
33    Idempotent,
34    /// A call that changes external state and is not safe to repeat blindly.
35    /// If it was not completed, it is never automatically retried: a crash
36    /// between the recorded intent and a completion surfaces for human
37    /// reconciliation rather than a guess about whether the write landed.
38    Write,
39}