fix(tui): trim drifting turn metadata (#5024)

Remove stale duplicated turn metadata and keep the runtime state authoritative. All substantive CI and Buildkite gates passed; the only failed check was the unavailable Claude review infrastructure.
This commit is contained in:
Hunter Bown
2026-08-01 05:12:32 -07:00
committed by GitHub
parent d81c5c5028
commit bddceb0446
7 changed files with 391 additions and 262 deletions
+72 -100
View File
@@ -41,7 +41,6 @@ use crate::models::{
};
use crate::prompts;
use crate::purge::{emit_purge_completed, emit_purge_failed, emit_purge_started, run_purge};
use crate::resource_telemetry::ResourceTelemetry;
#[cfg(test)]
use crate::route_runtime::resolve_runtime_route;
use crate::route_runtime::{
@@ -88,6 +87,20 @@ use super::turn::{TurnContext, post_turn_snapshot, pre_turn_snapshot};
const ENGINE_OP_CHANNEL_CAPACITY: usize = 32;
const GOAL_CONTINUATION_FAILURE_DETAIL_MAX_BYTES: usize = 512;
fn context_pressure_message(usage_percent: f64) -> Option<&'static str> {
if usage_percent >= crate::tui::context_inspector::CONTEXT_CRITICAL_THRESHOLD_PERCENT {
Some(
"Context pressure: critical — CRITICAL: stop expanding scope; run /compact immediately or finish the current task",
)
} else if usage_percent >= crate::tui::context_inspector::CONTEXT_WARNING_THRESHOLD_PERCENT {
Some(
"Context pressure: warning — ESCALATED: prefer /compact, narrow scope, or finish the current task",
)
} else {
None
}
}
fn agent_list_event(manager: &SubAgentManager) -> Event {
Event::AgentList {
agents: manager.list(),
@@ -2483,90 +2496,55 @@ impl Engine {
prompt_context: &NextTurnPromptContext,
system_prompt: Option<&SystemPrompt>,
) {
if let Some(line) = self.context_pressure_line(current_text, prompt_context, system_prompt)
{
lines.push(line);
}
if let Some(line) = self.active_goal_token_budget_line(prompt_context) {
lines.push(line);
}
}
/// One-line context-pressure signal, emitted **only** while the input
/// estimate sits at or above the warning/critical thresholds. No token
/// counts, percentages, or headroom figures: the model only learns that
/// the pressure band it is in has crossed a threshold. Between crossings
/// the line is byte-stable, so ordinary turns do not bust the prefix
/// cache.
fn context_pressure_line(
&self,
current_text: &str,
prompt_context: &NextTurnPromptContext,
system_prompt: Option<&SystemPrompt>,
) -> Option<String> {
let input_tokens = self.active_input_tokens_with_current_text(current_text, system_prompt);
if let Some(budget) = route_context_budget_for_route(
let budget = route_context_budget_for_route(
prompt_context.provider,
&prompt_context.model,
prompt_context.route_limits,
input_tokens,
) {
let usage_percent = budget.usage_percent();
let escalation = if usage_percent
>= crate::tui::context_inspector::CONTEXT_CRITICAL_THRESHOLD_PERCENT
{
" — CRITICAL: stop expanding scope; run /compact immediately or finish the current task"
} else if usage_percent
>= crate::tui::context_inspector::CONTEXT_WARNING_THRESHOLD_PERCENT
{
" — ESCALATED: prefer /compact, narrow scope, or finish the current task"
} else {
""
};
lines.push(format!(
"Context pressure: {} ({usage_percent:.1}% used, {} / {} tokens; {} input tokens available){escalation}",
budget.pressure.label(),
budget.input_tokens,
budget.window_tokens,
budget.available_input_tokens,
));
}
if let Some(line) = self.session_token_usage_line() {
lines.push(line);
}
if let Some(line) = self.active_goal_resource_line(prompt_context) {
lines.push(line);
}
)?;
context_pressure_message(budget.usage_percent()).map(str::to_string)
}
fn session_token_usage_line(&self) -> Option<String> {
let usage = &self.session.total_usage;
let total = usage.input_tokens.saturating_add(usage.output_tokens);
if total == 0 {
return None;
}
let mut line = format!(
"Session token usage: {total} total ({} input, {} output)",
usage.input_tokens, usage.output_tokens,
);
if let Some(hit_tokens) = usage.cache_read_input_tokens {
line.push_str(&format!(", cache hits {hit_tokens}"));
}
if let Some(write_tokens) = usage.cache_creation_input_tokens {
line.push_str(&format!(", cache writes {write_tokens}"));
}
Some(line)
}
fn active_goal_resource_line(&self, prompt_context: &NextTurnPromptContext) -> Option<String> {
/// Goal pacing for the model: the budget figure only, and only while a
/// goal is actually active. Usage/time deltas, rates, and continuation
/// counts are UI telemetry — they changed every turn and invalidated the
/// prefix cache without adding model-steering signal.
fn active_goal_token_budget_line(
&self,
prompt_context: &NextTurnPromptContext,
) -> Option<String> {
let objective = prompt_context.goal_objective.as_deref()?;
let snapshot = self.config.goal_state.lock().ok()?.snapshot();
let same_goal =
normalized_goal_objective(snapshot.objective.as_deref()).as_deref() == Some(objective);
let (tokens_used, time_used_seconds, continuation_count, token_budget) = if same_goal {
(
snapshot.tokens_used,
snapshot.time_used_seconds,
snapshot.continuation_count,
snapshot.token_budget,
)
let token_budget = if same_goal {
snapshot.token_budget
} else {
(0, 0, 0, prompt_context.goal_token_budget)
};
let mut telemetry = ResourceTelemetry::new(tokens_used, time_used_seconds);
if let Some(token_budget) = token_budget {
telemetry = telemetry.with_token_budget(u64::from(token_budget));
}
let mut line = format!("Active goal resource usage: {}", telemetry.human_summary());
if tokens_used > 0 && time_used_seconds > 0 {
let rate = tokens_used as f64 / time_used_seconds as f64;
line.push_str(&format!("; {rate:.1} tok/s"));
}
line.push_str(&format!("; {continuation_count} continuations"));
Some(line)
prompt_context.goal_token_budget
}?;
Some(format!("Active goal token budget: {token_budget}"))
}
async fn add_session_message(&mut self, message: Message) {
@@ -2606,7 +2584,7 @@ impl Engine {
/// Build `<turn_meta>` from an explicit snapshot of the session state a
/// turn installs *before* it writes the block.
///
/// Production installs mode, approval posture, policy narrowing, and the
/// Production installs approval posture, policy narrowing, and the
/// observed working set on `self`, then reads them back here.
/// `/preview-request` cannot install any of that — it describes a turn
/// that has not started — so it passes the values it would have installed,
@@ -2617,9 +2595,9 @@ impl Engine {
fn turn_metadata_block_from_snapshot(
&self,
_routed_model: &str,
auto_model: bool,
reasoning_effort: Option<&str>,
reasoning_effort_auto: bool,
_auto_model: bool,
_reasoning_effort: Option<&str>,
_reasoning_effort_auto: bool,
provenance: UserInputProvenance,
current_text: &str,
snapshot: TurnMetadataSnapshot<'_>,
@@ -2637,42 +2615,36 @@ impl Engine {
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
// Facts only (#4780). Mode doctrine and permission-question discipline
// ship once in the stable system prefix (mode/approval overlays); re-
// asserting hundreds of tokens of doctrine here out-shouts the
// constitution by salience every user message.
// Facts only (#4780 + turn-meta diet). Mode doctrine ships once in the
// stable system prefix. The permission posture does not: preserve its
// compact label so the model can distinguish Ask, Auto-Review, Full
// Access, and Never without repeating question-discipline prose.
// Route/effort/model lines are telemetry the model cannot act on.
let mut lines = vec![
format!("Current local date: {today}"),
// Workspace path moved here from the static `## Environment` block so
// the static system prefix stays byte-stable across sessions (see
// `render_environment_block` for the prefix-cache rationale).
format!("Current workspace: {}", self.config.workspace.display()),
format!("Current model: {}", prompt_context.model),
format!("Current mode: {}", prompt_context.mode.as_setting()),
format!(
"Current permission posture: {}",
approval_mode.permission_chip_label()
),
format!("Input provenance: {}", provenance.as_str()),
format!(
"Input authority: {}",
if provenance.can_authorize_work() {
"external_current_turn"
} else {
"non_authoritative"
}
),
];
if auto_model {
lines.push(format!("Auto model route: {}", prompt_context.model));
}
if reasoning_effort_auto && let Some(reasoning_effort) = reasoning_effort {
lines.push(format!("Auto reasoning effort: {reasoning_effort}"));
// On ordinary external turns the user's own message is authoritative by
// construction, so provenance is redundant. On non-external turns
// (sub-agent handoff, runtime events) the *reduced* authority is the
// sole signal, so surface it as one condensed line.
if !provenance.can_authorize_work() {
lines.push(format!(
"Input provenance: {} (non-authoritative)",
provenance.as_str()
));
}
// #3947: when runtime policy narrowed this turn's authority, the model
// learns that it happened, why, and the exact sentence the user saw
// not merely the already-narrowed posture above. Emitted only on a
// narrowed turn, so the ordinary turn's metadata stays byte-stable.
// learns that it happened, why, and the exact sentence the user saw.
// Emitted only on a narrowed turn, so the ordinary turn's metadata
// stays byte-stable.
if let Some(event) = policy_narrowing {
lines.push(format!("Authority narrowing: {}", event.reason().as_str()));
lines.push(format!("Authority transition: {}", event.transition()));
+46 -3
View File
@@ -1047,10 +1047,20 @@ mod tests {
let config = deepseek_config();
let (mut engine, _handle, _tmp) = preview_engine(&config);
engine.api_provider = ApiProvider::Deepseek;
engine.active_route_limits = Some(codewhale_config::route::RouteLimits {
let installed_limits = codewhale_config::route::RouteLimits {
context_tokens: Some(4_096),
input_tokens: None,
output_tokens: Some(512),
};
engine.active_route_limits = Some(installed_limits);
// Large enough to be critical for the installed 4K route, but safely
// below the warning threshold for the planned 123K route.
engine.session.messages.push(Message {
role: "user".to_string(),
content: vec![ContentBlock::Text {
text: "x".repeat(20_000),
cache_control: None,
}],
});
let prompt_context = NextTurnPromptContext::for_planned_turn(
ApiProvider::Openrouter,
@@ -1068,6 +1078,35 @@ mod tests {
None,
);
let system_prompt = engine.compose_stable_system_prompt(&prompt_context);
assert_eq!(
engine.context_pressure_line(
"cross-route budget",
&prompt_context,
system_prompt.as_ref()
),
None,
"the planned 123K route must not inherit the installed route's pressure"
);
let installed_context = NextTurnPromptContext::for_planned_turn(
ApiProvider::Deepseek,
"deepseek-v4-flash".to_string(),
Some(installed_limits),
AppMode::Agent,
None,
GoalStatus::Active,
None,
false,
None,
);
assert_eq!(
engine
.context_pressure_line("cross-route budget", &installed_context, None)
.as_deref(),
Some(
"Context pressure: critical — CRITICAL: stop expanding scope; run /compact immediately or finish the current task"
),
"control fixture must be critical under the installed 4K limits"
);
let message = engine.user_text_message_from_snapshot(
"cross-route budget".to_string(),
&prompt_context.model,
@@ -1092,8 +1131,12 @@ mod tests {
})
.next_back()
.expect("turn metadata text");
assert!(metadata.contains("123456 tokens"), "{metadata}");
assert!(!metadata.contains(" / 4096 tokens"), "{metadata}");
assert!(
!metadata.contains("Context pressure:"),
"planned route metadata must remain below warning: {metadata}"
);
assert!(!metadata.contains("123456 tokens"), "{metadata}");
assert!(!metadata.contains("4096 tokens"), "{metadata}");
}
#[tokio::test]
+209 -93
View File
@@ -6535,8 +6535,8 @@ fn normalize_representative_prompt(text: &str, workspace: &Path, home: &Path) ->
.fold(text.to_string(), |normalized, (path, replacement)| {
normalized.replace(path.to_string_lossy().as_ref(), replacement)
});
// The environment block truthfully reports the host OS per render; the
// contract tracks content stability modulo host facts, so pin it here.
// Platform remains an actionable host fact in the stable environment
// block. Pin it so this fixture measures prompt structure across hosts.
normalized.replace(
&format!("- platform: {}", std::env::consts::OS),
"- platform: <PLATFORM>",
@@ -9271,7 +9271,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
struct ModeCase {
name: &'static str,
mode: AppMode,
setting: &'static str,
prompt_marker: &'static str,
shell_policy: ShellPolicy,
sandbox: ExpectedSandbox,
@@ -9286,7 +9285,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
ModeCase {
name: "plan",
mode: AppMode::Plan,
setting: "plan",
prompt_marker: "##### Mode: Plan",
shell_policy: ShellPolicy::None,
sandbox: ExpectedSandbox::ReadOnly,
@@ -9299,7 +9297,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
ModeCase {
name: "agent",
mode: AppMode::Agent,
setting: "agent",
prompt_marker: "##### Mode: Agent",
shell_policy: ShellPolicy::Full,
sandbox: ExpectedSandbox::WorkspaceWrite,
@@ -9312,7 +9309,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
ModeCase {
name: "agent-full-access",
mode: AppMode::Agent,
setting: "agent",
prompt_marker: "##### Mode: Agent",
shell_policy: ShellPolicy::Full,
sandbox: ExpectedSandbox::DangerFullAccess,
@@ -9325,7 +9321,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
ModeCase {
name: "auto-compat",
mode: AppMode::Auto,
setting: "agent",
prompt_marker: "##### Mode: Agent",
shell_policy: ShellPolicy::Full,
sandbox: ExpectedSandbox::WorkspaceWrite,
@@ -9338,7 +9333,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
ModeCase {
name: "operate",
mode: AppMode::Operate,
setting: "operate",
prompt_marker: "##### Mode: Operate",
shell_policy: ShellPolicy::Full,
sandbox: ExpectedSandbox::WorkspaceWrite,
@@ -9353,7 +9347,6 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
// surfaces now speak Act (invisible one-way permission shorthand).
name: "yolo",
mode: AppMode::Yolo,
setting: "agent",
prompt_marker: "##### Mode: Agent",
shell_policy: ShellPolicy::Full,
sandbox: ExpectedSandbox::DangerFullAccess,
@@ -9487,13 +9480,20 @@ fn mode_invariant_matrix_covers_context_catalog_subagents_and_prompt_metadata()
panic!("{}: expected text metadata block", case.name);
};
assert!(
text.contains(&format!("Current mode: {}", case.setting)),
text.contains(&format!(
"Current permission posture: {}",
case.approval_mode.permission_chip_label()
)),
"{}: {text}",
case.name
);
// turn_meta carries the mode as a *fact*; the doctrine ships once in
// the stable prefix (#4780). Assert both halves so neither can be
// dropped silently the way the overlay was.
// Mode doctrine ships once in the stable prefix (#4780). The turn
// block carries only the independently actionable permission posture.
assert!(
!text.contains("Current mode:"),
"{}: turn metadata must not repeat the mode: {text}",
case.name
);
assert!(
!text.contains(case.prompt_marker),
"{}: turn metadata must not re-embed mode doctrine: {text}",
@@ -11606,13 +11606,35 @@ fn turn_metadata_includes_current_local_date_without_working_set() {
let today = chrono::Local::now().format("%Y-%m-%d").to_string();
assert!(text.starts_with("<turn_meta>\n"));
assert!(text.contains(&format!("Current local date: {today}")));
assert!(text.contains("Current model: deepseek-v4-flash"));
assert!(text.contains("Input provenance: external_user"));
assert!(text.contains("Input authority: external_current_turn"));
assert!(
text.contains(&format!("Current workspace: {}", tmp.path().display())),
"workspace must remain in the block: {text}"
);
assert!(
text.contains("Current permission posture: Ask"),
"the active posture must remain model-visible: {text}"
);
// Turn-meta diet: no telemetry may re-enter the per-turn block.
for telemetry in [
"Current model:",
"Current mode:",
"Input provenance:",
"Input authority:",
"Auto model route:",
"Auto reasoning effort:",
"Session token usage:",
"Active goal resource usage:",
"Active goal token budget:",
] {
assert!(
!text.contains(telemetry),
"{telemetry} leaked into turn_meta: {text}"
);
}
}
#[test]
fn turn_metadata_surfaces_context_and_resource_usage() {
fn turn_metadata_surfaces_goal_budget_only_while_goal_active() {
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
model: "deepseek-v4-flash".to_string(),
@@ -11620,6 +11642,8 @@ fn turn_metadata_surfaces_context_and_resource_usage() {
..Default::default()
};
let (mut engine, _handle) = Engine::new(config, &Config::default());
// Even with session usage recorded, the per-turn block must not surface
// it: totals/cache figures are UI telemetry, not model steering signal.
engine.session.total_usage.add(&Usage {
input_tokens: 1_200,
output_tokens: 300,
@@ -11642,81 +11666,68 @@ fn turn_metadata_surfaces_context_and_resource_usage() {
panic!("expected text metadata block");
};
assert!(text.contains("Context pressure:"), "got: {text}");
assert!(text.contains("tokens;"), "got: {text}");
// The goal budget stays (model pacing), and only while the goal is active.
assert!(
text.contains("input tokens available"),
"context headroom should be model-visible: {text}"
text.contains("Active goal token budget: 2000"),
"goal budget should be model-visible: {text}"
);
assert!(
text.contains("Session token usage: 1500 total (1200 input, 300 output"),
"session usage should be model-visible: {text}"
);
assert!(text.contains("cache hits 800"), "got: {text}");
assert!(text.contains("cache writes 400"), "got: {text}");
assert!(
text.contains("Active goal resource usage:"),
"active goal resource usage should be model-visible: {text}"
);
assert!(text.contains("50% budget"), "got: {text}");
assert!(text.contains("10.0 tok/s"), "got: {text}");
}
// Usage/time deltas, rates, and continuation counts are telemetry.
for telemetry in [
"Session token usage:",
"cache hits",
"cache writes",
"Active goal resource usage:",
"tok/s",
"continuations",
"50% budget",
] {
assert!(
!text.contains(telemetry),
"{telemetry} leaked into turn_meta: {text}"
);
}
#[test]
fn turn_metadata_escalates_context_pressure_at_warning_threshold() {
// Without an active goal the budget line must vanish entirely.
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
model: "deepseek-v4-flash".to_string(),
workspace: tmp.path().to_path_buf(),
..Default::default()
};
let (mut engine, _handle) = Engine::new(config, &Config::default());
// Fabricate high context usage by stuffing the session with a large user message.
let large = "x".repeat(900_000);
engine.session.messages.push(Message {
role: "user".to_string(),
content: vec![ContentBlock::Text {
text: large,
cache_control: None,
}],
});
let user_msg = engine.user_text_message_with_turn_metadata("wrap up".to_string());
let last_block = user_msg.content.last().expect("turn metadata block");
let ContentBlock::Text { text, .. } = last_block else {
let (engine, _handle) = Engine::new(config, &Config::default());
let user_msg = engine.user_text_message_with_turn_metadata("no goal".to_string());
let ContentBlock::Text { text, .. } = user_msg.content.last().expect("turn metadata block")
else {
panic!("expected text metadata block");
};
assert!(
!text.contains("Active goal token budget:"),
"budget must not be emitted when no goal is active: {text}"
);
}
if text.contains("Context pressure:") {
let usage_line = text
.lines()
.find(|line| line.starts_with("Context pressure:"))
.expect("context pressure line");
if usage_line.contains('%') {
let percent = usage_line
.split('(')
.nth(1)
.and_then(|rest| rest.split('%').next())
.and_then(|value| value.trim().parse::<f64>().ok())
.unwrap_or(0.0);
if percent >= crate::tui::context_inspector::CONTEXT_WARNING_THRESHOLD_PERCENT {
assert!(
usage_line.contains("ESCALATED"),
"expected escalation copy at >=85%: {usage_line}"
);
} else {
assert!(
!usage_line.contains("ESCALATED"),
"below 85% should stay informational: {usage_line}"
);
}
}
#[test]
fn context_pressure_message_emits_only_at_warning_and_critical_thresholds() {
const WARNING: &str = "Context pressure: warning — ESCALATED: prefer /compact, narrow scope, or finish the current task";
const CRITICAL: &str = "Context pressure: critical — CRITICAL: stop expanding scope; run /compact immediately or finish the current task";
assert_eq!(context_pressure_message(84.99), None);
assert_eq!(context_pressure_message(85.0), Some(WARNING));
assert_eq!(context_pressure_message(94.99), Some(WARNING));
assert_eq!(context_pressure_message(95.0), Some(CRITICAL));
assert_eq!(context_pressure_message(100.0), Some(CRITICAL));
// Threshold labels steer a decision without exposing a continuously
// changing percentage, token count, or headroom value.
for line in [WARNING, CRITICAL] {
assert!(!line.contains('%'), "{line}");
assert!(!line.contains("tokens"), "{line}");
assert!(!line.contains("headroom"), "{line}");
}
}
#[test]
fn runtime_turn_metadata_marks_non_authoritative_input() {
fn runtime_turn_metadata_condenses_non_authoritative_provenance_to_one_line() {
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
workspace: tmp.path().to_path_buf(),
@@ -11732,12 +11743,18 @@ fn runtime_turn_metadata_marks_non_authoritative_input() {
panic!("expected text metadata block");
};
assert!(text.contains("Input provenance: assistant_generated"));
assert!(text.contains("Input authority: non_authoritative"));
// Reduced authority on a non-external turn is the sole signal: one
// condensed line, not the former two-line provenance/authority pair.
assert!(
text.contains("Input provenance: assistant_generated (non-authoritative)"),
"{text}"
);
assert!(!text.contains("Input authority:"), "{text}");
assert!(!text.contains("Input provenance: external_user"), "{text}");
}
#[test]
fn turn_metadata_includes_auto_model_route() {
fn turn_metadata_omits_route_and_reasoning_effort_telemetry() {
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
workspace: tmp.path().to_path_buf(),
@@ -11757,10 +11774,103 @@ fn turn_metadata_includes_auto_model_route() {
panic!("expected text metadata block");
};
assert!(text.contains("Current model: deepseek-v4-pro"));
assert!(text.contains("Auto model route: deepseek-v4-pro"));
assert!(text.contains("Auto reasoning effort: max"));
// Model, auto-route, and auto-reasoning-effort lines were pure telemetry
// and must never re-enter the per-turn block.
assert!(!text.contains("Current model:"), "{text}");
assert!(!text.contains("Auto model route:"), "{text}");
assert!(!text.contains("Auto reasoning effort:"), "{text}");
assert!(!text.contains("debug this regression"));
assert!(
text.starts_with(
"<turn_meta>
Current local date:"
),
"{text}"
);
}
#[test]
fn turn_metadata_is_byte_identical_across_identical_consecutive_turns() {
// Diet acceptance (captains-log #18/#21/#22): two identical consecutive
// turns must produce byte-identical `<turn_meta>` blocks. Pre-diet the
// block carried session totals, context-pressure counts, and goal usage
// rates that drifted between turns even with unchanged inputs; today the
// block carries only facts that are stable across ordinary turns.
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
model: "deepseek-v4-flash".to_string(),
workspace: tmp.path().to_path_buf(),
..Default::default()
};
let (mut engine, _handle) = Engine::new(config, &Config::default());
// Use explicit route limits so the fixture exercises the critical band
// without depending on a model catalog entry or provider default.
engine.session.messages.push(Message {
role: "user".to_string(),
content: vec![ContentBlock::Text {
text: "x".repeat(100_000),
cache_control: None,
}],
});
let prompt_context = NextTurnPromptContext::for_planned_turn(
ApiProvider::Deepseek,
"deepseek-v4-flash".to_string(),
Some(codewhale_config::route::RouteLimits {
context_tokens: Some(10_000),
input_tokens: None,
output_tokens: Some(512),
}),
AppMode::Agent,
None,
crate::tools::goal::GoalStatus::Active,
None,
false,
None,
);
let meta_of = |msg: &Message| -> String {
let ContentBlock::Text { text, .. } = msg.content.last().expect("turn metadata block")
else {
panic!("expected text metadata block");
};
text.clone()
};
let message_for = |engine: &Engine| {
engine.user_text_message_from_snapshot(
"stable input".to_string(),
&prompt_context.model,
false,
None,
false,
UserInputProvenance::ExternalUser,
TurnMetadataSnapshot {
prompt_context: &prompt_context,
system_prompt: None,
approval_mode: engine.session.approval_mode,
working_set: &engine.session.working_set,
policy_narrowing: None,
},
)
};
let first = message_for(&engine);
let first_meta = meta_of(&first);
assert!(
first_meta.contains("Context pressure: critical"),
"fixture must exercise the pressure line: {first_meta}"
);
// Turn 2 builds with the first message already in the session, exactly as
// a real turn sequence would; the block must not change.
engine.session.add_message(first);
let second = message_for(&engine);
let second_meta = meta_of(&second);
assert_eq!(
first_meta, second_meta,
"turn_meta must be byte-identical across identical consecutive turns"
);
}
#[test]
@@ -12011,8 +12121,11 @@ fn external_user_wording_does_not_downgrade_standing_authority() {
}
#[test]
fn turn_metadata_includes_plan_mode_as_fact_only() {
// #4780: turn_meta carries mode as a label, not the full mode doctrine.
fn turn_metadata_leaves_mode_entirely_to_the_system_overlay() {
// #4780 + turn-meta diet: mode doctrine ships once in the stable system
// overlay, and the steady-state mode label was removed from turn_meta as
// redundant with that overlay. Neither the label nor the doctrine may
// re-enter the per-turn block.
let tmp = tempdir().expect("tempdir");
let config = EngineConfig {
workspace: tmp.path().to_path_buf(),
@@ -12033,7 +12146,7 @@ fn turn_metadata_includes_plan_mode_as_fact_only() {
panic!("expected text metadata block");
};
assert!(text.contains("Current mode: plan"), "got: {text}");
assert!(!text.contains("Current mode:"), "got: {text}");
assert!(
!text.contains("Current mode policy"),
"mode doctrine must not re-enter turn_meta: {text}"
@@ -12050,7 +12163,8 @@ fn turn_metadata_includes_plan_mode_as_fact_only() {
#[test]
fn turn_metadata_projects_permission_posture_as_fact_only() {
// #4780: posture is a fact; question-discipline prose stays out of turn_meta.
// #4780 + turn-meta diet: the active posture remains an actionable fact;
// question-discipline prose stays out of turn_meta.
use crate::tui::approval::ApprovalMode;
let cases = [
@@ -12126,15 +12240,15 @@ fn turn_metadata_preserves_standing_full_access_for_subagent_handoff() {
panic!("expected text turn metadata");
};
// Act mode and the permission posture are independent: the child handoff
// must keep the visible Act mode and the standing Full Access authority.
assert!(text.contains("Current mode: agent"), "{text}");
// A child handoff cannot grant new authority, but it retains the standing
// posture and names its reduced provenance in one condensed line.
assert!(!text.contains("Current mode:"), "{text}");
assert!(
text.contains("Current permission posture: Full Access"),
"{text}"
);
assert!(
text.contains("Input authority: non_authoritative"),
text.contains("Input provenance: subagent_handoff (non-authoritative)"),
"{text}"
);
}
@@ -14247,9 +14361,11 @@ async fn background_completion_after_a_turn_is_delivered_once_on_the_next_turn()
/// Providers cache on the longest common prefix of the request, so anything
/// that rewrites an *already-sent* message — or the system prompt — between
/// turns invalidates every cached token after it and silently raises cost.
/// This is easy to regress because per-turn `<turn_meta>` legitimately varies
/// (context pressure and session token totals change every turn); what makes
/// that safe is that a message is frozen once it enters the session.
/// The turn-meta diet removed the per-turn telemetry (session totals, pressure
/// counts, goal rates) that used to make `<turn_meta>` drift every turn; it
/// now varies only on genuinely new signal (date boundary, working-set
/// changes, threshold crossings). Freezing a message once it enters the
/// session keeps every earlier message byte-identical regardless.
///
/// The test pins both halves of that contract:
/// 1. `<turn_meta>` is the *last* content block of a user message, so the
+28 -36
View File
@@ -171,39 +171,30 @@ fn translation_target_language_for_tag(locale_tag: &str) -> &'static str {
}
}
/// Render a `## Environment` block listing the resolved locale tag,
/// runtime version, host platform, login shell, and current working directory.
/// Render a `## Environment` block listing the resolved locale tag and the
/// actionable host facts that affect command syntax.
///
/// The block is appended to the workspace-static portion of the
/// system prompt (after mode prompt + project context, before
/// configured instructions / skills). `locale_tag` is resolved by the caller
/// from `Settings` so this function stays I/O-free.
/// The block is appended to the workspace-static portion of the system
/// prompt (after mode prompt + project context, before configured
/// instructions / skills). `locale_tag` is resolved by the caller from
/// `Settings` so this function stays I/O-free.
///
/// `platform` and `shell` remain because they change how commands must be
/// written and are stable for the life of the process. The release version was
/// removed by the turn-meta diet: it is telemetry the model cannot act on and
/// churned the otherwise-static prefix on every release. The live workspace
/// path is delivered per-turn via `<turn_meta>` (see `turn_metadata_block`).
fn render_environment_block(_workspace: &Path, locale_tag: &str) -> String {
let codewhale_version = env!("CARGO_PKG_VERSION");
let platform = std::env::consts::OS;
let shell = crate::shell_dispatcher::global_dispatcher()
.kind()
.binary()
.to_string();
// The workspace path (`pwd`) is intentionally delivered per-turn via the
// `<turn_meta>` block (see `turn_metadata_block`) rather than embedded here.
//
// Rationale: when the workspace path changes between sessions (e.g. an
// ephemeral per-session workspace), a volatile value inside the otherwise
// static system prefix invalidates the inference server's prefix cache at
// that exact point. The cache then only partially matches and the tail must
// be re-prefilled from the divergence boundary. On backends that pair prefix
// caching with speculative decoding, this partial re-prefill can perturb the
// logits at the boundary enough to degrade structured tool-call emission
// (the model regresses to bare text). Keeping the static system prefix
// byte-identical across sessions lets the prefix cache be reused; the live
// workspace path still reaches the model every turn through `turn_meta`.
format!(
"## Environment\n\
\n\
- lang: {locale_tag}\n\
- codewhale_version: {codewhale_version}\n\
- platform: {platform}\n\
- shell: {shell}"
)
@@ -1359,7 +1350,8 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval(
// Permissions fragment: configured `instructions = [...]` files (#454).
let permissions_body = instructions.and_then(render_instructions_block);
// Route fragment: active model / verbosity / translation posture.
// Route fragment: verbosity / translation posture (the model id was
// removed by the turn-meta diet — it is telemetry the model cannot act on).
let route_body = render_route_fragment(&session_context);
// Token-budget / continuity fragment: prior-session handoff relay.
@@ -1417,9 +1409,7 @@ fn render_route_fragment(session_context: &PromptSessionContext<'_>) -> String {
.filter(|value| !value.is_empty())
.unwrap_or("default");
format!(
"model: {}\nverbosity: {}\ntranslation: {}",
session_context.model_id.trim(),
verbosity,
"verbosity: {verbosity}\ntranslation: {}",
if session_context.translation_enabled {
"on"
} else {
@@ -2082,17 +2072,15 @@ mod tests {
}
#[test]
fn render_environment_block_lists_supplied_locale_and_workspace() {
fn render_environment_block_keeps_actionable_host_facts_without_version() {
let tmp = tempdir().expect("tempdir");
let block = render_environment_block(tmp.path(), "zh-Hans");
assert!(block.starts_with("## Environment"));
assert!(block.contains("- lang: zh-Hans"));
assert!(block.contains(&format!(
"- codewhale_version: {}",
env!("CARGO_PKG_VERSION")
)));
// pwd is now delivered per-turn via `turn_meta`, not in the static block.
// The workspace remains per-turn and the release version is telemetry;
// platform and shell still steer valid command syntax.
assert!(!block.contains("- pwd:"));
assert!(!block.contains("- codewhale_version:"));
assert!(block.contains("- platform:"));
assert!(block.contains("- shell:"));
}
@@ -2435,7 +2423,9 @@ mod tests {
));
assert!(prompt.contains("## Environment"));
assert!(prompt.contains("- lang: ja"));
assert!(prompt.contains("- codewhale_version:"));
assert!(!prompt.contains("- codewhale_version:"));
assert!(prompt.contains("- platform:"));
assert!(prompt.contains("- shell:"));
}
#[test]
@@ -2479,9 +2469,7 @@ mod tests {
let user_block_at = prompt
.find("<codewhale_user_constitution")
.expect("user constitution block");
let env_at = prompt
.find("- codewhale_version:")
.expect("rendered environment block");
let env_at = prompt.find("- lang:").expect("rendered environment block");
assert!(
base_at < user_block_at && user_block_at < env_at,
"user constitution should be its own layer after the base/project context and before volatile environment data"
@@ -3881,7 +3869,11 @@ mod tests {
assert!(flat.contains(crate::model_context::FragmentId::Workspace.marker()));
assert!(flat.contains(crate::model_context::FragmentId::Route.marker()));
assert!(flat.contains("## Environment"));
assert!(flat.contains("model: deepseek-v4-pro"));
assert!(
flat.contains("verbosity: concise") && flat.contains("translation: off"),
"route fragment keeps verbosity/translation but drops the model id"
);
assert!(!flat.contains("model: deepseek-v4-pro"));
assert!(flat.contains("<session_goal>"));
assert!(flat.contains("ship WorldState Blocks"));
assert!(flat.contains("remember the cutover"));
+13 -7
View File
@@ -53,20 +53,17 @@ const SHELL_COMPLETION_EVENT_SUFFIX: &str = "\n</codewhale:runtime_event>";
const SUBAGENT_HANDOFF_TURN_META: &str = concat!(
"<turn_meta>\n",
"Input provenance: subagent_handoff\n",
"Input authority: non_authoritative\n",
"Input provenance: subagent_handoff (non-authoritative)\n",
"</turn_meta>",
);
const SHELL_COMPLETION_HANDOFF_TURN_META: &str = concat!(
"<turn_meta>\n",
"Input provenance: shell_completion\n",
"Input authority: non_authoritative\n",
"Input provenance: shell_completion (non-authoritative)\n",
"</turn_meta>",
);
const RESTORED_CHECKPOINT_TURN_META: &str = concat!(
"<turn_meta>\n",
"Input provenance: subagent_handoff\n",
"Input authority: non_authoritative\n",
"Input provenance: subagent_handoff (non-authoritative)\n",
"Restore projection: subagent_checkpoint_v1\n",
"</turn_meta>",
);
@@ -258,6 +255,15 @@ fn is_subagent_handoff_turn_meta(text: &str) -> bool {
return false;
};
// Current shape (turn-meta diet): a single condensed provenance line.
if has_one_exact_metadata_line(
body,
"Input provenance:",
"Input provenance: subagent_handoff (non-authoritative)",
) {
return true;
}
// Legacy shape (pre-diet saved sessions): the two-line pair.
has_one_exact_metadata_line(
body,
"Input provenance:",
@@ -748,7 +754,7 @@ mod tests {
}
#[test]
fn restore_projection_accepts_runtime_generated_rich_turn_metadata() {
fn restore_projection_accepts_legacy_rich_turn_metadata() {
let raw = Message {
role: "user".to_string(),
content: vec![
+1 -1
View File
@@ -1079,7 +1079,6 @@ impl WorkingSet {
let mut lines: Vec<String> = Vec::new();
lines.push("## Repo Working Set".to_string());
lines.push(format!("Workspace: {}", workspace.display()));
if let Some(summary) = repo_summary {
lines.push(summary);
@@ -1771,6 +1770,7 @@ mod tests {
let block = ws.summary_block(tmp.path()).expect("block");
assert!(block.contains("Repo Working Set"));
assert!(!block.contains("Workspace:"));
assert!(block.contains("Cargo.toml"));
assert!(block.contains("src"));
assert!(block.contains("src/lib.rs"));
+22 -22
View File
@@ -5,43 +5,43 @@
"fixture_id": "representative-v1",
"stages": {
"base": {
"bytes": 11276,
"identity_sha256": "97bd3dbb0c3abccc560e97547d6d94a51553fcc319f77f617b95cb1c15a0b07d"
"bytes": 11232,
"identity_sha256": "cfc75fc3e8faceddaec4e0611534245d83e36cac0c121b22be050866a1a0b9a7"
},
"goal": {
"bytes": 14607,
"bytes": 14563,
"delta_bytes": 80,
"identity_sha256": "4c46a340f126b3cc13bfd939ea643452b365b452051c0ed7ce30c44a432dd739"
"identity_sha256": "26606dd13fe77413eaf8680d9663bf925658a57bbcbd8d5a882226f7e419089b"
},
"handoff": {
"bytes": 14995,
"bytes": 14951,
"delta_bytes": 388,
"identity_sha256": "72cb7f323828193f0515a9a68ce68fb1825d5bfa1617b03d03cfb2eead81d0dd"
"identity_sha256": "bd2e996753c4611ad99431b2fc7774a091519ed4f616b2be386e757bc6cf1526"
},
"instructions": {
"bytes": 11728,
"bytes": 11684,
"delta_bytes": 131,
"identity_sha256": "39c3f2fb4f58a05a05afa9917a99dadd2002fd27c8a3342b0542d7cdb7d649de"
"identity_sha256": "dc5fb6700cb5b96aad1bb2c2cb39299241fb62acd785fabd57ce4177ba6b9da6"
},
"memory": {
"bytes": 14527,
"bytes": 14483,
"delta_bytes": 1649,
"identity_sha256": "661944eb773653ef047e1d8d2d1512c5bc7be60fa8f33d224b439edc60ef0e1d"
"identity_sha256": "95e89028c7d09988878376d4d836c90bc2c6c5f87147ce46738a59a74abc8e1f"
},
"project": {
"bytes": 11597,
"bytes": 11553,
"delta_bytes": 321,
"identity_sha256": "07a84f222831b4ae963a7bc84a2931a9f4e4a2227ec699260bba84dcb341001c"
"identity_sha256": "435a780b13f567aa948a57eba201ad8cd154a7851cc72b44785d9d875c63e1bb"
},
"skill": {
"bytes": 12878,
"bytes": 12834,
"delta_bytes": 1150,
"identity_sha256": "0060208dc8e2c6034e0c1fc0d1b67a1d2d2487922128b145a3c3e3c1543ac857"
"identity_sha256": "7913d1a133f8fbdc4edad68f561f1f81199b2eac318625f05548e9f521a4dff7"
}
},
"system_prompt_blocks": 6,
"total_bytes": 14995,
"total_tokens_est": 3749
"total_bytes": 14951,
"total_tokens_est": 3738
},
"schema_version": 1,
"skill_discovery": {
@@ -62,22 +62,22 @@
"mode_instructions_bytes": 802,
"mode_instructions_tokens_est": 201,
"system_prompt_blocks": 4,
"system_prompt_bytes": 11276,
"system_prompt_tokens_est": 2819
"system_prompt_bytes": 11232,
"system_prompt_tokens_est": 2808
},
"operate": {
"mode_instructions_bytes": 1671,
"mode_instructions_tokens_est": 418,
"system_prompt_blocks": 4,
"system_prompt_bytes": 12145,
"system_prompt_tokens_est": 3037
"system_prompt_bytes": 12101,
"system_prompt_tokens_est": 3026
},
"plan": {
"mode_instructions_bytes": 517,
"mode_instructions_tokens_est": 130,
"system_prompt_blocks": 4,
"system_prompt_bytes": 10991,
"system_prompt_tokens_est": 2748
"system_prompt_bytes": 10947,
"system_prompt_tokens_est": 2737
}
}
},