Skip to main content
Execution graphs let you describe a multi-step workflow as a picture: each box is a task an agent will perform, and arrows show which task depends on which. Comis runs the graph for you, fanning tasks out in parallel where possible and stitching the results back together. You can also ask an agent in plain English (for example, “have four analysts research NVDA in parallel, then a trader give a verdict”) — the agent translates that description into a graph and runs it. Under the hood, an execution graph is a directed acyclic graph (DAG). Each node in the graph is a sub-agent task. Nodes can run in parallel when they have no dependencies, or wait for upstream nodes to finish before starting. The graph coordinator handles scheduling, result forwarding, timeouts, budget enforcement, and failure cascading automatically.

When to use execution graphs

Execution graphs are the right tool when your workflow has multiple steps that need to coordinate:
  • Research pipelines — search for information, analyze results, write a summary (sequential chain)
  • Fan-out/fan-in — analyze 5 repositories in parallel, then synthesize findings into a single report
  • Adversarial debate — have bull and bear analysts argue before a synthesizer makes a final call (debate node type)
  • Consensus voting — have multiple agents vote independently in parallel (vote node type)
  • Editorial refinement — draft, review, and polish through sequential agents (refine node type)
  • Human-in-the-loop — pause for user approval before executing a high-stakes action (approval-gate node type)
  • Budget-controlled batch processing — process multiple items with a total cost ceiling
If you just need one agent to call another, use sub-agent sessions instead. Execution graphs are for workflows with multiple coordinated steps.

Quick example

Here is a 3-node graph that researches a topic, analyzes the findings, and writes a report:
The research node runs first (no dependencies). When it finishes, analyze starts with the research output injected into its task text via {{research.result}}. Finally, write produces the report.

Key concepts

Nodes

Each node represents a sub-agent task. A graph can have between 1 and 20 nodes. Every node requires a node_id (unique within the graph) and a task (the instruction for the sub-agent). Optional node settings:

Node types

By default, each node spawns a single sub-agent to execute its task. For multi-agent orchestration patterns, set type_id and type_config to use a built-in node type: See Node Types for configuration details and examples.

Dependencies

Nodes declare their dependencies using depends_on arrays. The graph coordinator validates the dependency structure at define time — it detects cycles, missing references, duplicate node IDs, and self-dependencies. Nodes with no dependencies are root nodes and start immediately.

Barrier modes

Barrier modes control when a fan-in node (a node with multiple dependencies) should proceed:
All barrier modes wait for every dependency to reach a terminal state (completed, failed, or skipped) before evaluating. A best-effort node does not fire as soon as one dependency completes — it waits until all dependencies have finished, then checks that at least one succeeded.

Failure policies

The graph-level on_failure setting controls what happens when a node fails:

Data flow

Nodes consume outputs from upstream nodes using {{nodeId.result}} templates directly in the task text. The nodeId in the template must appear in the consuming node’s depends_on array.
When analyze completes, its output text replaces {{analyze.result}} in the summarize node’s task description. User-provided variables: Use ${VARIABLE_NAME} syntax for values the user provides before execution. Variables are resolved at graph.execute time via the variables parameter. See the Developer Guide for details.
Nodes can only reference outputs from nodes listed in their depends_on array. The graph coordinator validates this at execution time and rejects invalid references.

Shared data folder

Each graph execution gets a temporary shared directory at ~/.comis/graph-runs/{graphId}/. All nodes in the graph can read and write files in this folder, making it useful for exchanging large payloads (reports, data files, images) that do not fit in text-based template passing. The folder persists after graph completion so output files remain accessible.

Budget and timeouts

Graphs support resource limits at both the node and graph level:
  • Per-node timeout (timeout_ms on the node) — kills the node’s sub-agent if it runs too long
  • Graph-level timeout (timeout_ms on the graph) — cancels all remaining nodes if the entire graph exceeds the time limit
  • Budget limits (budget.max_tokens, budget.max_cost) — cancels the graph if cumulative token usage or cost across all nodes exceeds the limit

Token budgets

