Home > EulerAgent > Tutorials > Graph > Graph 05. Interrupt Hooks — Pausing Exec...

Graph 05. Interrupt Hooks — Pausing Execution Before and After Nodes

Learning Objectives

After completing this tutorial, you will be able to:


Prerequisites

mkdir -p examples/graphs/hooks

# LangGraph 체크포인트 기능 확인
python -c "from langgraph.graph import StateGraph; print('LangGraph StateGraph OK')"

Expected output:

LangGraph StateGraph OK

What Are Interrupt Hooks?

Interrupt hooks leverage LangGraph's checkpoint mechanism to pause the graph before or after a specific node executes.

Two Types of Interrupts

Type Declaration Pause Timing State Snapshot
interrupt_before interrupt_before: true Immediately before the node executes State up to the previous node
interrupt_after interrupt_after: true Immediately after the node executes State after the node completes
노드 실행 타임라인:

interrupt_before: true 선언 시:
  이전_노드 → [일시 정지] → 해당_노드 → 다음_노드

interrupt_after: true 선언 시:
  이전_노드 → 해당_노드 → [일시 정지] → 다음_노드

HITL force_tool vs interrupt_before/after

Both approaches create opportunities for human intervention, but they differ in mechanism and purpose.

Aspect HITL force_tool interrupt_before/after
Mechanism euleragent file-based JSONL approval queue LangGraph checkpoint
Purpose Approval before specific tool usage State inspection and resumption before/after nodes
Persistence Saved in approval queue file LangGraph checkpointer (memory/DB)
Resume method euleragent approve LangGraph graph.invoke(state)
Use cases Approving web searches, file writes, shell execution Critical node state inspection, debugging
Pattern support O X (Graph only)

When to Use Interrupts

interrupt_before 권장 시나리오:
  - publish 노드 전: "정말 배포하시겠습니까?"
  - payment 노드 전: "결제를 진행하시겠습니까?"
  - email_send 노드 전: "이 이메일을 발송하시겠습니까?"
  - 고위험 API 호출 전: 상태 검사

interrupt_after 권장 시나리오:
  - research 노드 후: 수집된 데이터 검토
  - draft 노드 후: 초안 검토 후 수정 여부 결정
  - 디버깅: 중간 상태를 로그로 확인

Step-by-Step Walkthrough

Step 1: interrupt_before — human_review Node

Set up an interrupt so a human can review before the critical publish step.

# examples/graphs/hooks/interrupt_before.yaml
id: graph.interrupt_before_demo
version: 1
category: demo
description: publish 노드 전 interrupt_before로 사람 검토
checkpointer: memory    # ← interrupt 사용 시 필수. 미선언 시 INTERRUPT_REQUIRES_CHECKPOINTER 오류

defaults:
  max_iterations: 2
  max_total_tool_calls: 20

nodes:
  - id: research
    kind: llm
    runner:
      mode: plan
      force_tool: web.search
    guardrails:
      tool_call_budget:
        web.search: 3

  - id: draft
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: article.md

  - id: human_review
    kind: llm
    interrupt_before: true    # ← 이 노드 실행 전에 그래프 일시 정지
    runner:
      mode: execute
    artifacts:
      primary: review_notes.md

  - id: publish
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: published.md

edges:
  - from: research
    to: draft
    when: "approvals_resolved"
  - from: draft
    to: human_review
    when: "true"
  - from: human_review
    to: publish
    when: "true"
  - from: publish
    to: finalize
    when: "true"

finalize:
  artifact: published.md
euleragent graph validate examples/graphs/hooks/interrupt_before.yaml

Expected output:

단계 3/3: Graph 추가 검증...
  [✓] interrupt_before: human_review (1개 노드)
  [✓] interrupt_after: 없음
  완료

결과: 유효 ✓
  참고: interrupt_before가 설정된 노드: [human_review]
  실행 시 LangGraph 체크포인터가 필요합니다.

