feat(skills): the skills section comes right after the identity paragraph, not last
`compose_turn_prompt` appended `# Your skills` after the task, the tool list, the workspace rules and the marker contract — 87–90% of the way into a 6 KB prompt. It is the one section that asks the agent to do something BEFORE it starts (read a procedure), and on the three `index`-arm runs the agents' narratives never mentioned it. Position was the untested lever in the retrieval work; this puts the section second, after "You are the … agent" and before "Task:", and measures it on the next mission. The readers (`mode_in_prompt`, `skill_was_indexed`, `skill_names_in`) match lines, not offsets, so every stored prompt still scores. Two tests pin the order. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
This commit is contained in:
co-authored by
Claude Opus 5
parent
4e342d1ff7
commit
a50c41a9c2
@@ -915,7 +915,47 @@ pub fn compose_turn_prompt(
|
||||
// is also what the scorer reads the arm back from. Two copies of this
|
||||
// sentence is two chances for the reader to stop recognising the writer.
|
||||
let preamble = crate::skill_delivery::preamble(mode);
|
||||
format!("{base}\n\n# Your skills\n\n{preamble}\n\n{skills}")
|
||||
let section = format!("# Your skills\n\n{preamble}\n\n{skills}");
|
||||
// After the identity paragraph and BEFORE the task. This section is the
|
||||
// one part of the prompt that asks the agent to do something before it
|
||||
// starts — read a procedure — and until 2026-09-18 it was appended last,
|
||||
// after the task, the tool list, the workspace rules and the marker
|
||||
// contract, sitting 87–90% of the way into a 6 KB prompt. On the three
|
||||
// `index`-arm runs the narratives never mentioned it at all. Position is
|
||||
// the untested lever; this is the test.
|
||||
match base.split_once("\n\n") {
|
||||
Some((identity, rest)) if identity.starts_with("You are ") => {
|
||||
format!("{identity}\n\n{section}\n\n{rest}")
|
||||
}
|
||||
_ => format!("{section}\n\n{base}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod prompt_order_tests {
|
||||
use super::compose_turn_prompt;
|
||||
use crate::skill_delivery::Mode;
|
||||
|
||||
/// The section comes right after the identity paragraph, before the task —
|
||||
/// not appended after everything else.
|
||||
#[test]
|
||||
fn skills_come_after_identity_and_before_the_task() {
|
||||
let base = "You are the \"x\" agent. Do your part.\n\nTask: MISSION: y\n\nTOOLS AVAILABLE";
|
||||
let p = compose_turn_prompt(base, Some("--- SKILL: a ---\nbody"), Mode::Files);
|
||||
let i_id = p.find("You are the").unwrap();
|
||||
let i_sk = p.find("# Your skills").unwrap();
|
||||
let i_task = p.find("Task: MISSION").unwrap();
|
||||
assert!(i_id < i_sk && i_sk < i_task, "order was identity={i_id} skills={i_sk} task={i_task}\n{p}");
|
||||
assert!(p.ends_with("TOOLS AVAILABLE"), "the base's tail is untouched");
|
||||
}
|
||||
|
||||
/// A base with no identity paragraph still gets the section first.
|
||||
#[test]
|
||||
fn skills_lead_when_there_is_no_identity_paragraph() {
|
||||
let p = compose_turn_prompt("Task: y", Some("--- SKILL: a ---\nbody"), Mode::Files);
|
||||
assert!(p.starts_with("# Your skills"), "{p}");
|
||||
assert!(p.ends_with("Task: y"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `role=alias,role=alias` into a map (blank/malformed entries skipped).
|
||||
|
||||
Reference in New Issue
Block a user