> EulerAgent > 튜토리얼 > 패턴 > 패턴 08. 에어갭 패턴과 Ops 설계 — 웹 차단 +...

패턴 08. 에어갭 패턴과 Ops 설계 — 웹 차단 + 정책 준수

학습 목표

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

사전 준비

euleragent agent list
euleragent pattern list

1. 에어갭(Airgap) 패턴이란?

에어갭은 원래 네트워크 보안 용어로, 외부 인터넷에서 완전히 격리된 시스템을 의미합니다. 패턴에서 에어갭은 모든 외부 네트워크 접근을 차단한 패턴을 말합니다.

에어갭이 필요한 이유

규정 준수 (Compliance): - GDPR: EU 고객 데이터를 처리할 때 데이터가 EU 외부로 나가면 안 됨 - HIPAA: 환자 의료 정보를 외부 서비스에 전송 금지 - SOX: 재무 데이터 처리 시 외부 조회 금지

보안 정책: - 내부 소스 코드나 영업 비밀이 포함된 태스크 - 분류된(classified) 정보 처리 - 내부 취약점 보고서 (외부에 노출되면 위험)

내부 전용 데이터: - 사내 FAQ 업데이트 - 내부 온보딩 문서 - 회사 정책 문서 초안

에어갭 vs 일반 패턴

구분 일반 패턴 에어갭 패턴
web.search 사용 가능 (HITL) 완전 차단
web.fetch 사용 가능 (HITL) 완전 차단
git.push HITL 승인 후 가능 필요시 HITL
LLM 내부 지식 사용 가능 사용 가능
내부 파일 읽기 사용 가능 사용 가능

2. exclude_tools로 에어갭 구현

모든 노드에 exclude_tools를 적용합니다.

nodes:
  - id: analyze
    kind: llm
    runner:
      mode: execute
      # 웹 접근 완전 차단
      exclude_tools: [web.search, web.fetch]

  - id: draft
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]

  - id: policy_check
    kind: llm
    runner:
      mode: plan
      force_tool: file.write
      # 파일 쓰기는 허용, 웹 접근 차단
      exclude_tools: [web.search, web.fetch]

exclude_tools에 있는 도구를 사용하려 하면 런타임이 자동으로 차단합니다.


3. Ops 패턴의 특성

운영(Operations) 패턴은 일반 리서치/작성 패턴과 다른 특성을 가집니다.

빠른 응답 시간: 운영 상황은 대기 시간이 짧아야 합니다. 긴 루프는 피합니다.

명확한 판단 기준: 모호함을 줄이고 예측 가능한 결과를 냅니다.

짧은 max_iterations: 보통 1-2회. 3회 이상은 운영 상황에 부적합합니다.

구조화된 출력: 티켓, 보고서, 알림 등 표준 형식을 사용합니다.

에스컬레이션 경로: 자동 처리 불가 시 인간에게 빠르게 넘기는 경로가 있어야 합니다.


4. 패턴 1: 기본 에어갭 패턴

내부 데이터만으로 고객 지원 티켓을 처리하는 패턴.

노드 흐름도

┌─────────────────────────────────────────────────────────────────┐
│ ops.internal_triage 패턴 흐름도 (에어갭)                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [classify]                                                     │
│     │ 티켓 분류 (llm/execute, 웹 차단)                             │
│     │ when: true                                                │
│     ▼                                                           │
│  [draft_resolution]  ◄── HITL PAUSE (file.write, 정책 검토)      │
│     │ 해결책 초안 (llm/plan, 웹 차단)                              │
│     │ when: approvals_resolved                                  │
│     ▼                                                           │
│  [evaluate] ──── when: judge.route == finalize ─────────────────┐
│     │ 품질 평가 (judge, 웹 차단)                                   │
│     │ when: judge.route == revise                               │
│     ▼                                                           │
│  [revise]                                                       │
│     │ 개선 (llm/execute, 웹 차단)                                 │
│     └────────────────────────► [evaluate] (최대 2회)             │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ [FINALIZE]  triage_result.md 저장                        │◄──┘
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

