Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions crates/goose-cli/src/session/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,15 +549,10 @@ async fn configure_session_prompts(
tracing::warn!("Failed to save extension state: {}", e);
}

session
.agent
.extend_system_prompt(super::prompt::get_cli_prompt())
.await;

if let Some(ref additional_prompt) = session_config.additional_system_prompt {
session
.agent
.extend_system_prompt(additional_prompt.clone())
.extend_system_prompt("additional".to_string(), additional_prompt.clone())
.await;
}

Expand Down
1 change: 0 additions & 1 deletion crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ mod elicitation;
mod export;
mod input;
mod output;
mod prompt;
mod task_execution_display;
mod thinking;

Expand Down
17 changes: 0 additions & 17 deletions crates/goose-cli/src/session/prompt.rs

This file was deleted.

26 changes: 9 additions & 17 deletions crates/goose-server/src/routes/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use goose::agents::ExtensionConfig;
use goose::config::resolve_extensions_for_new_session;
use goose::config::{Config, GooseMode};
use goose::model::ModelConfig;
use goose::prompt_template::render_template;
use goose::providers::create;
use goose::recipe::Recipe;
use goose::recipe_deeplink;
Expand All @@ -32,7 +31,7 @@ use goose::{
use rmcp::model::{CallToolRequestParams, Content};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
Expand Down Expand Up @@ -420,10 +419,6 @@ async fn update_from_session(
message: format!("Failed to get session: {}", err),
status: StatusCode::INTERNAL_SERVER_ERROR,
})?;
let context: HashMap<&str, Value> = HashMap::new();
let desktop_prompt =
render_template("desktop_prompt.md", &context).expect("Prompt should render");
let mut update_prompt = desktop_prompt;
if let Some(recipe) = session.recipe {
match build_recipe_with_parameter_values(
&recipe,
Expand All @@ -433,11 +428,13 @@ async fn update_from_session(
{
Ok(Some(recipe)) => {
if let Some(prompt) = apply_recipe_to_agent(&agent, &recipe, true).await {
update_prompt = prompt;
agent
.extend_system_prompt("recipe".to_string(), prompt)
.await;
}
}
Ok(None) => {
// Recipe has missing parameters - use default prompt
// Recipe has missing parameters
}
Err(e) => {
return Err(ErrorResponse {
Expand All @@ -447,7 +444,6 @@ async fn update_from_session(
}
}
}
agent.extend_system_prompt(update_prompt).await;

Ok(StatusCode::OK)
}
Expand Down Expand Up @@ -707,11 +703,6 @@ async fn restart_agent_internal(
status: StatusCode::INTERNAL_SERVER_ERROR,
})?;

let context: HashMap<&str, Value> = HashMap::new();
let desktop_prompt =
render_template("desktop_prompt.md", &context).expect("Prompt should render");
let mut update_prompt = desktop_prompt;

if let Some(ref recipe) = session.recipe {
match build_recipe_with_parameter_values(
recipe,
Expand All @@ -721,11 +712,13 @@ async fn restart_agent_internal(
{
Ok(Some(recipe)) => {
if let Some(prompt) = apply_recipe_to_agent(&agent, &recipe, true).await {
update_prompt = prompt;
agent
.extend_system_prompt("recipe".to_string(), prompt)
.await;
}
}
Ok(None) => {
// Recipe has missing parameters - use default prompt
// Recipe has missing parameters
}
Err(e) => {
return Err(ErrorResponse {
Expand All @@ -735,7 +728,6 @@ async fn restart_agent_internal(
}
}
}
agent.extend_system_prompt(update_prompt).await;

Ok(extension_results)
}
Expand Down
8 changes: 1 addition & 7 deletions crates/goose-server/src/routes/recipe_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ use crate::state::AppState;
use anyhow::Result;
use axum::http::StatusCode;
use goose::agents::Agent;
use goose::prompt_template::render_template;
use goose::recipe::build_recipe::{build_recipe_from_template, RecipeError};
use goose::recipe::local_recipes::{get_recipe_library_dir, list_local_recipes};
use goose::recipe::validate_recipe::validate_recipe_template_from_content;
use goose::recipe::Recipe;
use serde::Serialize;
use serde_json::Value;
use tracing::error;
use utoipa::ToSchema;

Expand Down Expand Up @@ -170,9 +168,5 @@ pub async fn apply_recipe_to_agent(
)
.await;

recipe.instructions.as_ref().map(|instructions| {
let mut context: HashMap<&str, Value> = HashMap::new();
context.insert("recipe_instructions", Value::String(instructions.clone()));
render_template("desktop_recipe_instruction.md", &context).expect("Prompt should render")
})
recipe.instructions.as_ref().cloned()
}
4 changes: 3 additions & 1 deletion crates/goose-server/src/routes/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ async fn update_session_user_recipe_values(
status,
})?;
if let Some(prompt) = apply_recipe_to_agent(&agent, &recipe, false).await {
agent.extend_system_prompt(prompt).await;
agent
.extend_system_prompt("recipe".to_string(), prompt)
.await;
}
Ok(Json(UpdateSessionUserRecipeValuesResponse { recipe }))
}
Expand Down
7 changes: 4 additions & 3 deletions crates/goose/src/agents/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,8 @@ impl Agent {
let created_final_output_tool = FinalOutputTool::new(response);
let final_output_system_prompt = created_final_output_tool.system_prompt();
*final_output_tool = Some(created_final_output_tool);
self.extend_system_prompt(final_output_system_prompt).await;
self.extend_system_prompt("final_output".to_string(), final_output_system_prompt)
.await;
}

pub async fn add_sub_recipes(&self, sub_recipes_to_add: Vec<SubRecipe>) {
Expand Down Expand Up @@ -1614,9 +1615,9 @@ impl Agent {
}))
}

pub async fn extend_system_prompt(&self, instruction: String) {
pub async fn extend_system_prompt(&self, key: String, instruction: String) {
let mut prompt_manager = self.prompt_manager.lock().await;
prompt_manager.add_system_prompt_extra(instruction);
prompt_manager.add_system_prompt_extra(key, instruction);
}

pub async fn update_provider(
Expand Down
46 changes: 27 additions & 19 deletions crates/goose/src/agents/prompt_manager.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(test)]
use chrono::DateTime;
use chrono::Utc;
use indexmap::IndexMap;
use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;
Expand All @@ -19,7 +20,7 @@ const MAX_TOOLS: usize = 50;

pub struct PromptManager {
system_prompt_override: Option<String>,
system_prompt_extras: Vec<String>,
system_prompt_extras: IndexMap<String, String>,
current_date_timestamp: String,
}

Expand Down Expand Up @@ -173,24 +174,25 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {

// Add hints if provided
if let Some(hints) = self.hints {
system_prompt_extras.push(hints);
system_prompt_extras.insert("hints".to_string(), hints);
}

if goose_mode == GooseMode::Chat {
system_prompt_extras.push(
system_prompt_extras.insert(
"chat_mode".to_string(),
"Right now you are in the chat only mode, no access to any tool use and system."
.to_string(),
);
}

let sanitized_system_prompt_extras: Vec<String> = system_prompt_extras
.into_iter()
.map(|extra| sanitize_unicode_tags(&extra))
.collect();

if sanitized_system_prompt_extras.is_empty() {
if system_prompt_extras.is_empty() {
base_prompt
} else {
let sanitized_system_prompt_extras: Vec<String> = system_prompt_extras
.into_values()
.map(|extra| sanitize_unicode_tags(&extra))
.collect();

format!(
"{}\n\n# Additional Instructions:\n\n{}",
base_prompt,
Expand All @@ -204,7 +206,7 @@ impl PromptManager {
pub fn new() -> Self {
PromptManager {
system_prompt_override: None,
system_prompt_extras: Vec::new(),
system_prompt_extras: IndexMap::new(),
// Use the fixed current date time so that prompt cache can be used.
// Filtering to an hour to balance user time accuracy and multi session prompt cache hits.
current_date_timestamp: Utc::now().format("%Y-%m-%d %H:00").to_string(),
Expand All @@ -215,14 +217,15 @@ impl PromptManager {
pub fn with_timestamp(dt: DateTime<Utc>) -> Self {
PromptManager {
system_prompt_override: None,
system_prompt_extras: Vec::new(),
system_prompt_extras: IndexMap::new(),
current_date_timestamp: dt.format("%Y-%m-%d %H:%M:%S").to_string(),
}
}

/// Add an additional instruction to the system prompt
pub fn add_system_prompt_extra(&mut self, instruction: String) {
self.system_prompt_extras.push(instruction);
/// Add an additional instruction to the system prompt with a key
/// Using the same key will replace the previous instruction
pub fn add_system_prompt_extra(&mut self, key: String, instruction: String) {
self.system_prompt_extras.insert(key, instruction);
}

/// Override the system prompt with custom text
Expand Down Expand Up @@ -275,7 +278,7 @@ mod tests {
fn test_build_system_prompt_sanitizes_extras() {
let mut manager = PromptManager::new();
let malicious_extra = "Extra instruction\u{E0041}\u{E0042}\u{E0043}hidden";
manager.add_system_prompt_extra(malicious_extra.to_string());
manager.add_system_prompt_extra("test".to_string(), malicious_extra.to_string());

let result = manager.builder().build();

Expand All @@ -289,9 +292,14 @@ mod tests {
#[test]
fn test_build_system_prompt_sanitizes_multiple_extras() {
let mut manager = PromptManager::new();
manager.add_system_prompt_extra("First\u{E0041}instruction".to_string());
manager.add_system_prompt_extra("Second\u{E0042}instruction".to_string());
manager.add_system_prompt_extra("Third\u{E0043}instruction".to_string());
manager
.add_system_prompt_extra("test1".to_string(), "First\u{E0041}instruction".to_string());
manager.add_system_prompt_extra(
"test2".to_string(),
"Second\u{E0042}instruction".to_string(),
);
manager
.add_system_prompt_extra("test3".to_string(), "Third\u{E0043}instruction".to_string());

let result = manager.builder().build();

Expand All @@ -307,7 +315,7 @@ mod tests {
fn test_build_system_prompt_preserves_legitimate_unicode_in_extras() {
let mut manager = PromptManager::new();
let legitimate_unicode = "Instruction with 世界 and 🌍 emojis";
manager.add_system_prompt_extra(legitimate_unicode.to_string());
manager.add_system_prompt_extra("test".to_string(), legitimate_unicode.to_string());

let result = manager.builder().build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,14 @@ expression: system_prompt
You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.
goose is being developed as an open-source software project.

goose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,
claude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).
These models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10
months prior to the current date.

# Extensions

Extensions allow other applications to provide context to goose. Extensions connect goose to different data sources and
tools.
You are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level
problems using the tools in these extensions, and can interact with multiple at once.

If the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional
extensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the
extension_name. You should only enable extensions found from the search_available_extensions tool.
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
new ones.
Extensions provide additional tools and context from different data sources and applications.
You can dynamically enable or disable extensions as needed to help complete tasks.

No extensions are defined. You should let the user know that they should add extensions.


# Response Guidelines

- Use Markdown formatting for all responses.
- Follow best practices for Markdown, including:
- Using headers for organization.
- Bullet points for lists.
- Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic
links using angle brackets (e.g., <http://example.com/>).
- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the
language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.
- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.
Use Markdown formatting for all responses.
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,10 @@ expression: system_prompt
You are a general-purpose AI agent called goose, created by Block, the parent company of Square, CashApp, and Tidal.
goose is being developed as an open-source software project.

goose uses LLM providers with tool calling capability. You can be used with different language models (gpt-4o,
claude-sonnet-4, o1, llama-3.2, deepseek-r1, etc).
These models have varying knowledge cut-off dates depending on when they were trained, but typically it's between 5-10
months prior to the current date.

# Extensions

Extensions allow other applications to provide context to goose. Extensions connect goose to different data sources and
tools.
You are capable of dynamically plugging into new extensions and learning how to use them. You solve higher level
problems using the tools in these extensions, and can interact with multiple at once.

If the Extension Manager extension is enabled, you can use the search_available_extensions tool to discover additional
extensions that can help with your task. To enable or disable extensions, use the manage_extensions tool with the
extension_name. You should only enable extensions found from the search_available_extensions tool.
If Extension Manager is not available, you can only work with currently enabled extensions and cannot dynamically load
new ones.
Extensions provide additional tools and context from different data sources and applications.
You can dynamically enable or disable extensions as needed to help complete tasks.

Because you dynamically load extensions, your conversation history may refer
to interactions with extensions that are not currently active. The currently
Expand All @@ -31,20 +18,11 @@ in your tool specification.

## test

test supports resources, you can use extensionmanager__read_resource,
and extensionmanager__list_resources on this extension.
test supports resources.
### Instructions
how to use this extension


# Response Guidelines

- Use Markdown formatting for all responses.
- Follow best practices for Markdown, including:
- Using headers for organization.
- Bullet points for lists.
- Links formatted correctly, either as linked text (e.g., [this is linked text](https://example.com)) or automatic
links using angle brackets (e.g., <http://example.com/>).
- For code examples, use fenced code blocks by placing triple backticks (` ``` `) before and after the code. Include the
language identifier after the opening backticks (e.g., ` ```python `) to enable syntax highlighting.
- Ensure clarity, conciseness, and proper formatting to enhance readability and usability.
Use Markdown formatting for all responses.
Loading
Loading