Skip to main content

laser_sdk/provenance/
topic.rs

1use iggy::prelude::Identifier;
2
3/// A well-known agent topic, or a `Custom` one. Each maps to an Iggy topic name.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum AgentTopic<'a> {
6    /// The session lane: commands, replies, tool and model records, user
7    /// turns, lifecycle, and state, keyed by session.
8    Sessions,
9    /// High-volume chunk streams, collapsed into their response on a timeline.
10    Streams,
11    /// Process heartbeats listing the sessions a process holds leases on.
12    Heartbeats,
13    /// Session control requests, sent by operators only.
14    Control,
15    /// Memory records.
16    Memory,
17    /// Policy evidence and other audit records.
18    Audit,
19    /// The agent card registry: agents republish their cards here, and the
20    /// registry read model folds it to the latest card per agent.
21    Registry,
22    /// The workflow journal: the engine records each step's outcome here, keyed by
23    /// the workflow run, so a crashed run resumes by replaying it.
24    WorkflowJournal,
25    /// The dead-letter topic.
26    Dlq,
27    /// Any other topic, by `Identifier`.
28    Custom(&'a Identifier),
29}
30
31impl AgentTopic<'_> {
32    /// The static topic name, or `None` for `Custom`.
33    pub const fn name(&self) -> Option<&'static str> {
34        match self {
35            Self::Sessions => Some(laser_wire::topics::AGENT_SESSIONS),
36            Self::Streams => Some(laser_wire::topics::AGENT_STREAMS),
37            Self::Heartbeats => Some(laser_wire::topics::AGENT_HEARTBEATS),
38            Self::Control => Some(laser_wire::topics::AGENT_CONTROL),
39            Self::Memory => Some(laser_wire::topics::AGENT_MEMORY),
40            Self::Audit => Some(laser_wire::topics::AGENT_AUDIT),
41            Self::Registry => Some(laser_wire::topics::AGENT_REGISTRY),
42            Self::WorkflowJournal => Some(laser_wire::topics::AGENT_JOURNAL),
43            Self::Dlq => Some(laser_wire::topics::AGENT_DLQ),
44            Self::Custom(_) => None,
45        }
46    }
47
48    /// The Iggy `Identifier` for this topic.
49    pub fn as_identifier(&self) -> Identifier {
50        match self {
51            Self::Custom(id) => (**id).clone(),
52            other => Identifier::named(other.name().expect("non-custom topic has a static name"))
53                .expect("static topic name is a valid identifier"),
54        }
55    }
56
57    /// The topic name as an owned `String`.
58    pub fn topic_string(&self) -> String {
59        match self {
60            Self::Custom(id) => id.to_string(),
61            other => other
62                .name()
63                .expect("non-custom topic has a static name")
64                .to_owned(),
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn given_a_well_known_topic_when_converted_then_should_map_to_its_identifier() {
75        assert_eq!(AgentTopic::Sessions.name(), Some("agent.sessions"));
76        assert_eq!(
77            AgentTopic::Sessions.as_identifier(),
78            Identifier::named("agent.sessions").expect("the topic name is a valid identifier")
79        );
80    }
81
82    #[test]
83    fn given_a_custom_topic_when_converted_then_should_carry_its_identifier() {
84        let id = Identifier::named("agent.metrics").expect("the topic name is a valid identifier");
85        let topic = AgentTopic::Custom(&id);
86        assert_eq!(topic.name(), None);
87        assert_eq!(topic.as_identifier(), id);
88    }
89}