Step 2: Verify interrupt_before in IR with graph compile

euleragent graph compile examples/graphs/hooks/interrupt_before.yaml \
  --out /tmp/interrupt_before_ir.json

# IR에서 interrupt 관련 필드 확인
python -m json.tool /tmp/interrupt_before_ir.json | \
  python -c "
import sys, json
d = json.load(sys.stdin)
# 각 노드의 interrupt 설정 확인
for node in d['nodes']:
    if node.get('interrupt_before') or node.get('interrupt_after'):
        print(f\"노드 '{node['id']}': interrupt_before={node['interrupt_before']}, interrupt_after={node['interrupt_after']}\")
# langgraph_builder의 interrupt 설정 확인
print('langgraph_builder.interrupt_before:', d['langgraph_builder']['interrupt_before'])
print('langgraph_builder.interrupt_after:', d['langgraph_builder']['interrupt_after'])
"

Expected output:

노드 'human_review': interrupt_before=True, interrupt_after=False
langgraph_builder.interrupt_before: ['human_review']
langgraph_builder.interrupt_after: []

Check the interrupt_before and interrupt_after lists in the IR's langgraph_builder section. These lists are used when calling LangGraph's graph.compile(interrupt_before=[...], interrupt_after=[...]).

{
  "langgraph_builder": {
    "add_nodes": ["research", "draft", "human_review", "publish", "finalize"],
    "add_edges": [...],
    "interrupt_before": ["human_review"],
    "interrupt_after": []
  }
}

At runtime, this materializes into the following LangGraph code.

# euleragent 내부 생성 코드 (개념적)
compiled = graph.compile(
    checkpointer=checkpointer,        # 상태 저장용 체크포인터
    interrupt_before=["human_review"]  # human_review 전에 일시 정지
)

Step 3: interrupt_after — Reviewing After the draft Node

Use interrupt_after when you want to review the generated draft after the draft node completes.

# examples/graphs/hooks/interrupt_after.yaml
id: graph.interrupt_after_demo
version: 1
category: demo
description: draft 노드 후 interrupt_after로 초안 검토
checkpointer: memory    # ← interrupt 사용 시 필수

defaults:
  max_iterations: 3
  max_total_tool_calls: 25

nodes:
  - id: research
    kind: llm
    runner:
      mode: execute

  - id: draft
    kind: llm
    interrupt_after: true    # ← 이 노드 실행 후 그래프 일시 정지
    runner:
      mode: execute
    artifacts:
      primary: draft.md

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]

  - id: revise
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: draft.md

edges:
  - from: research
    to: draft
    when: "true"
  - 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: draft.md
euleragent graph validate examples/graphs/hooks/interrupt_after.yaml

Expected output:

결과: 유효 ✓
  참고: interrupt_after가 설정된 노드: [draft]

Here is the IR representation of the draft node with interrupt_after: true.

{
  "id": "draft",
  "kind": "llm",
  "interrupt_before": false,
  "interrupt_after": true,
  "runner": { "mode": "execute" },
  "artifacts": { "primary": "draft.md" }
}

Step 4: Using Both Interrupts Simultaneously

Use interrupt_after: true (after draft) and interrupt_before: true (before publish) together. This creates two intervention points: one for reviewing the completed draft, and one for final confirmation before deployment.

# examples/graphs/hooks/interrupt_both.yaml
id: graph.interrupt_both_demo
version: 1
category: demo
description: draft 후 + publish 전 두 번의 interrupt
checkpointer: memory    # ← interrupt 사용 시 필수

defaults:
  max_iterations: 2
  max_total_tool_calls: 30

nodes:
  - id: research
    kind: llm
    runner:
      mode: execute

  - id: draft
    kind: llm
    interrupt_after: true    # ← 초안 완성 후 검토 기회
    runner:
      mode: execute
    artifacts:
      primary: content.md

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]

  - id: revise
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: content.md

  - id: prepare_publish
    kind: llm
    runner:
      mode: execute
    artifacts:
      primary: publish_ready.md

  - id: publish
    kind: llm
    interrupt_before: true   # ← 배포 직전 최종 확인
    runner:
      mode: execute
    artifacts:
      primary: published.md

