From f0508bbac0fef101044114e284fd1849a6b6ca41 Mon Sep 17 00:00:00 2001 From: Gopal Date: Sun, 28 Dec 2025 10:20:50 +0530 Subject: [PATCH 1/2] fix: Check YAML kind field before parsing agent configs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When loading agents from a directory or running an agent file, we now check the `kind` field before attempting to parse as Agent configs. This prevents confusing errors when the file is a different resource type. Changes: - Added yaml_file_has_kind helper to check kind before parsing - Updated AgentRegistry.load_directory to skip non-Agent files - Updated TriggerHandler.load_agents_from_directory to check kind - Updated aofctl run agent to give clear error for wrong kind Before: Parsing a Trigger file as Agent would fail with: "Error: Failed to parse agent config: pr_review.yaml Field: spec Error: missing field `model`" After: Clear error message with hint: "Error: Wrong resource type: pr_review.yaml Expected: kind: Agent Found: kind: Trigger Hint: Use 'aofctl run trigger' instead of 'aofctl run agent'" Fixes #89 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- crates/aof-core/src/registry.rs | 26 ++++++++++++++++++++++++ crates/aof-triggers/src/handler/mod.rs | 28 ++++++++++++++++++++++++-- crates/aofctl/src/commands/run.rs | 21 ++++++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/crates/aof-core/src/registry.rs b/crates/aof-core/src/registry.rs index ea6ecc2..60231cb 100644 --- a/crates/aof-core/src/registry.rs +++ b/crates/aof-core/src/registry.rs @@ -72,6 +72,12 @@ impl Registry for AgentRegistry { let file_path = entry.path(); if file_path.extension().map_or(false, |e| e == "yaml" || e == "yml") { + // Skip non-Agent YAML files (Trigger, Fleet, Flow, etc.) + if !yaml_file_has_kind(&file_path, "Agent") { + tracing::debug!("Skipping non-Agent file: {:?}", file_path); + continue; + } + match load_yaml_file::(&file_path) { Ok(agent) => { let name = agent.name.clone(); @@ -663,6 +669,26 @@ fn load_yaml_file(path: &Path) -> AofResult { Ok(resource) } +/// Check if a YAML file has a specific `kind` field value +/// Returns true if the file has the expected kind, false otherwise +fn yaml_file_has_kind(path: &Path, expected_kind: &str) -> bool { + // Helper struct to just parse kind field + #[derive(serde::Deserialize)] + struct KindCheck { + kind: Option, + } + + let content = match std::fs::read_to_string(path) { + Ok(c) => c, + Err(_) => return false, + }; + + match serde_yaml::from_str::(&content) { + Ok(check) => check.kind.as_deref() == Some(expected_kind), + Err(_) => false, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/aof-triggers/src/handler/mod.rs b/crates/aof-triggers/src/handler/mod.rs index 3f4b510..123a194 100644 --- a/crates/aof-triggers/src/handler/mod.rs +++ b/crates/aof-triggers/src/handler/mod.rs @@ -767,6 +767,12 @@ impl TriggerHandler { continue; } + // Check if file has kind: Agent before trying to parse + if !Self::yaml_file_has_kind(&path, "Agent") { + debug!("Skipping non-Agent file: {:?}", path); + continue; + } + // Try to load as agent match runtime.load_agent_from_file(path.to_str().unwrap()).await { Ok(agent_name) => { @@ -774,8 +780,8 @@ impl TriggerHandler { count += 1; } Err(e) => { - // Not all YAML files are agents, this is fine - debug!("Skipping {:?}: {}", path, e); + // Agent file failed to parse - this is a real error + warn!("Failed to load agent from {:?}: {}", path, e); } } } @@ -787,6 +793,24 @@ impl TriggerHandler { Ok(count) } + /// Check if a YAML file has a specific `kind` field value + fn yaml_file_has_kind(path: &std::path::Path, expected_kind: &str) -> bool { + #[derive(serde::Deserialize)] + struct KindCheck { + kind: Option, + } + + let content = match std::fs::read_to_string(path) { + Ok(c) => c, + Err(_) => return false, + }; + + match serde_yaml::from_str::(&content) { + Ok(check) => check.kind.as_deref() == Some(expected_kind), + Err(_) => false, + } + } + /// Register a platform pub fn register_platform(&mut self, platform: Arc) { let name = platform.platform_name(); diff --git a/crates/aofctl/src/commands/run.rs b/crates/aofctl/src/commands/run.rs index 097c315..4b3699f 100644 --- a/crates/aofctl/src/commands/run.rs +++ b/crates/aofctl/src/commands/run.rs @@ -27,7 +27,26 @@ struct K8sMetadata { /// Parse agent config with detailed error messages including field path and line numbers fn parse_agent_config(content: &str, file_path: &str) -> Result { - // First, try the normal parse + // First, check if this is the right kind of resource + #[derive(serde::Deserialize)] + struct KindCheck { + kind: Option, + } + + if let Ok(check) = serde_yaml::from_str::(content) { + if let Some(kind) = check.kind { + if kind != "Agent" { + return Err(anyhow!( + "Wrong resource type: {}\n\n Expected: kind: Agent\n Found: kind: {}\n\n Hint: Use 'aofctl run {}' instead of 'aofctl run agent'\n", + file_path, + kind, + kind.to_lowercase() + )); + } + } + } + + // Now try the normal parse let deserializer = serde_yaml::Deserializer::from_str(content); let result: Result = serde_path_to_error::deserialize(deserializer); From d3526951c9905d31301ae2f8c52f3d8685e72cf7 Mon Sep 17 00:00:00 2001 From: Gopal Date: Sun, 28 Dec 2025 11:10:53 +0530 Subject: [PATCH 2/2] fix: Provide accurate hints for non-Agent resource types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Triggers: Explain they're for 'aofctl serve', not 'run' - Fleet/Flow/Workflow: Suggest correct 'aofctl run ' command - Unknown types: Generic helpful message 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- crates/aofctl/src/commands/run.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/aofctl/src/commands/run.rs b/crates/aofctl/src/commands/run.rs index 4b3699f..5498aec 100644 --- a/crates/aofctl/src/commands/run.rs +++ b/crates/aofctl/src/commands/run.rs @@ -36,11 +36,18 @@ fn parse_agent_config(content: &str, file_path: &str) -> Result { if let Ok(check) = serde_yaml::from_str::(content) { if let Some(kind) = check.kind { if kind != "Agent" { + let hint = match kind.as_str() { + "Trigger" => "Triggers are used with 'aofctl serve', not 'aofctl run'".to_string(), + "Fleet" => "Use 'aofctl run fleet ' to run a fleet".to_string(), + "Flow" | "AgentFlow" => "Use 'aofctl run flow ' to run a flow".to_string(), + "Workflow" => "Use 'aofctl run workflow ' to run a workflow".to_string(), + _ => format!("This file contains a {} resource, not an Agent", kind), + }; return Err(anyhow!( - "Wrong resource type: {}\n\n Expected: kind: Agent\n Found: kind: {}\n\n Hint: Use 'aofctl run {}' instead of 'aofctl run agent'\n", + "Wrong resource type: {}\n\n Expected: kind: Agent\n Found: kind: {}\n\n Hint: {}\n", file_path, kind, - kind.to_lowercase() + hint )); } }