1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//! [`RetryPolicy`]: the per-effect rule for retrying a *live* tool execution
//! that failed, encoded as pure data.
use Effect;
/// Whether and how a failed live tool execution may be retried, as a function
/// of the tool's [`Effect`].
///
/// This encodes the retry column of the effect table. It is pure data
/// and logic: it decides nothing and enforces nothing on its own.
///
/// # Scope: live execution only
///
/// This governs a tool call that is executing **now** and failed. It says
/// nothing about replay: a *completed* recorded call is read from the log and
/// never re-executed, whatever its effect. That rule lives in
/// [`salvor_core::Effect`] and the replay cursor, not here.
///
/// # Consumer: the agent loop
///
/// This type is consumed by the runtime loop, which owns retry enforcement.
/// When a live [`DynTool::call_json`](crate::DynTool::call_json) returns a
/// [`ToolError::Handler`](crate::ToolError::Handler), the loop asks
/// [`RetryPolicy::for_effect`] what it is allowed to do:
///
/// - [`Retry`](Self::Retry): re-execute the call.
/// - [`RetryWithSameKey`](Self::RetryWithSameKey): re-execute, reusing the
/// original idempotency key (carried on [`ToolCtx`](crate::ToolCtx)) so the
/// provider collapses duplicate attempts into one effect.
/// - [`Never`](Self::Never): do not auto-retry; the write's at-most-once intent
/// was recorded before execution, and a crash between intent and completion
/// surfaces as `NeedsReconciliation` for a human.
///
/// This layer only classifies. Recording intent, counting attempts, and
/// surfacing reconciliation are the loop's job.