Skip to main content

starweaver_context/
resumable_state.rs

1//! Serializable context state used for session restoration.
2
3use std::collections::BTreeMap;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use starweaver_core::{AgentId, ConversationId, Metadata, RunId, SessionId, TaskId, TraceContext};
9use starweaver_model::{ContentPart, ModelMessage, ToolReturnPart};
10use starweaver_usage::{Usage, UsageSnapshotEntry};
11
12use crate::{AgentInfo, MessageBus, ModelConfig, SecurityConfig, StateStore, ToolConfig};
13
14/// Baseline export profile for context restoration state.
15#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
16pub enum ResumableExportMode {
17    /// Curated portable export behavior for session restoration.
18    #[default]
19    Curated,
20    /// Full Starweaver runtime state export behavior.
21    Full,
22}
23
24/// Export options for context restoration state.
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub struct ResumableExportOptions {
27    mode: ResumableExportMode,
28    /// Include subagent history.
29    pub include_subagent: bool,
30    /// Include the usage ledger.
31    pub include_usage_ledger: bool,
32}
33
34impl Default for ResumableExportOptions {
35    fn default() -> Self {
36        Self::curated()
37    }
38}
39
40impl ResumableExportOptions {
41    /// Full Starweaver runtime state export behavior.
42    #[must_use]
43    pub const fn full() -> Self {
44        Self {
45            mode: ResumableExportMode::Full,
46            include_subagent: true,
47            include_usage_ledger: true,
48        }
49    }
50
51    /// Curated portable export behavior for session restoration.
52    #[must_use]
53    pub const fn curated() -> Self {
54        Self {
55            mode: ResumableExportMode::Curated,
56            include_subagent: true,
57            include_usage_ledger: false,
58        }
59    }
60
61    /// Include or exclude the usage ledger.
62    #[must_use]
63    pub const fn with_usage_ledger(mut self, include_usage_ledger: bool) -> Self {
64        self.include_usage_ledger = include_usage_ledger;
65        self
66    }
67
68    /// Include or exclude subagent state.
69    #[must_use]
70    pub const fn with_subagent(mut self, include_subagent: bool) -> Self {
71        self.include_subagent = include_subagent;
72        self
73    }
74
75    /// Return whether subagent state is included.
76    #[must_use]
77    pub const fn include_subagent(self) -> bool {
78        self.include_subagent
79    }
80
81    /// Return whether the usage ledger is included.
82    #[must_use]
83    pub const fn include_usage_ledger(self) -> bool {
84        self.include_usage_ledger
85    }
86
87    /// Return whether Starweaver runtime extensions are included.
88    #[must_use]
89    pub const fn include_starweaver_extensions(self) -> bool {
90        matches!(self.mode, ResumableExportMode::Full)
91    }
92
93    /// Return whether runtime config and security policy are included.
94    #[must_use]
95    pub const fn include_runtime_policy(self) -> bool {
96        matches!(self.mode, ResumableExportMode::Full)
97    }
98}
99
100/// Serializable state used to restore an agent context.
101#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
102pub struct ResumableState {
103    /// Agent identifier.
104    pub agent_id: AgentId,
105    /// Current run identifier when exported.
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub run_id: Option<RunId>,
108    /// Stable logical session affinity identifier.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub session_id: Option<SessionId>,
111    /// Parent run identifier when this state belongs to a delegated child run.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub parent_run_id: Option<RunId>,
114    /// Parent-scoped delegated task identifier when this state belongs to a lightweight task.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub parent_task_id: Option<TaskId>,
117    /// Conversation identifier.
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub conversation_id: Option<ConversationId>,
120    /// Canonical message history.
121    #[serde(default, skip_serializing_if = "Vec::is_empty")]
122    pub message_history: Vec<ModelMessage>,
123    /// Tool returns to inject at the start of the next run.
124    #[serde(default, skip_serializing_if = "Vec::is_empty")]
125    pub pending_tool_returns: Vec<ToolReturnPart>,
126    /// Serialized subagent history, keyed by agent id.
127    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
128    pub subagent_history: BTreeMap<String, Vec<ModelMessage>>,
129    /// User prompt content collected for the current run.
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub user_prompts: Option<Vec<ContentPart>>,
132    /// Visible assistant response immediately before the current user prompt.
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub previous_assistant_response_reference: Option<String>,
135    /// Accumulated user steering messages for compact restore.
136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
137    pub steering_messages: Vec<String>,
138    /// Rendered handoff message.
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub handoff_message: Option<String>,
141    /// Legacy shell environment accepted on restore but never emitted by current exports.
142    #[serde(default, skip_serializing)]
143    pub shell_env: BTreeMap<String, String>,
144    /// Metadata for deferred tool calls.
145    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
146    pub deferred_tool_metadata: BTreeMap<String, Metadata>,
147    /// Serialized agent registry.
148    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
149    pub agent_registry: BTreeMap<String, AgentInfo>,
150    /// Legacy agent-owned approval selector accepted for wire compatibility but never applied.
151    #[serde(default, skip_serializing)]
152    pub approval_required_tools: Vec<String>,
153    /// Legacy MCP approval selector accepted for wire compatibility but never applied.
154    #[serde(default, skip_serializing)]
155    pub approval_required_mcp_servers: Vec<String>,
156    /// Security-related runtime configuration.
157    #[serde(default, skip_serializing_if = "SecurityConfig::is_default")]
158    pub security: SecurityConfig,
159    /// File paths to mention for on-demand inspection on the next request.
160    ///
161    /// The legacy field name is retained for serialized-state compatibility.
162    #[serde(default, skip_serializing_if = "Vec::is_empty")]
163    pub auto_load_files: Vec<String>,
164    /// Serialized tasks from the typed task manager, keyed by task id.
165    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
166    pub tasks: BTreeMap<String, Value>,
167    /// Persisted notes, keyed by note id.
168    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
169    pub notes: BTreeMap<String, String>,
170    /// Tool names loaded through tool search.
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub tool_search_loaded_tools: Vec<String>,
173    /// Namespace IDs loaded through tool search.
174    #[serde(default, skip_serializing_if = "Vec::is_empty")]
175    pub tool_search_loaded_namespaces: Vec<String>,
176
177    /// Accumulated usage. Starweaver extension.
178    #[serde(default, skip_serializing_if = "Usage::is_empty")]
179    pub usage: Usage,
180    /// Per-run cumulative usage ledger entries keyed by stable source id.
181    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
182    pub usage_snapshot_entries: BTreeMap<String, UsageSnapshotEntry>,
183    /// Model/runtime configuration used for injected runtime context and tool policies.
184    #[serde(default, skip_serializing_if = "ModelConfig::is_default")]
185    pub model_config: ModelConfig,
186    /// Tool-level configuration used by first-party and host tools.
187    #[serde(default, skip_serializing_if = "ToolConfig::is_default")]
188    pub tool_config: ToolConfig,
189    /// Context creation time used for elapsed runtime context.
190    #[serde(default = "Utc::now", skip_serializing_if = "is_default_started_at")]
191    pub started_at: DateTime<Utc>,
192    /// State domains. Starweaver extension.
193    #[serde(default, skip_serializing_if = "StateStore::is_empty")]
194    pub state: StateStore,
195    /// Pending bus messages. Starweaver extension.
196    #[serde(default, skip_serializing_if = "MessageBus::is_empty")]
197    pub message_bus: MessageBus,
198    /// Trace correlation snapshot. Starweaver extension.
199    #[serde(default, skip_serializing_if = "TraceContext::is_empty")]
200    pub trace_snapshot: TraceContext,
201    /// Run metadata. Starweaver extension.
202    #[serde(default, skip_serializing_if = "Metadata::is_empty")]
203    pub metadata: Metadata,
204    /// Host extension data preserved across state export and restore.
205    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
206    pub extra: BTreeMap<String, Value>,
207}
208
209impl starweaver_core::VersionedRecord for ResumableState {
210    const SCHEMA: &'static str = "starweaver.context.resumable_state";
211    const ALLOW_BARE_V0: bool = true;
212}
213
214const fn is_default_started_at(value: &DateTime<Utc>) -> bool {
215    value.timestamp() == 0 && value.timestamp_subsec_nanos() == 0
216}