fix(prompts): single precedence source, facts-only turn_meta, lean prefix
Closes the constitution P0 cluster and the first prefix-token cut: - #4777: delete the inverted MEMORY_GUIDANCE Tier ladder; assert precedence only in BASE_PROMPT § Whose word wins; slim Authority Recap to a pointer; reject rank-restating recap overrides; align .codewhale/constitution.json authority[] with the canonical five. - #4778: strip Tier/Statute/Article vocabulary from approval, compaction, memory, and personality overlays so layers describe behavior, not rank. - #4780: turn_meta keeps mode/posture as facts and stops re-embedding mode doctrine and permission-question essays every user message. - #4781: project context pack defaults off (opt-in via project_pack=true). - #4784: compress LANGUAGE_PROMPT while keeping English law / user-language reply, including reasoning_content and next-turn switch. Tests: only_the_constitution_states_precedence, memory hygiene, turn_meta fact-only assertions, project_pack default-off.
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"authority": [
|
||||
"current user request",
|
||||
"live code and tests",
|
||||
"GitHub issue/PR details",
|
||||
"AGENTS.md and project CLAUDE.md",
|
||||
"memory",
|
||||
"previous-session handoffs"
|
||||
"the user's request, this turn",
|
||||
"this constitution",
|
||||
"project law and instructions — nearest in scope wins",
|
||||
"standing user-global preferences",
|
||||
"memory and previous-session handoffs"
|
||||
],
|
||||
"protected_invariants": [
|
||||
"Keep the active first-turn tool-catalog head byte-stable (DeepSeek KV prefix-cache invariant); changes to it must be one-time and deterministic.",
|
||||
"Preserve old-session transcript replay: never remove a tool's registration just because it is deprecated/hidden.",
|
||||
"Stable Rust only (edition 2024); no nightly features.",
|
||||
"Keep the codewhale CLI dispatcher and the codewhale-tui binary in sync when crates/tui changes."
|
||||
"Keep the codewhale CLI dispatcher and the codewhale-tui binary in sync when crates/tui changes.",
|
||||
"Precedence is stated only in BASE_PROMPT § Whose word wins; other layers describe behavior, not rank."
|
||||
],
|
||||
"branch_policy": "Start from live branch and handoff truth. Never commit directly to main; use the active integration branch or a fresh codex/... branch/worktree for isolated work, and open reviewable PRs into main. One PR per logical workstream; do not mix unrelated fixes.",
|
||||
"verification_policy": {
|
||||
|
||||
@@ -59,6 +59,20 @@ existing one behave the way it already claimed to.
|
||||
`shutdown`) can reach a runaway turn instead of waiting on the very turn
|
||||
they were meant to stop.
|
||||
|
||||
- Precedence is stated only in the constitution's "Whose word wins" section.
|
||||
Memory hygiene no longer ships an inverted Tier list that put the
|
||||
constitution above the user's current request; approval, compaction, and
|
||||
personality overlays describe behavior without rank vocabulary; and the
|
||||
authority recap points at the single source rather than restating a second
|
||||
ladder.
|
||||
|
||||
- `<turn_meta>` carries facts (mode, posture, model, workspace), not mode
|
||||
doctrine or permission-question essays re-asserted every user message.
|
||||
|
||||
- The project context pack (pretty-printed workspace tree) is off by default
|
||||
and opt-in via `[context] project_pack = true`. Language law is compressed
|
||||
while keeping the English-constitution / user-language-reply contract.
|
||||
|
||||
## [0.9.1] - 2026-07-24
|
||||
|
||||
### Dogfood follow-ups (2026-07-24)
|
||||
|
||||
@@ -1838,7 +1838,10 @@ pub struct ContextConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: Option<bool>,
|
||||
/// Include a deterministic project context pack in the stable prompt
|
||||
/// prefix. Default: true; set `[context] project_pack = false` to disable.
|
||||
/// prefix. Default: false — the pack is a large pretty-printed directory
|
||||
/// listing the model can rebuild with one `File` call (#4781). Set
|
||||
/// `[context] project_pack = true` to opt in (useful for weak tool-calling
|
||||
/// models).
|
||||
#[serde(default)]
|
||||
pub project_pack: Option<bool>,
|
||||
/// Ignored (was: seam verbatim window).
|
||||
@@ -5320,7 +5323,7 @@ impl Config {
|
||||
|
||||
#[must_use]
|
||||
pub fn project_context_pack_enabled(&self) -> bool {
|
||||
self.context.project_pack.unwrap_or(true)
|
||||
self.context.project_pack.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Return whether shell execution is allowed for noninteractive and
|
||||
|
||||
@@ -5341,8 +5341,12 @@ fn removed_context_per_model_table_is_ignored_for_compatibility() -> Result<()>
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_context_pack_defaults_on_and_can_be_disabled() {
|
||||
fn project_context_pack_defaults_off_and_can_be_enabled() {
|
||||
// #4781: project context pack is opt-in (large pretty-printed tree).
|
||||
let mut config = Config::default();
|
||||
assert!(!config.project_context_pack_enabled());
|
||||
|
||||
config.context.project_pack = Some(true);
|
||||
assert!(config.project_context_pack_enabled());
|
||||
|
||||
config.context.project_pack = Some(false);
|
||||
|
||||
@@ -415,7 +415,7 @@ impl Default for EngineConfig {
|
||||
skills_scan_codewhale_only: false,
|
||||
plugin_registry: None,
|
||||
instructions: Vec::new(),
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
translation_enabled: false,
|
||||
show_thinking: true,
|
||||
// High backstop rather than a working ceiling: the in-turn
|
||||
@@ -735,6 +735,10 @@ fn subagent_mailbox_best_effort_send_permitted(
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Mode doctrine for the stable system prefix. Kept as a pure lookup so
|
||||
/// call sites that need the overlay text (not turn_meta — see #4780) share
|
||||
/// one mapping.
|
||||
#[allow(dead_code)] // retained for approval-gate / prefix helpers; turn_meta no longer embeds it
|
||||
fn mode_runtime_instructions(mode: AppMode) -> &'static str {
|
||||
match mode {
|
||||
AppMode::Agent | AppMode::Auto | AppMode::Yolo => prompts::AGENT_MODE,
|
||||
@@ -744,6 +748,9 @@ impl Engine {
|
||||
.trim()
|
||||
}
|
||||
|
||||
/// Per-posture question discipline. Lives with the approval overlays in the
|
||||
/// stable prefix / gate errors — not re-asserted every turn (#4780).
|
||||
#[allow(dead_code)] // surface via approval-gate errors when those are tightened
|
||||
fn permission_question_discipline(
|
||||
approval_mode: crate::tui::approval::ApprovalMode,
|
||||
) -> &'static str {
|
||||
@@ -2506,6 +2513,10 @@ 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.
|
||||
let mut lines = vec![
|
||||
format!("Current local date: {today}"),
|
||||
// Workspace path moved here from the static `## Environment` block so
|
||||
@@ -2514,20 +2525,10 @@ impl Engine {
|
||||
format!("Current workspace: {}", self.config.workspace.display()),
|
||||
format!("Current model: {routed_model}"),
|
||||
format!("Current mode: {}", self.current_mode.as_setting()),
|
||||
"Current mode policy source: runtime".to_string(),
|
||||
format!(
|
||||
"Current mode policy:\n{}",
|
||||
Self::mode_runtime_instructions(self.current_mode)
|
||||
),
|
||||
format!(
|
||||
"Current permission posture: {}",
|
||||
self.session.approval_mode.permission_chip_label()
|
||||
),
|
||||
"Current permission policy source: effective runtime authority".to_string(),
|
||||
format!(
|
||||
"Current question discipline: {}",
|
||||
Self::permission_question_discipline(self.session.approval_mode)
|
||||
),
|
||||
format!("Input provenance: {}", provenance.as_str()),
|
||||
format!(
|
||||
"Input authority: {}",
|
||||
|
||||
@@ -9884,7 +9884,8 @@ fn external_user_wording_does_not_downgrade_standing_authority() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_metadata_includes_plan_mode_policy() {
|
||||
fn turn_metadata_includes_plan_mode_as_fact_only() {
|
||||
// #4780: turn_meta carries mode as a label, not the full mode doctrine.
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let config = EngineConfig {
|
||||
workspace: tmp.path().to_path_buf(),
|
||||
@@ -9907,41 +9908,32 @@ fn turn_metadata_includes_plan_mode_policy() {
|
||||
|
||||
assert!(text.contains("Current mode: plan"), "got: {text}");
|
||||
assert!(
|
||||
text.contains("Current mode policy source: runtime"),
|
||||
"got: {text}"
|
||||
!text.contains("Current mode policy"),
|
||||
"mode doctrine must not re-enter turn_meta: {text}"
|
||||
);
|
||||
assert!(text.contains("##### Mode: Plan"), "got: {text}");
|
||||
assert!(
|
||||
text.contains("All writes, patches, shell commands,")
|
||||
&& text.contains("and code execution are blocked"),
|
||||
"got: {text}"
|
||||
!text.contains("##### Mode: Plan"),
|
||||
"mode overlay text must not re-enter turn_meta: {text}"
|
||||
);
|
||||
assert!(
|
||||
!text.contains("All writes, patches, shell commands,"),
|
||||
"mode doctrine must not re-enter turn_meta: {text}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_metadata_projects_effective_permission_question_discipline() {
|
||||
fn turn_metadata_projects_permission_posture_as_fact_only() {
|
||||
// #4780: posture is a fact; question-discipline prose stays out of turn_meta.
|
||||
use crate::tui::approval::ApprovalMode;
|
||||
|
||||
let cases = [
|
||||
(
|
||||
ApprovalMode::Suggest,
|
||||
"Ask",
|
||||
"Tool approvals and user decisions are separate",
|
||||
),
|
||||
(
|
||||
ApprovalMode::Auto,
|
||||
"Auto-Review",
|
||||
"Do not ask the user questions or pause for a user decision",
|
||||
),
|
||||
(
|
||||
ApprovalMode::Bypass,
|
||||
"Full Access",
|
||||
"Full Access does not authorize invented intent",
|
||||
),
|
||||
(ApprovalMode::Never, "Never", "Remain read-only"),
|
||||
(ApprovalMode::Suggest, "Ask"),
|
||||
(ApprovalMode::Auto, "Auto-Review"),
|
||||
(ApprovalMode::Bypass, "Full Access"),
|
||||
(ApprovalMode::Never, "Never"),
|
||||
];
|
||||
|
||||
for (approval_mode, posture, question_marker) in cases {
|
||||
for (approval_mode, posture) in cases {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
let config = EngineConfig {
|
||||
workspace: tmp.path().to_path_buf(),
|
||||
@@ -9964,10 +9956,13 @@ fn turn_metadata_projects_effective_permission_question_discipline() {
|
||||
"{posture}: {text}"
|
||||
);
|
||||
assert!(
|
||||
text.contains("Current permission policy source: effective runtime authority"),
|
||||
"{posture}: {text}"
|
||||
!text.contains("Current permission policy source"),
|
||||
"{posture}: doctrine must not re-enter turn_meta: {text}"
|
||||
);
|
||||
assert!(
|
||||
!text.contains("Current question discipline"),
|
||||
"{posture}: question discipline must not re-enter turn_meta: {text}"
|
||||
);
|
||||
assert!(text.contains(question_marker), "{posture}: {text}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+141
-44
@@ -60,7 +60,7 @@ impl Default for PromptSessionContext<'_> {
|
||||
Self {
|
||||
user_memory_block: None,
|
||||
goal_objective: None,
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
locale_tag: "en",
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
@@ -490,10 +490,56 @@ pub fn set_locale_closer_vi_override(s: String) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -993,19 +1039,16 @@ fn render_core_tool_group(group: &[&str], core_tools: &[&str]) -> Option<String>
|
||||
}
|
||||
|
||||
/// Authority recap block — appended at the end of the system prompt,
|
||||
/// just before the user's first message. Uses recency bias constructively:
|
||||
/// this is the last thing the model reads before generating, so it
|
||||
/// reinforces the Constitutional hierarchy without occupying cache-stable
|
||||
/// prefix space.
|
||||
/// just before the user's first message. Uses recency bias constructively
|
||||
/// without restating ranks: precedence is stated only in `BASE_PROMPT`
|
||||
/// § Whose word wins (#4777).
|
||||
const AUTHORITY_RECAP: &str = "\
|
||||
## Authority Recap
|
||||
|
||||
Codewhale's constitution governs your behavior. Ground truth underlies the
|
||||
whole list: the user may override a fact, but no one may invent one. When
|
||||
guidance conflicts, the user's request this turn outranks this constitution,
|
||||
which outranks nearest-scope project law and instructions, which outrank
|
||||
standing user-global preferences, which outrank memory and previous-session
|
||||
handoffs. When in doubt, consult ### Whose word wins.";
|
||||
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")
|
||||
@@ -1105,7 +1148,7 @@ pub fn system_prompt_for_mode_with_context_and_skills(
|
||||
PromptSessionContext {
|
||||
user_memory_block,
|
||||
goal_objective: None,
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
locale_tag: "en",
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
@@ -2413,7 +2456,7 @@ start it",
|
||||
PromptSessionContext {
|
||||
user_memory_block: None,
|
||||
goal_objective: None,
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
locale_tag: "ja",
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
@@ -2648,29 +2691,83 @@ start it",
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_guidance_matches_constitutional_tier_order() {
|
||||
let guidance = MEMORY_GUIDANCE
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let current_request_at = guidance
|
||||
.find("the user's current request (Tier 2)")
|
||||
.expect("current request tier present");
|
||||
let statutes_at = guidance
|
||||
.find("Statutes (Tier 3)")
|
||||
.expect("statutes tier present");
|
||||
let local_law_at = guidance
|
||||
.find("Local Law (Tier 5)")
|
||||
.expect("local law tier present");
|
||||
let live_evidence_at = guidance
|
||||
.find("live evidence (Tier 6)")
|
||||
.expect("live evidence tier present");
|
||||
|
||||
fn memory_guidance_does_not_state_precedence() {
|
||||
// #4777: only BASE_PROMPT § Whose word wins states ranks. Memory
|
||||
// hygiene keeps the imperative→preference rule and drops the
|
||||
// inverted Tier list that used to put Constitution above the user.
|
||||
let guidance = MEMORY_GUIDANCE.to_ascii_lowercase();
|
||||
for forbidden in [
|
||||
"tier 1",
|
||||
"tier 2",
|
||||
"tier 7",
|
||||
"statute",
|
||||
"regulation",
|
||||
"local law",
|
||||
"constitutional hierarchy",
|
||||
] {
|
||||
assert!(
|
||||
!guidance.contains(forbidden),
|
||||
"MEMORY_GUIDANCE must not restate ranks (found {forbidden:?})"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
current_request_at < statutes_at
|
||||
&& statutes_at < local_law_at
|
||||
&& local_law_at < live_evidence_at,
|
||||
"memory guidance must keep the current request above memory and local law"
|
||||
MEMORY_GUIDANCE.contains("treated as a preference")
|
||||
&& MEMORY_GUIDANCE.contains("not a command"),
|
||||
"keep the imperative-as-preference rule"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_constitution_states_precedence() {
|
||||
// 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),
|
||||
("OUTPUT_PROMPT", OUTPUT_PROMPT),
|
||||
("AUTHORITY_RECAP", AUTHORITY_RECAP),
|
||||
];
|
||||
let rank_markers = [
|
||||
"Tier 1",
|
||||
"Tier 2",
|
||||
"Tier 3",
|
||||
"Tier 4",
|
||||
"Tier 5",
|
||||
"Tier 6",
|
||||
"Tier 7",
|
||||
"Tier 8",
|
||||
"Tier 9",
|
||||
"Statute",
|
||||
"Article IV",
|
||||
"Article V",
|
||||
"Article VII",
|
||||
"Local Law",
|
||||
"Regulation (Tier",
|
||||
];
|
||||
for (name, text) in overlays {
|
||||
for marker in rank_markers {
|
||||
assert!(
|
||||
!text.contains(marker),
|
||||
"{name} must not carry rank vocabulary {marker:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
BASE_PROMPT.contains("### Whose word wins"),
|
||||
"canonical precedence section must remain in BASE_PROMPT"
|
||||
);
|
||||
assert!(
|
||||
BASE_PROMPT.contains("This ordering is stated here and nowhere else"),
|
||||
"BASE_PROMPT must assert single-source precedence"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2717,6 +2814,7 @@ start it",
|
||||
PromptSessionContext {
|
||||
user_memory_block: None,
|
||||
goal_objective: None,
|
||||
// Explicit opt-in — pack is off by default (#4781).
|
||||
project_context_pack_enabled: true,
|
||||
locale_tag: "en",
|
||||
translation_enabled: false,
|
||||
@@ -2983,8 +3081,8 @@ start it",
|
||||
"Plan may summarize the user-facing mode delta"
|
||||
);
|
||||
assert!(
|
||||
NEVER_APPROVAL.contains("This approval policy is a Tier 2 Statute"),
|
||||
"the approval overlay keeps the policy authority explanation"
|
||||
NEVER_APPROVAL.contains("The write-block is a runtime setting"),
|
||||
"the approval overlay keeps the policy authority explanation without rank vocabulary"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3042,7 +3140,7 @@ start it",
|
||||
PromptSessionContext {
|
||||
user_memory_block: None,
|
||||
goal_objective: Some("Fix transcript corruption"),
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
locale_tag: "en",
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
@@ -3078,7 +3176,7 @@ start it",
|
||||
PromptSessionContext {
|
||||
user_memory_block: None,
|
||||
goal_objective: Some(" "),
|
||||
project_context_pack_enabled: true,
|
||||
project_context_pack_enabled: false,
|
||||
locale_tag: "en",
|
||||
translation_enabled: false,
|
||||
model_id: "codewhale",
|
||||
@@ -3128,13 +3226,12 @@ start it",
|
||||
"default static prompt must still include the language segment"
|
||||
);
|
||||
assert!(
|
||||
LANGUAGE_PROMPT.contains("latest user message first")
|
||||
&& LANGUAGE_PROMPT.contains("README.zh-CN.md")
|
||||
&& LANGUAGE_PROMPT.contains("tool results")
|
||||
&& LANGUAGE_PROMPT
|
||||
.contains("even when the `lang` field in `## Environment` is `en`")
|
||||
&& LANGUAGE_PROMPT.contains("Use the `lang` field only when"),
|
||||
"language segment must preserve the old default language-selection contract"
|
||||
LANGUAGE_PROMPT.contains("latest user message")
|
||||
&& LANGUAGE_PROMPT.contains("fallback, not an override")
|
||||
&& LANGUAGE_PROMPT.contains("localized READMEs")
|
||||
&& LANGUAGE_PROMPT.contains("Use the `lang` field only when")
|
||||
&& LANGUAGE_PROMPT.contains("constitution and other system law stay English"),
|
||||
"language segment must keep the mirror contract while staying short (#4784)"
|
||||
);
|
||||
assert!(
|
||||
LANGUAGE_PROMPT.contains("reasoning_content")
|
||||
|
||||
@@ -154,22 +154,38 @@ When guidance conflicts, each yields to the one before it:
|
||||
4. Your standing user-global preferences.
|
||||
5. Memory and previous-session handoffs.
|
||||
|
||||
This ordering is stated here and nowhere else. Every other layer describes what
|
||||
it does, not where it ranks.
|
||||
|
||||
At equal rank, the more specific and the more recent govern. Ground truth
|
||||
underlies the whole list: the user may override a fact, but no one may invent
|
||||
one. A tie you cannot break is not yours to break — name it, and ask.
|
||||
"#;
|
||||
/// Language mirroring law, split from the compact constitution in 0.9.0.
|
||||
///
|
||||
/// The constitution and internal law stay English (machine-facing, one
|
||||
/// invariant). User-facing prose — including `reasoning_content` — mirrors the
|
||||
/// user's language. Keep this block short; locale bookends reinforce the same
|
||||
/// contract from both ends of the prompt.
|
||||
pub const LANGUAGE_PROMPT: &str = r#"## Language
|
||||
|
||||
Choose the natural language for each turn from the latest user message first, both for `reasoning_content` and for the final reply. If the latest user message is clearly English, your `reasoning_content` and final reply must stay English. This remains true after reading non-English files, localized READMEs such as `README.zh-CN.md`, issue comments, docs, command output, or tool results.
|
||||
Answer the user in their language — including `reasoning_content` — so expanding
|
||||
thinking is not a jarring read-back. Choose that language from the **latest
|
||||
user message** first. Switch on the very next turn when they switch; do not
|
||||
carry the previous language forward.
|
||||
|
||||
If the latest user message is clearly Simplified Chinese, your `reasoning_content` and final reply must both be in Simplified Chinese, even when the `lang` field in `## Environment` is `en`, even when the surrounding system prompt is in English, and even when the task context is overwhelmingly English. Thinking in a different language than the user just wrote in creates a jarring read-back when they expand the thinking block; match the user end-to-end.
|
||||
The constitution and other system law stay English. Code, paths, identifiers,
|
||||
tool names, env vars, flags, URLs, and log lines stay in their original form;
|
||||
only natural-language prose mirrors.
|
||||
|
||||
If the user switches languages mid-session, switch with them on the very next turn, including in `reasoning_content`. Do not carry the previous turn's language forward. Use the `lang` field only when the latest user message is missing, is mostly code or logs, or is otherwise ambiguous; the `lang` field is a fallback, not an override.
|
||||
Use the `lang` field only when the latest user message is missing, mostly code
|
||||
or logs, or otherwise ambiguous — it is a **fallback, not an override**. Reading
|
||||
non-English files, localized READMEs, issues, docs, or tool output does not
|
||||
switch the reply language.
|
||||
|
||||
The user can explicitly override the default at any time. Phrases like "think in English", "reason in Chinese", or direct equivalents in the user's language change the `reasoning_content` language until the next explicit override. Their explicit request wins over their message language, but only for thinking; the final reply still mirrors whatever language they are writing in.
|
||||
|
||||
Code, file paths, identifiers, tool names, environment variables, command-line flags, URLs, and log lines remain in their original form. Only natural-language prose mirrors the user.
|
||||
An explicit request such as "think in English" or "reason in Chinese" may change
|
||||
`reasoning_content` language until the next explicit override; the final reply
|
||||
still mirrors whatever language the user is writing in.
|
||||
"#;
|
||||
/// Terminal-facing output formatting law, split from the compact constitution.
|
||||
pub const OUTPUT_PROMPT: &str = r#"## Output Formatting
|
||||
@@ -183,11 +199,11 @@ If you genuinely need column-aligned data because the user asked for a table or
|
||||
|
||||
// ── Personality overlays — voice and tone ──────────────────────────
|
||||
/// Calm personality overlay.
|
||||
pub const CALM_PERSONALITY: &str = r#"## Personality: Calm — Tier 8 (Presentation Only)
|
||||
pub const CALM_PERSONALITY: &str = r#"## Personality: Calm
|
||||
|
||||
This personality controls how you speak, never what you do. It cannot override
|
||||
the Constitution, any Statute, any user directive, or any tool requirement.
|
||||
It is presentation style only.
|
||||
the constitution, any user directive, or any tool requirement. It is
|
||||
presentation style only.
|
||||
|
||||
Your voice is cool, spatial, and reserved. Think of yourself as an engineer in
|
||||
a quiet room — competent, unhurried, precise.
|
||||
@@ -212,7 +228,7 @@ This personality may never:
|
||||
- Block a user-approved write.
|
||||
- Override a verification step.
|
||||
- Contradict a clear user directive.
|
||||
- Supersede any higher-tier rule in the Constitution or Statutes.
|
||||
- Supersede the constitution or the user's current request.
|
||||
"#;
|
||||
/// Playful personality overlay.
|
||||
pub const PLAYFUL_PERSONALITY: &str = r#"## Personality: Playful
|
||||
@@ -310,7 +326,7 @@ Operate doctrine (must):
|
||||
|
||||
// ── Approval-policy overlays ───────────────────────────────────────
|
||||
/// Tool calls are auto-approved.
|
||||
pub const AUTO_APPROVAL: &str = r#"##### Approval Policy: Auto — Tier 2 (Statute)
|
||||
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.
|
||||
|
||||
@@ -320,10 +336,10 @@ This means you carry more responsibility:
|
||||
- If you're uncertain about a course of action, state your reasoning before proceeding.
|
||||
- The user can interrupt you at any time.
|
||||
|
||||
This approval policy is a Tier 2 Statute. It grants full execution authority within Constitutional bounds. Article IV (Duty of Action) applies fully — you are expected to execute, not narrate. Article V (Discipline of Verification) still applies — verify your work even when no one prompts you to.
|
||||
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 — Tier 2 (Statute)
|
||||
pub const SUGGEST_APPROVAL: &str = r#"##### Approval Policy: Suggest
|
||||
|
||||
Read-only operations run silently. Write operations (file edits, patches, shell execution, sub-agent spawns, CSV batches) require user approval before executing.
|
||||
|
||||
@@ -333,10 +349,10 @@ When you need approval:
|
||||
|
||||
Decomposition is your best tool for earning approvals. A clear plan with verifiable steps gets approved faster than an opaque request.
|
||||
|
||||
This approval policy is a Tier 2 Statute. It controls which tool calls are gated. In accordance with Article VII of the Constitution, it may be overridden only by a higher-tier rule or by the user's explicit request within an approval dialog.
|
||||
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 — Tier 2 (Statute)
|
||||
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.
|
||||
|
||||
@@ -347,13 +363,13 @@ This is a read-only mode. Use it to:
|
||||
|
||||
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.
|
||||
|
||||
This approval policy is a Tier 2 Statute. It enforces the write-block mandated by Plan mode. In accordance with Article VII, the user may change this policy at any time — the block is a runtime setting, not a Constitutional prohibition.
|
||||
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`.
|
||||
pub const COMPACT_TEMPLATE: &str = r#"## Compaction Relay — Tier 9 (Precedent)
|
||||
pub const COMPACT_TEMPLATE: &str = r#"## Compaction Relay
|
||||
|
||||
The conversation above this point has been compacted. Below is a structured summary of what was discussed and decided. Read this first — it replaces re-reading the compressed transcript.
|
||||
|
||||
@@ -380,12 +396,11 @@ The conversation above this point has been compacted. Below is a structured summ
|
||||
### Next step
|
||||
[The single next action to take when resuming — one line, concrete]
|
||||
|
||||
**Staleability:** This handoff is Tier 9 in the Constitutional hierarchy. It
|
||||
is useful context but subordinate to live tool output, file contents, the
|
||||
current repository state, and the user's current request. A handoff that
|
||||
declares a blocker does not bind a user who says to proceed. A handoff that
|
||||
claims completion does not override evidence that the work is unfinished.
|
||||
Use this summary as orientation, not as law.
|
||||
**Staleability:** This handoff is useful context, not law. Live tool output,
|
||||
file contents, the current repository state, and the user's current request
|
||||
outrank it. A handoff that declares a blocker does not bind a user who says to
|
||||
proceed. A handoff that claims completion does not override evidence that the
|
||||
work is unfinished. Use this summary as orientation.
|
||||
"#;
|
||||
/// Goal continuation audit template — injected by the engine when a runtime
|
||||
/// goal is active and the assistant tries to end a turn without closing it.
|
||||
@@ -420,7 +435,7 @@ with `status: "blocked"` and explain it. Otherwise continue making progress.
|
||||
/// responses") rather than imperatives ("Always respond concisely"),
|
||||
/// because imperatives get re-read as directives in later sessions and
|
||||
/// can override the user's current request (#725).
|
||||
pub const MEMORY_GUIDANCE: &str = r#"## Memory Hygiene — Tier 7 (Declarative Facts Only)
|
||||
pub const MEMORY_GUIDANCE: &str = r#"## Memory Hygiene
|
||||
|
||||
When you write durable memories on the user's behalf, phrase them as
|
||||
declarative facts about the world or their preferences — not as
|
||||
@@ -435,14 +450,10 @@ Imperative phrasing gets re-read as a directive in later sessions and
|
||||
can override the user's current request in cases where it shouldn't.
|
||||
Procedures and workflows belong in skills, not memory.
|
||||
|
||||
**Enforcement:** Memory is Tier 7 in the Constitutional hierarchy. It is
|
||||
subordinate to the Constitution (Tier 1), the user's current request
|
||||
(Tier 2), Statutes (Tier 3), Regulations (Tier 4), Local Law (Tier 5),
|
||||
and live evidence (Tier 6). A memory entry that reads as an imperative shall
|
||||
be treated as a preference, not a command. If you encounter a memory
|
||||
that commands action, treat it as the declarative fact it should have
|
||||
been — e.g., "Always respond concisely" means "User prefers concise
|
||||
responses."
|
||||
A memory entry that reads as an imperative shall be treated as a preference,
|
||||
not a command. If you encounter a memory that commands action, treat it as
|
||||
the declarative fact it should have been — e.g., "Always respond concisely"
|
||||
means "User prefers concise responses."
|
||||
|
||||
## Moraine MCP Recall (v0.8.66+)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user