> EulerAgent > 튜토리얼 > 패턴 > 첫 번째 커스텀 패턴

패턴 03. 첫 번째 커스텀 패턴 — 단순 선형 파이프라인

학습 목표

이 튜토리얼을 마치면 다음을 할 수 있습니다.

사전 준비

# 환경 확인
ls .euleragent/
euleragent agent list

1. 계획: 무엇을 만들 것인가?

목표: 주제를 입력받아 간단한 기술 블로그 포스트를 작성하는 패턴.

흐름:

[research] → [write] → [FINALIZE]

이것이 가능한 가장 단순한 패턴입니다. 사이클 없음, 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: 섹션이 있는지 확인합니다.


다음 단계

첫 번째 커스텀 패턴을 성공적으로 만들고 실행했습니다. 선형 패턴은 간단하지만, 모든 고급 패턴의 기초입니다.

← 이전: 내장 패턴 실행 플레이북 목록으로 다음: Judge 노드와 품질 루프 →