YAML 파일: ops_internal_triage.yaml

id: ops.internal_triage
version: 1
category: ops
description: "에어갭 고객 지원 트리아지  외부 네트워크 완전 차단, 내부 지식만 활용"

defaults:
  max_iterations: 2
  max_total_tool_calls: 8
  pass_threshold: 0.80

nodes:
  # ── 노드 1: classify ──
  - id: classify
    kind: llm
    runner:
      mode: execute
      # 에어갭: 외부 네트워크 완전 차단
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        당신은 내부 지원팀 트리아지 전문가입니다.
        외부 정보 없이 내부 지식만으로 티켓을 분류하세요.

        분류 항목:
        - 카테고리: authentication / database / performance / ui / billing / other
        - 우선순위: critical / high / medium / low
        - 복잡도: simple / moderate / complex
        - 예상 해결 시간: 1h / 4h / 1d / 1w

        출력: 구조화된 분류 결과 (JSON 형식)
    artifacts:
      primary: classification.json

  # ── 노드 2: draft_resolution (정책 검토 게이트) ──
  - id: draft_resolution
    kind: llm
    runner:
      mode: plan
      force_tool: file.write
      max_loops: 1
      # 에어갭 유지 + 파일 쓰기는 허용
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        분류 결과를 바탕으로 해결책 초안을 작성하고 파일로 저장하세요.

        파일 형식:
        # 지원 티켓 해결책
        ## 티켓 정보
        - 카테고리: [분류 결과]
        - 우선순위: [분류 결과]

        ## 근본 원인 분석
        ## 즉시 조치 (1-2시간 내)
        ## 단기 해결책 (이번 주)
        ## 장기 예방 조치
        ## 필요 리소스

        이 파일은 담당자가 검토할 것입니다.
    guardrails:
      tool_call_budget:
        file.write: 1
    artifacts:
      primary: resolution_draft.md

  # ── 노드 3: evaluate (Judge) ──
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        내부 지원 해결책을 평가하세요. 외부 리서치 없이 실행 가능한가?

        평가 기준:
        - 내부 도구로 실행 가능 여부 (40%): 외부 서비스나 정보 없이 가능한가?
        - 명확성 (30%): 담당자가 바로 실행할 수 있을 만큼 구체적인가?
        - 완결성 (30%): 즉각 조치, 단기, 장기가 모두 다뤄졌는가?

  # ── 노드 4: revise ──
  - id: revise
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        피드백을 반영하여 해결책을 개선하세요.
        외부 도구나 서비스 없이 실행 가능한 해결책만 포함하세요.
    artifacts:
      primary: resolution_draft.md

edges:
  - from: classify
    to: draft_resolution
    when: "true"

  - from: draft_resolution
    to: evaluate
    when: "approvals_resolved"

  - from: evaluate
    to: finalize
    when: "judge.route == finalize"

  - from: evaluate
    to: revise
    when: "judge.route == revise"

  - from: revise
    to: evaluate
    when: "true"

finalize:
  artifact: resolution_draft.md

5. 패턴 2: 정책 검토 게이트 포함 에어갭

정책 문서를 로드하고 그에 맞게 해결책을 검증하는 고급 에어갭 패턴.

노드 흐름도

┌─────────────────────────────────────────────────────────────────┐
│ ops.policy_compliant 패턴 흐름도                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  [load_policy]                                                  │
│     │ 내부 정책 파일 로드 (llm/execute, 파일 읽기만 허용)             │
│     │ when: true                                                │
│     ▼                                                           │
│  [classify]                                                     │
│     │ 정책 기반 티켓 분류 (llm/execute, 웹 차단)                    │
│     │ when: true                                                │
│     ▼                                                           │
│  [draft]                                                        │
│     │ 해결책 초안 (llm/execute, 웹 차단)                           │
│     │ when: true                                                │
│     ▼                                                           │
│  [policy_check]  ◄── HITL PAUSE (반드시 정책 담당자 검토)           │
│     │ 정책 준수 확인 + 파일 저장 (llm/plan, max_loops=1)            │
│     │ when: approvals_resolved                                  │
│     ▼                                                           │
│  [evaluate] ──── when: judge.route == finalize ─────────────────┐
│     │ 품질 평가 (judge)                                          │
│     │ when: judge.route == revise                               │
│     ▼                                                           │
│  [revise] ────────────────────────────── [evaluate] (최대 1회)   │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │ [FINALIZE]  compliant_resolution.md 저장                 │◄──┘
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

