---
sidebar_position: 1
title: Graph & nodes
---

# Graph & nodes

A **graph** is a directed graph of agent invocations. You declare it in code:

```js
import { WorkflowGraph } from '@zibby/agent-workflow';
import { z } from '@zibby/core';

const graph = new WorkflowGraph()
  .addNode('plan',     { prompt: 'List 3 tasks for: {{goal}}', outputSchema: Plan,   agent: 'claude' })
  .addNode('execute',  { prompt: 'Do task: {{task}}',           outputSchema: Done,   agent: 'cursor' })
  .addNode('verify',   { prompt: 'Verify: {{result}}',          outputSchema: Status, agent: 'codex'  })
  .addEdge('plan', 'execute')
  .addEdge('execute', 'verify')
  .setEntryPoint('plan');
```

## Node config

Every node accepts:

| Field | Required | Description |
|---|---|---|
| `prompt` | yes | Either a string template (`{{state.X}}` interpolated) or a function `({ input, state }) => string`. |
| `outputSchema` | yes | A Zod schema. The node's output is validated against this before downstream nodes see it. Validation failure = node failure. |
| `agent` | no | Agent strategy override: `'cursor' | 'claude' | 'codex' | 'gemini' | 'assistant'`. Falls back to project default if omitted. |
| `retries` | no | Number of times to retry on failure (default: 0). |
| `skills` | no | Array of skill IDs to enable for this node — see [Skills](./skills). |
| `onComplete` | no | Async callback invoked with the node's validated output. |

## Edges

Three forms:

```js
// Linear: A → B
graph.addEdge('A', 'B');

// Conditional branching: state-driven routing
graph.addConditionalEdges('classify', (state) => {
  if (state.classify.severity === 'critical') return 'pageOncall';
  if (state.classify.severity === 'high')     return 'createIncident';
  return 'logAndExit';
});

// Multi-conditional with named labels (cleaner for many branches)
graph.addConditionalEdges('classify', {
  routes: (state) => state.classify.severity,
  labels: { critical: 'pageOncall', high: 'createIncident', _default: 'logAndExit' },
});
```

## Entry point

Exactly one node is the entry point:

```js
graph.setEntryPoint('plan');
```

This is the node that runs first when `graph.run(initialState)` is called.

## Running

```js
const { state } = await graph.run(agent, {
  input: { goal: 'add a dark-mode toggle' },
  agentType: 'cursor',
});

console.log(state.verify.status);
```

Each node's output lands at `state[nodeName]`, so downstream prompts can reference `{{state.plan.tasks}}` or `state.plan.tasks` from a function prompt.

## State immutability

State is append-only — every node-completion creates a new state object. Earlier values are preserved in the state's `_history` array, accessible via `state.rollback(N)` for retries that need to drop the latest N writes.
