Home > EulerAgent > Tutorials > Graph > Graph 01. Graph vs Pattern — What's Different?

Graph 01. Graph vs Pattern — What's Different?

Learning Objectives

After completing this tutorial, you will be able to:


Prerequisites

# Verify environment
euleragent --version
pip show langgraph | grep Version
euleragent graph --help

1. Relationship Between Pattern and Graph

1.1 Graph Is a Complete Superset of Pattern

Pattern and Graph in euleragent are not separate systems. Graph is a complete superset of Pattern. Here is what that means.

Pattern YAML ──────────────────────────────────────────
  ┌─────────────────────────────────────────────────┐
  │  id, version, category, description             │
  │  defaults (max_iterations, ...)                 │
  │  nodes (llm, judge, finalize kinds)             │
  │  edges (from, to, when conditions)              │
  │  finalize                                       │
  └─────────────────────────────────────────────────┘

Graph YAML ─────────────────────────────────────────────
  ┌─────────────────────────────────────────────────┐
  │  [All Pattern fields available as-is]           │
  │                                                 │
  │  + state_schema      ← New (type+reducer def)   │
  │  + parallel_groups   ← New (fan-out/fan-in)     │
  │  + interrupt_before  ← New (pause before node)  │
  │  + interrupt_after   ← New (pause after node)   │
  └─────────────────────────────────────────────────┘

Therefore, most valid Pattern YAML files will also pass euleragent graph validate. However, the Graph module runs through LangGraph StateGraph, so the execution engine is different.

1.2 Difference in Execution Engines

Pattern execution flow:
  YAML → euleragent sequential execution engine → Result

Graph execution flow:
  YAML → IR compilation → LangGraph StateGraph → Result

Pattern runs on a simple sequential state machine inside euleragent. Graph runs through LangGraph's StateGraph, leveraging parallel execution, checkpointing, conditional edges, and more.


2. What Has Been Added

2.1 state_schema — Shared State Definition

state_schema defines the structure of the state dictionary shared across the entire graph. To prevent conflicts when parallel branches access the same state key, you declare a reducer.

state_schema:
  findings:          # State key name
    type: list       # Data type
    merge: append_list   # Reducer strategy
  score:
    type: integer
    merge: sum_int
  summary:
    type: string
    merge: last_write

Without state_schema, LangGraph applies last_write by default, making results non-deterministic when parallel branches write to the same key.

2.2 parallel_groups — Fan-out/Fan-in

parallel_groups declares fan-out, which executes multiple nodes concurrently, and fan-in, which merges all completed branches into a single node.

parallel_groups:
  - id: research_group
    branches: [web_search, local_search, doc_search]
    join: merge_results

This declaration means web_search, local_search, and doc_search run concurrently, and the merge_results node executes after all branches complete.

2.3 interrupt_before / interrupt_after — Node-Level Pause

By leveraging LangGraph's checkpoint mechanism, you can pause graph execution before or after specific nodes.

checkpointer: memory   # ← Must declare at top level when using interrupt (INTERRUPT_REQUIRES_CHECKPOINTER)

nodes:
  - id: publish
    kind: llm
    interrupt_before: true   # Pause before publish execution
    runner:
      mode: execute

Required: Graphs using interrupt_before/interrupt_after must declare a checkpointer: field at the top level. Without it, graph validate will raise an INTERRUPT_REQUIRES_CHECKPOINTER error.

This is a different mechanism from Pattern's HITL (Human-In-The-Loop) force_tool approval queue. Interrupt is based on LangGraph checkpoints, while force_tool approval uses euleragent's file-based JSONL queue.

2.4 LangGraph Integration

The Graph module uses langgraph.graph.StateGraph to compile and execute graphs. This enables all LangGraph features including streaming, checkpointing, visualization, and more.


3. LangGraph StateGraph Architecture

3.1 Shared State Model

The core idea of LangGraph StateGraph is that all nodes read from and write to a single shared state dictionary.

# State type generated internally by LangGraph (conceptual representation)
from typing import TypedDict, Annotated
import operator

