kcode-kennedy-orchestration 0.1.0

Typed orchestration and session-control runtime for Kennedy
Documentation
use std::{collections::HashMap, path::Path};

use anyhow::Context;
use chrono::{DateTime, Datelike, Timelike, Utc};
use kcode_kennedy_sessions::RuntimeModel;

const PROMPT_FILES: [(&str, &str); 12] = [
    ("identity", "KennedyIdentity.txt"),
    ("conversationSession", "ConversationSession.txt"),
    ("freeTimeSession", "SelfTimeSession.txt"),
    ("wakeupSession", "WakeupSession.txt"),
    ("historyIngressSession", "HistoryIngressSession.txt"),
    ("audioIngressSession", "AudioIngressSession.txt"),
    ("telegramSession", "TelegramSession.txt"),
    ("telegramGroupSession", "TelegramGroupSession.txt"),
    ("codexHarness", "CodexHarness.txt"),
    ("kmapBasics", "KmapBasics.txt"),
    ("readTools", "ReadTools.txt"),
    ("writeTools", "WriteTools.txt"),
];

#[derive(Clone, Debug)]
pub struct Manuals(HashMap<String, String>);

impl Manuals {
    pub fn load(directory: &Path) -> anyhow::Result<Self> {
        let mut manuals = HashMap::new();
        for (key, filename) in PROMPT_FILES {
            let path = directory.join(filename);
            let text = std::fs::read_to_string(&path)
                .with_context(|| format!("reading system prompt {}", path.display()))?;
            let text = text.trim().to_owned();
            anyhow::ensure!(
                !text.is_empty(),
                "system prompt {} is empty",
                path.display()
            );
            manuals.insert(key.to_owned(), text);
        }
        Ok(Self(manuals))
    }

    pub fn compose_conversation(
        &self,
        runtime: &RuntimeModel,
        session_type: &str,
        session_context: &str,
    ) -> anyhow::Result<String> {
        let (session_key, writes) = match session_type {
            "free-time" => ("freeTimeSession", true),
            "wakeup" => ("wakeupSession", true),
            _ => ("conversationSession", false),
        };
        self.compose(runtime, session_key, writes, session_context, session_type)
    }

    pub fn compose_ingress(
        &self,
        runtime: &RuntimeModel,
        source_session_type: &str,
    ) -> anyhow::Result<String> {
        let session_key = if source_session_type == "audio" {
            "audioIngressSession"
        } else {
            "historyIngressSession"
        };
        self.compose(runtime, session_key, true, "", source_session_type)
    }

    fn compose(
        &self,
        runtime: &RuntimeModel,
        session_key: &str,
        writes: bool,
        session_context: &str,
        channel: &str,
    ) -> anyhow::Result<String> {
        let mut sections = vec![
            section("Kennedy's identity", self.required("identity")?),
            section("Session type", self.required(session_key)?),
        ];
        if matches!(channel, "telegram" | "telegram-group") {
            sections.push(section(
                "Telegram session",
                self.required("telegramSession")?,
            ));
        }
        if channel == "telegram-group" {
            sections.push(section(
                "Telegram group session",
                self.required("telegramGroupSession")?,
            ));
        }
        sections.extend([
            section("Kmap basics", self.required("kmapBasics")?),
            section(
                "Critical Kmap and context tools",
                self.required("readTools")?,
            ),
        ]);
        if writes {
            sections.push(section("Write tools", self.required("writeTools")?));
        }
        sections.push(section("Codex harness", self.required("codexHarness")?));
        if !session_context.trim().is_empty() {
            sections.push(section("Self-time schedule", session_context.trim()));
        }
        sections.push(section(
            "Current runtime",
            &runtime_description(runtime, Utc::now()),
        ));
        Ok(sections.join("\n\n"))
    }

    fn required(&self, key: &str) -> anyhow::Result<&str> {
        self.0
            .get(key)
            .map(String::as_str)
            .with_context(|| format!("missing system prompt section {key}"))
    }
}

