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:
Hunter B
2026-07-24 21:09:48 -07:00
parent c6b0f9675e
commit c0e7a1d2a2
8 changed files with 250 additions and 125 deletions
+7 -7
View File
@@ -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": {
+14
View File
@@ -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)
+5 -2
View File
@@ -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
+5 -1
View File
@@ -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);
+12 -11
View File
@@ -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: {}",
+23 -28
View File
@@ -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
View File
@@ -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")
+43 -32
View File
@@ -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+)