class GraphState(TypedDict):
    findings: Annotated[list, operator.add]     # append_list → operator.add
    score: Annotated[int, operator.add]         # sum_int → operator.add
    summary: str                                # last_write → default

Annotated[list, operator.add] means in LangGraph: "when multiple nodes write to this key concurrently, merge using operator.add (i.e., list concatenation)."

3.2 Branch Isolation vs Shared State

Pattern nodes execute sequentially, so there are no state conflicts. Graph parallel branches run in isolation but communicate through shared state.

Incorrect understanding:
  Branch A state ──→ (isolated) ──→ Independent state
  Branch B state ──→ (isolated) ──→ Independent state

Correct understanding:
  Branch A ──→ Shared state dictionary ←── Branch B
                     ↕
  Reducer resolves conflicts (operator.add, etc.)

3.3 Conditional Edges

LangGraph uses add_conditional_edges to implement Judge node routing.

# LangGraph code generated internally by euleragent (conceptual representation)
def route_judge(state: GraphState) -> str:
    return state.get("route", "finalize")

graph.add_conditional_edges(
    "evaluate",
    route_judge,
    {"finalize": "finalize", "revise": "revise"}
)

4. YAML → IR → LangGraph Compilation Pipeline

The compilation pipeline of the Graph module consists of 3 stages.

┌─────────────────────────────────────────────────────────────────┐
│ Stage 1: YAML Parsing                                           │
│   your_graph.yaml                                               │
│        ↓ (PyYAML parsing)                                       │
│   Python dictionary                                             │
└─────────────────────────────────────────────────────────────────┘
         ↓
┌─────────────────────────────────────────────────────────────────┐
│ Stage 2: Validation (euleragent graph validate)                  │
│   - Pattern base validation (node existence, edge connections)   │
│   - Graph additional validation (14 parallel error codes)        │
│   - state_schema type+merge compatibility check                  │
└─────────────────────────────────────────────────────────────────┘
         ↓
┌─────────────────────────────────────────────────────────────────┐
│ Stage 3: IR Generation (euleragent graph compile)                │
│   - Generate Graph IR JSON                                       │
│   - Mark graph_type: "graph"                                     │
│   - Include LangGraph StateGraph builder code                    │
│   - Convert parallel_groups → LangGraph parallel edges           │
│   - Convert state_schema → Annotated types                       │
└─────────────────────────────────────────────────────────────────┘
         ↓
┌─────────────────────────────────────────────────────────────────┐
│ At Runtime: LangGraph StateGraph Execution                       │
│   - Materialize IR into LangGraph StateGraph                     │
│   - Call .compile()                                              │
│   - Execute via .invoke() / .stream()                            │
└─────────────────────────────────────────────────────────────────┘

5. Step-by-Step Hands-On

Step 1: Verify Environment with euleragent doctor

euleragent doctor

Expected output:

euleragent doctor 실행 중...
  Python          3.11.4   ✓
  euleragent      0.9.0    ✓
  langgraph       1.0.9    ✓  (graph 모듈 사용 가능)
  pyyaml          6.0.1    ✓
  click           8.1.7    ✓

모든 진단 항목 통과.

If the LangGraph version is below 1.0.9 or is not installed, run the following.

pip install "langgraph>=1.0.9"

Step 2: Run graph validate (Using an Example File)

# 예제 디렉터리에 있는 병렬 그래프 검증
euleragent graph validate examples/graphs/parallel/02_research_parallel.yaml

Expected output:

검증 중: examples/graphs/parallel/02_research_parallel.yaml

단계 1/3: YAML 파싱... 완료
단계 2/3: Pattern 기본 검증...
  [✓] 노드 ID 유일성
  [✓] 엣지 소스/타겟 존재
  [✓] finalize 노드 도달 가능성
  [✓] judge route_values 커버리지
  완료
단계 3/3: Graph 추가 검증...
  [✓] state_schema 존재 (parallel_groups 사용)
  [✓] 모든 브랜치 writes_state 선언
  [✓] 조인 노드 존재 및 단일성
  [✓] 브랜치 수 제한 (≤8)
  [✓] 부작용 도구 미사용 (shell.exec, file.write)
  [✓] 모든 브랜치 조인 노드로 수렴
  [✓] state_schema merge 타입 호환성
  완료

