Skip to content
26 changes: 12 additions & 14 deletions crates/goose-cli/src/commands/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use goose::config::extensions::{
name_to_key, remove_extension, set_extension, set_extension_enabled,
};
use goose::config::permission::PermissionLevel;
use goose::config::{Config, ConfigError, ExperimentManager, ExtensionEntry, PermissionManager};
use goose::config::{
Config, ConfigError, ExperimentManager, ExtensionEntry, GooseMode, PermissionManager,
};
use goose::conversation::message::Message;
use goose::model::ModelConfig;
use goose::providers::{create, providers};
Expand Down Expand Up @@ -1241,45 +1243,41 @@ pub fn configure_goose_mode_dialog() -> Result<(), Box<dyn Error>> {

let mode = cliclack::select("Which goose mode would you like to configure?")
.item(
"auto",
GooseMode::Auto,
"Auto Mode",
"Full file modification, extension usage, edit, create and delete files freely"
)
.item(
"approve",
GooseMode::Approve,
"Approve Mode",
"All tools, extensions and file modifications will require human approval"
)
.item(
"smart_approve",
GooseMode::SmartApprove,
"Smart Approve Mode",
"Editing, creating, deleting files and using extensions will require human approval"
)
.item(
"chat",
GooseMode::Chat,
"Chat Mode",
"Engage with the selected provider without using tools, extensions, or file modification"
)
.interact()?;

config.set_param("GOOSE_MODE", mode)?;
match mode {
"auto" => {
config.set_param("GOOSE_MODE", Value::String("auto".to_string()))?;
GooseMode::Auto => {
cliclack::outro("Set to Auto Mode - full file modification enabled")?;
}
"approve" => {
config.set_param("GOOSE_MODE", Value::String("approve".to_string()))?;
GooseMode::Approve => {
cliclack::outro("Set to Approve Mode - all tools and modifications require approval")?;
}
"smart_approve" => {
config.set_param("GOOSE_MODE", Value::String("smart_approve".to_string()))?;
GooseMode::SmartApprove => {
cliclack::outro("Set to Smart Approve Mode - modifications require approval")?;
}
"chat" => {
config.set_param("GOOSE_MODE", Value::String("chat".to_string()))?;
GooseMode::Chat => {
cliclack::outro("Set to Chat Mode - no tools or modifications enabled")?;
}
_ => unreachable!(),
};
Ok(())
}
Expand Down
36 changes: 13 additions & 23 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod thinking;
use crate::session::task_execution_display::{
format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE,
};
use goose::config::GooseMode;
use goose::conversation::Conversation;
use std::io::Write;

Expand Down Expand Up @@ -533,25 +534,19 @@ impl CliSession {
Err(e) => output::render_error(&e.to_string()),
}
}
input::InputResult::GooseMode(mode) => {
input::InputResult::GooseMode(mode_arg) => {
save_history(&mut editor);

let config = Config::global();
let mode = mode.to_lowercase();

// Check if mode is valid
if !["auto", "approve", "chat", "smart_approve"].contains(&mode.as_str()) {
if let Ok(mode) = GooseMode::try_from(&mode_arg) {
config.set_param("GOOSE_MODE", mode).unwrap();
output::goose_mode_message(&format!("Goose mode set to '{}'", &mode_arg));
} else {
output::render_error(&format!(
"Invalid mode '{}'. Mode must be one of: auto, approve, chat",
mode
"Invalid mode '{}'. Mode must be one of: auto, approve, smart_approve, chat",
mode_arg
));
continue;
}

config
.set_param("GOOSE_MODE", Value::String(mode.to_string()))
.unwrap();
output::goose_mode_message(&format!("Goose mode set to '{}'", mode));
continue;
}
input::InputResult::Plan(options) => {
Expand Down Expand Up @@ -779,12 +774,9 @@ impl CliSession {
self.run_mode = RunMode::Normal;
// set goose mode: auto if that isn't already the case
let config = Config::global();
let curr_goose_mode =
config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
if curr_goose_mode != "auto" {
config
.set_param("GOOSE_MODE", Value::String("auto".to_string()))
.unwrap();
let curr_goose_mode = config.get_param("GOOSE_MODE").unwrap_or(GooseMode::Auto);
if curr_goose_mode != GooseMode::Auto {
config.set_param("GOOSE_MODE", GooseMode::Auto).unwrap();
}

// clear the messages before acting on the plan
Expand All @@ -799,10 +791,8 @@ impl CliSession {
output::hide_thinking();

// Reset run & goose mode
if curr_goose_mode != "auto" {
config
.set_param("GOOSE_MODE", Value::String(curr_goose_mode.to_string()))
.unwrap();
if curr_goose_mode != GooseMode::Auto {
config.set_param("GOOSE_MODE", curr_goose_mode).unwrap();
}
} else {
// add the plan response (assistant message) & carry the conversation forward
Expand Down
14 changes: 6 additions & 8 deletions crates/goose-server/src/routes/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use axum::{
routing::{get, post},
Json, Router,
};
use goose::config::PermissionManager;
use goose::config::{GooseMode, PermissionManager};

use goose::config::Config;
use goose::model::ModelConfig;
Expand Down Expand Up @@ -335,7 +335,7 @@ async fn get_tools(
Query(query): Query<GetToolsQuery>,
) -> Result<Json<Vec<ToolInfo>>, StatusCode> {
let config = Config::global();
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or(GooseMode::Auto);
let agent = state.get_agent_for_route(query.session_id).await?;
let permission_manager = PermissionManager::default();

Expand All @@ -346,14 +346,12 @@ async fn get_tools(
.map(|tool| {
let permission = permission_manager
.get_user_permission(&tool.name)
.or_else(|| {
if goose_mode == "smart_approve" {
.or_else(|| match goose_mode {
GooseMode::SmartApprove => {
permission_manager.get_smart_approve_permission(&tool.name)
} else if goose_mode == "approve" {
Some(PermissionLevel::AskBefore)
} else {
None
}
GooseMode::Approve => Some(PermissionLevel::AskBefore),
_ => None,
});

ToolInfo::new(
Expand Down
21 changes: 9 additions & 12 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::agents::tool_route_manager::ToolRouteManager;
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
use crate::agents::types::SessionConfig;
use crate::agents::types::{FrontendTool, ToolResultReceiver};
use crate::config::{get_enabled_extensions, Config};
use crate::config::{get_enabled_extensions, Config, GooseMode};
use crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD;
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
use crate::mcp_utils::ToolResult;
Expand Down Expand Up @@ -71,7 +71,7 @@ pub struct ReplyContext {
pub tools: Vec<Tool>,
pub toolshim_tools: Vec<Tool>,
pub system_prompt: String,
pub goose_mode: String,
pub goose_mode: GooseMode,
pub initial_messages: Vec<Message>,
pub config: &'static Config,
}
Expand Down Expand Up @@ -189,7 +189,7 @@ impl Agent {
// Add permission inspector (medium-high priority)
// Note: mode will be updated dynamically based on session config
tool_inspection_manager.add_inspector(Box::new(PermissionInspector::new(
"smart_approve".to_string(),
GooseMode::SmartApprove,
std::collections::HashSet::new(), // readonly tools - will be populated from extension manager
std::collections::HashSet::new(), // regular tools - will be populated from extension manager
)));
Expand Down Expand Up @@ -260,7 +260,7 @@ impl Agent {

// Update permission inspector mode to match the session mode
self.tool_inspection_manager
.update_permission_inspector_mode(goose_mode.clone())
.update_permission_inspector_mode(goose_mode)
.await;

Ok(ReplyContext {
Expand Down Expand Up @@ -1013,8 +1013,7 @@ impl Agent {
yield AgentEvent::Message(msg);
}

let mode = goose_mode.clone();
if mode.as_str() == "chat" {
if goose_mode == GooseMode::Chat {
// Skip all tool calls in chat mode
for request in remaining_requests {
let mut response = message_tool_response.lock().await;
Expand Down Expand Up @@ -1229,15 +1228,13 @@ impl Agent {
}))
}

fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> String {
fn determine_goose_mode(session: Option<&SessionConfig>, config: &Config) -> GooseMode {
let mode = session.and_then(|s| s.execution_mode.as_deref());

match mode {
Some("foreground") => "chat".to_string(),
Some("background") => "auto".to_string(),
_ => config
.get_param("GOOSE_MODE")
.unwrap_or_else(|_| "auto".to_string()),
Some("foreground") => GooseMode::Chat,
Some("background") => GooseMode::Auto,
_ => config.get_param("GOOSE_MODE").unwrap_or(GooseMode::Auto),
}
}

Expand Down
57 changes: 26 additions & 31 deletions crates/goose/src/agents/prompt_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#[cfg(test)]
use chrono::DateTime;
use chrono::Utc;
use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;

use crate::agents::extension::ExtensionInfo;
use crate::agents::recipe_tools::dynamic_task_tools::should_enabled_subagents;
use crate::agents::router_tools::llm_search_tool_prompt;
use crate::config::GooseMode;
use crate::{config::Config, prompt_template, utils::sanitize_unicode_tags};

pub struct PromptManager {
Expand All @@ -21,6 +23,18 @@ impl Default for PromptManager {
}
}

#[derive(Serialize)]
struct SystemPromptContext {
extensions: Vec<ExtensionInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_selection_strategy: Option<String>,
current_date_time: String,
suggest_disable: String,
goose_mode: GooseMode,
is_autonomous: bool,
enable_subagents: bool,
}

impl PromptManager {
pub fn new() -> Self {
PromptManager {
Expand Down Expand Up @@ -60,7 +74,6 @@ impl PromptManager {
model_name: &str,
router_enabled: bool,
) -> String {
let mut context: HashMap<&str, Value> = HashMap::new();
let mut extensions_info = extensions_info.clone();

// Add frontend instructions to extensions_info to simplify json rendering
Expand All @@ -82,36 +95,18 @@ impl PromptManager {
})
.collect();

context.insert(
"extensions",
serde_json::to_value(sanitized_extensions_info).unwrap(),
);

if router_enabled {
context.insert(
"tool_selection_strategy",
Value::String(llm_search_tool_prompt()),
);
}

context.insert(
"current_date_time",
Value::String(self.current_date_timestamp.clone()),
);

// Add the suggestion about disabling extensions if flag is true
context.insert(
"suggest_disable",
Value::String(suggest_disable_extensions_prompt.to_string()),
);

let config = Config::global();
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or("auto".to_string());
context.insert("goose_mode", Value::String(goose_mode.clone()));
context.insert(
"enable_subagents",
Value::Bool(should_enabled_subagents(model_name)),
);
let goose_mode = config.get_param("GOOSE_MODE").unwrap_or(GooseMode::Auto);

let context = SystemPromptContext {
extensions: sanitized_extensions_info,
tool_selection_strategy: router_enabled.then(llm_search_tool_prompt),
current_date_time: self.current_date_timestamp.clone(),
suggest_disable: suggest_disable_extensions_prompt.to_string(),
goose_mode,
is_autonomous: goose_mode == GooseMode::Auto,
enable_subagents: should_enabled_subagents(model_name),
};

let base_prompt = if let Some(override_prompt) = &self.system_prompt_override {
let sanitized_override_prompt = sanitize_unicode_tags(override_prompt);
Expand All @@ -124,7 +119,7 @@ impl PromptManager {
});

let mut system_prompt_extras = self.system_prompt_extras.clone();
if goose_mode == "chat" {
if goose_mode == GooseMode::Chat {
system_prompt_extras.push(
"Right now you are in the chat only mode, no access to any tool use and system."
.to_string(),
Expand Down
9 changes: 6 additions & 3 deletions crates/goose/src/config/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::config::paths::Paths;
use fs2::FileExt;
use keyring::Entry;
use once_cell::sync::OnceCell;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::env;
Expand Down Expand Up @@ -596,15 +596,18 @@ impl Config {
/// Returns a ConfigError if:
/// - There is an error reading or writing the config file
/// - There is an error serializing the value
pub fn set_param(&self, key: &str, value: Value) -> Result<(), ConfigError> {
pub fn set_param<V>(&self, key: &str, value: V) -> Result<(), ConfigError>
where
V: Serialize,
{
// Lock before reading to prevent race condition.
let _guard = self.guard.lock().unwrap();

// Load current values with recovery if needed
let mut values = self.load_values()?;

// Modify values
values.insert(key.to_string(), value);
values.insert(key.to_string(), serde_json::to_value(value)?);

// Save all values using the atomic write approach
self.save_values(values)
Expand Down
Loading
Loading