YAML 파일: ops_policy_compliant.yaml

id: ops.policy_compliant
version: 1
category: ops
description: "내부 정책 로드  분류  초안  정책 준수 검토 게이트  평가 (완전 에어갭)"

defaults:
  max_iterations: 1     # Ops 패턴은 짧게 유지
  max_total_tool_calls: 10

nodes:
  # ── 노드 1: load_policy ──
  - id: load_policy
    kind: llm
    runner:
      mode: execute
      # 파일 읽기만 허용. 웹 + 파일 쓰기 + 셸 차단
      exclude_tools: [web.search, web.fetch, file.write, shell.exec]
    prompt:
      system_append: |
        내부 정책 파일을 읽고 핵심 내용을 요약하세요.

        읽어야 할 파일:
        - policies/support_policy.md (지원 정책)
        - policies/escalation_rules.md (에스컬레이션 규칙)
        - policies/data_handling.md (데이터 처리 규정)

        파일이 없으면 "정책 파일 없음"으로 표시하세요.
        요약은 다음 단계에서 해결책 작성 시 활용됩니다.
    artifacts:
      primary: policy_summary.md

  # ── 노드 2: classify ──
  - id: classify
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        로드된 내부 정책을 기반으로 티켓을 분류하세요.
        에스컬레이션 규칙에 따라 자동 처리 가능한지 판단하세요.

        출력:
        - 자동 처리 가능 여부 (yes/no)
        - 불가능하면 이유
        - 담당 팀
        - 예상 SLA
    artifacts:
      primary: classification.md

  # ── 노드 3: draft ──
  - id: draft
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        정책 요약과 분류를 바탕으로 해결책 초안을 작성하세요.
        모든 단계가 내부 정책을 준수해야 합니다.
        데이터 처리 규정(GDPR/개인정보보호법)에 맞는 조치만 포함하세요.
    artifacts:
      primary: resolution.md

  # ── 노드 4: policy_check (정책 검토 게이트) ──
  - id: policy_check
    kind: llm
    runner:
      mode: plan
      force_tool: file.write
      max_loops: 1
      # 에어갭 유지
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        정책 준수 담당자를 위한 검토 문서를 생성하세요.

        파일에 포함할 내용:
        1. 정책 준수 체크리스트
        2. 해결책 초안 (원문)
        3. 각 조치가 어느 정책 조항에 근거하는지 매핑
        4. 잠재적 정책 위반 위험 항목 (있다면 빨간색 태그)

        담당자: 이 파일을 검토하고 정책 위반이 없으면 승인하세요.
    guardrails:
      tool_call_budget:
        file.write: 1
    artifacts:
      primary: policy_check_doc.md

  # ── 노드 5: evaluate ──
  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]
    prompt:
      system_append: |
        정책 검토를 통과한 해결책의 최종 품질을 평가하세요.

        - 실행 가능성: 내부 리소스만으로 즉시 실행 가능한가?
        - 정책 준수: 검토 통과 기준에 맞는가?
        - 완결성: 모든 액션 아이템이 명확한가?

  # ── 노드 6: revise ──
  - id: revise
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        피드백을 반영하여 해결책을 개선하세요.
        모든 내용이 정책 검토 게이트를 다시 통과하지 않아도
        될 정도로 명확하게 수정하세요.
    artifacts:
      primary: resolution.md

edges:
  - from: load_policy
    to: classify
    when: "true"

  - from: classify
    to: draft
    when: "true"

  - from: draft
    to: policy_check
    when: "true"

  - from: policy_check
    to: evaluate
    when: "approvals_resolved"

  - from: evaluate
    to: finalize
    when: "judge.route == finalize"

  - from: evaluate
    to: revise
    when: "judge.route == revise"

  - from: revise
    to: evaluate
    when: "true"

