From ab0b89dd678c9364f29b8820a48929132eae8210 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 22 Jul 2026 12:59:30 -0600 Subject: [PATCH] fix: fetch descriptions from graph agent configs as well when listing agents --- assets/agents/librarian/graph.yaml | 2 -- src/config/agent.rs | 57 +++++++++++++++++++++++++++--- src/config/request_context.rs | 21 +++++------ 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/assets/agents/librarian/graph.yaml b/assets/agents/librarian/graph.yaml index 08a3670..f1b21d4 100644 --- a/assets/agents/librarian/graph.yaml +++ b/assets/agents/librarian/graph.yaml @@ -8,8 +8,6 @@ description: | sisyphus alongside explore when unfamiliar libraries/APIs/frameworks are involved. - Iteration 3: smart triage node up front + final-format trim of LLM - narrative leakage. version: "1.0" global_tools: diff --git a/src/config/agent.rs b/src/config/agent.rs index de3b45d..5e192ab 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -1020,15 +1020,30 @@ pub fn list_agents_with_descriptions() -> Vec<(String, String)> { list_agents() .into_iter() .map(|name| { - let description = AgentConfig::load(&paths::agent_config_file(&name)) - .ok() - .map(|c| c.description) - .unwrap_or_default(); + let description = load_agent_description(&name); (name, description) }) .collect() } +#[derive(Deserialize)] +struct AgentMetadataStub { + #[serde(default)] + description: String, +} + +fn load_agent_description(name: &str) -> String { + if let Ok(config) = AgentConfig::load(&paths::agent_config_file(name)) { + return config.description; + } + if let Ok(contents) = read_to_string(paths::agent_graph_file(name)) + && let Ok(meta) = serde_yaml::from_str::(&contents) + { + return meta.description; + } + String::new() +} + pub fn complete_agent_variables(agent_name: &str) -> Vec<(String, Option)> { let config_path = paths::agent_config_file(agent_name); if !config_path.exists() { @@ -1218,4 +1233,38 @@ variables: assert_eq!(config.max_agent_depth, default_max_agent_depth()); assert_eq!(config.escalation_timeout, default_escalation_timeout()); } + + #[test] + fn agent_metadata_stub_extracts_description_from_graph_yaml() { + let yaml = r#" +name: librarian +description: External-reference research agent. +version: "1.0" +start: triage +nodes: {} +"#; + let meta: AgentMetadataStub = serde_yaml::from_str(yaml).unwrap(); + assert_eq!(meta.description, "External-reference research agent."); + } + + #[test] + fn agent_metadata_stub_extracts_multiline_description() { + let yaml = r#" +name: coder +description: | + Implementation agent. Plans, implements, and runs build + tests in a + bounded fix-loop until verified. +version: "1.0" +"#; + let meta: AgentMetadataStub = serde_yaml::from_str(yaml).unwrap(); + assert!(meta.description.starts_with("Implementation agent.")); + assert!(meta.description.contains("bounded fix-loop")); + } + + #[test] + fn agent_metadata_stub_defaults_when_description_missing() { + let yaml = "name: nameless\nversion: \"1.0\"\n"; + let meta: AgentMetadataStub = serde_yaml::from_str(yaml).unwrap(); + assert_eq!(meta.description, ""); + } } diff --git a/src/config/request_context.rs b/src/config/request_context.rs index f2b1af6..c8ccbd5 100644 --- a/src/config/request_context.rs +++ b/src/config/request_context.rs @@ -10,8 +10,8 @@ use super::{ AGENTS_DIR_NAME, Agent, AgentVariables, AppConfig, AppState, AssetCategory, CREATE_TITLE_ROLE, Input, InstallFilter, LEFT_PROMPT, LastMessage, MESSAGES_FILE_NAME, RIGHT_PROMPT, Role, RoleLike, SESSIONS_DIR_NAME, SUMMARIZATION_PROMPT, SUMMARY_CONTEXT_PROMPT, StateFlags, - TEMP_ROLE_NAME, TEMP_SESSION_NAME, WorkingMode, ensure_parent_exists, list_agents, memory, - paths, + TEMP_ROLE_NAME, TEMP_SESSION_NAME, WorkingMode, ensure_parent_exists, list_agents, + list_agents_with_descriptions, memory, paths, }; use super::{MessageContentToolCalls, prompts}; use crate::client::{Model, ModelType, list_models}; @@ -2338,21 +2338,18 @@ impl RequestContext { "rags" => print_asset_names("RAGs", &paths::list_rags()), "macros" => print_asset_names("macros", &paths::list_macros()), "agents" => { - let names = list_agents(); - if names.is_empty() { + let entries = list_agents_with_descriptions(); + if entries.is_empty() { println!("No agents found."); return Ok(()); } println!("Agents:"); - for name in names { - let description = AgentConfig::load(&paths::agent_config_file(&name)) - .ok() - .map(|c| c.description) - .filter(|d| !d.is_empty()); - match description { - Some(description) => println!(" • {name} — {description}"), - None => println!(" • {name}"), + for (name, description) in entries { + if description.is_empty() { + println!(" • {name}"); + } else { + println!(" • {name} — {description}"); } }