feat: teammates flag enabling peer messaging between concurrent graph agent nodes
Agent nodes marked teammates: true whose sub-agents run concurrently (map fan-out branches or multiple flagged agent nodes in one super-step) now form a team: each member gets a pre-provisioned inbox in a messaging-only PeerRegistry before any branch starts, a teammate roster injected into its prompt, peer resolution in agent__send_message, and a peers section in agent__list_running. Registries are scoped to a single fan-out and carry no lifecycle semantics: outputs still flow exclusively through collect_into/state reducers, finished peers reject new messages, and nothing registers in a supervisor. Also hardens the agent-recursion depth guard to work without a supervisor and adds validator warnings for zero-valued orchestration limits.
This commit is contained in:
11 files changed
+975
-45
No files matched your search
@@ -176,6 +176,7 @@ impl GraphValidator {
|
||||
self.validate_llm_nodes(graph, &mut result);
|
||||
self.validate_llm_skills(graph, &mut result);
|
||||
self.validate_max_concurrency(graph, &mut result);
|
||||
self.validate_orchestration_limits(graph, &mut result);
|
||||
self.validate_map_branches(graph, &mut result);
|
||||
self.validate_parallel_user_interaction(graph, &mut result);
|
||||
self.validate_parallel_writes(graph, &mut result);
|
||||
@@ -529,6 +530,23 @@ impl GraphValidator {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_orchestration_limits(&self, graph: &Graph, result: &mut ValidationResult) {
|
||||
if graph.max_agent_depth == Some(0) {
|
||||
result.warning(ValidationError::new(
|
||||
"`max_agent_depth: 0` forbids spawning any sub-agent, so every \
|
||||
`agent` node and dynamic spawn will fail; remove the key or \
|
||||
set it >= 1",
|
||||
));
|
||||
}
|
||||
if graph.max_concurrent_agents == Some(0) {
|
||||
result.warning(ValidationError::new(
|
||||
"`max_concurrent_agents: 0` leaves no slot for any spawn to \
|
||||
acquire, so agent spawning deadlocks; remove the key or set \
|
||||
it >= 1",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_map_branches(&self, graph: &Graph, result: &mut ValidationResult) {
|
||||
for (map_id, node) in &graph.nodes {
|
||||
let NodeType::Map(m) = &node.node_type else {
|
||||
@@ -1027,6 +1045,9 @@ mod tests {
|
||||
top_p: None,
|
||||
reasoning_effort: None,
|
||||
max_concurrent_jobs: None,
|
||||
can_spawn_agents: None,
|
||||
max_concurrent_agents: None,
|
||||
max_agent_depth: None,
|
||||
global_tools: Vec::new(),
|
||||
mcp_servers: Vec::new(),
|
||||
mcp_tools: None,
|
||||
@@ -1637,6 +1658,7 @@ mod tests {
|
||||
state_updates: None,
|
||||
output_schema: None,
|
||||
timeout: None,
|
||||
teammates: false,
|
||||
}),
|
||||
next: next.map(NextTargets::from),
|
||||
}
|
||||
@@ -2596,6 +2618,57 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_max_agent_depth_warns() {
|
||||
let mut graph = graph_with(vec![("e", end_node("e"))], "e");
|
||||
graph.max_agent_depth = Some(0);
|
||||
|
||||
let result = validator().validate(&graph);
|
||||
|
||||
assert!(result.is_valid());
|
||||
assert!(
|
||||
result
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("`max_agent_depth: 0`")),
|
||||
"expected max_agent_depth=0 warning: {:?}",
|
||||
result.warnings
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_max_concurrent_agents_warns() {
|
||||
let mut graph = graph_with(vec![("e", end_node("e"))], "e");
|
||||
graph.max_concurrent_agents = Some(0);
|
||||
|
||||
let result = validator().validate(&graph);
|
||||
|
||||
assert!(result.is_valid());
|
||||
assert!(
|
||||
result
|
||||
.warnings
|
||||
.iter()
|
||||
.any(|w| w.message.contains("`max_concurrent_agents: 0`")),
|
||||
"expected max_concurrent_agents=0 warning: {:?}",
|
||||
result.warnings
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unset_orchestration_limits_do_not_warn() {
|
||||
let graph = graph_with(vec![("e", end_node("e"))], "e");
|
||||
|
||||
let result = validator().validate(&graph);
|
||||
|
||||
assert!(
|
||||
!result.warnings.iter().any(|w| {
|
||||
w.message.contains("max_agent_depth") || w.message.contains("max_concurrent_agents")
|
||||
}),
|
||||
"unset limits should not warn: {:?}",
|
||||
result.warnings
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_max_concurrency_zero_errors() {
|
||||
let mut map = map_node_basic("m", "br", Some("end"));
|
||||
|
||||
Reference in new issue
Block a user