finalize:
  artifact: resolution.md

6. 검증

euleragent pattern validate ops_internal_triage.yaml
euleragent pattern validate ops_policy_compliant.yaml

예상 출력 (policy_compliant):

Validating pattern: ops_policy_compliant.yaml

  Stage 1 (Schema)      PASS
  Stage 2 (Structural)  PASS
    Airgap check: web.search excluded from all nodes ✓
    HITL gate: policy_check (file.write, max_loops=1) ✓
  Stage 3 (IR Analysis) PASS
    All nodes have exclude_tools=[web.search, web.fetch, shell.exec] ✓
    Cycle bounded: max_iterations=1 ✓

Validation complete: 0 errors, 0 warnings

Note: All nodes exclude web.search and web.fetch. This is an airgap pattern.

7. 실행 예시

기본 에어갭 패턴 실행

cp ops_internal_triage.yaml .euleragent/patterns/

euleragent pattern run ops.internal_triage my-agent \
  --task "고객 티켓 #T-2847: 결제 페이지에서 500 에러 발생. 오전 9시부터 지속. 영향 고객 약 200명." \
  --project default

예상 출력 (draft_resolution에서 pause):

[run:m4d7i1j5] Starting pattern: ops.internal_triage

  ✓ classify     Completed (4s)
                 카테고리: billing / 우선순위: critical / 복잡도: moderate
                 예상 해결 시간: 4h

  ⏸ draft_resolution  PAUSED — Waiting for HITL approval (file.write × 1)
                       [AIRGAP: web access blocked for this run]
euleragent approve list --run-id m4d7i1j5
Pending Approvals for run: m4d7i1j5
─────────────────────────────────────────────────
Node: draft_resolution (HUMAN GATE, AIRGAP MODE)

  #1  file.write  path=resolution_draft.md
      Content preview:
      # 지원 티켓 해결책 — T-2847
      ## 티켓 정보
      - 카테고리: billing
      - 우선순위: CRITICAL

      ## 근본 원인 분석
      결제 처리 서버의 데이터베이스 연결 풀 고갈 가능성.
      오전 9시 트래픽 증가 시점과 일치.

      ## 즉시 조치 (30분 내)
      1. 서버 로그 확인: /var/log/payment-service/error.log
      2. DB 연결 수 모니터링: kubectl exec ... psql -c "SELECT count(*) ..."
      3. 필요 시 결제 서버 재시작 (무중단)
      ...

  ⚠️  AIRGAP: No web resources were consulted. Internal knowledge only.
euleragent approve accept-all --run-id m4d7i1j5 --execute
euleragent pattern resume m4d7i1j5 --execute

예상 최종 출력:

  ✓ draft_resolution  Completed — resolution_draft.md saved
  ✓ evaluate          Completed — score: 0.88 → route: finalize
  ✓ finalize          Completed

[AIRGAP RUN SUMMARY]
  Web searches: 0 (blocked)
  File reads: 0
  Internal knowledge: used
  Total tool calls: 2 (file.write × 1, judge × 1)

Artifact: .euleragent/runs/m4d7i1j5/artifacts/resolution_draft.md

8. 에어갭 실행 감사 로그

에어갭 패턴의 실행 기록을 검토합니다.

# 감사 로그 확인
cat .euleragent/runs/m4d7i1j5/tool_calls.jsonl
{"ts":"2026-02-23T10:15:30Z","node":"draft_resolution","tool":"file.write","path":"resolution_draft.md","status":"proposed","hitl_required":true}
{"ts":"2026-02-23T10:17:45Z","node":"draft_resolution","tool":"file.write","path":"resolution_draft.md","status":"accepted","operator":"sean"}
{"ts":"2026-02-23T10:17:46Z","node":"draft_resolution","tool":"file.write","path":"resolution_draft.md","status":"executed","bytes":1847}

