feat(prompts): harvest static composer override

Refine the embedder static prompt composer direction from #2786 so it only owns the byte-stable base/personality prompt segment while runtime metadata, Context Management, and the compaction relay stay under CodeWhale prompt assembly.

Co-authored-by: h3c-hexin <13790929+h3c-hexin@users.noreply.github.com>
This commit is contained in:
Hunter Bown
2026-06-05 20:27:31 -07:00
committed by GitHub
parent 7dfc81b4bb
commit d868a0b96a
4 changed files with 167 additions and 2 deletions
+6
View File
@@ -52,6 +52,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Agent View prompt, @lbcheng888 for the earlier scaffold, and @BigBenLabs,
@lzx1545642258, @yangdaowan, @mangdehuang, @VerrPower, @hejia-v,
@nasus9527, and @ygzhang-cn for the GUI/VS Code demand and validation trail.
- Added a static prompt composer override for embedders that need to replace
the byte-stable base/personality prompt segment while leaving mode metadata,
approval policy, tool taxonomy, Context Management, and the Compaction Relay
under CodeWhale's runtime prompt assembly. This refines the embedder prompt
customization path from #2786 without weakening prompt-continuity safeguards.
Thanks @h3c-hexin.
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
managed session. The endpoint preserves thread title/model/mode/workspace
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
+2 -2
View File
@@ -663,8 +663,8 @@ Current v0.9 track credits:
HarnessPosture provider/model policy direction (#2733, #2738, #2734,
#2741, #2692, #2694, #2693)
- **[h3c-hexin](https://github.com/h3c-hexin)** — sub-agent model inheritance,
configured `skills_dir` discovery, and prompt-environment stability work
(#2736, #2737)
configured `skills_dir` discovery, prompt-environment stability, and static
prompt composer direction (#2736, #2737, #2786)
- **[gaord](https://github.com/gaord)** — runtime thread workspace updates and
completed-thread saved-session API work (#2640, #2639)
- **[cyq1017](https://github.com/cyq1017)** — restore-listing and
+6
View File
@@ -52,6 +52,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Agent View prompt, @lbcheng888 for the earlier scaffold, and @BigBenLabs,
@lzx1545642258, @yangdaowan, @mangdehuang, @VerrPower, @hejia-v,
@nasus9527, and @ygzhang-cn for the GUI/VS Code demand and validation trail.
- Added a static prompt composer override for embedders that need to replace
the byte-stable base/personality prompt segment while leaving mode metadata,
approval policy, tool taxonomy, Context Management, and the Compaction Relay
under CodeWhale's runtime prompt assembly. This refines the embedder prompt
customization path from #2786 without weakening prompt-continuity safeguards.
Thanks @h3c-hexin.
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
managed session. The endpoint preserves thread title/model/mode/workspace
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
+153
View File
@@ -299,6 +299,31 @@ static LOCALE_CLOSER_JA_OVERRIDE: std::sync::OnceLock<String> = std::sync::OnceL
static LOCALE_CLOSER_PT_BR_OVERRIDE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
static LOCALE_CLOSER_VI_OVERRIDE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
static AUTHORITY_RECAP_OVERRIDE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
static STATIC_PROMPT_COMPOSER: std::sync::OnceLock<Box<StaticPromptComposer>> =
std::sync::OnceLock::new();
/// Context passed to an embedder-provided static prompt composer.
///
/// This hook only replaces the byte-stable base/personality prompt segment.
/// Mode deltas, approval policy, tool taxonomy, Context Management, and the
/// Compaction Relay stay owned by CodeWhale's runtime prompt assembly.
#[non_exhaustive]
#[derive(Debug)]
pub struct StaticPromptCtx<'a> {
/// Active model identifier after caller-side routing.
pub model_id: &'a str,
/// Personality overlay requested for the base static prompt.
pub personality: Personality,
/// Whether shell tools are present in the runtime tool catalog.
pub shell_tools_available: bool,
/// Default base/personality prompt layers that would be used without an
/// override.
pub default_layers: &'a str,
}
/// Embedder hook for replacing CodeWhale's byte-stable base/personality prompt
/// segment.
pub type StaticPromptComposer = dyn Fn(&StaticPromptCtx<'_>) -> String + Send + Sync + 'static;
/// Replace `BASE_PROMPT` for all subsequent prompt composition. First call
/// wins; later calls return the rejected string. Set before spawning any
@@ -352,10 +377,26 @@ pub fn set_authority_recap_override(s: String) -> Result<(), String> {
set_prompt_override(&AUTHORITY_RECAP_OVERRIDE, s)
}
/// 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)
}
fn set_prompt_override(cell: &std::sync::OnceLock<String>, s: String) -> Result<(), String> {
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,
@@ -367,6 +408,10 @@ fn effective_base_prompt() -> &'static str {
effective_prompt_override(&BASE_PROMPT_OVERRIDE, BASE_PROMPT)
}
fn effective_static_prompt_composer() -> Option<&'static StaticPromptComposer> {
STATIC_PROMPT_COMPOSER.get().map(Box::as_ref)
}
fn effective_locale_preamble_zh_hans() -> &'static str {
effective_prompt_override(&LOCALE_PREAMBLE_ZH_HANS_OVERRIDE, LOCALE_PREAMBLE_ZH_HANS)
}
@@ -787,6 +832,22 @@ fn compose_prompt_with_approval_model_and_shell(
allow_shell: bool,
) -> String {
let shell_tools_available = allow_shell && mode != AppMode::Plan;
let default_layers =
compose_default_static_layers(personality, model_id, shell_tools_available);
apply_static_prompt_composer(
effective_static_prompt_composer(),
personality,
model_id,
shell_tools_available,
&default_layers,
)
}
fn compose_default_static_layers(
personality: Personality,
model_id: &str,
shell_tools_available: bool,
) -> String {
let base_prompt = render_base_prompt_for_tool_availability(
effective_base_prompt().trim(),
model_id,
@@ -806,6 +867,24 @@ fn compose_prompt_with_approval_model_and_shell(
out
}
fn apply_static_prompt_composer(
composer: Option<&StaticPromptComposer>,
personality: Personality,
model_id: &str,
shell_tools_available: bool,
default_layers: &str,
) -> String {
match composer {
Some(composer) => composer(&StaticPromptCtx {
model_id,
personality,
shell_tools_available,
default_layers,
}),
None => default_layers.to_string(),
}
}
fn render_base_prompt_for_tool_availability(
prompt: &str,
model_id: &str,
@@ -1217,6 +1296,80 @@ 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)
.err()
.expect("second composer should be rejected");
let ctx = StaticPromptCtx {
model_id: "deepseek-v4-pro",
personality: Personality::Calm,
shell_tools_available: true,
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] {
for shell_tools_available in [true, false] {
let default_layers = compose_default_static_layers(
personality,
"deepseek-v4-flash",
shell_tools_available,
);
let composed = apply_static_prompt_composer(
None,
personality,
"deepseek-v4-flash",
shell_tools_available,
&default_layers,
);
assert_byte_identical("unset static prompt composer", &default_layers, &composed);
}
}
}
#[test]
fn static_prompt_composer_receives_context_and_replaces_layers() {
let default_layers =
compose_default_static_layers(Personality::Calm, "deepseek-v4-pro", false);
let composer: Box<StaticPromptComposer> = Box::new(|ctx| {
assert_eq!(ctx.model_id, "deepseek-v4-pro");
assert_eq!(ctx.personality, Personality::Calm);
assert!(!ctx.shell_tools_available);
assert!(ctx.default_layers.contains("You are deepseek-v4-pro"));
assert!(ctx.default_layers.contains("Personality: Calm"));
assert!(!ctx.default_layers.contains("## Core Tool Taxonomy"));
assert!(!ctx.default_layers.contains("Approval Policy"));
"embedder static prompt".to_string()
});
let composed = apply_static_prompt_composer(
Some(composer.as_ref()),
Personality::Calm,
"deepseek-v4-pro",
false,
&default_layers,
);
assert_eq!(composed, "embedder static prompt");
}
fn contains_cjk(text: &str) -> bool {
text.chars().any(|ch| {
matches!(