Skip to main content

swink_agent_memory/
interrupt.rs

1//! Interrupt state persistence for resuming interrupted agent sessions.
2//!
3//! When an agent is interrupted (e.g., at a tool approval gate or by
4//! cancellation), the [`InterruptState`] captures enough context to resume
5//! exactly where the agent left off after a process restart.
6
7use serde::{Deserialize, Serialize};
8use swink_agent::{LlmMessage, ModelSpec};
9
10/// A tool call awaiting approval at the time of interruption.
11#[non_exhaustive]
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct PendingToolCall {
14    /// Unique identifier for this tool call invocation.
15    pub tool_call_id: String,
16    /// Name of the tool being called.
17    pub tool_name: String,
18    /// Tool arguments passed to the tool.
19    pub arguments: serde_json::Value,
20}
21
22impl PendingToolCall {
23    /// Creates a new pending tool call record.
24    #[must_use]
25    pub fn new(
26        tool_call_id: impl Into<String>,
27        tool_name: impl Into<String>,
28        arguments: serde_json::Value,
29    ) -> Self {
30        Self {
31            tool_call_id: tool_call_id.into(),
32            tool_name: tool_name.into(),
33            arguments,
34        }
35    }
36}
37
38/// Snapshot of agent state at the point of interruption.
39///
40/// Persisted as `{session_id}.interrupt.json` alongside the session JSONL file.
41/// File existence indicates an active interrupt; deletion means the agent has
42/// resumed.
43#[non_exhaustive]
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct InterruptState {
46    /// Unix timestamp of when the interrupt occurred.
47    pub interrupted_at: u64,
48    /// Tool calls awaiting approval at interrupt time.
49    pub pending_tool_calls: Vec<PendingToolCall>,
50    /// Conversation context frozen at the interrupt point.
51    pub context_snapshot: Vec<LlmMessage>,
52    /// Active system prompt at interrupt time.
53    pub system_prompt: String,
54    /// Active model at interrupt time.
55    pub model: ModelSpec,
56}
57
58impl InterruptState {
59    /// Creates a new interrupt state snapshot.
60    #[must_use]
61    pub fn new(
62        interrupted_at: u64,
63        pending_tool_calls: Vec<PendingToolCall>,
64        context_snapshot: Vec<LlmMessage>,
65        system_prompt: impl Into<String>,
66        model: ModelSpec,
67    ) -> Self {
68        Self {
69            interrupted_at,
70            pending_tool_calls,
71            context_snapshot,
72            system_prompt: system_prompt.into(),
73            model,
74        }
75    }
76}
77
78const _: () = {
79    const fn assert_send_sync<T: Send + Sync>() {}
80    assert_send_sync::<InterruptState>();
81    assert_send_sync::<PendingToolCall>();
82};
83
84#[cfg(test)]
85#[path = "interrupt_tests.rs"]
86mod tests;