Graph 02. Linear Graph — Your First Step, Same as Pattern
Learning Objectives
After completing this tutorial, you will be able to:
- Write the simplest linear Graph YAML from scratch.
- Understand the 3-stage validation process of
euleragent graph validate. - Visually inspect the graph structure using
euleragent graph show. - Generate LangGraph IR with
euleragent graph compileand verifygraph_type: "graph". - Explain why
state_schemais unnecessary in a linear graph. - Confirm that the same YAML also passes
euleragent pattern validate.
Prerequisites
# 작업 디렉터리 생성
mkdir -p examples/graphs/linear
# euleragent 및 graph 모듈 동작 확인
euleragent graph --help
Expected output:
Usage: euleragent graph [OPTIONS] COMMAND [ARGS]...
Graph 모듈 — LangGraph 기반 고급 워크플로우 (실험적)
Options:
--help Show this message and exit.
Commands:
compile Graph YAML을 LangGraph IR로 컴파일
list 사용 가능한 그래프 목록 조회
show 그래프 구조 확인
validate Graph YAML 유효성 검사
Step-by-Step Hands-On
Step 1: Write a Linear Graph YAML
Create the file examples/graphs/linear/my_linear.yaml with the following content.
# examples/graphs/linear/my_linear.yaml
id: graph.my_linear
version: 1
category: demo
description: 단순 선형 그래프 — research → draft → evaluate → finalize
defaults:
max_iterations: 2
max_total_tool_calls: 30
max_web_search_calls: 10
nodes:
- id: research
kind: llm
runner:
mode: plan
force_tool: web.search
min_proposals: 2
guardrails:
tool_call_budget:
web.search: 3
- id: draft
kind: llm
runner:
mode: execute
artifacts:
primary: report.md
- id: evaluate
kind: judge
judge:
schema: evaluator_v1
route_values: [finalize, revise]
- id: revise
kind: llm
runner:
mode: execute
artifacts:
primary: report.md
edges:
- from: research
to: draft
when: "approvals_resolved"
- from: draft
to: evaluate
when: "true"
- from: evaluate
to: finalize
when: "judge.route == finalize"
- from: evaluate
to: revise
when: "judge.route == revise"
- from: revise
to: evaluate
when: "true"
finalize:
artifact: report.md
Understanding This YAML Structure
research (plan mode + web.search HITL)
↓ [approvals_resolved: proceeds after human approval]
draft (execute mode → generates report.md)
↓ [always proceeds]
evaluate (judge → finalize or revise)
↓ [judge.route == finalize] [judge.route == revise]
finalize revise (revision)
↓ [always proceeds]
evaluate (re-evaluation)
This structure has the following characteristics.
- Linear + Loop — no parallel execution
- HITL Approval — the research node's
force_tool: web.searchrequires human approval before web searches - Judge Routing — the evaluate node selects between finalize and revise
- Max Iterations — loop count limited by
max_iterations: 2
Step 2: Run graph validate
euleragent graph validate examples/graphs/linear/my_linear.yaml
Expected output:
검증 중: examples/graphs/linear/my_linear.yaml
단계 1/3: YAML 파싱...
id: graph.my_linear
version: 1
노드 수: 4 (research, draft, evaluate, revise)
엣지 수: 5
완료
단계 2/3: Pattern 기본 검증...
[✓] 노드 ID 유일성 검사
[✓] 엣지 소스/타겟 존재 여부
[✓] finalize 도달 가능성 (evaluate → finalize 경로 존재)
[✓] judge 노드 route_values 커버리지
evaluate: [finalize, revise] ← 엣지와 일치
[✓] max_iterations 존재 (순환 루프 감지됨)
완료
단계 3/3: Graph 추가 검증...
[✓] parallel_groups 없음 — state_schema 불필요
[✓] interrupt hooks 없음 (선택적 기능)
완료
결과: 유효 ✓ (오류 없음, 경고 없음)
Notice that the validation is split into 3 stages. 1. YAML Parsing — syntax check 2. Pattern Base Validation — node/edge/judge rule checks 3. Graph Additional Validation — parallel rules, state_schema, interrupt checks
Step 3: Inspect Structure with graph show
euleragent graph show examples/graphs/linear/my_linear.yaml
Expected output:
그래프: graph.my_linear (v1)
설명: 단순 선형 그래프 — research → draft → evaluate → finalize
카테고리: demo
유형: graph (선형)
노드:
[llm] research — plan mode, force_tool: web.search
[llm] draft — execute mode, artifacts: report.md
[judge] evaluate — evaluator_v1, routes: [finalize, revise]
[llm] revise — execute mode, artifacts: report.md
[finalize] finalize — artifact: report.md
엣지:
research ──[approvals_resolved]──▶ draft
draft ──[always]──────────────▶ evaluate
evaluate ──[judge.route==finalize]▶ finalize
evaluate ──[judge.route==revise]──▶ revise
revise ──[always]──────────────▶ evaluate
병렬 그룹: 없음
상태 스키마: 없음 (선형 그래프)
interrupt hooks: 없음
Step 4: Generate IR with graph compile
euleragent graph compile examples/graphs/linear/my_linear.yaml
Expected output (IR JSON excerpt):
{
"graph_type": "graph",
"id": "graph.my_linear",
"version": 1,
"category": "demo",
"compiled_at": "2026-02-23T10:15:00Z",
"langgraph_version": "1.0.9",
"state_schema": null,
"parallel_groups": [],
"defaults": {
"max_iterations": 2,
"max_total_tool_calls": 30,
"max_web_search_calls": 10
},
"nodes": [
{
"id": "research",
"kind": "llm",
"runner": {
"mode": "plan",
"force_tool": "web.search",
"min_proposals": 2
},
"interrupt_before": false,
"interrupt_after": false,
"writes_state": [],
"reads_state": []
},
{
"id": "draft",
"kind": "llm",
"runner": { "mode": "execute" },
"artifacts": { "primary": "report.md" },
"interrupt_before": false,
"interrupt_after": false,
"writes_state": [],
"reads_state": []
},
{
"id": "evaluate",
"kind": "judge",
"judge": {
"schema": "evaluator_v1",
"route_values": ["finalize", "revise"]
},
"interrupt_before": false,
"interrupt_after": false
},
{
"id": "revise",
"kind": "llm",
"runner": { "mode": "execute" },
"artifacts": { "primary": "report.md" },
"interrupt_before": false,
"interrupt_after": false,
"writes_state": [],
"reads_state": []
}
],
"edges": [
{ "from": "research", "to": "draft", "when": "approvals_resolved" },
{ "from": "draft", "to": "evaluate", "when": "true" },
{ "from": "evaluate", "to": "finalize", "when": "judge.route == finalize" },
{ "from": "evaluate", "to": "revise", "when": "judge.route == revise" },
{ "from": "revise", "to": "evaluate", "when": "true" }
],
"finalize": { "artifact": "report.md" },
"langgraph_builder": {
"state_type": "TypedDict",
"add_nodes": ["research", "draft", "evaluate", "revise", "finalize"],
"set_entry_point": "research",
"add_edges": [
["research", "draft"],
["draft", "evaluate"],
["revise", "evaluate"]
],
"add_conditional_edges": [
{
"source": "evaluate",
"path_function": "route_evaluate",
"path_map": {
"finalize": "finalize",
"revise": "revise"
}
}
],
"interrupt_before": [],
"interrupt_after": [],
"set_finish_point": "finalize"
}
}
Key points to verify:
- "graph_type": "graph" — distinguishes from Pattern IR ("pattern")
- "state_schema": null — null because this is a linear graph
- "parallel_groups": [] — no parallel groups
- langgraph_builder.add_conditional_edges — judge routing is converted into LangGraph conditional edges
- "interrupt_before": false, "interrupt_after": false — added as default values on all nodes
Step 5: Save to a File
euleragent graph compile examples/graphs/linear/my_linear.yaml \
--out examples/graphs/linear/my_linear_compiled.json
echo "컴파일 완료:"
ls -lh examples/graphs/linear/my_linear_compiled.json
Expected output:
컴파일 완료:
-rw-r--r-- 1 user group 2.1K 2026-02-23 10:15 examples/graphs/linear/my_linear_compiled.json
Step 6: Confirm It Works Without state_schema
In a linear graph, state_schema is unnecessary. Here is why.
- Nodes execute sequentially, so there are no shared state key conflicts
- LangGraph operates with the default state type when there are no parallel writes
state_schemais only required when used withparallel_groups
# state_schema 없이 validate가 통과함을 확인
euleragent graph validate examples/graphs/linear/my_linear.yaml
# 결과: 유효 ✓ (state_schema 관련 에러 없음)
Step 7: Compare with Pattern validate
Run Pattern validation on the same YAML.
# Graph validate
euleragent graph validate examples/graphs/linear/my_linear.yaml
# Pattern validate (같은 파일)
euleragent pattern validate examples/graphs/linear/my_linear.yaml
Expected output comparison:
# Graph validate 출력
결과: 유효 ✓ (graph 모듈 3단계 검증 완료)
# Pattern validate 출력
결과: 유효 ✓ (pattern 모듈 2단계 검증 완료)
참고: graph 전용 필드(id 접두사 'graph.')는 무시됩니다.
Both validations pass. This practically confirms that Graph is a superset of Pattern.
Note that the id: graph.my_linear prefix graph. is a convention; Pattern also accepts it.
Step 8: View Registered Graphs with graph list
euleragent graph list
Expected output:
사용 가능한 그래프:
ID 카테고리 버전 위치
────────────────────────────── ───────── ───── ──────────────────────────────────────
graph.my_linear demo 1 examples/graphs/linear/my_linear.yaml
graph.simple_pipeline demo 1 examples/graphs/linear/01_simple_pipeline.yaml
graph.research_parallel research 1 examples/graphs/parallel/02_research_parallel.yaml
총 3개 그래프
Reviewing the Full YAML Structure
Let us revisit the full structure of the YAML written in this tutorial.
# examples/graphs/linear/my_linear.yaml
# 최상위 메타데이터 — Pattern과 동일
id: graph.my_linear
version: 1
category: demo
description: 단순 선형 그래프 — research → draft → evaluate → finalize
# 실행 제약 — Pattern과 동일
defaults:
max_iterations: 2 # judge 루프 최대 2회
max_total_tool_calls: 30 # 전체 도구 호출 예산
max_web_search_calls: 10 # 웹 검색 호출 예산
# 노드 목록 — Pattern과 동일한 구조
nodes:
- id: research
kind: llm
runner:
mode: plan # HITL 모드: 계획 제안 → 사람 승인 → 실행
force_tool: web.search # 반드시 web.search 도구 사용
min_proposals: 2 # 최소 2개 계획 제안
guardrails:
tool_call_budget:
web.search: 3 # 웹 검색 최대 3회
- id: draft
kind: llm
runner:
mode: execute # 직접 실행 모드 (승인 불필요)
artifacts:
primary: report.md # 결과물을 report.md에 저장
- id: evaluate
kind: judge # Judge 노드: 품질 평가 후 라우팅
judge:
schema: evaluator_v1 # 내장 평가 스키마
route_values: [finalize, revise] # 가능한 라우팅 목적지
- id: revise
kind: llm
runner:
mode: execute
artifacts:
primary: report.md
# 엣지 — 노드 간 연결 및 조건
edges:
- from: research
to: draft
when: "approvals_resolved" # research의 HITL 승인 완료 후
- from: draft
to: evaluate
when: "true" # 항상 진행
- from: evaluate
to: finalize
when: "judge.route == finalize" # judge가 finalize 선택 시
- from: evaluate
to: revise
when: "judge.route == revise" # judge가 revise 선택 시
- from: revise
to: evaluate
when: "true" # 수정 후 다시 평가
# 최종 노드
finalize:
artifact: report.md # 최종 결과물
Expected Output Summary
| Command | Expected Result |
|---|---|
graph validate |
Result: Valid (no errors, no warnings) |
graph show |
Displays structure with 4 nodes and 5 edges |
graph compile |
IR JSON, verify graph_type: "graph" |
pattern validate |
Result: Valid (Pattern also passes) |
Key Concepts Summary
| Concept | Description |
|---|---|
| Linear graph | Sequential graph without parallel_groups |
| state_schema not required | Reducers are unnecessary when there is no parallel execution |
| graph_type: "graph" | Marker that distinguishes from Pattern IR's "pattern" |
| 3-stage validation | YAML parsing → Pattern validation → Graph additional validation |
| Superset relationship | A linear Graph YAML also passes Pattern validate |
| approvals_resolved | HITL approval completion condition (when force_tool is used) |
| judge.route | Judge node routing condition |
Common Errors
Error 1: Mismatch Between judge.route Condition and route_values
# 오류: route_values에 "escalate"가 있지만 엣지에 없음
- id: evaluate
kind: judge
judge:
route_values: [finalize, revise, escalate] # ← 3개
edges:
- from: evaluate
to: finalize
when: "judge.route == finalize"
- from: evaluate
to: revise
when: "judge.route == revise"
# 'escalate' 엣지 없음!
오류: JUDGE_ROUTE_COVERAGE_ERROR
노드 'evaluate'의 route_values ['finalize', 'revise', 'escalate'] 중
'escalate'에 대한 엣지가 없습니다.
해결: 'escalate' 목적지 노드와 엣지를 추가하거나,
route_values에서 'escalate'를 제거하세요.
Error 2: Loop Without max_iterations
# 오류: 루프가 있는데 max_iterations가 없음
defaults:
max_total_tool_calls: 30
# max_iterations 없음!
edges:
- from: evaluate
to: revise
when: "judge.route == revise"
- from: revise
to: evaluate # ← 루프
when: "true"
오류: UNBOUNDED_CYCLE
evaluate → revise → evaluate 순환이 감지되었지만
defaults.max_iterations가 설정되지 않았습니다.
해결: defaults에 max_iterations: N (N >= 1)을 추가하세요.
Error 3: Non-Existent from Node
edges:
- from: nonexistent_node # ← 이 노드가 nodes 목록에 없음
to: draft
when: "true"
오류: EDGE_SOURCE_NOT_FOUND
엣지의 소스 노드 'nonexistent_node'가 nodes 목록에 없습니다.
해결: 노드 ID를 확인하거나 해당 노드를 nodes에 추가하세요.
Error 4: Unreachable finalize
edges:
- from: research
to: draft
when: "true"
# draft에서 evaluate로 가는 엣지 없음
# finalize에 도달할 수 없음!
오류: FINALIZE_UNREACHABLE
finalize 노드에 도달하는 경로가 없습니다.
현재 경로: research → draft (막힘)
해결: draft에서 evaluate 또는 finalize로 이어지는 엣지를 추가하세요.
Error 5: YAML Indentation Error
nodes:
- id: research
kind: llm # ← 들여쓰기 오류! runner가 같은 레벨에 있어야 함
오류: yaml.scanner.ScannerError
line 4, column 3: mapping values are not allowed here
해결: nodes 항목 내의 필드는 모두 같은 들여쓰기 레벨을 사용하세요.
Practice Exercise
Try writing the following linear graph on your own.
Goal: Code Review Assistant
- read_code node: Read code files (mode: plan)
- analyze node: Analyze code and identify improvements (mode: execute)
- review node: judge -- quality_pass / needs_revision
- suggest_fixes node: Write improvement suggestions (mode: execute)
- Final artifact: review_report.md
# 작성 후 검증
euleragent graph validate examples/graphs/linear/code_review.yaml
euleragent graph compile examples/graphs/linear/code_review.yaml
Previous: 01_concepts.md | Next: 03_judge_route.md