From fa04e0937305a800a6f15fc2ebd5d6b367c02fb1 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 26 Aug 2026 12:53:17 -0600 Subject: [PATCH] feat(graph): support max_concurrent_jobs at the graph level Graph agents could only inherit the app-wide job budget; the agent-level header in graph.yaml now accepts max_concurrent_jobs alongside model/temperature, flowing through AgentConfig::from_graph into the run-wide supervisor. Deliberately graph-wide, not per-node: jobs outlive the node that started them. --- graph.example.yaml | 6 ++++++ src/config/agent.rs | 3 +++ src/graph/types.rs | 4 ++++ src/graph/validator.rs | 1 + 4 files changed, 14 insertions(+) diff --git a/graph.example.yaml b/graph.example.yaml index f1ea9ae..f027538 100644 --- a/graph.example.yaml +++ b/graph.example.yaml @@ -36,6 +36,12 @@ top_p: null # Default sampling top-p for `llm` nodes reasoning_effort: null # Default reasoning effort for `llm` nodes that don't override it. # Only valid when the model declares reasoning_levels. +max_concurrent_jobs: 5 # Max background jobs (`job__*` tools) running at once across the + # whole graph run. Jobs live in the run-wide supervisor and outlive + # the `llm` node that started them, so the budget is graph-wide — + # there is no per-node override. Overrides the global setting; + # 0 disables background jobs for this graph agent. + global_tools: # Tool universe an `llm` node's `tools:` whitelist draws from - web_search_coyote.sh - fetch_url_via_curl.sh diff --git a/src/config/agent.rs b/src/config/agent.rs index 9fbf663..15638ea 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -854,6 +854,7 @@ impl AgentConfig { variables: graph.variables.clone(), can_spawn_agents: graph.has_agent_node(), max_concurrent_agents: default_max_concurrent_agents(), + max_concurrent_jobs: graph.max_concurrent_jobs, max_agent_depth: default_max_agent_depth(), escalation_timeout: default_escalation_timeout(), ..AgentConfig::default() @@ -1328,6 +1329,7 @@ variables: model: claude:claude-sonnet-4-6 temperature: 0.3 top_p: 0.8 + max_concurrent_jobs: 2 global_tools: - fetch_pdf.sh mcp_servers: @@ -1350,6 +1352,7 @@ variables: assert_eq!(config.model_id.as_deref(), Some("claude:claude-sonnet-4-6")); assert_eq!(config.temperature, Some(0.3)); assert_eq!(config.top_p, Some(0.8)); + assert_eq!(config.max_concurrent_jobs, Some(2)); assert_eq!(config.global_tools, vec!["fetch_pdf.sh"]); assert_eq!(config.mcp_servers, vec!["pubmed-search"]); assert_eq!(config.conversation_starters, vec!["Start here"]); diff --git a/src/graph/types.rs b/src/graph/types.rs index 18bdf5d..e45322b 100644 --- a/src/graph/types.rs +++ b/src/graph/types.rs @@ -28,6 +28,9 @@ pub struct Graph { #[serde(default, skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub max_concurrent_jobs: Option, + #[serde(default)] pub global_tools: Vec, @@ -895,6 +898,7 @@ nodes: assert!(graph.model.is_none()); assert!(graph.temperature.is_none()); assert!(graph.top_p.is_none()); + assert!(graph.max_concurrent_jobs.is_none()); assert!(graph.global_tools.is_empty()); assert!(graph.mcp_servers.is_empty()); assert!(graph.conversation_starters.is_empty()); diff --git a/src/graph/validator.rs b/src/graph/validator.rs index fe438f7..4b7cc64 100644 --- a/src/graph/validator.rs +++ b/src/graph/validator.rs @@ -998,6 +998,7 @@ mod tests { temperature: None, top_p: None, reasoning_effort: None, + max_concurrent_jobs: None, global_tools: Vec::new(), mcp_servers: Vec::new(), skills_enabled: None,