Skip to main content

rightkit_agent_engine_protocol/event/
durability.rs

1use super::ProtocolEvent;
2
3/// Whether an event is canonical replay truth or an ephemeral live projection.
4/// Durable facts belong in SQLite and must survive reconnect/replay. Live
5/// projections may be dropped/reordered without changing settled session truth.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum EventDurability {
8    DurableFact,
9    LiveProjection,
10}
11
12impl ProtocolEvent {
13    /// Conservative classification: only events that are unambiguously
14    /// presentation/control projections are non-durable. New variants default
15    /// to durable by virtue of the explicit catch-all arm.
16    pub fn durability(&self) -> EventDurability {
17        match self {
18            Self::AssistantDelta { .. }
19            | Self::ToolOutputDelta { .. }
20            | Self::StreamResyncRequired { .. }
21            | Self::PersistenceFailed { .. } => EventDurability::LiveProjection,
22            _ => EventDurability::DurableFact,
23        }
24    }
25
26    pub fn is_durable_fact(&self) -> bool {
27        self.durability() == EventDurability::DurableFact
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::TranscriptMessage;
35
36    #[test]
37    fn token_delta_and_resync_are_live_projections() {
38        assert_eq!(
39            ProtocolEvent::AssistantDelta {
40                session_id: "s".into(),
41                seq: 1,
42                created_at: "t".into(),
43                text: "x".into(),
44                is_reasoning: false,
45            }
46            .durability(),
47            EventDurability::LiveProjection
48        );
49        assert_eq!(
50            ProtocolEvent::StreamResyncRequired {
51                session_id: "s".into(),
52                seq: 2,
53                created_at: "t".into(),
54                after_seq: 0,
55                oldest_available_seq: 1,
56                high_water_mark: 1,
57            }
58            .durability(),
59            EventDurability::LiveProjection
60        );
61    }
62
63    #[test]
64    fn persistence_failed_is_live_only() {
65        let event = ProtocolEvent::PersistenceFailed {
66            session_id: "s".into(),
67            seq: 0,
68            created_at: "t".into(),
69            reason: "disk full".into(),
70        };
71        assert_eq!(event.durability(), EventDurability::LiveProjection);
72        assert!(!event.is_durable_fact());
73    }
74
75    #[test]
76    fn settled_assistant_message_is_durable_truth() {
77        let message = TranscriptMessage::Assistant {
78            id: "m".into(),
79            created_at: "t".into(),
80            content: "done".into(),
81            reasoning_content: None,
82            tool_calls: Vec::new(),
83            pinned: false,
84        };
85        assert!(ProtocolEvent::AssistantMessageCompleted {
86            session_id: "s".into(),
87            seq: 3,
88            created_at: "t".into(),
89            message,
90        }
91        .is_durable_fact());
92    }
93}