mirror of
https://github.com/instructkr/claw-code.git
synced 2026-10-31 00:04:39 +08:00
fix(runtime): name the session model in the system prompt (#3359)
The environment section rendered "Model family: Claude Opus 4.6" for every Anthropic model, so claude-opus-5-5, claude-sonnet-4-6, and any other Claude model told users it was Opus 4.6. Render a neutral "Claude" family label followed by the resolved model ID, and rebuild the system prompt on /model so the line follows a mid-session switch instead of keeping the startup model.
This commit is contained in:
1 parent
3cc90cfc7a
commit
66418c6127
4 files changed
+64
-15
No files matched your search
@@ -151,7 +151,7 @@ pub use policy_engine::{
|
||||
pub use prompt::{
|
||||
load_system_prompt, load_system_prompt_with_context, prepend_bullets, ContextFile,
|
||||
ModelFamilyIdentity, ProjectContext, PromptBuildError, SystemPromptBuilder,
|
||||
FRONTIER_MODEL_NAME, SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
|
||||
SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
|
||||
};
|
||||
pub use recovery_recipes::{
|
||||
attempt_recovery, recipe_for, EscalationPolicy, FailureScenario, RecoveryAttemptState,
|
||||
|
||||
@@ -38,8 +38,6 @@ impl From<ConfigError> for PromptBuildError {
|
||||
|
||||
/// Marker separating static prompt scaffolding from dynamic runtime context.
|
||||
pub const SYSTEM_PROMPT_DYNAMIC_BOUNDARY: &str = "__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__";
|
||||
/// Human-readable default frontier model name embedded into generated prompts.
|
||||
pub const FRONTIER_MODEL_NAME: &str = "Claude Opus 4.6";
|
||||
const MAX_INSTRUCTION_FILE_CHARS: usize = 4_000;
|
||||
const MAX_TOTAL_INSTRUCTION_CHARS: usize = 12_000;
|
||||
const MAX_GIT_DIFF_CHARS: usize = 50_000;
|
||||
@@ -56,7 +54,7 @@ impl ModelFamilyIdentity {
|
||||
#[must_use]
|
||||
pub const fn family_label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Claude => FRONTIER_MODEL_NAME,
|
||||
Self::Claude => "Claude",
|
||||
Self::Generic => "an AI assistant",
|
||||
}
|
||||
}
|
||||
@@ -158,6 +156,7 @@ pub struct SystemPromptBuilder {
|
||||
os_name: Option<String>,
|
||||
os_version: Option<String>,
|
||||
model_family: Option<ModelFamilyIdentity>,
|
||||
model: Option<String>,
|
||||
append_sections: Vec<String>,
|
||||
project_context: Option<ProjectContext>,
|
||||
config: Option<RuntimeConfig>,
|
||||
@@ -191,6 +190,12 @@ impl SystemPromptBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_model(mut self, model: impl Into<String>) -> Self {
|
||||
self.model = Some(model.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_project_context(mut self, project_context: ProjectContext) -> Self {
|
||||
self.project_context = Some(project_context);
|
||||
@@ -294,9 +299,11 @@ impl SystemPromptBuilder {
|
||||
|context| context.current_date.clone(),
|
||||
);
|
||||
let identity = self.model_family.unwrap_or_default();
|
||||
let mut lines = vec!["# Environment context".to_string()];
|
||||
lines.extend(prepend_bullets(vec![
|
||||
format!("Model family: {}", identity.family_label()),
|
||||
let mut items = vec![format!("Model family: {}", identity.family_label())];
|
||||
if let Some(model) = &self.model {
|
||||
items.push(format!("Model: {model}"));
|
||||
}
|
||||
items.extend([
|
||||
format!("Working directory: {cwd}"),
|
||||
format!("Date: {date}"),
|
||||
format!(
|
||||
@@ -304,7 +311,9 @@ impl SystemPromptBuilder {
|
||||
self.os_name.as_deref().unwrap_or("unknown"),
|
||||
self.os_version.as_deref().unwrap_or("unknown")
|
||||
),
|
||||
]));
|
||||
]);
|
||||
let mut lines = vec!["# Environment context".to_string()];
|
||||
lines.extend(prepend_bullets(items));
|
||||
lines.join("\n")
|
||||
}
|
||||
}
|
||||
@@ -670,11 +679,18 @@ pub fn load_system_prompt(
|
||||
current_date: impl Into<String>,
|
||||
os_name: impl Into<String>,
|
||||
os_version: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
model_family: ModelFamilyIdentity,
|
||||
) -> Result<Vec<String>, PromptBuildError> {
|
||||
let cwd = cwd.into();
|
||||
let (sections, _) =
|
||||
load_system_prompt_with_context(cwd, current_date, os_name, os_version, model_family)?;
|
||||
let (sections, _) = load_system_prompt_with_context(
|
||||
cwd,
|
||||
current_date,
|
||||
os_name,
|
||||
os_version,
|
||||
model,
|
||||
model_family,
|
||||
)?;
|
||||
Ok(sections)
|
||||
}
|
||||
|
||||
@@ -684,6 +700,7 @@ pub fn load_system_prompt_with_context(
|
||||
current_date: impl Into<String>,
|
||||
os_name: impl Into<String>,
|
||||
os_version: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
model_family: ModelFamilyIdentity,
|
||||
) -> Result<(Vec<String>, ProjectContext), PromptBuildError> {
|
||||
let cwd = cwd.into();
|
||||
@@ -693,6 +710,7 @@ pub fn load_system_prompt_with_context(
|
||||
let memory_enabled = config.feature_config().auto_memory_enabled();
|
||||
let mut builder = SystemPromptBuilder::new()
|
||||
.with_os(os_name, os_version)
|
||||
.with_model(model)
|
||||
.with_model_family(model_family)
|
||||
.with_project_context(project_context.clone())
|
||||
.with_runtime_config(config);
|
||||
@@ -1255,6 +1273,7 @@ mod tests {
|
||||
"2026-03-31",
|
||||
"linux",
|
||||
"6.8",
|
||||
"claude-opus-4-6",
|
||||
ModelFamilyIdentity::Claude,
|
||||
)
|
||||
.expect("system prompt should load")
|
||||
@@ -1304,6 +1323,7 @@ mod tests {
|
||||
"2026-03-31",
|
||||
"linux",
|
||||
"6.8",
|
||||
"claude-opus-4-6",
|
||||
ModelFamilyIdentity::Claude,
|
||||
)
|
||||
.expect("system prompt should load")
|
||||
@@ -1341,7 +1361,30 @@ mod tests {
|
||||
.render();
|
||||
|
||||
// then: the Claude model family label is preserved by default
|
||||
assert!(prompt.contains("Model family: Claude Opus 4.6"));
|
||||
assert!(prompt.contains(" - Model family: Claude\n"));
|
||||
assert!(!prompt.contains("Model:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_configured_model_instead_of_a_fixed_claude_version() {
|
||||
// given: a prompt builder for the model the session actually uses
|
||||
let project_context = ProjectContext {
|
||||
cwd: PathBuf::from("/tmp/project"),
|
||||
current_date: "2026-03-31".to_string(),
|
||||
..ProjectContext::default()
|
||||
};
|
||||
|
||||
// when: rendering the system prompt environment section
|
||||
let prompt = SystemPromptBuilder::new()
|
||||
.with_os("linux", "6.8")
|
||||
.with_model("claude-sonnet-4-6")
|
||||
.with_model_family(ModelFamilyIdentity::Claude)
|
||||
.with_project_context(project_context)
|
||||
.render();
|
||||
|
||||
// then: the prompt names that model rather than a hardcoded Opus release
|
||||
assert!(prompt.contains(" - Model family: Claude\n - Model: claude-sonnet-4-6\n"));
|
||||
assert!(!prompt.contains("Opus"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1364,9 +1407,9 @@ mod tests {
|
||||
.find(|line| line.contains("Model family:"))
|
||||
.expect("model family line should render");
|
||||
|
||||
// then: the model family line is neutral and excludes Claude Opus 4.6
|
||||
// then: the model family line is neutral and excludes Claude
|
||||
assert_eq!(model_family_line, " - Model family: an AI assistant");
|
||||
assert!(!model_family_line.contains("Claude Opus 4.6"));
|
||||
assert!(!model_family_line.contains("Claude"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -5012,6 +5012,7 @@ fn print_system_prompt(
|
||||
date,
|
||||
env::consts::OS,
|
||||
"unknown",
|
||||
model,
|
||||
model_family_identity_for(model),
|
||||
)?;
|
||||
let (project_root, _) =
|
||||
@@ -8749,11 +8750,12 @@ impl LiveCli {
|
||||
let previous = self.model.clone();
|
||||
let session = self.runtime.session().clone();
|
||||
let message_count = session.messages.len();
|
||||
let system_prompt = build_system_prompt(&model)?;
|
||||
let runtime = build_runtime(
|
||||
session,
|
||||
&self.session.id,
|
||||
model.clone(),
|
||||
self.system_prompt.clone(),
|
||||
system_prompt.clone(),
|
||||
true,
|
||||
true,
|
||||
TerminalTail::new(),
|
||||
@@ -8763,6 +8765,7 @@ impl LiveCli {
|
||||
)?;
|
||||
self.replace_runtime(runtime)?;
|
||||
self.model.clone_from(&model);
|
||||
self.system_prompt = system_prompt;
|
||||
println!(
|
||||
"{}",
|
||||
format_model_switch_report(&previous, &model, message_count)
|
||||
@@ -12360,6 +12363,7 @@ fn build_system_prompt(model: &str) -> Result<Vec<String>, Box<dyn std::error::E
|
||||
DEFAULT_DATE,
|
||||
env::consts::OS,
|
||||
"unknown",
|
||||
model,
|
||||
model_family_identity_for(model),
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -4374,6 +4374,7 @@ fn build_agent_system_prompt(subagent_type: &str, model: &str) -> Result<Vec<Str
|
||||
DEFAULT_AGENT_SYSTEM_DATE.to_string(),
|
||||
std::env::consts::OS,
|
||||
"unknown",
|
||||
model,
|
||||
model_family_identity_for(model),
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
@@ -9553,7 +9554,8 @@ mod tests {
|
||||
|
||||
// then: the prompt renders a generic model family identity
|
||||
assert!(prompt.contains("Model family: an AI assistant"));
|
||||
assert!(!prompt.contains("Model family: Claude Opus 4.6"));
|
||||
assert!(!prompt.contains("Model family: Claude"));
|
||||
assert!(prompt.contains("Model: openai/gpt-4.1-mini"));
|
||||
|
||||
fs::remove_dir_all(root).expect("cleanup temp workspace");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user