edges:
  - from: research
    to: draft
    when: "true"
  - from: draft
    to: evaluate
    when: "true"
  - from: evaluate
    to: prepare_publish
    when: "judge.route == finalize"
  - from: evaluate
    to: revise
    when: "judge.route == revise"
  - from: revise
    to: evaluate
    when: "true"
  - from: prepare_publish
    to: publish
    when: "true"
  - from: publish
    to: finalize
    when: "true"

finalize:
  artifact: published.md
euleragent graph validate examples/graphs/hooks/interrupt_both.yaml

Expected output:

단계 3/3: Graph 추가 검증...
  [✓] interrupt_before: publish (1개 노드)
  [✓] interrupt_after: draft (1개 노드)
  [✓] 두 interrupt 호환성 검사 통과
      (같은 노드에 interrupt_before와 interrupt_after 동시 선언 없음)
  완료

결과: 유효 ✓
  interrupt_after:  [draft]
  interrupt_before: [publish]
euleragent graph compile examples/graphs/hooks/interrupt_both.yaml \
  --out /tmp/interrupt_both_ir.json

python -m json.tool /tmp/interrupt_both_ir.json | \
  python -c "
import sys, json
d = json.load(sys.stdin)
lb = d['langgraph_builder']
print('interrupt_before:', lb['interrupt_before'])
print('interrupt_after:', lb['interrupt_after'])
"

Expected output:

interrupt_before: ['publish']
interrupt_after: ['draft']

Both values are passed to the LangGraph graph.compile() call.

# LangGraph 내부 코드 (개념적)
compiled = graph.compile(
    checkpointer=checkpointer,
    interrupt_before=["publish"],
    interrupt_after=["draft"]
)

Step 5: Comparing Interrupt and HITL force_tool

Compare both approaches side by side for achieving the same goal (human intervention).

# HITL force_tool 방식 (Pattern 및 Graph 모두 지원)
nodes:
  - id: research
    kind: llm
    runner:
      mode: plan            # plan 모드에서 tool 사용 계획 제안
      force_tool: web.search  # 반드시 web.search 도구 사용
    # 실행 흐름:
    #   1. LLM이 web.search 계획 제안
    #   2. euleragent가 approvals.jsonl에 승인 요청 기록
    #   3. 사람이 euleragent approve 명령으로 승인
    #   4. 승인 후 web.search 실행
    #   5. approvals_resolved 조건이 충족됨

# interrupt_before 방식 (Graph 전용)
nodes:
  - id: publish
    kind: llm
    interrupt_before: true   # LangGraph 체크포인트로 일시 정지
    runner:
      mode: execute
    # 실행 흐름:
    #   1. LangGraph가 publish 노드 직전에 실행 중단
    #   2. LangGraph 상태가 체크포인터에 저장
    #   3. 사람이 상태 확인 후 graph.invoke()로 재개
    #   4. publish 노드 실행 계속

Expected Output Summary

Command Expected Result
graph validate interrupt_before.yaml Valid, interrupt_before: [human_review]
graph compile interrupt_before.yaml IR interrupt_before: ["human_review"]
graph validate interrupt_after.yaml Valid, interrupt_after: [draft]
graph validate interrupt_both.yaml Valid, both interrupts confirmed
graph compile interrupt_both.yaml IR with interrupt_before: ["publish"], interrupt_after: ["draft"]

Key Concepts Summary

Concept Description
interrupt_before: true Pauses at LangGraph checkpoint before the node executes
interrupt_after: true Pauses at LangGraph checkpoint after the node executes
Checkpointer LangGraph component that persists the paused graph state
HITL force_tool euleragent file-based approval queue (Pattern + Graph)
Interrupt LangGraph checkpoint-based pause (Graph only)
graph.compile(interrupt_before=[...]) LangGraph API where IR interrupt settings are mapped to