There are two distinct token guardrails, and they apply at different scopes:
  • Per-node token budget — a tokenBudget cap on a single node’s sub-agent. It is enforced two ways: as a live mid-run hard stop (the sub-agent’s tool calls are blocked once it has spent its cap) and as a post-completion accounting check. A node that exceeds its budget fails — it is not retried, even when retries is set, because a retry would re-burn the budget. The graph’s on_failure policy then decides the cascade: under fail-fast the failure aborts downstream dependents, under continue siblings whose barrier is still satisfiable keep running.
  • Cumulative graph budget (budget.max_tokens / budget.max_cost) — bounds the total spend summed across every node, and cancels the whole graph when the total is exceeded.
Where a node’s per-node budget comes from (first match wins):
  1. The node’s own tokenBudget, when set.
  2. The operator default security.agentToAgent.tokenBudget (a positive integer applies the same cap to every node; see Configuration).
  3. The inherited graph sharebudget.max_tokens divided by the total node count — but only when the graph sets a token budget. The share is floored at 1 token, so an under-provisioned budget (max_tokens smaller than the node count) yields a tight-but-usable per-node cap rather than a 0 cap that would brick every node.
  4. Otherwise unbounded — a graph with no budget set anywhere behaves exactly as before.
The two checks run in series, node-first: when a single node’s overage also tips the cumulative graph budget, that node fails its own budget first, then the graph aborts. This ordering is deterministic — never a race — so one runaway sub-agent is contained to its own node rather than silently consuming the entire graph’s allowance.
The per-node tokenBudget field is present on the graph node schema and is honored by the coordinator. Today it is reached through the operator default and the inherited graph share (above); a dedicated per-node authoring key on the pipeline tool is a planned follow-up. To bound an individual node now, set a graph budget.max_tokens (each node inherits its share) or the operator default.
The per-node tokenBudget (and the operator default / inherited share that feed it) applies to regular graph nodes. Driver-typed nodes (a node with a typeId, e.g. a debate or iterate driver that spawns multiple sub-agent rounds) are not bounded by the per-node token budget today — they are governed instead by the cumulative graph budget (budget.max_tokens / budget.max_cost), which sums every driver round’s spend and cancels the graph when the total is exceeded. Per-node budgeting for driver nodes (a cumulative-across-rounds cap with a mid-round hard stop) is a planned follow-up; to bound a driver node today, set the cumulative graph budget.

Graph lifecycle

Every graph follows the same lifecycle:
  1. Define — validate the graph structure (DAG check, dependency resolution) without executing
  2. Execute — start the graph; root nodes run immediately, others wait for dependencies
  3. Monitor — check status of running and completed nodes
  4. Complete or cancel — the graph reaches a terminal state when all nodes finish, or it can be manually cancelled
Node status transitions: pending -> ready -> running -> completed / failed / skipped Graph status values: running, completed, failed, cancelled

Worked example: four-analyst NVDA research team

A common request: “Have four analysts research NVDA in parallel, then have a trader read every analysis and give a final verdict.” This is a classic fan-out / fan-in pattern. Here is the complete graph:
The DAG looks like this:
The four analysts run concurrently. Once all four reach a terminal state and all succeeded (barrier_mode: all), the verdict node fires with their outputs templated into its task. If you set barrier_mode: majority on verdict, it would fire when any three of the four analysts succeed — useful when you want resilience to a single flaky tool. With best-effort, it fires as long as one succeeds. The graph-level on_failure: continue means a single analyst failing does not cancel the others — the verdict node decides what to do with whichever analyses completed. You can build this same graph in the Visual Builder by dragging four nodes, connecting them all to a fifth node, and pasting the task templates.

The pipeline tool

Agents create and manage execution graphs using the pipeline built-in tool. It supports 10 actions: define, execute, status, outputs, cancel, save, load, list, delete, and from_intent (the one-line-intent synthesizer, on by default). See the Pipeline Tool Guide for the complete action reference with examples.

Visual builder

The web dashboard includes a visual graph builder where you can design execution graphs by dragging and connecting nodes, then save and execute them directly. See the Visual Builder Guide for details.

