Skip to main content

tj_core/dream/
backend.rs

1//! Backend abstraction for dream Pass A: given a task's existing events
2//! and a full transcript, return the significant events that were missed.
3
4use crate::event::EventType;
5use serde::{Deserialize, Serialize};
6
7/// One missed event the backend proposes appending.
8#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
9pub struct BackfillEvent {
10    pub event_type: EventType,
11    /// Which task this belongs to (one of the input's candidate task ids).
12    pub task_id: String,
13    pub text: String,
14    /// RFC3339 timestamp of the transcript turn this was inferred from,
15    /// so the event sorts into its correct place in the chain.
16    pub timestamp: String,
17}
18
19/// Input for one session's backfill call.
20#[derive(Debug, Clone, Serialize)]
21pub struct BackfillInput {
22    /// Candidate task contexts active in this session.
23    pub tasks: Vec<BackfillTaskContext>,
24    /// The full session transcript, flattened to role-tagged turns.
25    pub transcript: String,
26}
27
28#[derive(Debug, Clone, Serialize)]
29pub struct BackfillTaskContext {
30    pub task_id: String,
31    pub title: String,
32    /// Text of events already captured for this task (for dedup context).
33    pub existing_events: Vec<String>,
34}
35
36pub trait DreamBackend {
37    /// Return the events the realtime classifier missed for this session.
38    fn backfill(&self, input: &BackfillInput) -> anyhow::Result<Vec<BackfillEvent>>;
39}
40
41/// Test backend that returns a canned list, ignoring the input.
42pub struct MockDreamBackend {
43    pub events: Vec<BackfillEvent>,
44}
45
46impl DreamBackend for MockDreamBackend {
47    fn backfill(&self, _input: &BackfillInput) -> anyhow::Result<Vec<BackfillEvent>> {
48        Ok(self.events.clone())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn mock_backend_returns_canned_events() {
58        let be = MockDreamBackend {
59            events: vec![BackfillEvent {
60                event_type: EventType::Decision,
61                task_id: "tj-1".into(),
62                text: "Chose A over B.".into(),
63                timestamp: "2026-06-08T10:00:00Z".into(),
64            }],
65        };
66        let input = BackfillInput {
67            tasks: vec![],
68            transcript: "x".into(),
69        };
70        let out = be.backfill(&input).unwrap();
71        assert_eq!(out.len(), 1);
72        assert_eq!(out[0].task_id, "tj-1");
73        assert_eq!(out[0].event_type, EventType::Decision);
74    }
75}