refactor(tui): delete the dead prompt compatibility layer

Dead-code audit 2026-08-03 (P1 row "Prompt compatibility layer"),
verified against the v0.9.4 train with rg across the workspace — every
deleted item had zero production callers:

- locale preamble/closer override setters (8) and the authority-recap
  override setter + validator: never called; the OnceLock cells and
  effective_* readers stay, so composition falls back to the bundled
  constants exactly as before
- set_static_prompt_composer_override + helper: never called; the
  composer cell/read path (apply_static_prompt_composer,
  effective_base_prompt_source) is unchanged
- SHELL_POLICY_DISABLED: unreferenced constant
- Personality::Playful + PLAYFUL_PERSONALITY overlay, and the dead
  Personality::{from_settings, prompt} helpers: tone is folded into the
  constitution preamble; only Calm ever shipped
- compose_prompt wrapper: test-only callers rewritten to the live
  compose_prompt_with_approval_model_and_shell(.., "codewhale") path
- system_prompt_with_world_state / build_system_prompt: zero callers
- unused mode/approval constants YOLO_MODE, AUTO_APPROVAL,
  SUGGEST_APPROVAL, NEVER_APPROVAL and legacy AGENT_PROMPT (plus their
  content-guard tests); Yolo maps to AGENT_MODE in mode_doctrine and
  approval policy no longer inlines prompt overlays

