diff --git a/src/app/runtime/actions/mod.rs b/src/app/runtime/actions/mod.rs index 114a166..2054a37 100644 --- a/src/app/runtime/actions/mod.rs +++ b/src/app/runtime/actions/mod.rs @@ -702,6 +702,7 @@ fn spawn_turn(state: &AppStateRest) { } }; let context_window = crate::app::runtime::context::window::resolve(&state.app_config, &state.settings); + let concise_output = state.settings.concise_output; let (temperature, max_tokens) = crate::app::mode::effort::generation_params( state.misc.effort_level, state.settings.max_tokens, @@ -746,6 +747,7 @@ fn spawn_turn(state: &AppStateRest) { max_tokens, abort_flag, hive_mind_converged, + concise_output, }; let result = run_agent_turn(&tc, &messages, &events_q); if let Err(e) = result { @@ -778,6 +780,10 @@ struct TurnCtx { /// of this turn — whether a hive-mind convergence already completed /// earlier in this session. hive_mind_converged: bool, + /// Snapshot of `Settings.concise_output` taken at the start of this + /// turn, so the system-prompt assembly above can read it without + /// `TurnCtx` needing a `Settings` reference. + concise_output: bool, } /// Build an ASCII tree of the workspace directory structure for the @@ -964,12 +970,23 @@ fn run_agent_turn( // workspace tree and reads all memory files each time). let tree_info = generate_workspace_tree(&tc.workspace_roots); let memory_section = build_memory_section(&tc.ctx.memory_dir); + let concise_section = if tc.concise_output { + "\n\nWrite tersely: drop articles (a/an/the), filler words (just/really/basically/\ + actually/simply), pleasantries (sure/certainly/of course/happy to), and hedging. \ + Fragments are fine. Code, commands, file paths, and error text must stay byte-exact \ + — never abbreviate or paraphrase those. Exception: for destructive-operation \ + confirmations and security-relevant warnings, always give full detail regardless of \ + this instruction — clarity matters more than brevity when something risky is at stake." + } else { + "" + }; let system_text = format!( - "{}\n\n{}\n\n{}{}", + "{}\n\n{}\n\n{}{}{}", crate::resources::SYSTEM_PROMPT, crate::resources::SYSTEM_TOOLS, tree_info, memory_section, + concise_section, ); if !msgs.iter().any(|m| matches!(m.role, crate::dto::chat::message::Role::System)) { let sys = ChatMessage::system(system_text); diff --git a/src/model/settings.rs b/src/model/settings.rs index 41122f7..aa6b960 100644 --- a/src/model/settings.rs +++ b/src/model/settings.rs @@ -50,6 +50,14 @@ pub struct Settings { /// entire hive-mind convergence forever. #[serde(default = "default_hive_mind_node_timeout_ms")] pub hive_mind_node_timeout_ms: u64, + /// Off by default. When enabled, appends an instruction to the + /// system prompt asking the model to write tersely — drop articles, + /// filler words, hedging, and pleasantries; keep code, commands, and + /// error text byte-exact — with an explicit exception for + /// destructive-operation confirmations and security warnings, which + /// always get full detail regardless of this setting. + #[serde(default)] + pub concise_output: bool, } impl Default for Settings { @@ -71,6 +79,7 @@ impl Default for Settings { lsp_auto_provision: true, lsp_languages: Vec::new(), hive_mind_node_timeout_ms: default_hive_mind_node_timeout_ms(), + concise_output: false, } } } @@ -123,6 +132,38 @@ mod tests { assert_eq!(settings.hive_mind_node_timeout_ms, 600_000); } + #[test] + fn concise_output_defaults_to_false() { + assert!(!Settings::default().concise_output); + } + + #[test] + fn missing_concise_output_field_falls_back_to_default() { + // Simulates loading a settings.json written before this field + // existed — #[serde(default)] must fill it in rather than + // failing the whole parse. + let old_json = r#"{ + "internet_mode": "Off", + "provider": "zen", + "model": "deepseek-v4-flash-free", + "api_keys": {}, + "max_tokens": null, + "temperature": null, + "review_enabled": true, + "review_max_lessons_per_run": 5, + "adaptive_review_max_skip": 3, + "verify_command": null, + "verify_timeout_ms": 30000, + "workflow_max_concurrency": 5, + "session_archive_enabled": true, + "lsp_auto_provision": true, + "lsp_languages": [] + }"#; + let parsed: Settings = serde_json::from_str(old_json) + .expect("must parse even without the new field present"); + assert!(!parsed.concise_output); + } + #[test] fn missing_hive_mind_node_timeout_field_falls_back_to_default() { // Simulates loading a settings.json written before this field