Domain Types
The execution graph system is built on three core types defined in@comis/core. These types represent the graph structure, individual nodes, and resource limits.
ExecutionGraph
GraphNode
Each node represents a sub-agent task with optional dependency constraints, per-node timeouts, retry behavior, built-in node types, and context verbosity control. Upstream node outputs are referenced directly via{{nodeId.result}} templates in task text, where nodeId must appear in the node’s dependsOn array.
GraphBudget
Resource limits applied across the entire graph execution.Node Lifecycle
Each node transitions through a defined set of states during execution:pending— Waiting for dependencies to reach terminal stateready— All dependency barriers satisfied, eligible for schedulingrunning— Agent is actively executing the taskcompleted— Task finished successfully with outputfailed— Task encountered an errorskipped— Dependencies failed and barrier cannot be satisfied
running -> completed | failed | cancelled
Failure Strategies
TheonFailure field controls how the graph responds when a node fails:
fail-fast(default) — If any node fails, skip all dependent nodes (cascade) and fail the graph as soon as all running nodes finish. Dependents with failed or skipped dependencies are immediately marked as skipped.continue— If a node fails, only skip dependent nodes whose barrier can never be satisfied. Independent nodes and nodes with satisfied barriers continue executing.
Barrier Modes
For nodes with multiple dependencies, thebarrierMode controls when the node becomes ready:
All barrier modes require dependencies to reach a terminal state (completed, failed, or skipped) before the node becomes ready. A
best-effort node does not fire as soon as one dependency completes — it waits for all dependencies to finish.
Graph Validation
ThevalidateAndSortGraph() function performs both schema and structural validation before a graph can execute.
Schema Validation (Zod)
- Node count: 1-20 nodes
- Required fields:
nodeId(non-empty string),task(non-empty string) - Type checks on all optional fields
Structural Validation (DAG)
- Unique node IDs — No two nodes can share the same
nodeId - No self-dependencies — A node cannot list itself in
dependsOn - Valid references — All
dependsOnentries must point to existing nodes - Acyclicity — The graph must be a DAG (no circular dependencies)
GraphValidationError
Validation failures return a structuredGraphValidationError with a kind field:
Usage
ValidatedGraph contains the original graph paired with its topological execution order — the sequence in which nodes can be safely scheduled.
Graph Coordinator
The graph coordinator is the runtime engine that executes validated graphs end-to-end. It lives in the daemon process and orchestrates node scheduling, result forwarding, and lifecycle management. Key responsibilities:- Node spawning — Each node is spawned as a sub-agent via the SubAgentRunner. Nodes without dependencies start immediately; dependent nodes wait for their barriers.
- Completion tracking — Event-driven via
session:sub_agent_completedevents. A single global handler routes completion events to the correct graph instance. - Result forwarding — Upstream node outputs are injected into downstream node task descriptions via
{{nodeId.result}}template interpolation (see Data Flow). - Timeout enforcement — Both per-node timeouts (kills the individual sub-agent) and graph-level timeouts (cancels all running nodes).
- Concurrency limiting — Three-tier concurrency control: per-node (
maxParallelSpawns, default 10 forspawn_alltyped nodes), per-graph (maxConcurrency, default 4 concurrent nodes), and global (maxGlobalSubAgents, default 20 across all graphs). Ready nodes wait in a FIFO queue when limits are reached. - Aggregate announcement — A single completion message summarizing all node results is sent to the originating channel when the graph finishes.
The graph coordinator runs inside the daemon process. It is not directly accessible from other packages — you interact with execution graphs by creating them through the CLI, RPC, or web dashboard.
Graph Events
The event bus emits events at key points during graph execution:
Subscribe to these events via the Event Bus for monitoring, logging, or triggering downstream actions.
Data Flow
Nodes pass data to downstream nodes using{{nodeId.result}} templates directly in the task text. The nodeId in the template must appear in the consuming node’s dependsOn array.
research completes, its output text replaces {{research.result}} in the summarize node’s task description. This creates a direct data flow between nodes without shared state or intermediate variable mappings.
Context Envelope
The coordinator also builds a context envelope around each node’s task, providing awareness of the graph structure and upstream results. ThecontextMode field controls how much upstream output is included in the envelope (see Context Verbosity Modes).
User Variables
Use${VARIABLE_NAME} syntax in task text for values the user provides at execution time. Variables are resolved before template interpolation when the graph starts via the variables parameter passed to graph.execute.
graph.define returns a userVariables array listing all ${VAR} placeholders found in node tasks. Unresolved variables at execution time produce a warning but do not block execution.
Shared Data Folder
When a graph starts, the coordinator creates a per-graph shared directory at~/.comis/graph-runs/{graphId}/. All nodes in the graph get read-write access to this directory, enabling file-based data exchange between nodes.
Each node receives the shared folder path in its context envelope under a “Shared Pipeline Folder” section. Nodes can write output files — reports, structured data, artifacts — for other nodes to read. This complements template-based data passing with file-based exchange for larger payloads that would exceed the result truncation limit.
Directory structure
Lifecycle
The shared directory is created with owner-only permissions (0o700) at graph start. The directory persists after graph completion so output files remain accessible. Driver artifacts (debate transcripts, vote tallies, etc.) are also written to this directory (see Node Types).
Security
The directory is created with0o700 permissions, restricting access to the owner. File tool access is enforced through the safe-path-wrapper’s sharedPaths mechanism, which grants read-write access only to nodes within the same graph execution. Nodes in different graph runs cannot access each other’s shared directories.
The shared data folder is separate from
{{nodeId.result}} template passing. Use templates for passing text results between nodes. Use the shared folder for larger artifacts like files, reports, or structured data that would exceed the result truncation limit (default 12000 characters).Per-Node Retry
Nodes can be configured to retry automatically on failure using theretries field.
When a node fails and has retries remaining, the coordinator waits with exponential backoff (1s, 2s, 4s) before restarting the node from scratch. The node’s status transitions back to
ready during retry, making it visually distinct from permanently failed nodes.
retryAttempt (current attempt number, starting at 0) and retriesRemaining (how many retries are left). These are visible in graph.status responses.
NodeTypeDriver Interface
TheNodeTypeDriver interface defines the contract for pluggable graph node type drivers. Drivers are pure synchronous functions — they receive context and return action objects that the graph coordinator interprets and executes. The coordinator handles all async operations (agent spawning, I/O, event emission).
NodeDriverAction
Every driver method returns aNodeDriverAction — a discriminated union identified by the action field. The coordinator interprets each action and performs the corresponding async operation.
NodeDriverContext
TheNodeDriverContext object is passed to every driver method, providing node metadata and a state bag for persisting data across turns.
The
getState / setState pair enables drivers to track multi-turn progress (e.g., current debate round, accumulated vote tallies, refinement chain position) without external storage.
Driver Lifecycle
The coordinator drives execution through a turn-based loop, calling driver methods and interpreting the returned actions.-
Initialize — The coordinator calls
initialize(ctx)on the driver, which returns the first action (typicallyspawnfor sequential drivers orspawn_allfor parallel drivers). Agraph:driver_lifecycleevent fires with phaseinitialized. -
Turn loop (sequential) — For sequential drivers (
debate,refine,collaborate): the coordinator spawns a single agent. When the agent completes, the coordinator callsonTurnComplete(ctx, output)which returns the next action. This loop continues until the driver returnscompleteorfail. -
Turn loop (parallel) — For parallel drivers (
vote,map-reduce): the coordinator spawns all agents viaspawn_all. When all agents complete, the coordinator callsonParallelTurnComplete(ctx, outputs)which returns the next action (e.g.,completefor vote tallying, orspawnfor the reducer in map-reduce). -
Completion — When the driver returns
complete, agraph:driver_lifecycleevent fires with phasecompleted, the output is stored, and dependent nodes become eligible for scheduling. -
Failure — When the driver returns
failor an unhandled error occurs, agraph:driver_lifecycleevent fires with phasefailed, and the node transitions to the failed state. -
Abort — On timeout, cancellation, or graph shutdown, the coordinator calls
onAbort(ctx)and emitsgraph:driver_lifecyclewith phaseaborted.
graph:driver_lifecycle event with the corresponding phase value. See the Event Bus reference for the full event payload.
Three-Tier Concurrency Model
The coordinator enforces three independent concurrency limits to prevent resource exhaustion while maximizing parallelism.Tier 1 — Per-Node Parallel Cap (maxParallelSpawns)
Limits how many agents a single
spawn_all action can start simultaneously. Prevents a single map-reduce node with 50 mappers from monopolizing all available agent slots.
Tier 2 — Per-Graph Concurrency (maxConcurrency)
Limits how many nodes within one graph can execute concurrently. Independent nodes with satisfied dependencies run in parallel up to this cap. Additional ready nodes wait in a per-graph queue.
Tier 3 — Global Sub-Agent Cap (maxGlobalSubAgents)
Limits the total number of concurrent sub-agents across ALL active graphs. When the cap is hit, new spawns queue in FIFO order and drain as agents complete. This prevents multiple concurrent graphs from overwhelming LLM provider rate limits or system resources.
The
graph.status RPC (called without a graphId) returns live concurrency stats:
graph.status response format.
The per-graph and global caps are configurable via YAML config under
security.agentToAgent.graphMaxConcurrency and security.agentToAgent.graphMaxGlobalSubAgents. The per-node parallel cap (maxParallelSpawns) is currently a coordinator default (10) and is not exposed as a YAML key.Node Types
Nodes can optionally use a built-in node type via thetypeId and typeConfig fields. When typeId is set, the node uses a driver that controls multi-agent orchestration patterns (debates, voting, review chains, etc.) instead of spawning a single sub-agent.
If typeId is not set, the node behaves as a regular single-agent task — this is the default and most common pattern.
Available Types
debate — Multi-Round Adversarial Debate
Two or more agents argue in rounds, with an optional synthesizer producing the final output.
In each round, agents speak in round-robin order. Each agent sees the full debate transcript from prior turns. After all rounds complete, an optional synthesizer agent produces the final output based on the entire discussion.
{nodeId}-debate-transcript.md in the graph’s shared data folder. If no synthesizer is specified, the last debater’s final turn becomes the node output.
vote — Parallel Independent Voting
All agents vote independently and concurrently, with results tallied into a summary.
refine — Sequential Refinement Chain
Each agent reviews and improves the previous agent’s output, like an editorial pipeline.
collaborate — Sequential Multi-Perspective
Agents contribute perspectives sequentially, each building on all prior contributions. Collaborative, not adversarial.
approval-gate — Human Approval Checkpoint
Pauses pipeline execution and sends a message to the user’s channel, waiting for their response before continuing. Requires the graph to be triggered from a channel context (Telegram, Discord, etc.).
map-reduce — Parallel Processing with Aggregation
Splits work across multiple agents in parallel, then a reducer agent aggregates all results.
Custom driver registration is not yet available in the public API. The 7 built-in drivers (
agent, debate, vote, refine, collaborate, approval-gate, map-reduce) cover the standard multi-agent orchestration patterns. The NodeTypeDriver interface is documented above for contributors and internal extension.Context Verbosity Modes
ThecontextMode field controls how much upstream output the coordinator includes in each node’s context envelope.
Example: Research Pipeline
A complete execution graph that researches a topic, analyzes sources, and writes a report:{{nodeId.result}} templates in task text. The fail-fast strategy ensures the pipeline stops quickly if any stage fails, and the budget limits prevent runaway costs.
Web Dashboard: Pipelines
Visual pipeline builder and monitor
Plugins
Hook into graph lifecycle events
Architecture
Domain types and validation
Event Bus
graph:* events reference
