Graph 01. Graph vs Pattern — What's Different?
Learning Objectives
After completing this tutorial, you will be able to:
- Accurately explain the relationship between Pattern and Graph.
- List the 4 key features that Graph adds on top of Pattern.
- Understand the LangGraph StateGraph architecture and the shared state model.
- Explain the YAML → IR → LangGraph compilation pipeline.
- Explain why the
INVALID_CONCURRENT_GRAPH_UPDATEerror occurs. - Determine when to use Graph instead of Pattern.
Prerequisites
- euleragent installation complete (
pip install -e .) docs/tutorials/pattern/01_*.mdthrough03_*.mdcompleted- LangGraph 1.0.9 or higher installed
# 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_aftermust declare acheckpointer:field at the top level. Without it,graph validatewill raise anINTERRUPT_REQUIRES_CHECKPOINTERerror.
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