//! First-class, inspectable system prompt context. //! //! `PromptContext` captures the agent-specific inputs to prompt rendering //! as a serializable struct. Users can dump it as JSON and inspect //! individual sections. //! //! Rendering is done by `ToolBridge::render_prompt()` which delegates to //! `TemplateRenderer` in `kigi-tools`. This struct does NOT own a //! render engine — it provides placeholders and discovered sections. use crate::config::PromptMode; use crate::prompt::agents_md::{self, AgentConfigFile}; use crate::prompt::template::{apply_patch_template, base_template, subagent_template}; use serde::de; use serde::{Deserialize, Serialize}; /// Selects which base template to use for `Extend` mode rendering. /// /// Built-in variants decrypt the template on demand and never store /// the plaintext persistently, ensuring it is zeroed after use. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] #[serde(rename_all = "snake_case")] pub enum TemplateOverride { /// Use the standard base template (or subagent template based on audience). #[default] None, /// Use the apply-patch profile prompt template (decrypted on demand). Codex, /// A caller-provided custom template string. Custom(String), } /// Backward-compatible deserialization: accepts both the new tagged format /// (`"none"`, `"codex"`, `{"custom": "..."}`) and the legacy format where /// `system_prompt` was `Option` (a raw template string). impl<'de> Deserialize<'de> for TemplateOverride { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct Visitor; impl<'de> de::Visitor<'de> for Visitor { type Value = TemplateOverride; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(r#""none", "codex", "cursor", {"custom": "..."}, or a template string"#) } fn visit_str(self, v: &str) -> Result { match v { "none" => Ok(TemplateOverride::None), "codex" => Ok(TemplateOverride::Codex), other => Ok(TemplateOverride::Custom(other.to_owned())), } } fn visit_map>( self, mut map: M, ) -> Result { match map.next_key::()? { Some(ref k) if k == "custom" => { let val: String = map.next_value()?; Ok(TemplateOverride::Custom(val)) } Some(other) => Err(de::Error::unknown_field(&other, &["custom"])), Option::None => Err(de::Error::custom(r#"expected {"custom": "..."}"#)), } } } deserializer.deserialize_any(Visitor) } } /// Controls which base template and catalog sections are rendered. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "snake_case")] pub enum PromptAudience { /// Top-level interactive session. Full base template, all catalog sections. #[default] Primary, /// Child/subagent session. Compact base template, no persona/subagent catalogs. Subagent, } use kigi_tools::bridge::ToolBridge; /// Agent-specific inputs for system prompt rendering. /// /// Serializable (JSON/YAML) so users can dump it and inspect fields. /// Rendering goes through `ToolBridge::render_prompt()`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PromptContext { /// Schema version for forward-compatible persistence. pub version: u32, /// Which prompt mode produced this context. pub prompt_mode: PromptMode, /// Whether this is a primary (parent) or subagent (child) session. /// Controls base template choice and catalog section rendering. #[serde(default)] pub audience: PromptAudience, /// Custom body: appended after base template (Extend) or the entire /// prompt (Full). `None` = base template only. #[serde(skip_serializing_if = "Option::is_none")] pub prompt_body: Option, /// Which base template to use for `Extend` mode. /// `TemplateOverride::None` = standard base/subagent template. /// `TemplateOverride::Codex` = apply-patch profile template (decrypted on demand). /// `TemplateOverride::Custom` = caller-provided template string. #[serde(default, skip_serializing_if = "is_template_override_none")] pub system_prompt: TemplateOverride, /// AGENTS.md files discovered during build, in precedence order /// (repo root → CWD; deeper files override). pub agents_md_files: Vec, /// Pre-rendered persona summaries for system prompt injection. /// Each entry is a formatted string like: /// `- **reviewer** [user]: Writes structured review notes...` #[serde(default)] pub persona_summaries: Vec, /// ISO-8601 UTC timestamp captured at build time. pub build_timestamp_utc: String, /// Whether the memory system is enabled for this session. /// When true, the system prompt includes a `` section telling /// the model it can use `memory_search` and `memory_get`. #[serde(default)] pub memory_enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub memory_global_path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub memory_workspace_path: Option, /// Role instructions to include in the system prompt. /// Moved from the user task prompt so they're part of durable identity. #[serde(default, skip_serializing_if = "Option::is_none")] pub role_instructions: Option, /// Persona instructions to include in the system prompt. /// Moved from the user task prompt so they're part of durable identity. #[serde(default, skip_serializing_if = "Option::is_none")] pub persona_instructions: Option, /// OS name for the `` system prompt block. #[serde(default, skip_serializing_if = "Option::is_none")] pub os_name: Option, /// User's default shell for the `` system prompt block. #[serde(default, skip_serializing_if = "Option::is_none")] pub shell_path: Option, /// Model-facing working directory for the `` system prompt block. #[serde(default, skip_serializing_if = "Option::is_none")] pub working_directory: Option, /// Current date (`YYYY-MM-DD`) in the user's local timezone, for the /// `` system prompt block. #[serde(default, skip_serializing_if = "Option::is_none")] pub current_date: Option, /// Whether the agent is running in a non-interactive (headless / SDK / /// stdio / generic-ACP). #[serde(default)] pub is_non_interactive: bool, /// Identity in the primary grok-build system prompt (`You are