Preserved: BASE_PROMPT and the constitution (incl. today's
verify-then-stop amendment), mode/session/approval assembly,
environment/skills blocks, WorldState types, the config-dir
constitution override path (#3638), and CALM_PERSONALITY for the #2953
regression guard.

Tests: prompts:: 108 passed; execpolicy:: 11 passed; engine prompt
fixture/baseline tests pass; clippy --all-targets clean; cargo check
-p codewhale-tui clean.
This commit is contained in:
Hmbown
2026-08-03 11:05:01 -07:00
parent d2f07593ea
commit a98b184f52
2 changed files with 44 additions and 413 deletions
+44 -290
View File
@@ -11,7 +11,7 @@
//! single-file operation.
use crate::models::{SystemBlock, SystemPrompt};
use crate::project_context::{ProjectContext, load_project_context_with_parents};
use crate::project_context::load_project_context_with_parents;
use std::path::{Path, PathBuf};
use std::sync::{LazyLock, Mutex};
@@ -387,13 +387,13 @@ fn user_constitution_disabled_by_setup_state() -> bool {
// prompt text in `text.rs` directly; the test suite below guards content
// and ordering invariants (constitution structure and binding gates #4032,
// byte-stable prefix ordering, prefix privacy #4632).
pub use text::{
AGENT_MODE, BASE_PROMPT, CALM_PERSONALITY, COMPACT_TEMPLATE, CORE_EXECUTION_PROFILE_PROMPT,
GOAL_CONTINUATION_PROMPT, LANGUAGE_PROMPT, MEMORY_GUIDANCE, OPERATE_MODE, OUTPUT_PROMPT,
PLAN_MODE, PLAYFUL_PERSONALITY,
};
#[cfg(test)]
use text::{AGENT_PROMPT, AUTO_APPROVAL, NEVER_APPROVAL, SUGGEST_APPROVAL, YOLO_MODE};
use text::CALM_PERSONALITY;
pub use text::{
AGENT_MODE, BASE_PROMPT, COMPACT_TEMPLATE, CORE_EXECUTION_PROFILE_PROMPT,
GOAL_CONTINUATION_PROMPT, LANGUAGE_PROMPT, MEMORY_GUIDANCE, OPERATE_MODE, OUTPUT_PROMPT,
PLAN_MODE,
};
// ── Embedder prompt overrides ──
// Let an embedder replace these compile-time prompt constants at startup,
@@ -444,106 +444,6 @@ pub fn set_base_prompt_override(s: String) -> Result<(), String> {
set_prompt_override(&BASE_PROMPT_OVERRIDE, s)
}
/// Replace the Simplified-Chinese locale preamble (`## 语言要求`).
pub fn set_locale_preamble_zh_hans_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_PREAMBLE_ZH_HANS_OVERRIDE, s)
}
/// Replace the Japanese locale preamble.
pub fn set_locale_preamble_ja_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_PREAMBLE_JA_OVERRIDE, s)
}
/// Replace the Brazilian-Portuguese locale preamble.
pub fn set_locale_preamble_pt_br_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_PREAMBLE_PT_BR_OVERRIDE, s)
}
/// Replace the Vietnamese locale preamble.
pub fn set_locale_preamble_vi_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_PREAMBLE_VI_OVERRIDE, s)
}
/// Replace the Simplified-Chinese locale closer (`## 语言再次提醒`).
pub fn set_locale_closer_zh_hans_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_CLOSER_ZH_HANS_OVERRIDE, s)
}
/// Replace the Japanese locale closer.
pub fn set_locale_closer_ja_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_CLOSER_JA_OVERRIDE, s)
}
/// Replace the Brazilian-Portuguese locale closer.
pub fn set_locale_closer_pt_br_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_CLOSER_PT_BR_OVERRIDE, s)
}
/// Replace the Vietnamese locale closer.
pub fn set_locale_closer_vi_override(s: String) -> Result<(), String> {
set_prompt_override(&LOCALE_CLOSER_VI_OVERRIDE, s)
}
/// Replace the trailing `## Authority Recap` block.
///
/// The recap must not restate or reorder ranks — precedence lives only in
/// `BASE_PROMPT` § Whose word wins (#4777). Reject overrides that introduce
/// numbered ranks or claim a different ordering.
pub fn set_authority_recap_override(s: String) -> Result<(), String> {
validate_authority_recap_override(&s)?;
set_prompt_override(&AUTHORITY_RECAP_OVERRIDE, s)
}
fn validate_authority_recap_override(s: &str) -> Result<(), String> {
// Retired rank vocabulary and any restated ordering both re-introduce a
// second authority ladder; refuse them. Precedence lives only in
// BASE_PROMPT § Whose word wins (#4777).
let lower = s.to_ascii_lowercase();
for forbidden in [
"tier ",
"statute",
"regulation",
"local law",
"article ",
"outrank",
"whose word wins", // override may *point* at the section only via the default; custom text that re-embeds the ladder is refused below
] {
// Allow the default pointer phrase "consult ### Whose word wins".
if forbidden == "whose word wins" {
continue;
}
if lower.contains(forbidden) {
return Err(format!(
"authority recap override must not restate ranks (found {forbidden:?}); \
precedence is only in BASE_PROMPT § Whose word wins"
));
}
}
// A custom numbered 1..5 ladder is the classic reorder footgun.
let has_numbered_ladder = (1..=5).all(|n| {
lower.contains(&format!("\n{n}."))
|| lower.contains(&format!("\n{n})"))
|| lower.contains(&format!(" {n}. "))
});
if has_numbered_ladder {
return Err(
"authority recap override must not restate a numbered authority ladder; \
precedence is only in BASE_PROMPT § Whose word wins"
.to_string(),
);
}
Ok(())
}
/// Replace the byte-stable base/personality prompt segment for subsequent
/// prompt composition. First call wins; later calls return the rejected
/// composer so embedders can preserve ownership.
pub fn set_static_prompt_composer_override(
f: Box<StaticPromptComposer>,
) -> Result<(), Box<StaticPromptComposer>> {
set_static_prompt_composer(&STATIC_PROMPT_COMPOSER, f)
}
// ── Config-directory prompt overrides (issue #3638) ──
// Bridge the embedder override hooks above to a user-facing source: an
// optional file in the Codewhale config directory. This lets users repurpose
@@ -677,13 +577,6 @@ fn set_prompt_override(cell: &std::sync::OnceLock<String>, s: String) -> Result<
cell.set(s)
}
fn set_static_prompt_composer(
cell: &std::sync::OnceLock<Box<StaticPromptComposer>>,
f: Box<StaticPromptComposer>,
) -> Result<(), Box<StaticPromptComposer>> {
cell.set(f)
}
fn effective_prompt_override<'a>(
cell: &'a std::sync::OnceLock<String>,
fallback: &'static str,
@@ -983,46 +876,15 @@ dự án có là tiếng Anh, quá trình suy nghĩ của bạn cũng không đ
tích lũy trong ngữ cảnh. Trừ khi người dùng yêu cầu rõ ràng việc chuyển đổi (ví dụ \"think in English\"), \
hãy tiếp tục suy nghĩ và trả lời bằng tiếng Việt.";
/// Shell policy guidance for `allow_shell=false`. Referenced from the
/// Runtime Policy Reference so the model can adapt without mutating the
/// static system-prompt prefix (preserves DeepSeek prefix cache across
/// shell-access toggles).
pub const SHELL_POLICY_DISABLED: &str = "Shell tools unavailable. For mandatory-use items referencing \
`exec_shell`, use `code_execution` (Python sandbox). For GitHub triage, use \
`github_issue_context` / `github_pr_context` as primary route.";
// ── Personality selection ─────────────────────────────────────────────
/// Which personality overlay to apply.
/// Which personality overlay to apply. Tone is folded into the constitutional
/// preamble, so this is a compile-time marker carried through the static-prompt
/// composer context rather than a separate overlay.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Personality {
/// Cool, spatial, reserved — the default.
/// Cool, spatial, reserved — the default and only shipped personality.
Calm,
/// Warm, energetic, playful — alternative for fun mode.
Playful,
}
impl Personality {
/// Resolve from the `calm_mode` settings flag.
/// When `calm_mode` is true → Calm; when false → Playful (future).
/// For now, always returns Calm — Playful is wired but opt-in.
#[must_use]
pub fn from_settings(calm_mode: bool) -> Self {
if calm_mode {
Self::Calm
} else {
// Future: when playful mode is exposed in settings, return Playful here.
// For now, calm is the only default.
Self::Calm
}
}
fn prompt(self) -> &'static str {
match self {
Self::Calm => CALM_PERSONALITY,
Self::Playful => PLAYFUL_PERSONALITY,
}
}
}
// ── Composition ───────────────────────────────────────────────────────
@@ -1050,10 +912,6 @@ whole list: the user may override a fact, but no one may invent one. When
guidance conflicts, consult ### Whose word wins — that is the only place
precedence is stated.";
pub fn compose_prompt(personality: Personality) -> String {
compose_prompt_with_approval_model_and_shell(personality, "codewhale")
}
pub(crate) fn compose_prompt_with_approval_model_and_shell(
personality: Personality,
model_id: &str,
@@ -1418,23 +1276,6 @@ fn render_route_fragment(session_context: &PromptSessionContext<'_>) -> String {
)
}
/// Assemble a cache-stable constitution prefix with a typed WorldState layer.
///
/// This is the Codex-parity assembly point: constitution stays byte-stable for
/// prefix caching; volatile concerns live in `WorldState` fragments with
/// markers, caps, and `render_diff` retain-unchanged behavior. Callers that
/// still need a flat string can use `WorldStateSnapshot::render_text`.
pub fn system_prompt_with_world_state(
constitution: impl Into<String>,
world_state: crate::model_context::WorldState,
) -> SystemPrompt {
let snapshot = crate::model_context::WorldStateSnapshot {
constitution: constitution.into(),
world_state,
};
SystemPrompt::Blocks(snapshot.to_system_blocks())
}
/// Build a WorldState from the common volatile session facts.
///
/// Does not load constitution — callers keep that as the stable base.
@@ -1468,16 +1309,6 @@ pub fn world_state_from_session_facts(
state
}
/// Build a system prompt with explicit project context
pub fn build_system_prompt(base: &str, project_context: Option<&ProjectContext>) -> SystemPrompt {
let full_prompt =
match project_context.and_then(super::project_context::ProjectContext::as_system_block) {
Some(project_block) => format!("{}\n\n{}", base.trim(), project_block),
None => base.trim().to_string(),
};
SystemPrompt::Text(full_prompt)
}
#[cfg(test)]
mod tests {
// Don't assert on prose. If you wouldn't fail a code review for
@@ -1591,43 +1422,17 @@ mod tests {
assert_eq!(effective_prompt_override(&cell, "fallback"), "first");
}
#[test]
fn static_prompt_composer_storage_returns_rejected_composer() {
let cell = std::sync::OnceLock::new();
let first: Box<StaticPromptComposer> =
Box::new(|ctx| format!("first:{}", ctx.default_layers.len()));
let second: Box<StaticPromptComposer> =
Box::new(|ctx| format!("second:{}", ctx.default_layers.len()));
assert!(set_static_prompt_composer(&cell, first).is_ok());
let rejected = set_static_prompt_composer(&cell, second)
.expect_err("second composer should be rejected");
let ctx = StaticPromptCtx {
model_id: "deepseek-v4-pro",
personality: Personality::Calm,
default_layers: "fallback",
};
assert_eq!(rejected(&ctx), "second:8");
assert_eq!(
cell.get().expect("first composer retained")(&ctx),
"first:8"
);
}
#[test]
fn static_prompt_composer_unset_keeps_default_layers_byte_identical() {
for personality in [Personality::Calm, Personality::Playful] {
let default_layers = compose_default_static_layers(personality, "deepseek-v4-flash");
let composed = apply_static_prompt_composer(
None,
personality,
"deepseek-v4-flash",
&default_layers,
);
let default_layers = compose_default_static_layers(Personality::Calm, "deepseek-v4-flash");
let composed = apply_static_prompt_composer(
None,
Personality::Calm,
"deepseek-v4-flash",
&default_layers,
);
assert_byte_identical("unset static prompt composer", &default_layers, &composed);
}
assert_byte_identical("unset static prompt composer", &default_layers, &composed);
}
#[test]
@@ -2036,7 +1841,7 @@ mod tests {
// 0.9.0 has no personality tier. Voice and tone live in the
// compact constitution rather than a separate section, so
// personality remains folded in by omission.
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(
!prompt.contains("Personality: Calm — Tier 8"),
"Personality tier should not appear as a separate section"
@@ -2677,14 +2482,9 @@ mod tests {
// Composed overlays must describe behavior, never their own rank.
let overlays = [
("CALM_PERSONALITY", CALM_PERSONALITY),
("PLAYFUL_PERSONALITY", PLAYFUL_PERSONALITY),
("AGENT_MODE", AGENT_MODE),
("PLAN_MODE", PLAN_MODE),
("YOLO_MODE", YOLO_MODE),
("OPERATE_MODE", OPERATE_MODE),
("AUTO_APPROVAL", AUTO_APPROVAL),
("SUGGEST_APPROVAL", SUGGEST_APPROVAL),
("NEVER_APPROVAL", NEVER_APPROVAL),
("COMPACT_TEMPLATE", COMPACT_TEMPLATE),
("MEMORY_GUIDANCE", MEMORY_GUIDANCE),
("LANGUAGE_PROMPT", LANGUAGE_PROMPT),
@@ -2828,7 +2628,7 @@ mod tests {
#[test]
fn compose_prompt_includes_all_layers() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
// Base layer — balanced Constitution; procedural recipes stay out.
assert!(prompt.contains("## Codewhale"));
assert!(prompt.contains("### Whose word wins"));
@@ -2922,7 +2722,7 @@ mod tests {
#[test]
fn compose_prompt_deterministic_order() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
let base_pos = prompt.find("## Codewhale").unwrap();
let article_pos = prompt.find("### Ground truth").unwrap();
@@ -2933,7 +2733,7 @@ mod tests {
fn base_prompt_is_mode_agnostic() {
// Mode and approval text are no longer inlined into compose_prompt —
// they travel as request-time runtime metadata.
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(!prompt.contains("Mode: Agent"));
assert!(!prompt.contains("Mode: YOLO"));
assert!(!prompt.contains("Mode: Plan"));
@@ -2972,11 +2772,11 @@ mod tests {
#[test]
fn mode_prompts_remain_small_deltas_not_base_policy_copies() {
for (name, prompt) in [
("agent", AGENT_MODE),
("plan", PLAN_MODE),
("yolo", YOLO_MODE),
] {
assert!(
PLAN_MODE.contains("All writes, patches, shell commands"),
"Plan may summarize the user-facing mode delta"
);
for (name, prompt) in [("agent", AGENT_MODE), ("plan", PLAN_MODE)] {
// Measure semantic size on LF so Windows autocrlf checkouts do not
// inflate char/3 token estimates via extra `\r` bytes.
let normalized = prompt.replace("\r\n", "\n").replace('\r', "\n");
@@ -3012,38 +2812,9 @@ mod tests {
}
}
#[test]
fn mode_prompts_do_not_inline_full_approval_policy_overlays() {
for (name, mode_prompt) in [
("agent", AGENT_MODE),
("plan", PLAN_MODE),
("yolo", YOLO_MODE),
] {
for (approval_name, approval_prompt) in [
("auto", AUTO_APPROVAL),
("suggest", SUGGEST_APPROVAL),
("never", NEVER_APPROVAL),
] {
assert!(
!mode_prompt.contains(approval_prompt.trim()),
"{name} mode prompt must not inline the full {approval_name} approval overlay"
);
}
}
assert!(
PLAN_MODE.contains("All writes, patches, shell commands"),
"Plan may summarize the user-facing mode delta"
);
assert!(
NEVER_APPROVAL.contains("The write-block is a runtime setting"),
"the approval overlay keeps the policy authority explanation without rank vocabulary"
);
}
#[test]
fn approval_policy_no_longer_inlined_in_base_prompt() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(!prompt.contains("Mode: Agent"));
assert!(!prompt.contains("Approval Policy:"));
// The compact Constitutional preamble is still present.
@@ -3053,14 +2824,9 @@ mod tests {
#[test]
fn personality_is_folded_into_constitution() {
// v4 has no separate personality tier. Voice and tone live in
// the preamble, so both Calm and Playful compose_prompt calls
// produce identical output (no personality overlay is appended).
let calm = compose_prompt(Personality::Calm);
let playful = compose_prompt(Personality::Playful);
assert_eq!(
calm, playful,
"personality enum is a no-op — both produce identical output"
);
// the preamble, so composition appends no personality overlay.
let calm = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(!calm.contains("## Personality:"));
assert!(calm.contains("Take the work seriously. Don't take"));
assert!(calm.contains("You are Codewhale"));
}
@@ -3149,7 +2915,7 @@ mod tests {
#[test]
fn agent_mode_tool_guidance_avoids_defensive_tool_suppression() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(!prompt.contains("Tool Selection Guide"));
for tool in ["`File`", "`Git`", "`Run`", "`Bash`"] {
assert!(!AGENT_MODE.contains(tool));
@@ -3172,7 +2938,7 @@ mod tests {
/// reinforcement lives in its own static segment plus locale bookends.
#[test]
fn language_segment_present_outside_reduced_constitution() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(
!BASE_PROMPT.contains("## Language"),
"0.9.0 constitution.md should stay reduced; language belongs in its own segment"
@@ -3200,7 +2966,7 @@ mod tests {
#[test]
fn output_formatting_segment_present_outside_reduced_constitution() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(
!BASE_PROMPT.contains("## Output Formatting"),
"0.9.0 constitution.md should stay reduced; output formatting belongs in its own segment"
@@ -3261,7 +3027,7 @@ mod tests {
#[test]
fn english_base_prompt_avoids_native_script_language_priming() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(
!contains_cjk(&prompt),
"English base prompt should keep native-script reinforcement in locale bookends only"
@@ -3308,7 +3074,7 @@ mod tests {
/// by project law/instructions sitting above memory/handoffs.
#[test]
fn project_instructions_outrank_memory_in_whose_word_wins() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
let project_at = prompt
.find("3. Project law and instructions")
.expect("Whose word wins must rank project instructions");
@@ -3324,7 +3090,7 @@ mod tests {
#[test]
fn workspace_orientation_guidance_present() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(prompt.contains("Project law and instructions"));
assert!(
prompt.contains("the nearest in\nscope winning over the broader")
@@ -3433,19 +3199,13 @@ mod tests {
#[test]
fn preamble_carries_tone_and_ownership_guidance() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(prompt.contains("The A is already yours"));
assert!(prompt.contains("Your competence is a settled fact"));
assert!(prompt.contains("Take the work seriously. Don't take"));
assert!(prompt.contains("Let the work speak"));
}
#[test]
fn legacy_constants_still_available() {
// Verify the legacy .txt constant still compiles and contains expected content
assert!(AGENT_PROMPT.lines().next().is_some());
}
// ── Cache-prefix stability harness (#263 step 2) ───────────────────────
//
// These tests pin the byte-stability invariant required for DeepSeek's
@@ -3460,15 +3220,9 @@ mod tests {
// Suspect #4 from #263: mode prompt churn within a single mode.
// Two calls with identical (mode, personality) inputs must produce
// identical bytes — anything else is a cache buster.
for personality in [Personality::Calm, Personality::Playful] {
let a = compose_prompt(personality);
let b = compose_prompt(personality);
assert_byte_identical(
&format!("compose_prompt(personality={personality:?})"),
&a,
&b,
);
}
let a = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
let b = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert_byte_identical("compose_prompt(Personality::Calm)", &a, &b);
}
#[test]
@@ -3818,7 +3572,7 @@ mod tests {
/// guidance travels via the constitution preamble instead.
#[test]
fn default_prompt_does_not_include_calm_personality_overlay() {
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
let calm_text = CALM_PERSONALITY;
let first_calm_line = calm_text.lines().find(|l| !l.is_empty()).unwrap_or("");
assert!(
@@ -3960,7 +3714,7 @@ mod tests {
#[test]
fn default_prompt_stays_under_2953_static_baseline() {
const ISSUE_2953_BASELINE_CHARS: usize = 30_461;
let prompt = compose_prompt(Personality::Calm);
let prompt = compose_prompt_with_approval_model_and_shell(Personality::Calm, "codewhale");
assert!(
prompt.chars().count() < ISSUE_2953_BASELINE_CHARS,
-123
View File
@@ -230,19 +230,6 @@ This personality may never:
- Contradict a clear user directive.
- Supersede the constitution or the user's current request.
"#;
/// Playful personality overlay.
pub const PLAYFUL_PERSONALITY: &str = r#"## Personality: Playful
Your voice is warm, energetic, and playful. You're still precise — you just have more fun doing it.
- Open with personality: "Alright, let's dig into this." or "Ooh, interesting problem."
- Occasional light humor is welcome. Puns, metaphors, and analogies that illuminate the work.
- Use em dashes, parenthetical asides, and a conversational cadence.
- Celebrate wins briefly: "Nice — that compiled on the first try."
- When things go sideways, keep it light: "Well, that didn't go as planned. Let me try another angle."
- Match the user's energy. If they're casual, be casual. If they get technical, tighten up.
- Avoid corporate cheerfulness. Be genuinely warm, not performatively positive.
"#;
// ── Mode deltas — permissions, workflow expectations, mode rules ───
/// Agent mode (Act) delta.
@@ -273,13 +260,6 @@ delegation, it may support parallel investigation. After presenting the plan,
ask the user to reply with revisions or switch to Act (`/mode act`) to
implement, then wait. Do not announce the mode.
"#;
/// Full-access mode delta.
pub const YOLO_MODE: &str = r#"##### Mode: YOLO
All actions are auto-approved within the user's scope. Verify destructive
targets and preserve unrelated work. When `work_update` is present, use it only
for genuinely multi-step work. Do not announce the mode.
"#;
/// Operate mode delta.
///
/// Hard doctrine (not soft preferences): the parent session is the conductor,
@@ -315,48 +295,6 @@ Operate doctrine (must):
unless asked.
"#;
// ── Approval-policy overlays ───────────────────────────────────────
/// Tool calls are auto-approved.
pub const AUTO_APPROVAL: &str = r#"##### Approval Policy: Auto
All tool calls are pre-approved. You will not see approval prompts — your actions execute immediately.
This means you carry more responsibility:
- Pause before destructive operations (deletes, force-pushes, `rm -rf`).
- When `work_update` is present, use it for multi-step work so progress stays visible.
- If you're uncertain about a course of action, state your reasoning before proceeding.
- The user can interrupt you at any time.
Execute rather than narrate. Verification still applies — check your work even when no one prompts you to.
"#;
/// Tool calls require confirmation.
pub const SUGGEST_APPROVAL: &str = r#"##### Approval Policy: Suggest
Read-only operations run silently. File edits and patches whose targets all stay inside this workspace also run without approval, because version control keeps them reviewable — but only when they steer clear of `.git` internals, runtime state (`.codewhale`), and sensitive files (`.env`, credentials, key material). Spawning a read-only sub-agent role (scout, planner, reviewer, verifier, consultant) runs without approval too; the child's own gates keep it read-only. Every other write requires user approval before executing: paths outside the workspace, those excluded paths, shell execution, write-capable sub-agent spawns, CSV batches.
When you need approval:
1. For multi-step changes, use `work_update` when it is present; otherwise state the approach briefly.
2. The user will see your proposed action and can approve or deny it.
Decomposition is your best tool for earning approvals. A clear plan with verifiable steps gets approved faster than an opaque request.
This policy only controls which tool calls are gated. The user may change it at any time, including by approving or denying a specific prompt.
"#;
/// Tool calls are blocked.
pub const NEVER_APPROVAL: &str = r#"##### Approval Policy: Never
All write operations are blocked. You can read, search, and investigate, but you cannot modify the workspace.
This is a read-only mode. Build thorough plans, investigate codebases, trace
logic, and gather context. When `work_update` is present, use it as the one
canonical list. When read-only delegation is present, it may support parallel
exploration.
If the user asks you to edit files, run shell commands, apply patches, or otherwise change the workspace while this policy is active, do not draft a large implementation first. Stop early, say that the current approval policy blocks writes, and give the exact escape hatch: run `/config approval_mode suggest` for prompted writes, or select Full Access only in a trusted workspace.
The write-block is a runtime setting the user may change at any time — not a prohibition in the constitution itself.
"#;
// ── Runtime templates ──────────────────────────────────────────────
/// Compaction relay template — written into the system prompt so the
/// model knows the format to use when writing `.codewhale/handoff.md`.
@@ -469,64 +407,3 @@ tool errors, and distinguish child reports from evidence you verified. Write
`None.` where a section has no entries. If blocked, name the missing fact or
capability. Then stop.
"#;
// ── Legacy prompt constants (kept for backwards compatibility) ─────
/// Legacy base prompt (the retired `agent.txt` — now decomposed into the
/// constitution + overlays above). Still available for callers that haven't
/// migrated to the layered API.
pub const AGENT_PROMPT: &str = r#"## Mode: agent
Read-only tools (reads, searches, persistent RLM session tools, git inspection) run silently.
Any write, patch, shell execution, sub-agent start, or CSV batch operation will ask for approval first.
Before requesting approval for multi-step writes, lay out your work with `work_update` so the user
can see what you intend to do and approve with context. Do not create a second
strategy checklist. For simple writes, state the direct edit and proceed through the normal approval
flow.
## Sub-agent completion sentinel
When you open a sub-agent via `agent`, the child runs independently.
You will receive a `<codewhale:subagent.done>` element in the transcript when it finishes.
Read its `summary` field and integrate the work — do not re-do what the child already did.
Use the returned transcript handle with `handle_read` only when the completion summary is insufficient.
Write child prompts as a compact Subagent Brief:
QUESTION: exact question or task.
SCOPE: files, PRs, issue IDs, commands, or behavior areas to inspect.
ALREADY_KNOWN: facts you already checked; do not repeat unless contradicted.
EFFORT: quick | medium | thorough.
STOP_CONDITION: evidence enough to return.
OUTPUT: VERDICT, EVIDENCE, GAPS, NEXT.
Child model choice is explicit. Use `model_strength: "same"` when the child needs your current
capability level. Use `model_strength: "faster"` for read-only lookup/search, status, or other
low-risk tasks that should run on a smaller/faster same-family model — `type: "scout"` already
defaults to `model_strength: "faster"` for exactly this kind of bounded read-only work, so you only
need to set it for non-scout children. Use an exact `model` only when you know the
provider-specific id; it overrides `model_strength`.
Child thinking is explicit too. Use `thinking: "off"` for fast scout/lookups, `thinking: "high"`
for ordinary reasoning, `thinking: "max"` for hard design/debug/release/security work, and
`thinking: "auto"` when you want Codewhale to choose from the child prompt. Omit it to inherit the
parent thinking mode; explicit `thinking` overrides the default off used with `model_strength:
"faster"`.
Prefer parallel exploration for broad investigations. For repo, version, branch, benchmark,
API-surface, bug, PR, issue, or multi-module investigations, start by splitting independent
read-only exploration across 2-4 `type: "scout"` Fleet workers when that will reduce uncertainty
faster than reading sequentially. Each child runs concurrently in one turn and returns findings you
synthesize; keep architecture decisions, integration, verification, and the final response in the
parent. Do not open sub-agents for tiny one-step tasks — the spawn overhead is not worth it for a
single read or search.
For `type: "scout"`, default to `EFFORT: quick`: stay read-only, aim for about 3-5 tool calls,
do not broaden once QUESTION is answered, and return partial findings if the next step would be
speculative or duplicative. Review/verifier children can spend more calls but should stop after
decisive evidence. Builder/repair children are not subject to the 3-5 call cap; ask them to
checkpoint before expanding scope or after repeated failures.
Sub-agent outputs are self-reports, not verified facts. Re-check material claims before relying on
them: read changed files directly, run the relevant tests, and inspect unexpected results. Keep
final verification in the parent.
"#;