use chrono::{DateTime, Datelike, Timelike, Utc};
use kcode_kennedy_prompts::SystemPrompts;
use kcode_kennedy_sessions::RuntimeModel;
#[derive(Clone, Debug)]
pub struct Manuals(SystemPrompts);
#[derive(Clone, Copy, Debug)]
struct SessionRuntimeFacts {
opened_at: DateTime<Utc>,
context_limit_tokens: u64,
}
impl Manuals {
pub fn open() -> Self {
Self(kcode_kennedy_prompts::open())
}
pub fn compose_conversation(
&self,
runtime: &RuntimeModel,
session_type: &str,
session_context: &str,
opened_at: DateTime<Utc>,
) -> String {
let (session_prompt, writes) = match session_type {
"free-time" => (self.0.self_time_session(), true),
"wakeup" => (self.0.wakeup_session(), true),
_ => (self.0.conversation_session(), false),
};
self.compose(
runtime,
session_prompt,
writes,
session_context,
session_type,
SessionRuntimeFacts {
opened_at,
context_limit_tokens: runtime.context_window_tokens.saturating_mul(70) / 100,
},
)
}
pub fn compose_ingress(
&self,
runtime: &RuntimeModel,
source_session_type: &str,
opened_at: DateTime<Utc>,
) -> String {
let session_prompt = if source_session_type == "audio" {
self.0.audio_ingress_session()
} else {
self.0.history_ingress_session()
};
self.compose(
runtime,
session_prompt,
true,
"",
source_session_type,
SessionRuntimeFacts {
opened_at,
context_limit_tokens: runtime.context_window_tokens,
},
)
}
pub fn subagent_codex_prompt(&self) -> &'static str {
self.0.codex_harness()
}
fn compose(
&self,
runtime: &RuntimeModel,
session_prompt: &str,
writes: bool,
session_context: &str,
channel: &str,
runtime_facts: SessionRuntimeFacts,
) -> String {
let mut sections = vec![
section("Kennedy's identity", self.0.identity()),
section("Session type", session_prompt),
];
if matches!(channel, "telegram" | "telegram-group") {
sections.push(section("Telegram session", self.0.telegram_session()));
}
if channel == "telegram-group" {
sections.push(section(
"Telegram group session",
self.0.telegram_group_session(),
));
}
sections.extend([
section("Kmap basics", self.0.kmap_basics()),
section("Critical Kmap and context tools", self.0.read_tools()),
]);
if writes {
sections.push(section("Write tools", self.0.write_tools()));
}
sections.push(section("Codex harness", self.0.codex_harness()));
if !session_context.trim().is_empty() {
sections.push(section("Self-time schedule", session_context.trim()));
}
sections.push(section(
"Session runtime",
&runtime_description(
runtime,
runtime_facts.opened_at,
runtime_facts.context_limit_tokens,
),
));
sections.join("\n\n")
}
}
fn section(title: &str, content: &str) -> String {
format!("{title}\n\n{content}")
}
pub fn runtime_description(
runtime: &RuntimeModel,
opened_at: DateTime<Utc>,
context_limit_tokens: u64,
) -> String {
format!(
"You are running on {} with {} thinking mode. This session opened at {}. The active failure-avoidance context limit is {} tokens.",
runtime.model,
runtime.reasoning_effort,
human_utc_datetime(opened_at),
context_limit_tokens,
)
}
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 chrono::TimeZone;
use super::*;
fn testing_manuals() -> Manuals {
Manuals::open()
}
fn testing_runtime() -> RuntimeModel {
RuntimeModel {
model: "gpt-5.6-sol".into(),
reasoning_effort: "xhigh".into(),
context_window_tokens: 1_000_000,
}
}
fn opened_at() -> DateTime<Utc> {
Utc.with_ymd_and_hms(2026, 7, 6, 4, 23, 0).unwrap()
}
#[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", "", opened_at());
assert!(!browser.contains("This session belongs to a Telegram conversation."));
assert!(!browser.contains("This is a Telegram group session"));
let private = manuals.compose_conversation(&runtime, "telegram", "", opened_at());
assert!(private.contains("This session belongs to a Telegram conversation."));
assert!(!private.contains("This is a Telegram group session"));
let group = manuals.compose_conversation(&runtime, "telegram-group", "", opened_at());
assert!(group.contains("This session belongs to a Telegram conversation."));
assert!(group.contains("This is a Telegram group session"));
let group_ingress = manuals.compose_ingress(&runtime, "telegram-group", opened_at());
assert!(group_ingress.contains("This session belongs to a Telegram conversation."));
assert!(group_ingress.contains("This is a Telegram group session"));
let audio = manuals.compose_ingress(&runtime, "audio", opened_at());
assert!(!audio.contains("This session belongs to a Telegram conversation."));
assert!(!audio.contains("This is a Telegram group session"));
}
#[test]
fn wakeup_sessions_have_their_own_autonomous_write_prompt() {
let prompt =
testing_manuals().compose_conversation(&testing_runtime(), "wakeup", "", opened_at());
assert!(prompt.contains("You are in a wakeup session"));
assert!(prompt.contains("ConnectNodes"));
assert!(!prompt.contains("This is a conversation session with a user."));
assert!(!prompt.contains("This session belongs to a Telegram conversation."));
}
#[test]
fn subagent_codex_prompt_is_exactly_one_bundled_layer() {
let prompt = testing_manuals().subagent_codex_prompt();
assert_eq!(prompt, kcode_kennedy_prompts::open().codex_harness());
}
#[test]
fn ordinary_prompt_omits_static_context_control_manuals() {
let prompt = testing_manuals().compose_conversation(
&testing_runtime(),
"conversation",
"",
opened_at(),
);
for omitted in ["DehydrateBoxes", "SummarizeBox", "HydrateBox"] {
assert!(
!prompt.contains(omitted),
"ordinary prompt unexpectedly contains {omitted}"
);
}
for retained in ["LoadNodes", "RunSubagent", "NoteToSelf", "BoxesIntoObjects"] {
assert!(
prompt.contains(retained),
"ordinary prompt unexpectedly omits {retained}"
);
}
}
#[test]
fn writable_prompt_defines_map_marker_without_legacy_short_description_phrase() {
let prompt = testing_manuals().compose_conversation(
&testing_runtime(),
"free-time",
"",
opened_at(),
);
let normalized = prompt.split_whitespace().collect::<Vec<_>>().join(" ");
let marker = normalized
.find("map marker")
.expect("writable prompt should define map marker");
let beyond = normalized
.find("what lies beyond the hop")
.expect("map marker definition should explain what lies beyond the hop");
assert!(
marker.abs_diff(beyond) < 256,
"map marker definition should keep its explanation together"
);
for key in ["shortDescription", "newShortDescription"] {
assert!(prompt.contains(key), "writable prompt should retain {key}");
}
assert!(!prompt.contains("short description"));
}
#[test]
fn runtime_facts_use_the_stable_session_open_time_and_limit() {
let prompt = testing_manuals().compose_conversation(
&testing_runtime(),
"conversation",
"",
opened_at(),
);
assert!(prompt.contains("This session opened at July 6th, 2026, 4:23am UTC."));
assert!(prompt.contains("active failure-avoidance context limit is 700000 tokens"));
assert!(!prompt.contains("current date and time"));
}
}