1use std::sync::{Arc, Mutex};
8
9use crate::{
10 domain::{
11 AgentError, AgentId, DestinationKind, DestinationRef, EntityRef, EventId, JournalCursor,
12 RunId, SourceKind, SourceRef, SpanId, TraceId,
13 },
14 event::{
15 AgentEvent, CompiledEventFilter, ContentCaptureMode, EVENT_SCHEMA_VERSION, EventCursor,
16 EventDeliverySemantics, EventEnvelope, EventFamily, EventFilter, EventFrame, EventKind,
17 EventStreamScope, cursor_compatible,
18 },
19 event_bus::AgentEventStream,
20};
21
22pub trait RunSubscriptionSource: Send + Sync {
27 fn subscribe_all(&self, cursor: Option<EventCursor>) -> Result<AgentEventStream, AgentError>;
31
32 fn subscribe_run(
36 &self,
37 run_id: RunId,
38 cursor: Option<EventCursor>,
39 ) -> Result<AgentEventStream, AgentError>;
40
41 fn subscribe_agent(
45 &self,
46 agent_id: AgentId,
47 cursor: Option<EventCursor>,
48 ) -> Result<AgentEventStream, AgentError>;
49
50 fn subscribe_events(
54 &self,
55 filter: CompiledEventFilter,
56 cursor: Option<EventCursor>,
57 ) -> Result<AgentEventStream, AgentError>;
58
59 fn replay_run_from_cursor(
63 &self,
64 run_id: RunId,
65 cursor: JournalCursor,
66 ) -> Result<AgentEventStream, AgentError>;
67
68 fn latest_terminal_event(&self, run_id: &RunId) -> Result<Option<EventFrame>, AgentError>;
72}
73
74#[derive(Clone, Debug, Default)]
75pub struct InMemorySubscriptionHub {
78 frames: Arc<Mutex<Vec<EventFrame>>>,
79 live_floor_seq: Arc<Mutex<u64>>,
80}
81
82impl InMemorySubscriptionHub {
83 pub fn publish(&self, frame: EventFrame) -> Result<(), AgentError> {
87 self.frames
88 .lock()
89 .map_err(|_| AgentError::contract_violation("subscription hub lock poisoned"))?
90 .push(frame);
91 Ok(())
92 }
93
94 pub fn publish_all(
98 &self,
99 frames: impl IntoIterator<Item = EventFrame>,
100 ) -> Result<(), AgentError> {
101 let mut locked = self
102 .frames
103 .lock()
104 .map_err(|_| AgentError::contract_violation("subscription hub lock poisoned"))?;
105 locked.extend(frames);
106 Ok(())
107 }
108
109 pub fn expire_live_before(&self, event_seq: u64) -> Result<(), AgentError> {
113 *self
114 .live_floor_seq
115 .lock()
116 .map_err(|_| AgentError::contract_violation("subscription hub lock poisoned"))? =
117 event_seq;
118 Ok(())
119 }
120
121 pub fn frames(&self) -> Result<Vec<EventFrame>, AgentError> {
125 Ok(self
126 .frames
127 .lock()
128 .map_err(|_| AgentError::contract_violation("subscription hub lock poisoned"))?
129 .clone())
130 }
131
132 fn subscribe_scope(
133 &self,
134 requested_scope: EventStreamScope,
135 filter: CompiledEventFilter,
136 cursor: Option<EventCursor>,
137 ) -> Result<AgentEventStream, AgentError> {
138 cursor_compatible(&requested_scope, cursor.as_ref())?;
139
140 if let Some(cursor) = cursor.as_ref() {
141 if self.cursor_expired(cursor)? {
142 return self.resume_expired_cursor(requested_scope, filter, cursor.clone());
143 }
144 }
145
146 let start_after = cursor.as_ref().map(|cursor| cursor.event_seq);
147 let live_floor = self.live_floor()?;
148 Ok(AgentEventStream::new(
149 self.frames()?
150 .into_iter()
151 .filter(|frame| frame.event.envelope.event_seq >= live_floor)
152 .filter(|frame| {
153 start_after.is_none_or(|event_seq| frame.event.envelope.event_seq > event_seq)
154 })
155 .filter(|frame| filter.matches_envelope(&frame.event.envelope))
156 .map(|frame| frame_for_scope(frame, requested_scope.clone())),
157 ))
158 }
159
160 fn resume_expired_cursor(
161 &self,
162 requested_scope: EventStreamScope,
163 filter: CompiledEventFilter,
164 cursor: EventCursor,
165 ) -> Result<AgentEventStream, AgentError> {
166 match (&requested_scope, cursor.journal_cursor.as_ref()) {
167 (EventStreamScope::Run(run_id), Some(journal_cursor)) => {
168 let live_floor = self.live_floor()?;
169 let mut frames = self
170 .journal_backed_run_frames_after(run_id, journal_cursor)?
171 .into_iter()
172 .filter(|frame| frame.event.envelope.event_seq < live_floor)
173 .map(|frame| derived_replay_frame(frame, requested_scope.clone()))
174 .collect::<Vec<_>>();
175 frames.extend(
176 self.frames()?
177 .into_iter()
178 .filter(|frame| frame.event.envelope.event_seq >= live_floor)
179 .filter(|frame| frame.event.envelope.event_seq > cursor.event_seq)
180 .filter(|frame| filter.matches_envelope(&frame.event.envelope))
181 .map(|frame| frame_for_scope(frame, requested_scope.clone())),
182 );
183 Ok(AgentEventStream::new(frames))
184 }
185 (EventStreamScope::Run(run_id), None) => {
186 Ok(AgentEventStream::new([self.gap_diagnostic_frame(
187 run_id.clone(),
188 requested_scope,
189 cursor,
190 )?]))
191 }
192 (_, Some(_)) => Err(AgentError::host_configuration_needed(
193 "host archive required for expired non-run event cursor replay",
194 )),
195 (_, None) => Err(AgentError::host_configuration_needed(
196 "expired non-run event cursor has no durable archive cursor",
197 )),
198 }
199 }
200
201 fn replay_run(
202 &self,
203 run_id: &RunId,
204 cursor: &JournalCursor,
205 ) -> Result<Vec<EventFrame>, AgentError> {
206 self.journal_backed_run_frames_after(run_id, cursor)
207 .map(|frames| {
208 frames
209 .into_iter()
210 .map(|frame| derived_replay_frame(frame, EventStreamScope::Run(run_id.clone())))
211 .collect()
212 })
213 }
214
215 fn journal_backed_run_frames_after(
216 &self,
217 run_id: &RunId,
218 cursor: &JournalCursor,
219 ) -> Result<Vec<EventFrame>, AgentError> {
220 let cursor_seq = journal_cursor_seq(cursor);
221 Ok(self
222 .frames()?
223 .into_iter()
224 .filter(|frame| &frame.event.envelope.run_id == run_id)
225 .filter(|frame| {
226 frame
227 .event
228 .envelope
229 .journal_cursor
230 .as_ref()
231 .is_some_and(|journal_cursor| journal_cursor_seq(journal_cursor) > cursor_seq)
232 })
233 .collect())
234 }
235
236 fn cursor_expired(&self, cursor: &EventCursor) -> Result<bool, AgentError> {
237 Ok(cursor.event_seq < self.live_floor()?)
238 }
239
240 fn live_floor(&self) -> Result<u64, AgentError> {
241 Ok(*self
242 .live_floor_seq
243 .lock()
244 .map_err(|_| AgentError::contract_violation("subscription hub lock poisoned"))?)
245 }
246
247 fn gap_diagnostic_frame(
248 &self,
249 run_id: RunId,
250 requested_scope: EventStreamScope,
251 cursor: EventCursor,
252 ) -> Result<EventFrame, AgentError> {
253 let agent_id = self
254 .frames()?
255 .into_iter()
256 .rev()
257 .find(|frame| frame.event.envelope.run_id == run_id)
258 .map(|frame| frame.event.envelope.agent_id)
259 .unwrap_or_else(|| AgentId::new("agent.replay.unknown"));
260 let next_seq = cursor.event_seq.saturating_add(1);
261 let event = AgentEvent::with_redacted_summary(
262 EventEnvelope {
263 schema_version: EVENT_SCHEMA_VERSION,
264 event_id: EventId::new(format!(
265 "event.replay_failed.{}.{}",
266 run_id.as_str(),
267 next_seq
268 )),
269 event_seq: next_seq,
270 event_family: EventFamily::Recovery,
271 event_kind: EventKind::ReplayFailed,
272 payload_schema_version: 1,
273 timestamp: "1970-01-01T00:00:00Z".to_string(),
274 recorded_at: "1970-01-01T00:00:00Z".to_string(),
275 run_id: run_id.clone(),
276 session_id: None,
277 agent_id,
278 turn_id: None,
279 attempt_id: None,
280 message_id: None,
281 context_item_id: None,
282 trace_id: TraceId::new(format!("trace.replay_failed.{}", run_id.as_str())),
283 span_id: SpanId::new(format!("span.replay_failed.{next_seq}")),
284 parent_event_id: Some(cursor.event_id),
285 caused_by: None,
286 subject_ref: EntityRef::run(run_id),
287 related_refs: Vec::new(),
288 causal_refs: Vec::new(),
289 correlation: Default::default(),
290 tags: Vec::new(),
291 source: SourceRef::with_kind(SourceKind::Replay, "source.replay.subscription"),
292 destination: Some(DestinationRef::with_kind(
293 DestinationKind::EventStream,
294 "destination.event_stream.subscription",
295 )),
296 policy_refs: Vec::new(),
297 journal_cursor: None,
298 state_before: None,
299 state_after: None,
300 delivery_semantics: EventDeliverySemantics::DiagnosticOnly,
301 privacy: crate::domain::PrivacyClass::ContentRefsOnly,
302 content_capture: ContentCaptureMode::Off,
303 redaction_policy_id: "policy.redaction.default".to_string(),
304 runtime_package_fingerprint: "runtime.package.fingerprint.subscription".to_string(),
305 },
306 "live cursor expired and no journal cursor was available for run replay",
307 );
308 Ok(frame_for_scope(
309 EventFrame {
310 cursor: event.envelope.cursor(requested_scope.clone()),
311 event,
312 archive_cursor: None,
313 overflow: None,
314 },
315 requested_scope,
316 ))
317 }
318}
319
320impl RunSubscriptionSource for InMemorySubscriptionHub {
321 fn subscribe_all(&self, cursor: Option<EventCursor>) -> Result<AgentEventStream, AgentError> {
322 self.subscribe_scope(
323 EventStreamScope::All,
324 EventFilter::default().compile()?,
325 cursor,
326 )
327 }
328
329 fn subscribe_run(
330 &self,
331 run_id: RunId,
332 cursor: Option<EventCursor>,
333 ) -> Result<AgentEventStream, AgentError> {
334 self.subscribe_scope(
335 EventStreamScope::Run(run_id.clone()),
336 EventFilter::run(run_id).compile()?,
337 cursor,
338 )
339 }
340
341 fn subscribe_agent(
342 &self,
343 agent_id: AgentId,
344 cursor: Option<EventCursor>,
345 ) -> Result<AgentEventStream, AgentError> {
346 self.subscribe_scope(
347 EventStreamScope::Agent(agent_id.clone()),
348 EventFilter::agent(agent_id).compile()?,
349 cursor,
350 )
351 }
352
353 fn subscribe_events(
354 &self,
355 filter: CompiledEventFilter,
356 cursor: Option<EventCursor>,
357 ) -> Result<AgentEventStream, AgentError> {
358 self.subscribe_scope(filter.cursor_scope(), filter, cursor)
359 }
360
361 fn replay_run_from_cursor(
362 &self,
363 run_id: RunId,
364 cursor: JournalCursor,
365 ) -> Result<AgentEventStream, AgentError> {
366 Ok(AgentEventStream::new(self.replay_run(&run_id, &cursor)?))
367 }
368
369 fn latest_terminal_event(&self, run_id: &RunId) -> Result<Option<EventFrame>, AgentError> {
370 Ok(self.frames()?.into_iter().rev().find(|frame| {
371 &frame.event.envelope.run_id == run_id
372 && matches!(
373 frame.event.envelope.event_kind,
374 EventKind::RunCompleted | EventKind::RunFailed | EventKind::RunCancelled
375 )
376 }))
377 }
378}
379
380fn frame_for_scope(mut frame: EventFrame, scope: EventStreamScope) -> EventFrame {
381 frame.cursor = frame.event.envelope.cursor(scope);
382 frame
383}
384
385fn derived_replay_frame(mut frame: EventFrame, scope: EventStreamScope) -> EventFrame {
386 frame.event.envelope.delivery_semantics = EventDeliverySemantics::DerivedReplay;
387 frame.cursor = frame.event.envelope.cursor(scope);
388 frame
389}
390
391fn journal_cursor_seq(cursor: &JournalCursor) -> u64 {
392 cursor
393 .as_str()
394 .rsplit_once('.')
395 .and_then(|(_, seq)| seq.parse::<u64>().ok())
396 .unwrap_or(0)
397}