Graph 06. State Schema In Depth — Types, Reducers, and Design
Learning Objectives
After completing this tutorial, you will be able to:
- Explain why
state_schemais needed and describe parallel write conflicts. - Distinguish the purposes of the 5 types (string, integer, float, list, dict).
- Explain the LangGraph mapping for the 5 merge strategies (append_list, sum_int, concat_str, last_write, first_write).
- Use the type+strategy compatibility table, including the 14 forbidden combinations.
- Intentionally trigger and fix a
STATE_SCHEMA_MERGE_TYPE_MISMATCHerror. - Understand the
PARALLEL_NONDETERMINISTIC_MERGEwarning and choose safe designs. - Apply the state_schema design checklist for real-world parallel graphs.
Prerequisites
mkdir -p examples/graphs/state
# state_schema는 parallel_groups와 함께 사용됩니다
# 이 튜토리얼은 07_parallel_basics.md의 준비 단계이기도 합니다
euleragent graph --help
1. Why state_schema Is Needed
Parallel Write Conflict Scenario
In a linear graph without parallel branches, nodes execute sequentially and there are no state conflicts.
However, when using parallel_groups, multiple nodes can access the same state key simultaneously.
선형 실행 (충돌 없음):
research → findings 씀 → draft → findings 읽음 → finalize
병렬 실행 (충돌 가능):
branch_a → findings 씀 ─┐
branch_b → findings 씀 ─┤ → 동시에 같은 키에 쓰기 → 어느 값이 최종?
branch_c → findings 씀 ─┘
LangGraph resolves this conflict using reducer functions. Without a reducer,
an INVALID_CONCURRENT_GRAPH_UPDATE exception is raised.
The euleragent state_schema provides a declarative way to define these reducers.
LangGraph Reducer Internals
# state_schema의 append_list가 LangGraph 내부에서 변환되는 방식 (개념적)
from typing import Annotated, TypedDict
import operator
class GraphState(TypedDict):
# merge: append_list → Annotated[list, operator.add]
findings: Annotated[list, operator.add]
# 병렬 실행 시 리듀서 적용:
# branch_a가 쓴 값: ["결과 A"]
# branch_b가 쓴 값: ["결과 B"]
# 리듀서 적용: ["결과 A"] + ["결과 B"] = ["결과 A", "결과 B"]
# 최종 state["findings"] = ["결과 A", "결과 B"]
2. The 5 Types
type: list
Used to collect multiple items in order. Most commonly used for aggregating result lists from parallel branches.
state_schema:
findings:
type: list
merge: append_list # 권장: 병렬 결과 수집 시
type: string
Stores text data. Used for summaries, descriptions, path names, etc.
state_schema:
summary:
type: string
merge: last_write # 단일 브랜치가 쓸 때
# 또는
combined_text:
type: string
merge: concat_str # 여러 브랜치 텍스트 연결 시
type: integer
Used for storing integer counters, counts, and quantities.
state_schema:
result_count:
type: integer
merge: sum_int # 권장: 각 브랜치의 결과 수 합산
type: float
Used for storing decimal scores, probabilities, and ratios.
state_schema:
confidence_score:
type: float
merge: last_write # 단일 브랜치 (float은 sum/append 없음)
type: dict
Used for storing structured data such as metadata, configuration, and complex results.
state_schema:
metadata:
type: dict
merge: last_write # dict는 last_write 또는 first_write만 가능
3. The 5 Merge Strategies
append_list — List Concatenation (list only)
Collects all list results from multiple branches. The gold standard for aggregating parallel branch results.
findings:
type: list
merge: append_list
# 동작: operator.add (리스트 연결)
["결과 A", "결과 B"] + ["결과 C"] = ["결과 A", "결과 B", "결과 C"]
- Pros: Deterministic, collects all results
- Cons: Order is not guaranteed (depends on branch completion order)
- LangGraph:
Annotated[list, operator.add]
sum_int — Integer Summation (integer only)
Sums counters across multiple branches.
result_count:
type: integer
merge: sum_int
# 동작: operator.add (정수 덧셈)
branch_a 기여: 3
branch_b 기여: 5
합산 결과: 3 + 5 = 8
- Pros: Deterministic, complete summation
- LangGraph:
Annotated[int, operator.add]
concat_str — String Concatenation (string only)
Concatenates text from multiple branches. Caution is needed since concatenation is done without separators.
combined_notes:
type: string
merge: concat_str
# 동작: operator.add (문자열 연결)
branch_a: "첫 번째 메모. "
branch_b: "두 번째 메모."
결과: "첫 번째 메모. 두 번째 메모."
- Note: Since there is no separator, each branch should include trailing whitespace or newlines.
- LangGraph:
Annotated[str, operator.add]
last_write — Last Write Wins (all types)
The value from the last branch to complete becomes the final value. This is nondeterministic.
summary:
type: string
merge: last_write
위험 시나리오: 2개 브랜치가 같은 last_write 키를 씀
branch_a 완료: state["summary"] = "A의 요약"
branch_b 완료: state["summary"] = "B의 요약" ← 마지막 완료이므로 승리
결과: "B의 요약" (하지만 다음 실행에서는 A가 마지막일 수도 있음)
- Single branch: Safe (deterministic since only one writes)
- Multiple branches: Risky (nondeterministic) -- triggers
PARALLEL_NONDETERMINISTIC_MERGEwarning
first_write — First Write Wins (all types)
The value from the first branch to complete becomes the final value. Also nondeterministic.
primary_result:
type: string
merge: first_write
- Use case: When only the fastest result among multiple branches is needed
- Caution: There is no guarantee which branch will complete first
4. Type+Strategy Compatibility Table
euleragent only allows the following combinations. All others result in a STATE_SCHEMA_MERGE_TYPE_MISMATCH error.
| Type | append_list | sum_int | concat_str | last_write | first_write |
|---|---|---|---|---|---|
| list | O | X | X | O | O |
| string | X | X | O | O | O |
| integer | X | O | X | O | O |
| float | X | X | X | O | O |
| dict | X | X | X | O | O |
Bold entries: Recommended merge strategy for each type
Forbidden combinations (14): 1. list + sum_int 2. list + concat_str 3. string + append_list 4. string + sum_int 5. integer + append_list 6. integer + concat_str 7. float + append_list 8. float + sum_int 9. float + concat_str 10. dict + append_list 11. dict + sum_int 12. dict + concat_str 13. (Reserved) May be added in the future 14. (Reserved) May be added in the future
Step-by-Step Walkthrough
Step 1: Writing a Complete state_schema (3 Keys)
# examples/graphs/state/schema_demo.yaml
id: graph.schema_demo
version: 1
category: demo
description: state_schema 상세 데모 — findings + summary + error_count
state_schema:
findings:
type: list
merge: append_list # 각 브랜치가 수집한 결과 목록 합산
summary:
type: string
merge: last_write # 단일 노드가 최종 요약을 씀
error_count:
type: integer
merge: sum_int # 각 브랜치에서 발견한 오류 수 합산
defaults:
max_iterations: 2
max_total_tool_calls: 30
parallel_groups:
- id: search_group
branches: [web_branch, local_branch]
join: merge_results
nodes:
- id: start
kind: llm
runner:
mode: execute
- id: web_branch
kind: llm
runner:
mode: execute
writes_state: [findings, error_count]
- id: local_branch
kind: llm
runner:
mode: execute
writes_state: [findings, error_count]
- id: merge_results
kind: llm
runner:
mode: execute
reads_state: [findings, error_count]
writes_state: [summary]
artifacts:
primary: summary.md
edges:
- from: start
to: web_branch
when: "true"
- from: start
to: local_branch
when: "true"
- from: web_branch
to: merge_results
when: "true"
- from: local_branch
to: merge_results
when: "true"
- from: merge_results
to: finalize
when: "true"
finalize:
artifact: summary.md
euleragent graph validate examples/graphs/state/schema_demo.yaml
Expected output:
단계 3/3: Graph 추가 검증...
[✓] state_schema 존재 (parallel_groups 사용)
[✓] state_schema 타입+merge 호환성:
findings: list + append_list ✓
summary: string + last_write ✓
error_count: integer + sum_int ✓
[✓] 모든 브랜치 writes_state 선언:
web_branch: [findings, error_count] ✓
local_branch: [findings, error_count] ✓
[✓] merge_results reads_state 선언 ✓
완료
결과: 유효 ✓
Step 2: Demonstrating STATE_SCHEMA_MERGE_TYPE_MISMATCH Error
See what happens when you assign merge: append_list to type: string.
# examples/graphs/state/schema_mismatch.yaml
id: graph.schema_mismatch
version: 1
category: demo
state_schema:
findings:
type: string # ← string 타입
merge: append_list # ← 하지만 append_list는 list 전용! 불일치!
count:
type: integer
merge: concat_str # ← integer에 concat_str? 불일치!
score:
type: float
merge: sum_int # ← float에 sum_int? 불일치!
parallel_groups:
- id: grp
branches: [branch_a, branch_b]
join: join_node
nodes:
- id: branch_a
kind: llm
runner: {mode: execute}
writes_state: [findings, count, score]
- id: branch_b
kind: llm
runner: {mode: execute}
writes_state: [findings, count, score]
- id: join_node
kind: llm
runner: {mode: execute}
reads_state: [findings, count, score]
edges:
- {from: branch_a, to: join_node, when: "true"}
- {from: branch_b, to: join_node, when: "true"}
- {from: join_node, to: finalize, when: "true"}
finalize:
artifact: output.md
euleragent graph validate examples/graphs/state/schema_mismatch.yaml
Expected output:
단계 3/3: Graph 추가 검증...
[✗] state_schema 타입+merge 호환성 검사 실패:
오류: STATE_SCHEMA_MERGE_TYPE_MISMATCH (3개)
1. 키 'findings': type=string, merge=append_list
append_list는 type=list에서만 사용 가능합니다.
해결: merge를 last_write, first_write, 또는 concat_str로 변경하세요.
2. 키 'count': type=integer, merge=concat_str
concat_str는 type=string에서만 사용 가능합니다.
해결: merge를 last_write, first_write, 또는 sum_int로 변경하세요.
3. 키 'score': type=float, merge=sum_int
sum_int는 type=integer에서만 사용 가능합니다.
해결: merge를 last_write 또는 first_write로 변경하세요.
결과: 유효하지 않음 (오류 3개)
Step 3: Re-validate After Fixing
Fix the errors.
# 수정된 state_schema
state_schema:
findings:
type: string # string이라면
merge: concat_str # ← concat_str 사용 (또는 타입을 list로 변경)
count:
type: integer
merge: sum_int # ← integer에는 sum_int
score:
type: float
merge: last_write # ← float에는 last_write 또는 first_write
euleragent graph validate examples/graphs/state/schema_mismatch.yaml
# 결과: 유효 ✓ (수정 후)
Step 4: Demonstrating PARALLEL_NONDETERMINISTIC_MERGE
Observe the warning that occurs when 2 branches write to the same last_write key.
# examples/graphs/state/schema_nondeterministic.yaml
id: graph.schema_nondeterministic
version: 1
category: demo
description: 비결정론적 merge 경고 시연
state_schema:
summary:
type: string
merge: last_write # ← 두 브랜치가 이 키를 씀 → 비결정론적!
parallel_groups:
- id: grp
branches: [branch_a, branch_b]
join: join_node
nodes:
- id: branch_a
kind: llm
runner: {mode: execute}
writes_state: [summary] # ← summary에 씀
- id: branch_b
kind: llm
runner: {mode: execute}
writes_state: [summary] # ← branch_a와 같은 키에 씀 → 충돌!
- id: join_node
kind: llm
runner: {mode: execute}
reads_state: [summary]
edges:
- {from: branch_a, to: join_node, when: "true"}
- {from: branch_b, to: join_node, when: "true"}
- {from: join_node, to: finalize, when: "true"}
finalize:
artifact: output.md
euleragent graph validate examples/graphs/state/schema_nondeterministic.yaml
Expected output:
단계 3/3: Graph 추가 검증...
경고: PARALLEL_NONDETERMINISTIC_MERGE
상태 키 'summary' (type=string, merge=last_write)가 2개 이상의 브랜치에서 쓰입니다:
branch_a: writes_state=[summary]
branch_b: writes_state=[summary]
last_write 리듀서는 마지막으로 완료된 브랜치의 값을 선택합니다.
브랜치 완료 순서는 실행마다 달라질 수 있으므로 결과가 비결정론적입니다.
해결 방법:
1. summary 타입을 list로 변경하고 merge를 append_list로 변경하세요.
2. 또는 summary를 쓰는 브랜치를 하나로 줄이세요.
3. 또는 concat_str로 변경하여 두 브랜치 텍스트를 이어 붙이세요.
결과: 유효 (오류 없음, 경고 1개)
The warning is present but validation passes. However, if nondeterministic results are unacceptable, follow the suggested resolution methods.
5. state_schema Design Checklist
Use the following checklist when designing a state_schema for parallel graphs.
Checklist
1. parallel_groups가 있으면 → state_schema 필수
□ state_schema 최상위에 선언됨
2. 각 브랜치의 writes_state 파악
□ 모든 브랜치의 writes_state 목록화
□ 각 키를 몇 개 브랜치가 쓰는지 확인
3. 공유 키 식별 (2개 이상 브랜치가 쓰는 키)
□ 공유 키에 last_write 사용 시 → 비결정론적 경고 확인
□ 공유 키가 list 타입 → append_list 사용
□ 공유 키가 integer 타입 → sum_int 사용
□ 공유 키가 string 타입 → concat_str 고려 (또는 list로 변경)
4. 타입+merge 호환성
□ list 키: append_list, last_write, first_write 중 선택
□ string 키: last_write, first_write, concat_str 중 선택
□ integer 키: last_write, first_write, sum_int 중 선택
□ float 키: last_write, first_write 중 선택
□ dict 키: last_write, first_write 중 선택
5. 조인 노드의 reads_state
□ 조인 노드가 필요한 모든 키를 reads_state에 선언
6. 최종 검증
□ euleragent graph validate 통과
□ 경고(PARALLEL_NONDETERMINISTIC_MERGE) 검토 후 수용 또는 수정
Design Example: A Correct state_schema
# 3개 브랜치, 여러 키를 사용하는 올바른 설계
state_schema:
# 3개 브랜치 모두 쓰는 공유 키 → append_list (결정론적)
all_findings:
type: list
merge: append_list
# 3개 브랜치 모두 숫자를 더하는 키 → sum_int (결정론적)
total_sources:
type: integer
merge: sum_int
# branch_a만 쓰는 키 → last_write (단일 브랜치이므로 안전)
web_summary:
type: string
merge: last_write
# branch_b만 쓰는 키 → last_write (단일 브랜치이므로 안전)
local_summary:
type: string
merge: last_write
# 조인 노드만 쓰는 키 → last_write (병렬 아님)
final_report:
type: string
merge: last_write
Expected Output Summary
| Command | Expected Result |
|---|---|
graph validate schema_demo.yaml |
Valid, all 3 keys pass compatibility check |
graph validate schema_mismatch.yaml |
STATE_SCHEMA_MERGE_TYPE_MISMATCH x3 |
graph validate schema_nondeterministic.yaml |
Valid (1 warning: PARALLEL_NONDETERMINISTIC_MERGE) |
Key Concepts Summary
| Concept | Description |
|---|---|
| state_schema | Defines types and reducers for shared state keys |
| Reducer | Function that resolves parallel write conflicts |
| append_list | List concatenation -- list type only, deterministic |
| sum_int | Integer summation -- integer type only, deterministic |
| concat_str | String concatenation -- string type only, deterministic |
| last_write | Last write wins -- all types, potentially nondeterministic |
| first_write | First write wins -- all types, potentially nondeterministic |
| STATE_SCHEMA_MERGE_TYPE_MISMATCH | Forbidden type+merge combination used |
| PARALLEL_NONDETERMINISTIC_MERGE | 2+ branches write to the same last_write key |
Common Errors
Error 1: Using Hyphens in state_schema Key Names
state_schema:
my-findings: # ← 하이픈 불가! 언더스코어 사용
type: list
merge: append_list
오류: STATE_SCHEMA_INVALID_KEY_NAME
'my-findings': 키 이름에 하이픈(-)을 사용할 수 없습니다.
언더스코어(_) 또는 camelCase를 사용하세요: my_findings
Error 2: Declaring a Key in writes_state That Is Not in state_schema
state_schema:
findings:
type: list
merge: append_list
nodes:
- id: branch_a
writes_state: [findings, nonexistent_key] # ← nonexistent_key는 schema에 없음
오류: WRITES_STATE_KEY_NOT_IN_SCHEMA
노드 'branch_a'의 writes_state에 'nonexistent_key'가 있지만
state_schema에 해당 키가 없습니다.
해결: state_schema에 nonexistent_key를 추가하거나
writes_state에서 제거하세요.
Error 3: Using sum_int with float
state_schema:
score:
type: float
merge: sum_int # float은 sum_int 불가!
오류: STATE_SCHEMA_MERGE_TYPE_MISMATCH
키 'score': type=float, merge=sum_int
sum_int는 type=integer에서만 사용 가능합니다.
float 타입에서 합산이 필요하면 last_write 또는 first_write를 사용하세요.
(float sum 리듀서는 현재 미지원)
Practice Exercise
Exercise: Competitor Analysis state_schema Design
Design a state_schema for a 3-branch parallel competitor analysis graph with the following specification.
Branch configuration:
- price_research branch: collect pricing information
- feature_research branch: collect feature comparisons
- review_research branch: collect user reviews
Requirements: - Aggregate results from each branch into a single list - Sum the number of competitors found by each branch (integer) - Only price_research records the average price (float) - The merge_findings join node writes the final analysis report (string)
# 설계 후 검증
euleragent graph validate examples/graphs/state/competitor_analysis.yaml
Previous: 05_interrupt_hooks.md | Next: 07_parallel_basics.md