feat(graph): seed a child graph agent's state from an agent node's inputs map

Agent nodes gain an optional `inputs:` map of templates resolved against
the parent state and overlaid on the child graph agent's initial_state
before its executor starts. A lone {{key}} keeps the state value's JSON
type; mixed text renders to a string; initial_prompt is still owned by the
dispatcher. run_child_agent and run_active_agent_graph keep their
signatures as wrappers over the new *_with_inputs variants, and
run_agent_for_graph rejects inputs aimed at a config-only agent or
containing initial_prompt.

Design: plans/map-subgraph-branches-design.md §3.12 (C18), task T5.
This commit is contained in:
Dark-Alex-17 committed 2026-09-11 10:56:27 -06:00
1 parent 0bef0f2c39
commit 3393123baf
8 files changed
+283 -8

No files matched your search

+45
View File
@@ -240,6 +240,9 @@ pub struct AgentNode {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub inputs: Option<HashMap<String, String>>,
#[serde(default)]
pub teammates: bool,
}
@@ -634,6 +637,48 @@ nodes:
}
}
#[test]
fn agent_node_inputs_parse_optionally_and_empty_map_is_some() {
let yaml = r#"
name: g
start: with_inputs
nodes:
with_inputs:
id: with_inputs
type: agent
agent: helper
prompt: hi
inputs:
width: "{{n}}"
note: "Repo: {{repo}}"
next: without_inputs
without_inputs:
id: without_inputs
type: agent
agent: helper
prompt: hi
next: empty_inputs
empty_inputs:
id: empty_inputs
type: agent
agent: helper
prompt: hi
inputs: {}
"#;
let graph: Graph = serde_yaml::from_str(yaml).unwrap();
let inputs_of = |id: &str| match &graph.get_node(id).unwrap().node_type {
NodeType::Agent(n) => n.inputs.clone(),
_ => panic!("expected Agent variant"),
};
let with = inputs_of("with_inputs").expect("inputs should parse");
assert_eq!(with.len(), 2);
assert_eq!(with.get("width").map(String::as_str), Some("{{n}}"));
assert_eq!(with.get("note").map(String::as_str), Some("Repo: {{repo}}"));
assert_eq!(inputs_of("without_inputs"), None);
assert_eq!(inputs_of("empty_inputs"), Some(HashMap::new()));
}
#[test]
fn deserializes_mcp_tools_at_graph_and_node_level() {
let yaml = r#"