웹 검색 시도가 없음을 확인:

cat .euleragent/runs/m4d7i1j5/tool_calls.jsonl | grep "web.search"
# 출력 없음 — 웹 검색 완전히 차단됨

9. 실습 과제: 내부 FAQ 업데이트 패턴

사내 FAQ 문서를 업데이트하는 에어갭 패턴을 만들어보세요.

요구사항

힌트

id: docs.faq_update
version: 1
category: ops
description: "사내 FAQ 업데이트  에어갭, 인간 검토 게이트"

defaults:
  max_iterations: 1
  max_total_tool_calls: 5

nodes:
  - id: retrieve_existing
    kind: llm
    runner:
      mode: execute
      # 파일 쓰기, 웹, 셸 모두 차단 — 읽기만
      exclude_tools: [web.search, web.fetch, file.write, shell.exec]
    prompt:
      system_append: |
        docs/faq.md를 읽고 현재 FAQ 구조와 내용을 파악하세요.
        업데이트 요청 내용과 비교해 어느 섹션을 수정해야 하는지 파악하세요.

  - id: draft_update
    kind: llm
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch, shell.exec]
    # ...

  - id: diff_review
    kind: llm
    runner:
      mode: plan
      force_tool: file.write
      max_loops: 1
      exclude_tools: [web.search, web.fetch, shell.exec]
    prompt:
      system_append: |
        변경 사항을 diff 형식으로 파일에 저장하세요.
        기존 내용(-)과 새 내용(+)을 명확히 표시하세요.
    guardrails:
      tool_call_budget:
        file.write: 1

  - id: evaluate
    kind: judge
    judge:
      schema: evaluator_v1
      route_values: [finalize, revise]
    runner:
      mode: execute
      exclude_tools: [web.search, web.fetch]

edges:
  - from: retrieve_existing
    to: draft_update
    when: "true"

  - from: draft_update
    to: diff_review
    when: "true"

  - from: diff_review
    to: evaluate
    when: "approvals_resolved"

  - from: evaluate
    to: finalize
    when: "judge.route == finalize"

  - from: evaluate
    to: draft_update    # 수정 시 draft_update로 돌아감
    when: "judge.route == revise"

finalize:
  artifact: updated_faq.md

검증:

euleragent pattern validate faq_update.yaml

HITL_GATING_VIOLATION이 발생하지 않는지 (mode: plan과 force_tool이 올바르게 결합됐는지) 확인하세요.

실행:

euleragent pattern run docs.faq_update my-agent \
  --task "FAQ 업데이트: 신규 결제 수단(계좌이체) 추가, 환불 정책 수정(14일→30일), 구버전 앱 지원 종료 공지 추가" \
  --project default

10. 흔한 오류와 해결법

오류 1: HITL_GATING_VIOLATION

ERROR [HITL_GATING_VIOLATION]
  Node 'policy_check': force_tool 'file.write' is set but mode is 'execute'.
  force_tool requires mode: plan for HITL gating.

해결: mode: executemode: plan으로 변경합니다.

오류 2: 웹 차단됐는데 실행 시도

런타임 레벨에서 자동 차단됩니다.

[event] tool.blocked  {node: "classify", tool: "web.search", reason: "excluded_by_pattern", excluded_tools: ["web.search", "web.fetch"]}

에러가 아닌 경고로 기록되고, 에이전트는 해당 도구 없이 계속 실행됩니다.

오류 3: 에어갭 미적용 노드 발견

# 모든 노드의 exclude_tools 확인
euleragent pattern compile ops_policy_compliant.yaml | \
  python3 -c "import json,sys; d=json.load(sys.stdin); [print(k, d['nodes'][k]['runner'].get('exclude_tools',[])) for k in d['nodes']]"

일부 노드에 exclude_tools가 없으면 에어갭이 완전하지 않습니다.


다음 단계

에어갭 패턴과 Ops 설계를 배웠습니다. 이제 가장 복잡한 토폴로지 — 두 개의 독립적인 Judge 사이클을 다룹니다.

← 이전 목록으로 다음 →