From 4a1a26409f4114078f6af0272099dfe96a589fc0 Mon Sep 17 00:00:00 2001 From: Argenis Date: Fri, 20 Mar 2026 01:55:34 -0400 Subject: [PATCH] fix(skills): preserve TOML [[tools]] in Compact prompt injection mode (#4032) In Compact (MetadataOnly) mode, skill tools were omitted from the system prompt alongside instructions. This meant the LLM had no visibility into TOML-defined tools when running in Compact mode, defeating the primary advantage of TOML skills over MD skills (structured tool metadata). Now Compact mode skips only instructions (loaded on demand via read_skill) while still inlining tool definitions so the LLM knows which skill tools are available. Closes #3702 --- src/agent/prompt.rs | 7 +++++-- src/channels/mod.rs | 7 +++++-- src/skills/mod.rs | 44 +++++++++++++++++++++++++------------------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/agent/prompt.rs b/src/agent/prompt.rs index 0f144015e..245c5c106 100644 --- a/src/agent/prompt.rs +++ b/src/agent/prompt.rs @@ -445,7 +445,7 @@ mod tests { } #[test] - fn skills_section_compact_mode_omits_instructions_and_tools() { + fn skills_section_compact_mode_omits_instructions_but_keeps_tools() { let tools: Vec> = vec![]; let skills = vec![crate::skills::Skill { name: "deploy".into(), @@ -482,7 +482,10 @@ mod tests { assert!(output.contains("skills/deploy/SKILL.md")); assert!(output.contains("read_skill(name)")); assert!(!output.contains("Run smoke tests before deploy.")); - assert!(!output.contains("")); + // Compact mode should still include tools so the LLM knows about them + assert!(output.contains("")); + assert!(output.contains("release_checklist")); + assert!(output.contains("shell")); } #[test] diff --git a/src/channels/mod.rs b/src/channels/mod.rs index e67bdcdcc..cb42f92ac 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -7317,7 +7317,7 @@ BTC is currently around $65,000 based on latest tool output."# } #[test] - fn prompt_skills_compact_mode_omits_instructions_and_tools() { + fn prompt_skills_compact_mode_omits_instructions_but_keeps_tools() { let ws = make_workspace(); let skills = vec![crate::skills::Skill { name: "code-review".into(), @@ -7355,7 +7355,10 @@ BTC is currently around $65,000 based on latest tool output."# assert!(!prompt.contains("")); assert!(!prompt .contains("Always run cargo test before final response.")); - assert!(!prompt.contains("")); + // Compact mode should still include tools so the LLM knows about them + assert!(prompt.contains("")); + assert!(prompt.contains("lint")); + assert!(prompt.contains("shell")); } #[test] diff --git a/src/skills/mod.rs b/src/skills/mod.rs index 82a2fe604..129b824d8 100644 --- a/src/skills/mod.rs +++ b/src/skills/mod.rs @@ -714,26 +714,29 @@ pub fn skills_to_prompt_with_mode( ); write_xml_text_element(&mut prompt, 4, "location", &location); - if matches!(mode, crate::config::SkillsPromptInjectionMode::Full) { - if !skill.prompts.is_empty() { - let _ = writeln!(prompt, " "); - for instruction in &skill.prompts { - write_xml_text_element(&mut prompt, 6, "instruction", instruction); - } - let _ = writeln!(prompt, " "); + // In Full mode, inline both instructions and tools. + // In Compact mode, skip instructions (loaded on demand) but keep tools + // so the LLM knows which skill tools are available. + if matches!(mode, crate::config::SkillsPromptInjectionMode::Full) + && !skill.prompts.is_empty() + { + let _ = writeln!(prompt, " "); + for instruction in &skill.prompts { + write_xml_text_element(&mut prompt, 6, "instruction", instruction); } + let _ = writeln!(prompt, " "); + } - if !skill.tools.is_empty() { - let _ = writeln!(prompt, " "); - for tool in &skill.tools { - let _ = writeln!(prompt, " "); - write_xml_text_element(&mut prompt, 8, "name", &tool.name); - write_xml_text_element(&mut prompt, 8, "description", &tool.description); - write_xml_text_element(&mut prompt, 8, "kind", &tool.kind); - let _ = writeln!(prompt, " "); - } - let _ = writeln!(prompt, " "); + if !skill.tools.is_empty() { + let _ = writeln!(prompt, " "); + for tool in &skill.tools { + let _ = writeln!(prompt, " "); + write_xml_text_element(&mut prompt, 8, "name", &tool.name); + write_xml_text_element(&mut prompt, 8, "description", &tool.description); + write_xml_text_element(&mut prompt, 8, "kind", &tool.kind); + let _ = writeln!(prompt, " "); } + let _ = writeln!(prompt, " "); } let _ = writeln!(prompt, " "); @@ -1286,7 +1289,7 @@ command = "echo hello" } #[test] - fn skills_to_prompt_compact_mode_omits_instructions_and_tools() { + fn skills_to_prompt_compact_mode_omits_instructions_but_keeps_tools() { let skills = vec![Skill { name: "test".to_string(), description: "A test".to_string(), @@ -1316,7 +1319,10 @@ command = "echo hello" assert!(prompt.contains("read_skill(name)")); assert!(!prompt.contains("")); assert!(!prompt.contains("Do the thing.")); - assert!(!prompt.contains("")); + // Compact mode should still include tools so the LLM knows about them + assert!(prompt.contains("")); + assert!(prompt.contains("run")); + assert!(prompt.contains("shell")); } #[test]