From 8b61792de113e9483f9e0e91ec4b4f036a373f2f Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 10 Sep 2026 13:58:40 -0600 Subject: [PATCH] docs(graph): timeout: 0 disables the wall-clock bound Documents the new zero-means-unbounded rule on all five timeout fields (settings.timeout, agent, script, llm, rag), the validator warning that flags an explicit 0, how bounds compose outward-in across nested graph agents, and what interrupts an unbounded step. --- Graph-Agents.md | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Graph-Agents.md b/Graph-Agents.md index 26e7c10..4d19c2f 100644 --- a/Graph-Agents.md +++ b/Graph-Agents.md @@ -95,7 +95,7 @@ settings: max_loop_iterations: 100 # PER-NODE visit cap; default 100 (see below) log_state_snapshots: true # log state JSON before each node executes validate_before_run: true # run the graph validator on startup - timeout: 600 # optional overall timeout in seconds + timeout: 600 # optional overall timeout in seconds; 0 = no bound max_concurrency: 8 # cap on simultaneously running parallel branches; default 8 reducers: # optional; declares how parallel branches merge writes @@ -159,7 +159,21 @@ nodes: `fallback`s, so any cleanup/reporting nodes are skipped. Prefer per-node `timeout`s on `llm` nodes (which *do* route via `fallback`) as the operative bound, and treat the graph-level `timeout` as a generous - backstop. + backstop. `0` disables the bound entirely (equivalent to unset; + `--validate` flags the explicit `0` as an intentional choice) — for graphs + that must run until they complete, e.g. an end-to-end implementation run; + pair it with a sane `max_loop_iterations`. **Bounds compose outward-in:** + if this graph is run by another graph's `agent` node, that node's + `timeout` (default 300) still applies and will drop this graph regardless + of its own settings — set `timeout: 0` there too. Conversely a parent's + `timeout: 0` does not lift a child's non-zero `settings.timeout`; the + child's own executor enforces it. A root graph run directly is bounded + only by its own `settings.timeout`. An unbounded run ends only on + completion, error, or an enclosing bound; Ctrl-C is observed between + super-steps, and an in-flight agent step only stops when its own loop + observes the interrupt (a script step is killed only when its future is + dropped). The 300s agent default was a de-facto safety net for a wedged + child — `timeout: 0` is the explicit opt-out of that net. - **`initial_state`:** A JSON-compatible object. Values are seeded into state before any node runs and are referenced from any node via `{{key}}` templates. @@ -243,7 +257,7 @@ research_topic: type: agent agent: deep-researcher # name of an existing Coyote agent prompt: "Research {{topic}}" # interpolated against state - timeout: 600 # optional, in seconds (default 300) + timeout: 600 # optional, seconds; default 300, 0 = no bound inputs: # optional; only when the child is a GRAPH agent max_parallel: "{{inner_width}}" # seeds the child's initial_state (see below) teammates: false # optional; peer messaging with concurrent siblings (see below) @@ -258,7 +272,10 @@ research_topic: - **`prompt`:** The user message sent to the child agent. Templated against the current graph state. - **`timeout`:** Hard wall-clock cap. If the child agent exceeds it, the - whole graph fails (no built-in fallback path on agent nodes). + whole graph fails (no built-in fallback path on agent nodes). `0` + disables the bound; the child runs until it returns. Required on the + calling node to let a child graph's own `timeout: 0` take effect; the + child's non-zero `settings.timeout` still applies. - **`state_updates`:** Map of `state_key: "{{template}}"`. The child agent's final text is available inside this map as `{{output}}`. - **`inputs`:** Map of `child_state_key: "{{template}}"`. Each value is @@ -396,7 +413,7 @@ route_after_parse: id: route_after_parse type: script script: scripts/route_after_parse.py - timeout: 30 # seconds, default 30 + timeout: 30 # seconds; default 30, 0 = no bound fallback: handle_error # optional: where to route on script failure state_updates: # applied after stdout merge last_run: "{{some_value}}" @@ -571,7 +588,7 @@ grade_research: state_updates: grade: "{{output}}" output_schema: { ... } # optional, see "Structured Output" below - timeout: 120 # optional; node wall-clock cap in seconds (unset = no timeout) + timeout: 120 # optional; node wall-clock cap in seconds (unset or 0 = no timeout) next: synthesize ``` @@ -639,6 +656,7 @@ A node-level `timeout` counts as a failure like any other: it routes via per-node `timeout`s the preferred way to bound long-running `llm` nodes. Unlike the graph-level `settings.timeout`, a node timeout still gives the graph a chance to recover or report through its `fallback` route. +`timeout: 0` (or unset) disables the bound. ### Retries (`max_attempts`) @@ -712,7 +730,7 @@ research_context: - https://example.com/spec query: "{{initial_prompt}}" # templated; defaults to "{{initial_prompt}}" top_k: 5 # optional; default = the knowledge base's own top_k - timeout: 120 # optional; retrieval timeout in seconds (default 120) + timeout: 120 # optional; retrieval timeout in seconds (default 120; 0 = no bound) state_updates: # required in practice (see below) rag_context: "{{output.context}}" rag_sources: "{{output.sources}}" @@ -735,7 +753,7 @@ answer: query an upstream `llm` node produced. - **`top_k`:** Number of chunks to retrieve. Defaults to the knowledge base's own configured `top_k`. -- **`timeout`:** Retrieval timeout in seconds. Default 120. +- **`timeout`:** Retrieval timeout in seconds. Default 120; `0` = no bound. - **`state_updates`:** Where the result goes. A `rag` node with no `state_updates` discards its result (the validator warns). @@ -1060,6 +1078,8 @@ script node upstream. failed item). One failing item fails the map. - **Per-item timeouts.** `settings.timeout` applies to the whole super-step; node-level `timeout`s (llm/agent/script) apply per step. + `timeout: 0` on a branch's `agent`/`script` nodes is the explicit opt-out + for long-running chains; the item then runs until its terminal node. - **Shared state between items mid-run.** Items communicate via [teammate messaging](#teammate-messaging-in-fan-outs); results merge at collect.