패턴 08. 에어갭 패턴과 Ops 설계 — 웹 차단 + 정책 준수
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다.
exclude_tools로 웹 접근을 완전히 차단한 에어갭 패턴을 설계할 수 있다- 에어갭 패턴이 필요한 비즈니스 시나리오(보안 규정, GDPR, 내부 전용)를 설명할 수 있다
- Ops 특화 패턴의 설계 원칙(짧은 루프, 명확한 기준, 빠른 판단)을 적용할 수 있다
- 정책 검토 게이트를 포함한 내부 처리 파이프라인을 구현할 수 있다
HITL_GATING_VIOLATION과 에어갭 설정의 올바른 결합을 이해할 수 있다
사전 준비
05_web_research.md완료 (HITL 게이팅 이해)06_human_gate.md완료 (인간 검토 게이트 이해)
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 문서를 업데이트하는 에어갭 패턴을 만들어보세요.
요구사항
retrieve_existing: 기존 FAQ 파일 읽기 (file.read만 허용, 웹 차단)draft_update: 새로운 FAQ 항목 초안 작성 (웹 차단)diff_review: 변경 사항을 파일로 저장 + 인간 검토 (file.write HITL, max_loops: 1)evaluate: 품질 평가 (judge, route_values: [finalize, revise])- finalize:
updated_faq.md
힌트
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: execute를 mode: 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 사이클을 다룹니다.
- 다음 튜토리얼: 09_advanced_patterns.md — 이중 Judge, 복합 토폴로지, 패턴 배포
- 전체 레퍼런스: 10_reference.md — 모든 필드, 에러 코드, 체크리스트