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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! Interrupt state persistence for resuming interrupted agent sessions.
//!
//! When an agent is interrupted (e.g., at a tool approval gate or by
//! cancellation), the [`InterruptState`] captures enough context to resume
//! exactly where the agent left off after a process restart.
use serde::{Deserialize, Serialize};
use swink_agent::{LlmMessage, ModelSpec};
/// A tool call awaiting approval at the time of interruption.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PendingToolCall {
/// Unique identifier for this tool call invocation.
pub tool_call_id: String,
/// Name of the tool being called.
pub tool_name: String,
/// Tool arguments passed to the tool.
pub arguments: serde_json::Value,
}
impl PendingToolCall {
/// Creates a new pending tool call record.
#[must_use]
pub fn new(
tool_call_id: impl Into<String>,
tool_name: impl Into<String>,
arguments: serde_json::Value,
) -> Self {
Self {
tool_call_id: tool_call_id.into(),
tool_name: tool_name.into(),
arguments,
}
}
}
/// Snapshot of agent state at the point of interruption.
///
/// Persisted as `{session_id}.interrupt.json` alongside the session JSONL file.
/// File existence indicates an active interrupt; deletion means the agent has
/// resumed.
#[non_exhaustive]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InterruptState {
/// Unix timestamp of when the interrupt occurred.
pub interrupted_at: u64,
/// Tool calls awaiting approval at interrupt time.
pub pending_tool_calls: Vec<PendingToolCall>,
/// Conversation context frozen at the interrupt point.
pub context_snapshot: Vec<LlmMessage>,
/// Active system prompt at interrupt time.
pub system_prompt: String,
/// Active model at interrupt time.
pub model: ModelSpec,
}
impl InterruptState {
/// Creates a new interrupt state snapshot.
#[must_use]
pub fn new(
interrupted_at: u64,
pending_tool_calls: Vec<PendingToolCall>,
context_snapshot: Vec<LlmMessage>,
system_prompt: impl Into<String>,
model: ModelSpec,
) -> Self {
Self {
interrupted_at,
pending_tool_calls,
context_snapshot,
system_prompt: system_prompt.into(),
model,
}
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<InterruptState>();
assert_send_sync::<PendingToolCall>();
};
#[cfg(test)]
#[path = "interrupt_tests.rs"]
mod tests;