Home > EulerAgent > Tutorials > Pattern > Pattern 08. Airgap Patterns and Ops Design — Web Blocking +...

Pattern 08. Airgap Patterns and Ops Design — Web Blocking + Policy Compliance

Learning Objectives

After completing this tutorial, you will be able to:

Prerequisites

euleragent agent list
euleragent pattern list

1. What Is an Airgap Pattern?

Airgap is originally a network security term referring to a system completely isolated from the external internet. In patterns, an airgap refers to a pattern that blocks all external network access.

Why Airgap Is Needed

Compliance: - GDPR: When processing EU customer data, the data must not leave the EU - HIPAA: Patient medical information must not be transmitted to external services - SOX: External queries are prohibited when processing financial data

Security Policies: - Tasks involving internal source code or trade secrets - Processing classified information - Internal vulnerability reports (dangerous if exposed externally)

Internal-Only Data: - Internal FAQ updates - Internal onboarding documents - Company policy document drafts

Airgap vs Regular Patterns

Aspect Regular Pattern Airgap Pattern
web.search Available (HITL) Completely blocked
web.fetch Available (HITL) Completely blocked
git.push Available after HITL approval HITL if needed
LLM internal knowledge Available Available
Internal file reading Available Available

2. Implementing Airgap with exclude_tools

Apply exclude_tools to all nodes.

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]

If a tool listed in exclude_tools is invoked, the runtime automatically blocks it.


3. Characteristics of Ops Patterns

Operations patterns have distinct characteristics compared to typical research/writing patterns.

Fast response time: Operational situations require short wait times. Avoid long loops.

Clear decision criteria: Reduce ambiguity and produce predictable results.

Short max_iterations: Typically 1-2 iterations. More than 3 is unsuitable for operational scenarios.

Structured output: Use standard formats such as tickets, reports, and alerts.

Escalation path: There must be a path to quickly hand off to a human when automated resolution is not possible.


4. Pattern 1: Basic Airgap Pattern

A pattern that processes customer support tickets using only internal data.

Node Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ 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 File: 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. Pattern 2: Airgap with Policy Review Gate

An advanced airgap pattern that loads policy documents and validates solutions against them.

Node Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ 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 File: 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. Validation

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

Expected output (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. Execution Example

Running the Basic Airgap Pattern

cp ops_internal_triage.yaml .euleragent/patterns/

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

Expected output (paused at draft_resolution):

[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

Expected final output:

  ✓ 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. Airgap Execution Audit Log

Review the execution history of an airgap pattern.

# 감사 로그 확인
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}

Confirm that no web search attempts were made:

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

9. Practice Exercise: Internal FAQ Update Pattern

Create an airgap pattern that updates internal FAQ documents.

Requirements

Hint

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

Validation:

euleragent pattern validate faq_update.yaml

Verify that no HITL_GATING_VIOLATION occurs (i.e., that mode: plan and force_tool are properly combined).

Execution:

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

10. Common Errors and Solutions

Error 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.

Solution: Change mode: execute to mode: plan.

Error 2: Execution Attempted Despite Web Blocking

This is automatically blocked at the runtime level.

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

This is recorded as a warning rather than an error, and the agent continues execution without that tool.

Error 3: Node Found Without Airgap Applied

# 모든 노드의 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']]"

If some nodes are missing exclude_tools, the airgap is not complete.


Next Steps

You have learned about airgap patterns and Ops design. Now we move on to the most complex topology -- two independent Judge cycles.

← Prev Back to List Next →