> ## Documentation Index
> Fetch the complete documentation index at: https://comis-feature-matrix-channel.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Execution Graphs

> Multi-agent DAG orchestration -- define, execute, and monitor workflows where each node is a sub-agent task

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](/agent-tools/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:

```yaml theme={}
tool: pipeline
action: execute
nodes:
  - node_id: research
    task: "Search the web for recent developments in quantum computing"
  - node_id: analyze
    task: "Analyze the research findings and identify key trends: {{research.result}}"
    depends_on: [research]
  - node_id: write
    task: "Write a 500-word summary of quantum computing trends based on: {{analyze.result}}"
    depends_on: [analyze]
label: "Quantum Computing Research"
```

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:

| Setting        | Description                                                                                                                                                             |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `depends_on`   | Array of upstream node IDs that must complete first                                                                                                                     |
| `agent`        | Agent ID to run this node (defaults to the calling agent)                                                                                                               |
| `model`        | Model override for this node                                                                                                                                            |
| `timeout_ms`   | Per-node timeout in milliseconds                                                                                                                                        |
| `max_steps`    | Maximum agentic steps for the sub-agent                                                                                                                                 |
| `barrier_mode` | When to proceed if dependencies fail: `all`, `majority`, or `best-effort`                                                                                               |
| `retries`      | Number of automatic retries on failure (0-3, default 1). Uses exponential backoff: 1s, 2s, 4s, 8s, capped at 30s                                                        |
| `type_id`      | Built-in node type: `agent`, `debate`, `vote`, `refine`, `collaborate`, `approval-gate`, or `map-reduce`                                                                |
| `type_config`  | Type-specific configuration (required when `type_id` is set). See [Node Types](/developer-guide/pipelines#node-types)                                                   |
| `context_mode` | Controls upstream output verbosity: `full` (default), `summary` (500 chars + shared dir reference), `refs` (file path references only), or `none` (no upstream outputs) |

### 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:

| Type            | Pattern                           | Example                  |
| --------------- | --------------------------------- | ------------------------ |
| `debate`        | Adversarial rounds between agents | Bull vs bear analysis    |
| `vote`          | Independent parallel voting       | Analyst consensus        |
| `refine`        | Sequential improvement chain      | Draft -> edit -> polish  |
| `collaborate`   | Sequential additive contributions | Team brainstorming       |
| `approval-gate` | Human checkpoint via chat         | "Approve before trading" |
| `map-reduce`    | Parallel work + reducer           | Multi-source research    |

See [Node Types](/developer-guide/pipelines#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:

| Mode            | Behavior                                                                         |
| --------------- | -------------------------------------------------------------------------------- |
| `all` (default) | Every dependency must complete successfully                                      |
| `majority`      | More than 50% of dependencies must complete, and all must reach a terminal state |
| `best-effort`   | At least one dependency must complete, and all must reach a terminal state       |

<Tip>
  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.
</Tip>

### Failure policies

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

| Policy                | Behavior                                                                                                                         |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `fail-fast` (default) | A failed node immediately cascades: all downstream dependents are skipped                                                        |
| `continue`            | Downstream nodes are only skipped if their barrier can never be satisfied. Nodes whose barrier is still satisfiable keep running |

### 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.

```yaml theme={}
- node_id: summarize
  task: "Summarize the analysis: {{analyze.result}}"
  depends_on: [analyze]
```

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](/developer-guide/pipelines#data-flow) for details.

<Warning>
  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.
</Warning>

### 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](/reference/config-yaml#security)).
3. The **inherited graph share** -- `budget.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.

<Note>
  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.
</Note>

<Note>
  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.
</Note>

### 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:

```yaml theme={}
tool: pipeline
action: execute
label: "NVDA Research Team"
on_failure: continue
budget:
  max_tokens: 2000000
  max_cost: 5.00
nodes:
  # Fan-out: four analysts run in parallel (no depends_on)
  - node_id: fundamental
    task: "Analyze NVDA fundamentals: revenue growth, margins, P/E, balance sheet, key product lines. Cite sources."
    agent: equity-analyst
  - node_id: technical
    task: "Analyze NVDA technicals: 50/200 DMA, RSI, MACD, recent breakouts, volume profile."
    agent: technical-analyst
  - node_id: macro
    task: "Analyze macro context for NVDA: AI capex cycle, GPU demand, geopolitical risks, semi supply chain."
    agent: macro-analyst
  - node_id: sentiment
    task: "Analyze NVDA sentiment: institutional flows, analyst targets, options skew, social mentions."
    agent: sentiment-analyst

  # Fan-in: the trader waits for all four (barrier_mode: all is the default)
  - node_id: verdict
    task: |
      You are a senior trader. Read four analyses and issue a final verdict
      (BUY / HOLD / SELL) with reasoning, key risks, and a price target.

      Fundamental analysis:
      {{fundamental.result}}

      Technical analysis:
      {{technical.result}}

      Macro context:
      {{macro.result}}

      Sentiment:
      {{sentiment.result}}
    agent: senior-trader
    depends_on: [fundamental, technical, macro, sentiment]
    barrier_mode: all
```

The DAG looks like this:

```text theme={}
        fundamental ─┐
                     │
        technical ───┤
                     ├──► verdict
        macro ───────┤
                     │
        sentiment ───┘
```

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](/web-dashboard/pipelines)
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](/agent-tools/pipelines) 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](/web-dashboard/pipelines) for details.

## Configuration

Execution graphs require agent-to-agent communication to be enabled:

```yaml theme={}
security:
  agentToAgent:
    enabled: true
```

Without this setting, the `pipeline` tool will not be available to agents.

See [Configuration Reference](/reference/config-yaml) 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`](/reference/config-yaml#orchestration) 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`](/reference/json-rpc#obs-fleet-health) telemetry from the [`pipeline:authored`](/developer-guide/event-bus) 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`](/developer-guide/event-bus) 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:

```yaml theme={}
tool: pipeline
action: from_intent
pattern: debate
agents: ["bull", "bear"]   # PRO, CON
```

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`](/developer-guide/event-bus) audit event. See [Pipelines: from\_intent](/agent-tools/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:

| Event                           | When                                                                   | Key fields                                                                                         |
| ------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `graph:started`                 | Graph execution begins                                                 | `graphId`, `label`, `nodeCount`                                                                    |
| `graph:node_updated`            | A node transitions to a new status                                     | `graphId`, `nodeId`, `status`, `durationMs`, `error`                                               |
| `graph:completed`               | Graph reaches terminal state                                           | `graphId`, `status`, `durationMs`, `nodesCompleted`, `nodesFailed`, `nodesSkipped`, `cancelReason` |
| `graph:repaired`                | A weak-model graph is repaired to a canonical template (on by default) | `pattern`, `nodeCount`, `capabilityClass`                                                          |
| `graph:synthesized_from_intent` | A graph is synthesized from a one-line intent (on by default)          | `pattern`, `nodeCount`                                                                             |

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](/developer-guide/event-bus) for the full event reference.

## Related

<CardGroup cols={2}>
  <Card title="Pipeline Tool Guide" icon="diagram-project" href="/agent-tools/pipelines">
    Complete reference for the pipeline tool's 10 actions
  </Card>

  <Card title="Visual Builder" icon="window-maximize" href="/web-dashboard/pipelines">
    Design and run graphs from the web dashboard
  </Card>

  <Card title="Developer API" icon="code" href="/developer-guide/pipelines">
    API reference for building graph integrations
  </Card>

  <Card title="Sub-Agent Sessions" icon="users" href="/agents/subagent-lifecycle">
    How each graph node runs as a sub-agent
  </Card>
</CardGroup>
