Skip to main content

kcode_kennedy_orchestration/
prompts.rs

1use chrono::{DateTime, Datelike, Timelike, Utc};
2use kcode_kennedy_prompts::SystemPrompts;
3use kcode_kennedy_sessions::RuntimeModel;
4
5#[derive(Clone, Debug)]
6pub struct Manuals(SystemPrompts);
7
8impl Manuals {
9    pub fn open() -> Self {
10        Self(kcode_kennedy_prompts::open())
11    }
12
13    pub fn compose_conversation(
14        &self,
15        runtime: &RuntimeModel,
16        session_type: &str,
17        session_context: &str,
18    ) -> String {
19        let (session_prompt, writes) = match session_type {
20            "free-time" => (self.0.self_time_session(), true),
21            "wakeup" => (self.0.wakeup_session(), true),
22            _ => (self.0.conversation_session(), false),
23        };
24        self.compose(
25            runtime,
26            session_prompt,
27            writes,
28            session_context,
29            session_type,
30        )
31    }
32
33    pub fn compose_ingress(&self, runtime: &RuntimeModel, source_session_type: &str) -> String {
34        let session_prompt = if source_session_type == "audio" {
35            self.0.audio_ingress_session()
36        } else {
37            self.0.history_ingress_session()
38        };
39        self.compose(runtime, session_prompt, true, "", source_session_type)
40    }
41
42    fn compose(
43        &self,
44        runtime: &RuntimeModel,
45        session_prompt: &str,
46        writes: bool,
47        session_context: &str,
48        channel: &str,
49    ) -> String {
50        let mut sections = vec![
51            section("Kennedy's identity", self.0.identity()),
52            section("Session type", session_prompt),
53        ];
54        if matches!(channel, "telegram" | "telegram-group") {
55            sections.push(section("Telegram session", self.0.telegram_session()));
56        }
57        if channel == "telegram-group" {
58            sections.push(section(
59                "Telegram group session",
60                self.0.telegram_group_session(),
61            ));
62        }
63        sections.extend([
64            section("Kmap basics", self.0.kmap_basics()),
65            section("Critical Kmap and context tools", self.0.read_tools()),
66        ]);
67        if writes {
68            sections.push(section("Write tools", self.0.write_tools()));
69        }
70        sections.push(section("Codex harness", self.0.codex_harness()));
71        if !session_context.trim().is_empty() {
72            sections.push(section("Self-time schedule", session_context.trim()));
73        }
74        sections.push(section(
75            "Current runtime",
76            &runtime_description(runtime, Utc::now()),
77        ));
78        sections.join("\n\n")
79    }
80}
81
82fn section(title: &str, content: &str) -> String {
83    format!("{title}\n\n{content}")
84}
85
86pub fn runtime_description(runtime: &RuntimeModel, current_time: DateTime<Utc>) -> String {
87    format!(
88        "You are currently running on {} with {} thinking mode. The current date and time is {}.",
89        runtime.model,
90        runtime.reasoning_effort,
91        human_utc_datetime(current_time)
92    )
93}
94
95pub fn human_utc_datetime(value: DateTime<Utc>) -> String {
96    let day = value.day();
97    let suffix = match day % 100 {
98        11..=13 => "th",
99        _ => match day % 10 {
100            1 => "st",
101            2 => "nd",
102            3 => "rd",
103            _ => "th",
104        },
105    };
106    let hour = match value.hour() % 12 {
107        0 => 12,
108        hour => hour,
109    };
110    let period = if value.hour() < 12 { "am" } else { "pm" };
111    format!(
112        "{} {day}{suffix}, {}, {hour}:{:02}{period} UTC",
113        value.format("%B"),
114        value.year(),
115        value.minute()
116    )
117}
118
119#[cfg(test)]
120mod tests {
121    use chrono::TimeZone;
122
123    use super::*;
124
125    fn testing_manuals() -> Manuals {
126        Manuals::open()
127    }
128
129    fn testing_runtime() -> RuntimeModel {
130        RuntimeModel {
131            model: "gpt-5.6-sol".into(),
132            reasoning_effort: "xhigh".into(),
133            context_window_tokens: 1_000_000,
134        }
135    }
136
137    #[test]
138    fn human_time_uses_ordinals_and_unambiguous_twelve_hour_clock() {
139        assert_eq!(
140            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 6, 4, 23, 0).unwrap()),
141            "July 6th, 2026, 4:23am UTC"
142        );
143        assert_eq!(
144            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 11, 16, 34, 0).unwrap()),
145            "July 11th, 2026, 4:34pm UTC"
146        );
147        assert_eq!(
148            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 22, 0, 5, 0).unwrap()),
149            "July 22nd, 2026, 12:05am UTC"
150        );
151    }
152
153    #[test]
154    fn telegram_layers_are_scoped_to_telegram_channels() {
155        let manuals = testing_manuals();
156        let runtime = testing_runtime();
157        let browser = manuals.compose_conversation(&runtime, "conversation", "");
158        assert!(!browser.contains("This session belongs to a Telegram conversation."));
159        assert!(!browser.contains("This is a Telegram group session"));
160
161        let private = manuals.compose_conversation(&runtime, "telegram", "");
162        assert!(private.contains("This session belongs to a Telegram conversation."));
163        assert!(!private.contains("This is a Telegram group session"));
164
165        let group = manuals.compose_conversation(&runtime, "telegram-group", "");
166        assert!(group.contains("This session belongs to a Telegram conversation."));
167        assert!(group.contains("This is a Telegram group session"));
168
169        let group_ingress = manuals.compose_ingress(&runtime, "telegram-group");
170        assert!(group_ingress.contains("This session belongs to a Telegram conversation."));
171        assert!(group_ingress.contains("This is a Telegram group session"));
172
173        let audio = manuals.compose_ingress(&runtime, "audio");
174        assert!(!audio.contains("This session belongs to a Telegram conversation."));
175        assert!(!audio.contains("This is a Telegram group session"));
176    }
177
178    #[test]
179    fn wakeup_sessions_have_their_own_autonomous_write_prompt() {
180        let prompt = testing_manuals().compose_conversation(&testing_runtime(), "wakeup", "");
181        assert!(prompt.contains("You are in a wakeup session"));
182        assert!(prompt.contains("ConnectNodes"));
183        assert!(!prompt.contains("This is a conversation session with a user."));
184        assert!(!prompt.contains("This session belongs to a Telegram conversation."));
185    }
186}