feat(TASK-009): add fallback routing to agent nodes

Agent nodes gain an optional fallback target with llm-node parity: on any
failure (retries exhausted, hard error, or structured-extraction failure)
the failure chain is written into state as 'Agent node failed: <chain>'
and the chain routes to the fallback node. Without a fallback the error
propagates unchanged. Validator collects agent fallback edges for
missing-target checks, reachability, and map-branch locality.
This commit is contained in:
Dark-Alex-17 committed 2026-09-14 10:50:33 -06:00
1 parent 1c68331738
commit be0c5462ed
5 files changed
+416 -57

No files matched your search

+38
View File
@@ -243,6 +243,9 @@ pub struct AgentNode {
#[serde(default = "default_max_attempts")]
pub max_attempts: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fallback: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub inputs: Option<HashMap<String, String>>,
@@ -678,6 +681,41 @@ nodes:
}
}
#[test]
fn agent_node_fallback_parses_and_defaults_to_none() {
let yaml = r#"
name: g
start: a
nodes:
a:
id: a
type: agent
agent: helper
prompt: hi
fallback: b
next: b
b:
id: b
type: agent
agent: helper
prompt: hi
next: e
e:
id: e
type: end
output: done
"#;
let graph: Graph = serde_yaml::from_str(yaml).unwrap();
match &graph.get_node("a").unwrap().node_type {
NodeType::Agent(n) => assert_eq!(n.fallback.as_deref(), Some("b")),
_ => panic!("expected Agent variant"),
}
match &graph.get_node("b").unwrap().node_type {
NodeType::Agent(n) => assert!(n.fallback.is_none()),
_ => panic!("expected Agent variant"),
}
}
#[test]
fn agent_node_inputs_parse_optionally_and_empty_map_is_some() {
let yaml = r#"