fn section(title: &str, content: &str) -> String {
    format!("{title}\n\n{content}")
}

pub fn runtime_description(runtime: &RuntimeModel, current_time: DateTime<Utc>) -> String {
    format!(
        "You are currently running on {} with {} thinking mode. The current date and time is {}.",
        runtime.model,
        runtime.reasoning_effort,
        human_utc_datetime(current_time)
    )
}

pub fn human_utc_datetime(value: DateTime<Utc>) -> String {
    let day = value.day();
    let suffix = match day % 100 {
        11..=13 => "th",
        _ => match day % 10 {
            1 => "st",
            2 => "nd",
            3 => "rd",
            _ => "th",
        },
    };
    let hour = match value.hour() % 12 {
        0 => 12,
        hour => hour,
    };
    let period = if value.hour() < 12 { "am" } else { "pm" };
    format!(
        "{} {day}{suffix}, {}, {hour}:{:02}{period} UTC",
        value.format("%B"),
        value.year(),
        value.minute()
    )
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use chrono::TimeZone;

    use super::*;

    fn testing_manuals() -> Manuals {
        Manuals(
            PROMPT_FILES
                .into_iter()
                .map(|(key, _)| (key.to_owned(), format!("[{key}]")))
                .collect::<HashMap<_, _>>(),
        )
    }

    fn testing_runtime() -> RuntimeModel {
        RuntimeModel {
            model: "gpt-5.6-sol".into(),
            reasoning_effort: "xhigh".into(),
            context_window_tokens: 1_000_000,
        }
    }

    #[test]
    fn human_time_uses_ordinals_and_unambiguous_twelve_hour_clock() {
        assert_eq!(
            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 6, 4, 23, 0).unwrap()),
            "July 6th, 2026, 4:23am UTC"
        );
        assert_eq!(
            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 11, 16, 34, 0).unwrap()),
            "July 11th, 2026, 4:34pm UTC"
        );
        assert_eq!(
            human_utc_datetime(Utc.with_ymd_and_hms(2026, 7, 22, 0, 5, 0).unwrap()),
            "July 22nd, 2026, 12:05am UTC"
        );
    }

    #[test]
    fn telegram_layers_are_scoped_to_telegram_channels() {
        let manuals = testing_manuals();
        let runtime = testing_runtime();
        let browser = manuals
            .compose_conversation(&runtime, "conversation", "")
            .unwrap();
        assert!(!browser.contains("[telegramSession]"));
        assert!(!browser.contains("[telegramGroupSession]"));

        let private = manuals
            .compose_conversation(&runtime, "telegram", "")
            .unwrap();
        assert!(private.contains("[telegramSession]"));
        assert!(!private.contains("[telegramGroupSession]"));

        let group = manuals
            .compose_conversation(&runtime, "telegram-group", "")
            .unwrap();
        assert!(group.contains("[telegramSession]"));
        assert!(group.contains("[telegramGroupSession]"));

        let group_ingress = manuals.compose_ingress(&runtime, "telegram-group").unwrap();
        assert!(group_ingress.contains("[telegramSession]"));
        assert!(group_ingress.contains("[telegramGroupSession]"));

        let audio = manuals.compose_ingress(&runtime, "audio").unwrap();
        assert!(!audio.contains("[telegramSession]"));
        assert!(!audio.contains("[telegramGroupSession]"));
    }

    #[test]
    fn wakeup_sessions_have_their_own_autonomous_write_prompt() {
        let prompt = testing_manuals()
            .compose_conversation(&testing_runtime(), "wakeup", "")
            .unwrap();
        assert!(prompt.contains("[wakeupSession]"));
        assert!(prompt.contains("[writeTools]"));
        assert!(!prompt.contains("[conversationSession]"));
        assert!(!prompt.contains("[telegramSession]"));
    }
}