결과: 유효 (오류 없음)

Step 3: Run graph compile

# 단순 선형 그래프 컴파일
euleragent graph compile examples/graphs/linear/01_simple_pipeline.yaml

Expected output (IR JSON excerpt):

{
  "graph_type": "graph",
  "id": "graph.simple_pipeline",
  "version": 1,
  "compiled_at": "2026-02-23T10:00:00Z",
  "langgraph_version": "1.0.9",
  "state_schema": null,
  "parallel_groups": [],
  "nodes": [
    {
      "id": "research",
      "kind": "llm",
      "runner": { "mode": "plan", "force_tool": "web.search" },
      "interrupt_before": false,
      "interrupt_after": false
    },
    {
      "id": "draft",
      "kind": "llm",
      "runner": { "mode": "execute" }
    },
    {
      "id": "finalize",
      "kind": "finalize"
    }
  ],
  "edges": [
    { "from": "research", "to": "draft", "when": "approvals_resolved" },
    { "from": "draft", "to": "finalize", "when": "true" }
  ],
  "langgraph_builder": {
    "add_nodes": ["research", "draft", "finalize"],
    "add_edges": [
      ["research", "draft"],
      ["draft", "finalize"]
    ],
    "add_conditional_edges": [],
    "interrupt_before": [],
    "interrupt_after": []
  }
}

The graph_type: "graph" field distinguishes it from Pattern IR (graph_type: "pattern"). The langgraph_builder section previews how the LangGraph StateGraph will be constructed at runtime.

Step 4: Save the Compilation Result to a File

euleragent graph compile examples/graphs/linear/01_simple_pipeline.yaml \
  --out /tmp/compiled_linear.json

# 내용 확인
python -m json.tool /tmp/compiled_linear.json | head -60

Step 5: Review Validation Results in JSON Format

euleragent graph validate examples/graphs/parallel/02_research_parallel.yaml \
  --format json | python -m json.tool

Expected output:

{
  "valid": true,
  "errors": [],
  "warnings": [
    {
      "code": "PARALLEL_NONDETERMINISTIC_MERGE",
      "severity": "warning",
      "message": "노드 'summary_branch'가 last_write 키 'summary'에 씁니다. 2개 이상의 브랜치가 이 키를 쓰면 비결정론적 결과가 발생합니다.",
      "node": "summary_branch"
    }
  ],
  "graph_type": "graph",
  "parallel_groups_count": 1,
  "branch_count": 2
}

6. Understanding INVALID_CONCURRENT_GRAPH_UPDATE

This error occurs when LangGraph detects parallel writes to the same key without a reducer. euleragent's graph validate detects this in advance and reports it as a PARALLEL_STATE_KEY_MERGE_MISSING error.

When This Occurs

# 위험한 패턴 — state_schema 없이 parallel_groups 사용
parallel_groups:
  - id: grp
    branches: [branch_a, branch_b]
    join: join_node

nodes:
  - id: branch_a
    kind: llm
    writes_state: [findings]   # ← 'findings'에 쓰려고 함
  - id: branch_b
    kind: llm
    writes_state: [findings]   # ← 같은 키 'findings'에 쓰려고 함

# state_schema가 없으므로 LangGraph는 'findings'의 리듀서를 모름
# 런타임에 INVALID_CONCURRENT_GRAPH_UPDATE 발생!

How to Fix

# 올바른 패턴 — state_schema에 리듀서 명시
state_schema:
  findings:
    type: list
    merge: append_list   # ← 리듀서 선언으로 충돌 해결

parallel_groups:
  - id: grp
    branches: [branch_a, branch_b]
    join: join_node

Running graph validate can catch this issue before execution.

# 에러 감지 예시
euleragent graph validate bad_example.yaml