Common Errors

Error 1: Declaring interrupt_before + interrupt_after on the Same Node

- id: critical_node
  kind: llm
  interrupt_before: true   # ← 전
  interrupt_after: true    # ← 후 동시에
  runner:
    mode: execute
경고: INTERRUPT_DOUBLE_DECLARED
  노드 'critical_node'에 interrupt_before와 interrupt_after가 모두 true입니다.
  이 노드는 실행 전과 후 두 번 일시 정지됩니다.
  의도한 동작인지 확인하세요.

This is a warning, not an error. If this is intentional (reviewing both before and after execution), you can safely ignore it.

Error 2: Attempting to Use Interrupt in a Pattern

# Pattern validate로 interrupt가 있는 Graph YAML 검증
euleragent pattern validate examples/graphs/hooks/interrupt_before.yaml
경고: UNKNOWN_NODE_FIELD
  노드 'human_review'에 알 수 없는 필드: interrupt_before
  Pattern은 interrupt_before/after를 지원하지 않습니다.
  Graph 모듈에서 사용하세요: euleragent graph validate

Error 3: Using Interrupt Without a Checkpointer Declaration

Graphs that use interrupt_before/interrupt_after must have a checkpointer: field at the top level. Without it, an error occurs immediately during the graph validate step.

# BAD — checkpointer 없이 interrupt 사용
id: my_graph
version: 1
description: ...
# checkpointer: MISSING ← 이 줄이 없으면 아래 오류 발생

nodes:
  - id: review
    kind: llm
    interrupt_before: true   # ← 검증 시 INTERRUPT_REQUIRES_CHECKPOINTER 발생
    runner: {mode: execute}
오류: INTERRUPT_REQUIRES_CHECKPOINTER
  노드 'review'가 interrupt_before/interrupt_after를 선언했지만
  최상위에 checkpointer가 구성되지 않았습니다.
  LangGraph는 브레이크포인트에서 일시 정지 후 재개하기 위해
  체크포인터가 필요합니다.
  최상위에 'checkpointer: true' (또는 'checkpointer: memory') 를 추가하세요.

Fix:

# GOOD — checkpointer 선언
id: my_graph
version: 1
description: ...
checkpointer: memory   # ← 추가

nodes:
  - id: review
    kind: llm
    interrupt_before: true
    runner: {mode: execute}

Available values: true (default memory checkpointer), "memory", "sqlite". If persistent storage is needed, use "sqlite" and configure the path in the euleragent runtime environment.

Error 4: Declaring Interrupt on the finalize Node

finalize:
  artifact: output.md
  interrupt_before: true   # ← finalize는 최종 노드 — interrupt 불가
오류: INTERRUPT_ON_FINALIZE
  finalize 노드에는 interrupt_before/after를 선언할 수 없습니다.
  interrupt는 그래프 실행 중간 노드에만 유효합니다.
  finalize 전에 interrupt를 원한다면 finalize 직전 노드에 선언하세요.

Practice Exercise

Exercise: Three-Stage Review Workflow

Write a graph using interrupt hooks with the following specification.

  1. Declare checkpointer: memory at the top level (required)
  2. gather node: data collection (execute mode)
  3. analyze node: run analysis (execute mode, interrupt_after: true -- review analysis results)
  4. recommend node: generate recommendations (execute mode)
  5. send_report node: send the report (execute mode, interrupt_before: true -- final confirmation before sending)
euleragent graph validate examples/graphs/hooks/three_stage_review.yaml
euleragent graph compile examples/graphs/hooks/three_stage_review.yaml

Verify the following in the IR: - interrupt_after: ["analyze"] - interrupt_before: ["send_report"]


Previous: 04_bounded_loop.md | Next: 06_state_schema.md

← Prev Back to List Next →