feat: projects as backend sources with system prompt injection (#8739)

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Douwe Osinga
2026-05-07 10:30:15 -04:00
committed by GitHub
parent bced4ea5ec
commit ff42171903
29 changed files with 1241 additions and 919 deletions
+23
View File
@@ -366,6 +366,24 @@ impl Agent {
messages
}
async fn load_project_instructions(&self, session: &Session) -> Option<String> {
let thread_id = session.thread_id.as_deref()?;
let thread_mgr =
crate::session::ThreadManager::new(self.config.session_manager.storage().clone());
let thread = thread_mgr.get_thread(thread_id).await.ok()?;
let project_id = thread.metadata.project_id.as_deref()?;
let entry = crate::sources::read_project(project_id).ok()?;
let mut parts = Vec::new();
parts.push(format!("# Project: {}", entry.name));
if !entry.description.is_empty() {
parts.push(entry.description.clone());
}
if !entry.content.is_empty() {
parts.push(entry.content.clone());
}
Some(parts.join("\n\n"))
}
async fn prepare_reply_context(
&self,
session_id: &str,
@@ -1251,6 +1269,11 @@ impl Agent {
goose_mode,
initial_messages,
} = context;
if let Some(project_addendum) = self.load_project_instructions(&session).await {
system_prompt = format!("{system_prompt}\n\n{project_addendum}");
}
self.reset_retry_attempts().await;
let provider = self.provider().await?;
@@ -125,9 +125,10 @@ fn parse_agent_content(content: &str, path: &Path) -> Option<SourceEntry> {
name: metadata.name,
description,
content: body,
directory: path.to_string_lossy().into_owned(),
path: path.to_string_lossy().into_owned(),
global: false,
supporting_files: Vec::new(),
properties: std::collections::HashMap::new(),
})
}
@@ -171,9 +172,10 @@ fn scan_recipes_from_dir(
name,
description: recipe.description.clone(),
content: recipe.instructions.clone().unwrap_or_default(),
directory: path.to_string_lossy().into_owned(),
path: path.to_string_lossy().into_owned(),
global: false,
supporting_files: Vec::new(),
properties: std::collections::HashMap::new(),
});
}
Err(e) => {
@@ -598,9 +600,10 @@ impl SummonClient {
name: sr.name.clone(),
description,
content: String::new(),
directory: sr.path.clone(),
path: sr.path.clone(),
global: false,
supporting_files: Vec::new(),
properties: std::collections::HashMap::new(),
});
}
}
@@ -1160,7 +1163,7 @@ impl SummonClient {
}
}
let recipe_file = load_local_recipe_file(&source.directory)
let recipe_file = load_local_recipe_file(&source.path)
.map_err(|e| format!("Failed to load recipe '{}': {}", source.name, e))?;
let param_values: Vec<(String, String)> = params
@@ -1193,10 +1196,10 @@ impl SummonClient {
source: &SourceEntry,
params: &DelegateParams,
) -> Result<Recipe, String> {
let agent_content = if source.directory.is_empty() {
let agent_content = if source.path.is_empty() {
return Err("Agent source has no path".to_string());
} else {
std::fs::read_to_string(&source.directory)
std::fs::read_to_string(&source.path)
.map_err(|e| format!("Failed to read agent file: {}", e))?
};