# 출력:
# [오류] PARALLEL_STATE_SCHEMA_MISSING
#   parallel_groups가 선언되어 있지만 state_schema가 없습니다.
#   병렬 실행 시 INVALID_CONCURRENT_GRAPH_UPDATE 예외가 발생합니다.
#   해결: 최상위에 state_schema를 선언하고 각 브랜치의 writes_state
#   키에 대한 리듀서를 정의하세요.

7. The Role of the Reducer

A reducer defines "how to merge values when multiple branches write to the same state key."

euleragent merge LangGraph Type Annotation Behavior
append_list Annotated[list, operator.add] List concatenation ([a, b] + [c] = [a, b, c])
sum_int Annotated[int, operator.add] Integer summation (3 + 2 = 5)
concat_str Annotated[str, operator.add] String concatenation ("hello" + " world")
last_write str / list / int (default) Last write wins (non-deterministic)
first_write Custom reducer Keeps the first write, ignores subsequent ones
# 개념적 Python 코드 — euleragent가 내부적으로 생성
import operator
from typing import Annotated, TypedDict

class GraphState(TypedDict):
    findings: Annotated[list, operator.add]   # append_list
    score: Annotated[int, operator.add]       # sum_int
    notes: Annotated[str, operator.add]       # concat_str
    summary: str                              # last_write (기본)

8. When Should You Use Graph Instead of Pattern?

Decision Guide

Question 1: Do you need true parallel execution?
  → Yes: You need to query multiple independent data sources simultaneously
  → No: Sequential execution is sufficient → Use Pattern

Question 2: Do you need node-level interrupts?
  → Yes: A human needs to inspect state before/after specific nodes
  → No: HITL force_tool is sufficient → Use Pattern

Question 3: Do you need LangGraph checkpointing/streaming?
  → Yes: You need to save and resume graph state mid-execution
  → No: Use Pattern

If all answers are No → Use Pattern (simpler and more stable)
If one or more answers are Yes → Consider Graph (read 00_disclaimer.md first)

Specific Scenarios

Scenario Recommended
Research → Draft → Review (sequential) Pattern
Web search + local search running simultaneously Graph
Simple iterative refinement with loops Pattern
Parallel multi-agent research Graph
Simple judge routing Pattern
Parallel data collection + judge evaluation Graph
Emergency deployment (stability is top priority) Pattern

Expected Output

Here is a summary of the expected outputs for the commands executed in this tutorial.

# euleragent doctor
Python 3.11.4  | euleragent 0.9.0  | langgraph 1.0.9 # euleragent graph validate (유효한 경우)
결과: 유효 (오류 없음)

# euleragent graph compile
{
  "graph_type": "graph",
  "id": "...",
  "langgraph_builder": { ... }
}

Key Concepts Summary

Concept Description
Graph ⊃ Pattern Graph includes all features of Pattern
state_schema Defines shared state key types and reducers
parallel_groups Declares fan-out/fan-in concurrent execution
interrupt_before/after LangGraph checkpoint-based node-level pause
Reducer Function that resolves parallel write conflicts
IR JSON Intermediate representation compiled from YAML
INVALID_CONCURRENT_GRAPH_UPDATE LangGraph runtime error on parallel writes without a reducer

Common Errors

Error 1: LangGraph Version Too Low

오류: LANGGRAPH_COMPILE_FAILED
  langgraph 버전 0.2.x가 감지되었습니다. 1.0.9 이상이 필요합니다.
해결: pip install "langgraph>=1.0.9"

Error 2: graph Command Not Found

오류: No such command 'graph'.
해결: pip install -e . 로 재설치 후 euleragent graph --help 확인

Error 3: YAML Parse Error

오류: yaml.scanner.ScannerError: mapping values are not allowed here
해결: YAML 들여쓰기 확인 (스페이스 2칸 또는 4칸, 탭 혼용 금지)

Error 4: PARALLEL_STATE_SCHEMA_MISSING

오류: parallel_groups가 선언되어 있지만 state_schema가 없습니다.
해결: 최상위에 state_schema 섹션을 추가하고 각 브랜치의 writes_state
      키에 대한 type과 merge를 정의하세요.

Previous: 00_disclaimer.md | Next: 02_linear_graph.md

← Prev Back to List Next →