feat(system-prompt): add prompt profiles with CRUD, section options, and model overrides#466
feat(system-prompt): add prompt profiles with CRUD, section options, and model overrides#466
Conversation
…and model overrides - Add configurable system prompt profiles with full CRUD lifecycle via 6 new system_prompt.config.* RPC methods - Per-profile section options, enabled sections toggle grid, template variables with insertion, and live preview with token estimation - Model overrides UI with glob-based provider/model routing to different prompt profiles - New typed config (PromptProfilesConfig, PromptSectionId, etc.) in moltis-config, profile-aware prompt building in moltis-agents, profile resolution and RPC handlers in moltis-chat Entire-Checkpoint: 63e4befced95
Entire-Checkpoint: aec30e526ca5
Merging this PR will degrade performance by 11.95%
Performance Changes
Comparing Footnotes
|
Greptile SummaryThis PR adds a complete system-prompt profile management system: typed config structs ( Key issues found:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as Web UI (SystemPromptSection)
participant RPC as RPC Gateway
participant Config as moltis-config
participant Agent as moltis-agents (prompt.rs)
UI->>RPC: system_prompt.config.create {name, description}
RPC->>Config: update_config(|cfg| push new profile)
Config-->>RPC: Ok
RPC-->>UI: success + full config payload
UI->>RPC: system_prompt.config.update {profile, prompt_template, section_options, enabled_sections}
RPC->>Config: update_config(|cfg| find & update profile)
Config-->>RPC: Ok
RPC-->>UI: success + full config payload
UI->>RPC: system_prompt.config.set_default {name}
RPC->>Config: discover_and_load() — validate profile exists
RPC->>Config: update_config(|cfg| set default)
Config-->>RPC: Ok
RPC-->>UI: success + full config payload
UI->>RPC: system_prompt.config.delete {name}
RPC->>Config: update_config(|cfg| retain if not name)
Config-->>RPC: Ok
RPC->>Config: discover_and_load() — verify deletion
RPC-->>UI: success or error
UI->>RPC: system_prompt.config.overrides.save {overrides[]}
RPC->>Config: update_config(|cfg| replace overrides)
Config-->>RPC: Ok
RPC-->>UI: success + full config payload
UI->>RPC: chat.raw_prompt {preview_mode, prompt_profile, ...}
RPC->>Agent: resolve_prompt_profile(provider, model)
Agent-->>RPC: PromptProfileResolution
RPC->>Agent: build_system_prompt_with_profile(...)
Agent-->>RPC: rendered prompt string
RPC-->>UI: {prompt, char_count, estimated_tokens}
|
| if let Err(error) = moltis_config::update_config(|cfg| { | ||
| if cfg.prompt_profiles.profiles.iter().any(|p| p.name == name) { | ||
| return; | ||
| } | ||
| cfg.prompt_profiles | ||
| .profiles | ||
| .push(moltis_config::PromptProfileConfig { | ||
| name: name.clone(), | ||
| description, | ||
| ..moltis_config::PromptProfileConfig::default() | ||
| }); | ||
| }) { | ||
| return Err(ErrorShape::new( | ||
| error_codes::UNAVAILABLE, | ||
| format!("failed to persist system prompt config: {error}"), | ||
| )); | ||
| } | ||
|
|
||
| MethodRegistry::build_system_prompt_config_response() | ||
| }) | ||
| }), |
There was a problem hiding this comment.
Silent no-op returns success for duplicate profile names
When a profile with the same name already exists, the update_config closure returns early without creating anything. However, update_config itself still returns Ok, so this handler falls through to build_system_prompt_config_response() and returns a success response. The JS caller then shows Profile "${name}" created. even though nothing was actually created.
This misleads the user into thinking a new profile was created when in fact the existing one was untouched. Since system_prompt.config.create is explicitly a create operation (not an upsert), a duplicate name should be rejected with a clear error:
if let Err(error) = moltis_config::update_config(|cfg| {
if cfg.prompt_profiles.profiles.iter().any(|p| p.name == name) {
return;
}
cfg.prompt_profiles
.profiles
.push(moltis_config::PromptProfileConfig {
name: name.clone(),
description,
..moltis_config::PromptProfileConfig::default()
});
}) {Consider checking for the duplicate before calling update_config and returning an INVALID_REQUEST error:
{
let config = moltis_config::discover_and_load();
if config.prompt_profiles.profiles.iter().any(|p| p.name == name) {
return Err(ErrorShape::new(
error_codes::INVALID_REQUEST,
format!("profile '{name}' already exists"),
));
}
}| if let Some(profile) = cfg | ||
| .prompt_profiles | ||
| .profiles | ||
| .iter_mut() | ||
| .find(|profile| profile.name == profile_name) | ||
| { | ||
| if let Some(template) = prompt_template.clone() { | ||
| profile.prompt_template = template; | ||
| } | ||
| if let Some(template) = prompt_tail_template.clone() { | ||
| profile.prompt_tail_template = template; | ||
| } | ||
| if let Some(opts) = section_options { | ||
| moltis_chat::apply_section_options(&mut profile.section_options, opts); | ||
| } | ||
| if let Some(ref sections) = enabled_sections { | ||
| profile.enabled_sections.clone_from(sections); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| let mut profile = moltis_config::PromptProfileConfig { | ||
| name: profile_name.clone(), | ||
| ..moltis_config::PromptProfileConfig::default() | ||
| }; | ||
| if let Some(template) = prompt_template.clone() { | ||
| profile.prompt_template = template; | ||
| } | ||
| if let Some(template) = prompt_tail_template.clone() { | ||
| profile.prompt_tail_template = template; | ||
| } | ||
| if let Some(opts) = section_options { | ||
| moltis_chat::apply_section_options(&mut profile.section_options, opts); | ||
| } | ||
| if let Some(ref sections) = enabled_sections { | ||
| profile.enabled_sections.clone_from(sections); | ||
| } | ||
| cfg.prompt_profiles.profiles.push(profile); |
There was a problem hiding this comment.
update silently creates a new profile when the name doesn't exist
When the profile named profile_name is not found in the profiles list, the closure falls through to create a brand-new PromptProfileConfig and pushes it. This means system_prompt.config.update acts as an upsert.
This is unexpected: a caller with a typo in the profile name will silently create a phantom profile instead of getting an error. The explicit system_prompt.config.create RPC exists for creation; update should reject unknown profile names:
if let Some(profile) = cfg
.prompt_profiles
.profiles
.iter_mut()
.find(|profile| profile.name == profile_name)
{
// ... apply changes ...
return;
}
// ⚠️ This block silently creates a new profile if the name is unknown.
let mut profile = moltis_config::PromptProfileConfig {
name: profile_name.clone(),
..moltis_config::PromptProfileConfig::default()
};Consider returning an error here instead of pushing a new profile.
| prompt_template: normalizedTemplateValue(promptTemplate), | ||
| prompt_tail_template: normalizedTemplateValue(promptTailTemplate), | ||
| section_options: buildSectionOptionsPayload(), | ||
| enabled_sections: enabledSections.length ? enabledSections : null, |
There was a problem hiding this comment.
Empty
enabled_sections array is coerced to null, preventing a full section clear
When all toggleable sections are unchecked (or when a profile loads with an empty enabled_sections), enabledSections.length is 0, so the client sends enabled_sections: null. On the server, null is treated as "key absent — no change" by parse_enabled_sections, so the existing section list is never updated to the intended empty value.
In practice the prompt builder falls back to defaults when enabled_sections is empty, so the real-world impact is limited. However, the null-coercion creates a silent discrepancy between the user's intent and what gets persisted. Sending an empty array explicitly would be more accurate:
| enabled_sections: enabledSections.length ? enabledSections : null, | |
| enabled_sections: enabledSections, |
The server's parse_enabled_sections already handles an actual empty array correctly (it returns Some(vec![]), which does update the stored list).
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
withered-breeze-e956 | 6030745 | Commit Preview URL Branch Preview URL |
Mar 23 2026, 04:07 PM |
Summary
system_prompt.config.*RPC methods[[prompt_profiles.overrides]]TOML schemaPromptProfilesConfig,PromptSectionId,PromptSectionOptions, etc.) inmoltis-config, profile-aware prompt building inmoltis-agents, profile resolution and RPC handlers inmoltis-chatValidation
Completed
cargo +nightly-2025-11-30 fmt --all -- --checkpassescargo +nightly-2025-11-30 clippy --workspace --all-targets -- -D warningspassescargo check --workspacepassescargo test -p moltis-chat -p moltis-config -p moltis-agents -p moltis-gatewaypasses (128+ tests, 0 failures)biome checkpasses on all JS filestaplo fmtpassescrates/web/ui)Remaining
./scripts/local-validate.sh <PR_NUMBER>cd crates/web/ui && npx playwright test e2e/specs/settings-nav.spec.jsManual QA