use std::collections::BTreeSet;
use crate::runtime::config::AgentLoopConfig;
use bamboo_agent_core::tools::ToolSchema;
use bamboo_agent_core::{
parse_prompt_external_memory_sections, Message, PromptMemoryObservability, PromptSnapshot,
Session,
};
use bamboo_tools::guide::{context::GuideBuildContext, EnhancedPromptBuilder};
use super::super::prompt_context::merge_system_prompt_with_contexts;
const RUNTIME_PROMPT_COMPOSER_VERSION: &str = "bamboo.runtime-system-prompt.v3";
const RUNTIME_PROMPT_VERSION_KEY: &str = "runtime_prompt_composer_version";
const RUNTIME_PROMPT_FLAGS_KEY: &str = "runtime_prompt_component_flags";
const RUNTIME_PROMPT_LENGTHS_KEY: &str = "runtime_prompt_component_lengths";
const RUNTIME_PROMPT_SECTION_LAYOUT_KEY: &str = "runtime_prompt_section_layout";
pub(crate) const RUNTIME_PROMPT_SNAPSHOT_KEY: &str = "runtime_prompt_snapshot";
const RUNTIME_PROMPT_MEMORY_OBSERVABILITY_KEY: &str =
crate::runtime::runner::prompt_context::PROMPT_MEMORY_OBSERVABILITY_KEY;
const BASE_PROMPT_SECTION_ID: &str = "base_prompt";
const WORKSPACE_CONTEXT_SECTION_ID: &str = "workspace_context";
const INSTRUCTION_CONTEXT_SECTION_ID: &str = "instruction_context";
const ENV_CONTEXT_SECTION_ID: &str = "env_context";
const SKILL_CONTEXT_SECTION_ID: &str = "skill_context";
const TOOL_GUIDE_SECTION_ID: &str = "tool_guide_context";
const WORKSPACE_CONTEXT_START_MARKER: &str =
crate::runtime::context::WORKSPACE_CONTEXT_START_MARKER;
const WORKSPACE_CONTEXT_END_MARKER: &str = crate::runtime::context::WORKSPACE_CONTEXT_END_MARKER;
const WORKSPACE_CONTEXT_PREFIX: &str = crate::runtime::context::WORKSPACE_CONTEXT_PREFIX;
const ENV_CONTEXT_START_MARKER: &str = crate::runtime::context::ENV_CONTEXT_START_MARKER;
const ENV_CONTEXT_END_MARKER: &str = crate::runtime::context::ENV_CONTEXT_END_MARKER;
const INSTRUCTION_CONTEXT_START_MARKER: &str =
crate::runtime::context::instruction::INSTRUCTION_CONTEXT_START_MARKER;
const INSTRUCTION_CONTEXT_END_MARKER: &str =
crate::runtime::context::instruction::INSTRUCTION_CONTEXT_END_MARKER;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PromptLayer {
CoreStatic,
EnvironmentWorkspace,
EnvironmentInstruction,
EnvironmentConfiguration,
SkillMetadata,
CapabilityTool,
}
impl PromptLayer {
pub(super) fn as_str(self) -> &'static str {
match self {
Self::CoreStatic => "core_static",
Self::EnvironmentWorkspace => "environment_workspace",
Self::EnvironmentInstruction => "environment_instruction",
Self::EnvironmentConfiguration => "environment_configuration",
Self::SkillMetadata => "skill_metadata",
Self::CapabilityTool => "capability_tool",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PromptSection {
pub id: &'static str,
pub layer: PromptLayer,
pub is_dynamic: bool,
pub enabled: bool,
pub content: String,
}
impl PromptSection {
pub(crate) fn new(
id: &'static str,
layer: PromptLayer,
is_dynamic: bool,
content: impl Into<String>,
) -> Self {
let content = content.into();
let enabled = !content.trim().is_empty();
Self {
id,
layer,
is_dynamic,
enabled,
content,
}
}
pub(super) fn len(&self) -> usize {
self.content.len()
}
pub(super) fn scope_label(&self) -> &'static str {
if self.is_dynamic {
"dynamic"
} else {
"static"
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct PromptAssemblyReport {
pub version: &'static str,
pub sections: Vec<PromptSection>,
pub final_len: usize,
}
impl PromptAssemblyReport {
pub(crate) fn from_sections(sections: Vec<PromptSection>, final_prompt: &str) -> Self {
Self {
version: RUNTIME_PROMPT_COMPOSER_VERSION,
sections,
final_len: final_prompt.len(),
}
}
#[cfg(test)]
pub(crate) fn section(&self, id: &str) -> Option<&PromptSection> {
self.sections.iter().find(|section| section.id == id)
}
fn section_enabled_by_name(&self, id: &str) -> bool {
self.sections
.iter()
.any(|section| section.id == id && section.enabled)
}
fn dynamic_len_by_name(&self, id: &str) -> usize {
self.sections
.iter()
.filter(|section| section.id == id && section.enabled && section.is_dynamic)
.map(PromptSection::len)
.sum()
}
pub(crate) fn component_flags_value(&self) -> String {
format!(
"workspace={};instruction={};env={};skill={};tool_guide={};external_memory={};task_list={}",
self.section_enabled_by_name(WORKSPACE_CONTEXT_SECTION_ID) as u8,
self.section_enabled_by_name(INSTRUCTION_CONTEXT_SECTION_ID) as u8,
self.section_enabled_by_name(ENV_CONTEXT_SECTION_ID) as u8,
self.section_enabled_by_name(SKILL_CONTEXT_SECTION_ID) as u8,
self.section_enabled_by_name(TOOL_GUIDE_SECTION_ID) as u8,
self.section_enabled_by_name("external_memory") as u8,
self.section_enabled_by_name("task_list") as u8,
)
}
pub(crate) fn component_lengths_value(&self) -> String {
let base_len: usize = self
.sections
.iter()
.filter(|section| section.enabled && !section.is_dynamic)
.map(PromptSection::len)
.sum();
format!(
"base={};workspace={};instruction={};env={};skill={};tool_guide={};external_memory={};task_list={};final={}",
base_len,
self.dynamic_len_by_name(WORKSPACE_CONTEXT_SECTION_ID),
self.dynamic_len_by_name(INSTRUCTION_CONTEXT_SECTION_ID),
self.dynamic_len_by_name(ENV_CONTEXT_SECTION_ID),
self.dynamic_len_by_name(SKILL_CONTEXT_SECTION_ID),
self.dynamic_len_by_name(TOOL_GUIDE_SECTION_ID),
self.dynamic_len_by_name("external_memory"),
self.dynamic_len_by_name("task_list"),
self.final_len,
)
}
pub(crate) fn section_layout_value(&self) -> String {
self.sections
.iter()
.map(|section| {
format!(
"{}:{}:{}:{}:{}",
section.id,
section.layer.as_str(),
section.scope_label(),
section.enabled as u8,
section.len(),
)
})
.collect::<Vec<_>>()
.join(";")
}
pub(crate) fn to_prompt_snapshot(&self, effective_system_prompt: &str) -> PromptSnapshot {
let section_content = |id: &str| {
self.sections
.iter()
.find(|section| section.id == id && section.enabled)
.map(|section| section.content.trim().to_string())
.filter(|value| !value.is_empty())
};
let base_system_prompt = section_content(BASE_PROMPT_SECTION_ID).unwrap_or_default();
let workspace_context = section_content(WORKSPACE_CONTEXT_SECTION_ID);
let instruction_context = section_content(INSTRUCTION_CONTEXT_SECTION_ID);
let env_context = section_content(ENV_CONTEXT_SECTION_ID);
let skill_context = section_content(SKILL_CONTEXT_SECTION_ID);
let tool_guide_context = section_content(TOOL_GUIDE_SECTION_ID);
let enhancement_prompt = derive_enhancement_prompt(
&base_system_prompt,
effective_system_prompt,
workspace_context.as_deref(),
instruction_context.as_deref(),
env_context.as_deref(),
skill_context.as_deref(),
tool_guide_context.as_deref(),
None,
None,
);
let external_memory_parts = parse_prompt_external_memory_sections(None);
PromptSnapshot {
base_system_prompt,
enhancement_prompt,
workspace_context,
instruction_context,
env_context,
skill_context,
tool_guide_context,
dream_notebook: external_memory_parts.dream_notebook,
session_memory_note: external_memory_parts.session_memory_note,
project_memory_index: external_memory_parts.project_memory_index,
relevant_durable_memories: external_memory_parts.relevant_durable_memories,
project_dream: external_memory_parts.project_dream,
global_dream_fallback: external_memory_parts.global_dream_fallback,
prompt_memory_observability: None,
external_memory: None,
task_list: None,
effective_system_prompt: effective_system_prompt.trim().to_string(),
}
}
}
pub(crate) fn resolve_base_prompt_for_language<'a>(
config: &'a AgentLoopConfig,
session: &'a Session,
) -> &'a str {
config
.system_prompt
.as_deref()
.or_else(|| {
session
.messages
.iter()
.find(|message| matches!(message.role, bamboo_agent_core::Role::System))
.map(|message| message.content.as_str())
})
.unwrap_or_default()
}
pub(crate) fn build_tool_guide_context(
config: &AgentLoopConfig,
tool_schemas: &[ToolSchema],
base_prompt_for_language: &str,
session_id: &str,
activated_discoverable_tools: &BTreeSet<String>,
) -> String {
let normalized_base_prompt = normalize_base_prompt(base_prompt_for_language);
let mut guide_context = GuideBuildContext::from_system_prompt(&normalized_base_prompt);
guide_context.activated_discoverable_tools = activated_discoverable_tools.clone();
let tool_guide_context = EnhancedPromptBuilder::build(
Some(config.tool_registry.as_ref()),
tool_schemas,
&guide_context,
);
tracing::info!(
"[{}] Tool guide context built, length: {} chars",
session_id,
tool_guide_context.len()
);
tool_guide_context
}
pub(crate) fn apply_system_prompt_contexts(
session: &mut Session,
config: &AgentLoopConfig,
skill_context: &str,
tool_guide_context: &str,
) -> PromptAssemblyReport {
let (base_prompt, workspace_context, instruction_context, env_context, merged_prompt) =
if let Some(system_message) = session
.messages
.iter_mut()
.find(|message| matches!(message.role, bamboo_agent_core::Role::System))
{
let raw_base_prompt = config
.system_prompt
.as_deref()
.unwrap_or(&system_message.content)
.to_string();
let workspace_context = extract_workspace_context(&raw_base_prompt);
let instruction_context = workspace_context
.as_deref()
.and_then(workspace_path_from_context)
.and_then(crate::runtime::context::instruction::build_instruction_prompt_context);
let env_context = extract_env_context(&raw_base_prompt)
.or_else(crate::runtime::context::build_env_prompt_context);
let base_prompt = normalize_base_prompt(&raw_base_prompt);
let merged_prompt = merge_with_optional_contexts(
&base_prompt,
workspace_context.as_deref(),
instruction_context.as_deref(),
env_context.as_deref(),
skill_context,
tool_guide_context,
);
system_message.content = merged_prompt.clone();
(
base_prompt,
workspace_context,
instruction_context,
env_context,
merged_prompt,
)
} else {
let raw_base_prompt = config
.system_prompt
.as_deref()
.unwrap_or_default()
.to_string();
let workspace_context = extract_workspace_context(&raw_base_prompt);
let instruction_context = workspace_context
.as_deref()
.and_then(workspace_path_from_context)
.and_then(crate::runtime::context::instruction::build_instruction_prompt_context);
let env_context = extract_env_context(&raw_base_prompt)
.or_else(crate::runtime::context::build_env_prompt_context);
let base_prompt = normalize_base_prompt(&raw_base_prompt);
let merged_prompt = merge_with_optional_contexts(
&base_prompt,
workspace_context.as_deref(),
instruction_context.as_deref(),
env_context.as_deref(),
skill_context,
tool_guide_context,
);
if !merged_prompt.is_empty() {
session
.messages
.insert(0, Message::system(merged_prompt.clone()));
}
(
base_prompt,
workspace_context,
instruction_context,
env_context,
merged_prompt,
)
};
let sections = build_prompt_sections(
base_prompt.as_str(),
workspace_context.as_deref(),
instruction_context.as_deref(),
env_context.as_deref(),
skill_context,
tool_guide_context,
);
let report = PromptAssemblyReport::from_sections(sections, merged_prompt.as_str());
persist_runtime_prompt_metadata(session, &report);
persist_prompt_snapshot_metadata(session, report.to_prompt_snapshot(merged_prompt.as_str()));
log_runtime_prompt_assembly_summary(session, &report, merged_prompt.as_str());
report
}
pub(crate) fn persist_prompt_snapshot_metadata(session: &mut Session, snapshot: PromptSnapshot) {
session.prompt_snapshot = Some(snapshot);
}
pub fn read_prompt_snapshot_metadata(session: &Session) -> Option<PromptSnapshot> {
session.prompt_snapshot.clone().or_else(|| {
session
.metadata
.get(RUNTIME_PROMPT_SNAPSHOT_KEY)
.and_then(|raw| serde_json::from_str::<PromptSnapshot>(raw).ok())
})
}
fn read_prompt_memory_observability(session: &Session) -> Option<PromptMemoryObservability> {
session
.prompt_snapshot
.as_ref()
.and_then(|snapshot| snapshot.prompt_memory_observability.clone())
.or_else(|| {
session
.metadata
.get(RUNTIME_PROMPT_MEMORY_OBSERVABILITY_KEY)
.and_then(|raw| serde_json::from_str::<PromptMemoryObservability>(raw).ok())
})
}
pub fn refresh_prompt_snapshot_from_session(session: &mut Session) {
let effective_system_prompt = session
.messages
.iter()
.find(|message| matches!(message.role, bamboo_agent_core::Role::System))
.map(|message| message.content.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_default();
let workspace_context = session
.metadata
.get("workspace_path")
.and_then(|workspace_path| {
crate::runtime::context::build_workspace_prompt_context(workspace_path)
})
.or_else(|| extract_workspace_context(&effective_system_prompt));
let instruction_context = session
.metadata
.get("workspace_path")
.and_then(|workspace_path| {
crate::runtime::context::instruction::build_instruction_prompt_context(workspace_path)
})
.or_else(|| {
workspace_context
.as_deref()
.and_then(workspace_path_from_context)
.and_then(crate::runtime::context::instruction::build_instruction_prompt_context)
});
let env_context = extract_env_context(&effective_system_prompt)
.or_else(crate::runtime::context::build_env_prompt_context);
let skill_context = extract_wrapped_section(
&effective_system_prompt,
"<!-- BAMBOO_SKILL_CONTEXT_START -->",
"<!-- BAMBOO_SKILL_CONTEXT_END -->",
);
let tool_guide_context = extract_wrapped_section(
&effective_system_prompt,
"<!-- BAMBOO_TOOL_GUIDE_START -->",
"<!-- BAMBOO_TOOL_GUIDE_END -->",
);
let external_memory = extract_wrapped_section(
&effective_system_prompt,
"<!-- BAMBOO_EXTERNAL_MEMORY_START -->",
"<!-- BAMBOO_EXTERNAL_MEMORY_END -->",
);
let task_list = extract_wrapped_section(
&effective_system_prompt,
"<!-- BAMBOO_TASK_LIST_START -->",
"<!-- BAMBOO_TASK_LIST_END -->",
);
let base_system_prompt = session
.metadata
.get("base_system_prompt")
.cloned()
.unwrap_or_else(|| normalize_base_prompt(&effective_system_prompt));
let enhancement_prompt = session.metadata.get("enhance_prompt").cloned().or_else(|| {
derive_enhancement_prompt(
&base_system_prompt,
&effective_system_prompt,
workspace_context.as_deref(),
instruction_context.as_deref(),
env_context.as_deref(),
skill_context.as_deref(),
tool_guide_context.as_deref(),
external_memory.as_deref(),
task_list.as_deref(),
)
});
let external_memory_parts = parse_prompt_external_memory_sections(external_memory.as_deref());
persist_prompt_snapshot_metadata(
session,
PromptSnapshot {
base_system_prompt,
enhancement_prompt,
workspace_context,
instruction_context,
env_context,
skill_context,
tool_guide_context,
dream_notebook: external_memory_parts.dream_notebook,
session_memory_note: external_memory_parts.session_memory_note,
project_memory_index: external_memory_parts.project_memory_index,
relevant_durable_memories: external_memory_parts.relevant_durable_memories,
project_dream: external_memory_parts.project_dream,
global_dream_fallback: external_memory_parts.global_dream_fallback,
prompt_memory_observability: read_prompt_memory_observability(session),
external_memory,
task_list,
effective_system_prompt,
},
);
}
fn build_prompt_sections(
base_prompt: &str,
workspace_context: Option<&str>,
instruction_context: Option<&str>,
env_context: Option<&str>,
skill_context: &str,
tool_guide_context: &str,
) -> Vec<PromptSection> {
vec![
PromptSection::new(
BASE_PROMPT_SECTION_ID,
PromptLayer::CoreStatic,
false,
base_prompt,
),
PromptSection::new(
WORKSPACE_CONTEXT_SECTION_ID,
PromptLayer::EnvironmentWorkspace,
true,
workspace_context.unwrap_or_default(),
),
PromptSection::new(
INSTRUCTION_CONTEXT_SECTION_ID,
PromptLayer::EnvironmentInstruction,
true,
instruction_context.unwrap_or_default(),
),
PromptSection::new(
ENV_CONTEXT_SECTION_ID,
PromptLayer::EnvironmentConfiguration,
true,
env_context.unwrap_or_default(),
),
PromptSection::new(
SKILL_CONTEXT_SECTION_ID,
PromptLayer::SkillMetadata,
true,
skill_context,
),
PromptSection::new(
TOOL_GUIDE_SECTION_ID,
PromptLayer::CapabilityTool,
true,
tool_guide_context,
),
]
}
fn normalize_base_prompt(prompt: &str) -> String {
let without_workspace = strip_workspace_context(prompt);
let without_instruction = strip_instruction_context(&without_workspace);
let without_env = strip_env_context(&without_instruction);
merge_system_prompt_with_contexts(&without_env, "", "")
}
fn merge_with_optional_contexts(
base_prompt: &str,
workspace_context: Option<&str>,
instruction_context: Option<&str>,
env_context: Option<&str>,
skill_context: &str,
tool_guide_context: &str,
) -> String {
let merged = merge_system_prompt_with_contexts(base_prompt, skill_context, tool_guide_context);
let mut sections = Vec::new();
if let Some(workspace_context) = workspace_context
.map(str::trim)
.filter(|value| !value.is_empty())
{
sections.push(workspace_context.to_string());
}
if let Some(instruction_context) = instruction_context
.map(str::trim)
.filter(|value| !value.is_empty())
{
sections.push(instruction_context.to_string());
}
if let Some(env_context) = env_context.map(str::trim).filter(|value| !value.is_empty()) {
sections.push(env_context.to_string());
}
if sections.is_empty() {
merged
} else {
format!("{}\n\n{}", merged.trim_end(), sections.join("\n\n"))
}
}
fn derive_enhancement_prompt(
base_system_prompt: &str,
effective_system_prompt: &str,
workspace_context: Option<&str>,
instruction_context: Option<&str>,
env_context: Option<&str>,
skill_context: Option<&str>,
tool_guide_context: Option<&str>,
external_memory: Option<&str>,
task_list: Option<&str>,
) -> Option<String> {
let mut stripped = effective_system_prompt.trim().to_string();
for section in [
workspace_context,
instruction_context,
env_context,
skill_context,
tool_guide_context,
external_memory,
task_list,
]
.into_iter()
.flatten()
.map(str::trim)
.filter(|value| !value.is_empty())
{
stripped = stripped.replace(section, "");
}
let normalized = stripped.trim().to_string();
let base = base_system_prompt.trim();
if base.is_empty() || normalized.is_empty() || normalized == base {
None
} else if let Some(remainder) = normalized.strip_prefix(base) {
let remainder = remainder.trim();
(!remainder.is_empty()).then(|| remainder.to_string())
} else {
None
}
}
fn extract_workspace_context(prompt: &str) -> Option<String> {
extract_wrapped_section(
prompt,
WORKSPACE_CONTEXT_START_MARKER,
WORKSPACE_CONTEXT_END_MARKER,
)
.or_else(|| extract_legacy_workspace_context(prompt))
}
fn extract_env_context(prompt: &str) -> Option<String> {
extract_wrapped_section(prompt, ENV_CONTEXT_START_MARKER, ENV_CONTEXT_END_MARKER)
}
fn strip_workspace_context(prompt: &str) -> String {
strip_wrapped_section(
prompt,
WORKSPACE_CONTEXT_START_MARKER,
WORKSPACE_CONTEXT_END_MARKER,
)
.map(|stripped| stripped.trim().to_string())
.unwrap_or_else(|| strip_legacy_workspace_context(prompt).trim().to_string())
}
fn strip_instruction_context(prompt: &str) -> String {
strip_wrapped_section(
prompt,
INSTRUCTION_CONTEXT_START_MARKER,
INSTRUCTION_CONTEXT_END_MARKER,
)
.map(|stripped| stripped.trim().to_string())
.unwrap_or_else(|| prompt.trim().to_string())
}
fn strip_env_context(prompt: &str) -> String {
strip_wrapped_section(prompt, ENV_CONTEXT_START_MARKER, ENV_CONTEXT_END_MARKER)
.map(|stripped| stripped.trim().to_string())
.unwrap_or_else(|| prompt.trim().to_string())
}
fn workspace_path_from_context(context: &str) -> Option<&str> {
let context = context.trim();
let start = context.find(WORKSPACE_CONTEXT_PREFIX)?;
let rest = &context[start + WORKSPACE_CONTEXT_PREFIX.len()..];
let first_line = rest.lines().next()?.trim();
(!first_line.is_empty()).then_some(first_line)
}
fn extract_wrapped_section(prompt: &str, start_marker: &str, end_marker: &str) -> Option<String> {
let start_idx = prompt.find(start_marker)?;
let section_start = start_idx + start_marker.len();
let end_rel_idx = prompt[section_start..].find(end_marker)?;
let section_end = section_start + end_rel_idx;
let section = prompt[start_idx..section_end + end_marker.len()].trim();
(!section.is_empty()).then(|| section.to_string())
}
fn strip_wrapped_section(prompt: &str, start_marker: &str, end_marker: &str) -> Option<String> {
let start_idx = prompt.find(start_marker)?;
let section_start = start_idx + start_marker.len();
let end_rel_idx = prompt[section_start..].find(end_marker)?;
let end_idx = section_start + end_rel_idx + end_marker.len();
let before = prompt[..start_idx].trim_end();
let after = prompt[end_idx..].trim_start();
Some(match (before.is_empty(), after.is_empty()) {
(true, true) => String::new(),
(true, false) => after.to_string(),
(false, true) => before.to_string(),
(false, false) => format!("{before}\n\n{after}"),
})
}
fn extract_legacy_workspace_context(prompt: &str) -> Option<String> {
let start_idx = prompt.find(WORKSPACE_CONTEXT_PREFIX)?;
let guidance = crate::runtime::context::workspace_prompt_guidance();
let end_idx = prompt[start_idx..]
.find(&guidance)
.map(|guidance_rel_idx| start_idx + guidance_rel_idx + guidance.len())
.unwrap_or(prompt.len());
let section = prompt[start_idx..end_idx].trim();
(!section.is_empty()).then(|| section.to_string())
}
fn strip_legacy_workspace_context(prompt: &str) -> String {
let Some(start_idx) = prompt.find(WORKSPACE_CONTEXT_PREFIX) else {
return prompt.to_string();
};
let guidance = crate::runtime::context::workspace_prompt_guidance();
let end_idx = prompt[start_idx..]
.find(&guidance)
.map(|guidance_rel_idx| start_idx + guidance_rel_idx + guidance.len())
.unwrap_or(prompt.len());
let before = prompt[..start_idx].trim_end();
let after = prompt[end_idx..].trim_start();
match (before.is_empty(), after.is_empty()) {
(true, true) => String::new(),
(true, false) => after.to_string(),
(false, true) => before.to_string(),
(false, false) => format!("{before}\n\n{after}"),
}
}
fn log_runtime_prompt_assembly_summary(
session: &Session,
report: &PromptAssemblyReport,
final_prompt: &str,
) {
let session_id = session.id.as_str();
tracing::info!(
"[{}] Runtime prompt assembly summary: composer_version={}, {}, {}, sections={}",
session_id,
report.version,
report.component_flags_value(),
report.component_lengths_value(),
report.section_layout_value(),
);
tracing::debug!(
"[{}] Runtime prompt assembly details: final_len={}, section_count={}",
session_id,
report.final_len,
report.sections.len(),
);
tracing::debug!(
"[{}] ========== EFFECTIVE SYSTEM PROMPT AFTER SESSION SETUP ==========",
session_id
);
tracing::debug!("[{}] {}", session_id, final_prompt);
tracing::debug!(
"[{}] ========== END EFFECTIVE SYSTEM PROMPT AFTER SESSION SETUP ==========",
session_id
);
}
fn persist_runtime_prompt_metadata(session: &mut Session, report: &PromptAssemblyReport) {
session.metadata.insert(
RUNTIME_PROMPT_VERSION_KEY.to_string(),
report.version.to_string(),
);
session.metadata.insert(
RUNTIME_PROMPT_FLAGS_KEY.to_string(),
report.component_flags_value(),
);
session.metadata.insert(
RUNTIME_PROMPT_LENGTHS_KEY.to_string(),
report.component_lengths_value(),
);
session.metadata.insert(
RUNTIME_PROMPT_SECTION_LAYOUT_KEY.to_string(),
report.section_layout_value(),
);
}