패턴 03. 첫 번째 커스텀 패턴 — 단순 선형 파이프라인
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다.
- 3-노드 선형 패턴을 처음부터 작성하고 실행할 수 있다
- YAML의 모든 최상위 필드와 노드 필드의 의미를 설명할 수 있다
validate,show,compile,run명령의 차이와 용도를 구분할 수 있다- 패턴 파일을 워크스페이스에 설치하고 ID로 참조할 수 있다
system_append로 LLM 행동을 노드 단위로 제어할 수 있다
사전 준비
01_concepts.md완료 (패턴 개념 이해)02_builtin_patterns.md완료 (패턴 실행 경험)- 텍스트 에디터 준비 (VS Code, vim 등)
- 작업 디렉토리: 프로젝트 루트 (
euleragent init이 실행된 곳)
# 환경 확인
ls .euleragent/
euleragent agent list
1. 계획: 무엇을 만들 것인가?
목표: 주제를 입력받아 간단한 기술 블로그 포스트를 작성하는 패턴.
흐름:
[research] → [write] → [FINALIZE]
research: LLM이 주제에 대한 핵심 정보와 아이디어를 조사합니다 (웹 검색 없이, LLM 내부 지식만)write: 조사 내용을 바탕으로 블로그 포스트를 작성합니다FINALIZE:blog_post.md에 저장합니다
이것이 가능한 가장 단순한 패턴입니다. 사이클 없음, HITL 없음, Judge 없음. 완전한 선형 흐름입니다.
┌─────────────────────────────────────────────────────────────────┐
│ my_first.pattern 흐름도 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [research] │
│ │ 주제 조사, 핵심 포인트 정리 (llm/execute) │
│ │ when: true │
│ ▼ │
│ [write] │
│ │ 블로그 포스트 작성 (llm/execute) │
│ │ when: true │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [FINALIZE] blog_post.md 저장 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. YAML 파일 작성
my_first_pattern.yaml 파일을 생성합니다. 각 필드에 대한 설명을 주석으로 달았습니다.
# 작업 디렉토리에서 파일 생성
touch my_first_pattern.yaml
파일 내용:
# ─────────────────────────────────────────────
# 최상위 메타데이터
# ─────────────────────────────────────────────
# 패턴의 고유 식별자. "카테고리.설명" 형식 권장
# 워크스페이스에 설치 후 이 ID로 참조됨
id: my_first.pattern
# 스키마 버전. 현재 항상 1
version: 1
# 카테고리. 패턴 list에서 그룹핑에 사용됨
# 자유 문자열 (research, code, ops, writing 등)
category: writing
# 패턴 설명. pattern list와 show에서 표시됨
description: "주제를 입력받아 기술 블로그 포스트를 작성하는 단순 선형 패턴"
# ─────────────────────────────────────────────
# 기본값 설정 (defaults)
# 모든 노드에 공통 적용되는 설정
# ─────────────────────────────────────────────
defaults:
# 사이클(루프)이 있을 경우 최대 반복 횟수
# 이 패턴은 사이클이 없으므로 필수는 아니지만, 안전을 위해 선언
max_iterations: 1
# 전체 실행에서 도구 호출 최대 횟수
max_total_tool_calls: 10
# ─────────────────────────────────────────────
# 노드 정의 (nodes)
# 각 노드는 하나의 작업 단계
# ─────────────────────────────────────────────
nodes:
# ── 노드 1: research ──
- id: research # 노드의 고유 ID. 엣지에서 from/to로 참조됨
kind: llm # 노드 타입: llm (실행), judge (평가), finalize는 별도 선언
runner:
# mode: execute — LLM이 직접 도구를 실행함
# mode: plan — LLM이 도구 실행을 "제안"하고 HITL 승인 대기
mode: execute
# exclude_tools: 이 노드에서 사용 금지할 도구 목록
# 웹 검색 없이 LLM 지식만 사용하도록 강제
exclude_tools: [web.search, web.fetch]
prompt:
# system_append: 이 노드에서만 추가로 적용되는 시스템 프롬프트
# 에이전트의 기본 시스템 프롬프트에 "추가"됨 (덮어쓰지 않음)
system_append: |
당신은 기술 블로그 전문 리서처입니다.
주어진 주제에 대해 다음을 정리하세요:
1. 핵심 개념 3-5개
2. 초보자가 알아야 할 배경 지식
3. 실무 관련 포인트 2-3개
4. 참고할 만한 관련 기술 목록
출력 형식: 마크다운 섹션으로 구조화된 리서치 노트
artifacts:
# primary: 이 노드의 주요 출력 아티팩트 파일명
# .euleragent/runs/<run-id>/artifacts/ 아래에 저장됨
primary: research_notes.md
# ── 노드 2: write ──
- id: write
kind: llm
runner:
mode: execute
# max_loops: 이 노드가 실행될 최대 횟수
# 선형 패턴에서는 1이 기본이지만 명시적으로 선언
max_loops: 1
exclude_tools: [web.search, web.fetch, shell.exec]
prompt:
system_append: |
당신은 기술 블로그 작가입니다.
리서치 노트를 바탕으로 완성된 블로그 포스트를 작성하세요.
요구사항:
- 길이: 800-1200 단어
- 구성: 도입부 → 본문(3개 섹션) → 결론
- 독자: 경험 있는 개발자 (기초 설명 최소화)
- 톤: 실용적이고 직접적
- 코드 예시 포함 (해당하는 경우)
- 마크다운 포맷
artifacts:
primary: blog_post.md
# ─────────────────────────────────────────────
# 엣지 정의 (edges)
# 노드 간의 전환 조건
# ─────────────────────────────────────────────
edges:
- from: research # 출발 노드 ID
to: write # 도착 노드 ID
when: "true" # 전환 조건: "true" = 항상 전환
- from: write
to: finalize # "finalize"는 예약된 노드 이름
when: "true"
# ─────────────────────────────────────────────
# 최종 노드 설정 (finalize)
# nodes 배열에 추가하지 않음! 최상위 키로 선언
# ─────────────────────────────────────────────
finalize:
# 최종 결과물로 저장할 아티팩트 파일명
# write 노드의 primary 아티팩트와 일치시키는 것이 관례
artifact: blog_post.md
3. 검증: validate
euleragent pattern validate my_first_pattern.yaml
예상 출력 (모두 통과):
Validating pattern: my_first_pattern.yaml
Stage 1 (Schema) PASS Required fields: id, version, nodes, edges, finalize ✓
Types: all valid ✓
Stage 2 (Structural) PASS Node IDs unique: research, write ✓
Edge references valid ✓
No judge nodes — route coverage check skipped ✓
Stage 3 (IR Analysis) PASS Entry node: research (no incoming edges) ✓
No cycles detected — max_iterations not required ✓
All nodes reachable: research, write ✓
finalize reachable from all paths ✓
Validation complete: 0 errors, 0 warnings
JSON 형식으로 받으면 CI/CD에서 활용하기 좋습니다:
euleragent pattern validate my_first_pattern.yaml --format json
{
"pattern_id": "my_first.pattern",
"source": "my_first_pattern.yaml",
"stages": {
"schema": {
"status": "pass",
"errors": []
},
"structural": {
"status": "pass",
"errors": []
},
"ir_analysis": {
"status": "pass",
"errors": [],
"warnings": []
}
},
"overall": "pass",
"error_count": 0,
"warning_count": 0
}
4. 구조 확인: show
euleragent pattern show my_first_pattern.yaml
예상 출력:
Pattern: my_first.pattern (source: my_first_pattern.yaml)
Category: writing
Version: 1
Description: 주제를 입력받아 기술 블로그 포스트를 작성하는 단순 선형 패턴
Defaults:
max_iterations: 1
max_total_tool_calls: 10
Nodes:
[entry] research llm mode=execute exclude=[web.search, web.fetch]
write llm mode=execute exclude=[web.search, web.fetch, shell.exec]
Edges:
research → write when: true
write → finalize when: true
Finalize:
artifact: blog_post.md
Topology: linear (no cycles)
5. 컴파일: compile
IR JSON을 확인해 런타임이 패턴을 어떻게 해석하는지 봅니다.
euleragent pattern compile my_first_pattern.yaml
예상 출력 (IR JSON):
{
"id": "my_first.pattern",
"version": 1,
"category": "writing",
"description": "주제를 입력받아 기술 블로그 포스트를 작성하는 단순 선형 패턴",
"entry_node": "research",
"nodes": {
"research": {
"id": "research",
"kind": "llm",
"runner": {
"mode": "execute",
"force_tool": null,
"exclude_tools": ["web.search", "web.fetch"],
"min_proposals": null,
"max_loops": null,
"hitl_required": false
},
"prompt": {
"system_append": "당신은 기술 블로그 전문 리서처입니다..."
},
"guardrails": null,
"artifacts": {
"primary": "research_notes.md"
},
"resolved_defaults": {
"max_iterations": 1,
"max_total_tool_calls": 10
},
"outgoing_edges": [
{
"to": "write",
"condition": { "type": "always" }
}
]
},
"write": {
"id": "write",
"kind": "llm",
"runner": {
"mode": "execute",
"force_tool": null,
"exclude_tools": ["web.search", "web.fetch", "shell.exec"],
"max_loops": 1,
"hitl_required": false
},
"prompt": {
"system_append": "당신은 기술 블로그 작가입니다..."
},
"artifacts": {
"primary": "blog_post.md"
},
"outgoing_edges": [
{
"to": "__finalize__",
"condition": { "type": "always" }
}
]
}
},
"adjacency": {
"research": ["write"],
"write": ["__finalize__"]
},
"cycles": [],
"finalize": {
"artifact": "blog_post.md"
}
}
to: finalize가 IR에서는 to: "__finalize__"로 변환된 것을 볼 수 있습니다. 이는 finalize가 예약 노드 ID이기 때문입니다.
6. 워크스페이스에 설치
패턴을 로컬 파일 경로 대신 ID(my_first.pattern)로 참조하려면 워크스페이스 패턴 디렉토리에 복사합니다.
# 패턴 디렉토리 확인 또는 생성
mkdir -p .euleragent/patterns
# 패턴 파일 설치
cp my_first_pattern.yaml .euleragent/patterns/my_first_pattern.yaml
# 설치 확인 — 목록에 나타나는지 확인
euleragent pattern list
예상 출력:
Built-in Patterns
─────────────────────────────────────────────────────────
report.evidence research Evidence-based report writing
code.tdd code Test-driven development workflow
ops.triage ops Operations ticket triage
research.broad_to_narrow research Broad-to-narrow research synthesis
Workspace Patterns (.euleragent/patterns/)
─────────────────────────────────────────────────────────
my_first.pattern writing 주제를 입력받아 기술 블로그 포스트를 작성하는 단순 선형 패턴
5 patterns available.
이제 파일 경로 대신 ID로 참조할 수 있습니다:
# 파일 경로로 참조 (설치 전)
euleragent pattern validate my_first_pattern.yaml
# ID로 참조 (설치 후)
euleragent pattern validate my_first.pattern
euleragent pattern show my_first.pattern
7. 실행
euleragent pattern run my_first.pattern my-agent \
--task "Python의 asyncio와 async/await 패턴에 대한 기술 블로그 포스트 주제" \
--project default
예상 출력:
[run:f2c5a8e1] Starting pattern: my_first.pattern
[run:f2c5a8e1] Agent: my-agent
[run:f2c5a8e1] Task: Python의 asyncio와 async/await 패턴에 대한 기술 블로그 포스트 주제
✓ research Completed (12s) — research_notes.md generated
✓ write Completed (18s) — blog_post.md generated (1,047 words)
✓ finalize Completed
Run f2c5a8e1 completed.
Artifact: .euleragent/runs/f2c5a8e1/artifacts/blog_post.md
Total time: 31 seconds
8. 결과물 확인
# 최종 블로그 포스트
cat .euleragent/runs/f2c5a8e1/artifacts/blog_post.md
# 중간 아티팩트 (research 노드 출력)
cat .euleragent/runs/f2c5a8e1/artifacts/research_notes.md
# 실행 요약
euleragent runs status f2c5a8e1
# 전체 이벤트 스트림
cat .euleragent/runs/f2c5a8e1/pattern_events.jsonl
euleragent runs status 출력 예시:
Run: f2c5a8e1
Pattern: my_first.pattern
Agent: my-agent
Status: completed
Started: 2026-02-23 14:32:10
Completed: 2026-02-23 14:32:41
Duration: 31s
Node Executions:
research completed 12s
write completed 18s
finalize completed 1s
Artifacts:
research_notes.md (2,134 bytes)
blog_post.md (5,892 bytes) ← final
9. 주요 개념 설명
mode: execute vs mode: plan
이 패턴에서 두 노드 모두 mode: execute를 사용했습니다. 이 모드에서 LLM은 도구를 직접 사용할 수 있습니다. 단, exclude_tools로 금지한 도구는 사용할 수 없습니다.
mode: plan은 다음 튜토리얼(05_web_research)에서 다룹니다. 이 모드에서는 LLM이 도구 사용을 "제안"만 하고, 실제 실행은 인간 승인 후에 이루어집니다.
system_append의 상속 구조
system_append는 에이전트의 기본 시스템 프롬프트에 추가됩니다. 에이전트의 정체성(예: "당신은 helpful assistant입니다")은 유지되면서, 노드에서 더 구체적인 역할과 출력 형식을 지정할 수 있습니다.
에이전트 기본 시스템 프롬프트
+ research 노드의 system_append
= research 노드에서 LLM이 받는 최종 시스템 프롬프트
to: finalize가 특별한 이유
edges에서 to: finalize는 예약된 값입니다. 이는 finalize: 섹션에 정의된 최종 노드로 이동을 의미합니다. 일반 노드 ID로 finalize를 사용하면 RESERVED_NODE_ID 에러가 발생합니다.
artifacts.primary의 역할
artifacts.primary는 이 노드의 주요 출력 파일명입니다. 다음 노드는 이전 노드의 아티팩트를 컨텍스트로 자동으로 받습니다. write 노드는 research 노드가 생성한 research_notes.md의 내용을 자동으로 컨텍스트로 받습니다.
10. 실습 과제: system_append로 출력 형식 제어
write 노드의 system_append를 수정해서 다른 스타일의 블로그 포스트를 생성해보세요.
과제 1: 뉴스레터 스타일
# write 노드의 system_append를 다음으로 교체
prompt:
system_append: |
당신은 기술 뉴스레터 에디터입니다.
다음 형식으로 뉴스레터 섹션을 작성하세요:
## 이번 주 주제: [주제명]
**왜 중요한가?** (2-3문장)
**핵심 내용** (불릿 포인트 5개)
**실무 적용 팁** (불릿 포인트 3개)
**더 알아보기** (관련 주제 링크 형식으로)
길이: 400-600 단어. 친근하고 열정적인 톤.
과제 2: 튜토리얼 스타일
prompt:
system_append: |
당신은 기술 튜토리얼 작가입니다.
다음 구조로 단계별 튜토리얼을 작성하세요:
## 전제 조건
## 1단계: [제목]
## 2단계: [제목]
## 3단계: [제목]
## 요약 및 다음 단계
각 단계에 실행 가능한 코드 예시를 포함하세요.
독자: Python 기초 지식 있는 개발자
검증 후 실행:
euleragent pattern validate my_first_pattern.yaml
euleragent pattern run my_first.pattern my-agent \
--task "FastAPI로 REST API 만들기" \
--project default
흔한 오류와 해결법
오류 1: EDGE_UNKNOWN_NODE
ERROR [EDGE_UNKNOWN_NODE] Edge references unknown node: 'wirte' (did you mean 'write'?)
원인: 엣지의 from 또는 to에 오타가 있습니다.
해결: 노드 ID와 엣지의 from/to 값이 정확히 일치하는지 확인합니다.
오류 2: NO_ENTRY_NODE
ERROR [NO_ENTRY_NODE] No entry node found. Every node has an incoming edge.
원인: 모든 노드에 수신 엣지가 있어서 진입점이 없습니다. 종종 finalize를 노드로 선언하고 엣지를 연결했을 때 발생합니다.
해결: 진입점이 되어야 하는 첫 번째 노드에 수신 엣지가 없는지 확인합니다.
오류 3: RESERVED_NODE_ID
ERROR [RESERVED_NODE_ID] Node ID 'finalize' is reserved. Choose a different ID.
원인: nodes 배열에 id: finalize로 노드를 선언했습니다.
해결: 노드 이름을 final_step 등으로 변경하고, 최종 노드는 최상위 finalize: 키로만 선언합니다.
오류 4: 엣지에 to: finalize 오류
ERROR [EDGE_UNKNOWN_NODE] Edge references unknown node: 'finalize'
원인: Stage 2에서 finalize를 일반 노드로 오해하는 경우. 보통 finalize: 섹션을 빠뜨렸을 때 발생합니다.
해결: YAML 최상위에 finalize: 섹션이 있는지 확인합니다.
다음 단계
첫 번째 커스텀 패턴을 성공적으로 만들고 실행했습니다. 선형 패턴은 간단하지만, 모든 고급 패턴의 기초입니다.
- 다음 튜토리얼: 04_judge_and_loop.md — 이 패턴에 Judge 노드를 추가해서 품질 루프를 만듭니다
- 웹 검색 추가: 05_web_research.md —
research노드에 실제 웹 검색을 추가합니다 - 인간 검토 추가: 06_human_gate.md —
write후 사람이 검토하는 단계를 추가합니다