Configuration

Execution graphs require agent-to-agent communication to be enabled:
Without this setting, the pipeline tool will not be available to agents. See Configuration Reference for all security settings.

Small-model authoring

Frontier models write valid execution graphs directly. Weaker models sometimes emit a graph that is almost right — the correct shape but a schema slip — or would benefit from describing the workflow in one line rather than hand-writing every node. The small-model authoring layer helps them, and it is on by default (full capability out of the box): every flag under orchestration.authoring defaults to true, so the authoring aids below are available without operator action. An operator sets a flag false to opt it out (e.g. if obs.fleet.health telemetry from the pipeline:authored event shows an aid is not helping). Governance is preserved end to end. None of the paths below execute a graph directly: the repair producer and the intent synthesizer return a graph, which is then dispatched through the same graph.execute path a hand-authored graph takes. A synthesized or repaired graph runs the identical validation, tool denylist, and child⊆parent sandbox-intersection checks as one you wrote by hand. The authoring layer can never produce a graph that bypasses governance.

Conservative repair

When a weak-tier model emits a schema-invalid graph and orchestration.authoring.repairProducer is on, the daemon attempts a deterministic, conservative match of the raw graph to one of the four canonical templates (research-fanout, debate, vote, map-reduce) by shape — node count, the fan-in topology, and disambiguating keywords. It repairs the graph only on a single unambiguous match; it never guesses. If the shape fits more than one template, the daemon returns a structured “did you mean this template?” response rather than synthesizing a graph the model did not intend. A no-match (or a repaired graph that still fails validation) returns the unchanged fail-closed validation error. The calling model’s tier is resolved server-side from the authoring agent — a tool-supplied capabilityClass is never trusted — and only weak tiers take the repair path (frontier authoring is left untouched). A successful repair emits the graph:repaired audit event (counts/ids only).

Synthesis from a one-line intent

When orchestration.authoring.intentAction is on, the pipeline tool exposes a from_intent action: instead of hand-writing the node list, an agent expresses a one-line intent — a canonical pattern plus a few agent or task names — and the daemon deterministically expands it into a validated graph. For example, the bull-vs-bear debate above is authorable from one line:
The synthesizer maps the pattern to its canonical template, fills the agent/task slots, and runs the same parse + validation a hand-authored graph runs before dispatching through graph.execute. An invalid intent (for example debate with fewer than two agents) is rejected before any graph runs; gated off, the daemon refuses the from_intent call entirely. A governed synthesis emits the graph:synthesized_from_intent audit event. See Pipelines: from_intent for the full pattern reference. from_intent produces template-shaped graphs — plain agent nodes in the named fan-out → fan-in shape — not the typed orchestration drivers. A synthesized debate is a one-shot pro/con fan-in into a moderator, not the multi-round type_id: debate driver; for the typed multi-round drivers (with type_config.rounds / synthesizer), author the graph with define/execute and per-node type_id + type_config. Accordingly, from_intent takes no rounds parameter.

Grammar-constrained schema (best-effort)

When orchestration.authoring.gbnfConstrain is on, the daemon grammar-constrains the raw pipeline tool schema for GBNF-eligible local providers (the local/default family — never a cloud provider inferred by name), nudging a local model toward emitting structurally-valid graph JSON. This is best-effort and removal/relaxation-only: it never widens field value validation, and the daemon-side schema validation remains the single source of truth for what a graph may contain. GBNF makes a valid emission more likely; it does not replace — and cannot weaken — the daemon’s governance.

Events

The graph coordinator emits three lifecycle events during execution, plus two audit events for the (default-on) small-model authoring layer: Both authoring events are counts/ids/enums only — never a graph body, a node config value, a task/label string, or the intent text — and fire only after the graph passes the same validation a hand-authored graph runs. See Event Bus for the full event reference.

Pipeline Tool Guide

Complete reference for the pipeline tool’s 10 actions

Visual Builder

Design and run graphs from the web dashboard

Developer API

API reference for building graph integrations

Sub-Agent Sessions

How each graph node runs as a sub-agent