> EulerAgent > 튜토리얼 > 기본 > 03. HITL 승인 워크플로우 — 제안 검토, 수정,...

03. HITL 승인 워크플로우 — 제안 검토, 수정, 실행


학습 목표

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


사전 준비

euleragent new my-assistant --template personal-assistant
euleragent new code-agent --template code-assistant

HITL 승인이란?

HITL(Human-In-The-Loop)는 에이전트가 위험한 작업을 수행하기 전에 사람의 검토와 승인을 받는 메커니즘입니다.

euleragent는 deny-all 원칙에 따라 다음 도구들은 항상 사람의 승인이 필요합니다:

도구 위험 수준 이유
file.write medium 파일 시스템 수정
file.delete high 데이터 영구 삭제
shell.exec high 임의 코드 실행
web.search medium 외부 네트워크 통신
web.fetch medium 외부 URL 접근
git.commit medium 코드 베이스 변경
git.push high 원격 저장소 변경
email.send high 외부 이메일 발송
payment.charge high 금전 트랜잭션
llm.external_call high 외부 LLM API 비용

이 외에도 euleragent는 도구 호출이 아닌 시스템 수준의 승인도 지원합니다:

승인 종류 (kind) 위험 수준 이유
source_enable medium MCP 소스 활성화 — 외부 검색 서비스 연결
llm_profile_enable high 외부 LLM 프로필 활성화 — --llm-plan / --llm-final로 외부 LLM 사용 시

참고: kind: source_enable 승인은 SearchRouter가 새로운 MCP 소스를 활성화하려 할 때 자동으로 생성됩니다. kind: llm_profile_enable 승인은 --llm-plan / --llm-finalis_external: true 프로필을 지정했을 때 Runner가 자동 생성합니다. 승인 없이도 런은 계속 실행되며 (로컬 기본 제공자로 폴백), 승인 후 다음 런부터 외부 프로필이 적용됩니다. MCP 소스 승인의 상세 실습은 08_mcp_provider_and_tools.md, 외부 LLM 프로필 승인의 전체 사이클은 09_scoped_llm_profile.md를 참조하세요.


단계별 실습

Step 1: 승인 대기 목록 생성

승인 실습을 위해 여러 도구 호출을 포함하는 태스크를 실행합니다:

euleragent run my-assistant \
  --task "report.md 파일을 만들고, AI 에이전트 프레임워크를 웹에서 검색한 후, 결과를 파일에 저장해줘" \
  --mode plan \
  --max-loops 3

예상 출력:

Run a1b2c3d4e5f6 started (agent: my-assistant, mode: plan)

[loop 1/3] Generating plan...
  → Proposed: web.search (risk: medium)
      query: "AI agent framework 2025 comparison"

[loop 2/3] Continuing...
  → Proposed: file.write (risk: medium)
      path: report.md

Run a1b2c3d4e5f6 completed (state: PENDING_APPROVAL)

2 approval(s) pending.
  Use: euleragent approve list --run-id a1b2c3d4e5f6

Step 2: 승인 목록 확인

euleragent approve list

예상 출력:

Pending approvals (2):

  ID              TOOL         RISK     RUN              STATUS    CREATED
  apv_w1x2y3      web.search   medium   a1b2c3d4e5f6     pending   10:30:00
  apv_p4q5r6      file.write   medium   a1b2c3d4e5f6     pending   10:30:02

특정 런의 승인만 보려면:

euleragent approve list --run-id a1b2c3d4e5f6

특정 도구의 승인만 보려면:

euleragent approve list --tool web.search

Step 3: 승인 레코드 상세 확인

승인 레코드의 모든 필드를 확인합니다:

euleragent approve show apv_w1x2y3

예상 출력:

{
  "id": "apv_w1x2y3",
  "run_id": "a1b2c3d4e5f6",
  "tool_name": "web.search",
  "params": {
    "query": "AI agent framework 2025 comparison",
    "top_k": 5
  },
  "risk_level": "medium",
  "side_effects": ["external_network"],
  "status": "pending",
  "created_at": "2026-02-23T10:30:00Z",
  "agent": "my-assistant",
  "loop": 1,
  "context": "Task: report.md 파일을 만들고 AI 에이전트 프레임워크를 웹에서 검색..."
}

각 필드의 의미:

필드 설명
id 승인 레코드 고유 식별자
run_id 이 승인을 생성한 실행 ID
tool_name 실행하려는 도구 이름
params 도구에 전달될 파라미터
risk_level 위험 수준 (low/medium/high)
side_effects 예상되는 부작용 목록
status 현재 상태 (pending/accepted/rejected/executed)
created_at 승인 레코드 생성 시각
agent 이 승인을 생성한 에이전트
loop 에이전틱 루프 번호
context 이 승인이 생성된 컨텍스트

Step 4: 개별 승인 수락

내용을 확인한 후 승인합니다:

euleragent approve accept apv_w1x2y3 --actor "user:you"

예상 출력:

Accepted: apv_w1x2y3 (web.search)
  Status: accepted (not yet executed)
  Use --execute flag to execute immediately.

--execute 없이 수락하면 상태가 accepted로 변경되지만 실제 실행은 아직 되지 않습니다. 나중에 일괄 실행하거나 명시적으로 실행할 수 있습니다.

즉시 실행하려면:

euleragent approve accept apv_w1x2y3 --actor "user:you" --execute

예상 출력:

Accepted and executed: apv_w1x2y3 (web.search)
  Result: 5 search results retrieved
  Stored in: .euleragent/runs/a1b2c3d4e5f6/tool_calls.jsonl

필수: --actor 플래그 또는 EULERAGENT_ACTOR 환경변수로 승인자 정보를 제공해야 합니다 (deny-all-default 정책): bash euleragent approve accept apv_w1x2y3 --actor "user:alice" --execute EULERAGENT_ACTOR 환경변수를 설정하면 매번 --actor를 입력하지 않아도 됩니다.


Step 5: 파라미터 수정 후 승인

승인 전에 도구 파라미터를 수정할 수 있습니다. 예를 들어 검색 쿼리가 너무 광범위하다면 더 구체적으로 변경합니다:

euleragent approve accept apv_w1x2y3 \
  --actor "user:you" \
  --edit-params '{"query": "open source AI agent framework Python 2025", "top_k": 3}'

예상 출력:

Accepted with modified params: apv_w1x2y3 (web.search)
  Original query: "AI agent framework 2025 comparison"
  Modified query: "open source AI agent framework Python 2025"
  top_k: 5 → 3

파라미터 수정 후 즉시 실행:

euleragent approve accept apv_w1x2y3 \
  --actor "user:you" \
  --edit-params '{"query": "open source AI agent framework Python 2025", "top_k": 3}' \
  --execute

--edit-params를 사용하는 경우: - 검색 쿼리에 민감한 정보가 포함된 경우 - 파일 저장 경로를 안전한 위치로 변경할 때 - 파라미터가 너무 광범위해서 범위를 제한할 때 - LLM이 생성한 파라미터를 더 정확하게 조정할 때


Step 6: 승인 거부

위험하거나 부적절한 도구 호출은 거부할 수 있습니다:

euleragent approve reject apv_p4q5r6 --actor "user:you" --reason "파일 경로가 안전하지 않습니다. /tmp/ 아래에 저장해야 합니다."

예상 출력:

Rejected: apv_p4q5r6 (file.write)
  Reason: 파일 경로가 안전하지 않습니다. /tmp/ 아래에 저장해야 합니다.
  Status: rejected

거부 이유는 감사 로그에 기록되어 나중에 추적할 수 있습니다. 거부된 승인은 실행되지 않으며, 에이전트는 이 사실을 다음 실행에서 확인할 수 있습니다.


Step 7: 일괄 수락 — 런 ID 필터

같은 실행(run)의 모든 승인을 한 번에 수락합니다:

euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you"

예상 출력:

Accepted 2 approval(s) for run a1b2c3d4e5f6.
  [accepted] web.search (apv_w1x2y3)
  [accepted] file.write (apv_p4q5r6)
  Status: accepted (not yet executed)

즉시 실행 포함:

euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you" --execute

# actor 정보 기록과 함께 일괄 수락
euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "ops:batch-review" --execute

예상 출력:

Accepted and executed 2 approval(s) for run a1b2c3d4e5f6.
  [OK] web.search (apv_w1x2y3) — 5 results retrieved
  [OK] file.write (apv_p4q5r6) — report.md created (1.2KB)
Executed 2/2 successfully.

Run a1b2c3d4e5f6 state: RUN_FINALIZED

Step 8: 일괄 수락 — 도구 필터

특정 도구 유형의 승인만 일괄 처리합니다:

# web.search 승인만 수락
euleragent approve accept-all --tool web.search --actor "user:you" --execute
# file.write 승인만 수락 (모든 런에서)
euleragent approve accept-all --tool file.write --actor "user:you"
# 특정 런의 web.search 승인만 수락
euleragent approve accept-all --run-id a1b2c3d4e5f6 --tool web.search --actor "user:you" --execute

예상 출력:

Accepted 1 approval(s) matching tool=web.search.
  [OK] web.search (apv_w1x2y3) — executed
1 approval(s) remain pending (different tool type).

Step 9: 일괄 거부

위험한 실행의 모든 승인을 거부합니다:

euleragent approve reject-all --run-id a1b2c3d4e5f6 --actor "user:you" --reason "보안 검토가 필요합니다"

예상 출력:

Rejected 2 approval(s) for run a1b2c3d4e5f6.
  [rejected] web.search (apv_w1x2y3)
  [rejected] file.write (apv_p4q5r6)
  Reason: 보안 검토가 필요합니다

Step 10: 위험 수준별 처리 방침

위험 수준에 따라 검토 강도를 조정합니다.

Low 위험 도구

file.read, memory.search, git.status 등은 기본적으로 require_approval에 포함되지 않아 자동 실행됩니다. 별도의 승인이 필요 없습니다.

Medium 위험 도구

file.write, web.search, git.commit 등은 require_approval에 포함되어 승인이 필요합니다. 대부분의 경우 내용을 확인한 후 수락하면 됩니다.

권장 검토 항목: - file.write: 저장 경로가 워크스페이스 내부인지 확인 - web.search: 쿼리에 민감한 정보가 없는지 확인 - git.commit: 커밋 메시지와 변경 파일이 의도한 것인지 확인

High 위험 도구

shell.exec, file.delete, email.send, payment.charge, git.push 등은 항상 신중하게 검토해야 합니다.

# shell.exec 승인 상세 확인 예시
euleragent approve show apv_s1h2e3
{
  "id": "apv_s1h2e3",
  "tool_name": "shell.exec",
  "params": {
    "command": "rm -rf ./old_data/",
    "working_dir": "/home/user/my-project"
  },
  "risk_level": "high",
  "side_effects": ["filesystem_modification", "irreversible"],
  "status": "pending"
}

High 위험 도구 검토 체크리스트: - [ ] 명령어/작업이 예상한 것인가? - [ ] 작업 범위가 의도한 것으로 제한되어 있는가? - [ ] 되돌릴 수 없는 작업인가? (백업이 있는가?) - [ ] 외부로 전송되는 데이터에 민감한 정보가 없는가?


Step 10-1: MCP 소스 활성화 승인 (kind: source_enable)

에이전트가 web.search를 호출하면 내부의 SearchRouter가 workspace.yamlmcp.sources에서 후보 소스를 평가합니다. 새로운 소스를 처음 활성화할 때는 kind: source_enable 승인이 생성됩니다.

소스 활성화 승인 레코드 확인

euleragent approve show apv_src_t1v2

예상 출력:

{
  "id": "apv_src_t1v2",
  "run_id": "a1b2c3d4e5f6",
  "kind": "source_enable",
  "tool_name": "web.search",
  "params": {
    "query": "AI agent framework 2025 comparison"
  },
  "resolved_source_id": "brave",
  "candidate_sources": ["brave", "tavily", "local_kb"],
  "routing_reason": "brave scored highest for broad web queries (score: 0.91)",
  "risk_level": "medium",
  "side_effects": ["external_network", "api_key_usage"],
  "status": "pending",
  "created_at": "2026-02-23T10:30:00Z",
  "agent": "my-assistant",
  "loop": 1,
  "context": "SearchRouter selected 'brave' from source-set 'default'"
}

기존 도구 승인 레코드와 비교한 추가 필드:

필드 설명
kind 승인 종류 — source_enable은 MCP 소스 활성화 승인
resolved_source_id SearchRouter가 선택한 최종 소스 ID
candidate_sources 평가된 후보 소스 목록
routing_reason SearchRouter가 해당 소스를 선택한 이유

소스 오버라이드 — 다른 소스로 변경

SearchRouter가 선택한 소스가 적절하지 않다면 --edit-params로 다른 소스를 지정할 수 있습니다:

euleragent approve accept apv_src_t1v2 \
  --actor "user:you" \
  --edit-params '{"resolved_source_id": "tavily"}' \
  --execute

예상 출력:

Accepted with modified params: apv_src_t1v2 (source_enable)
  Original source: brave
  Override source: tavily
  Executed: web.search via tavily — 5 results retrieved

이 방식으로 사람이 최종적으로 어떤 외부 서비스를 사용할지 통제할 수 있습니다. candidate_sources에 포함된 소스만 오버라이드 대상으로 지정할 수 있습니다.

팁: 특정 소스를 항상 우선 사용하려면 --source-setworkspace.yaml의 소스 우선순위 설정을 조합하세요. 이렇게 하면 매번 수동으로 오버라이드할 필요가 줄어듭니다.


Step 11: 감사 추적 확인

모든 승인 내역은 감사 로그에 기록됩니다:

euleragent logs a1b2c3d4e5f6

예상 출력:

Run: a1b2c3d4e5f6
  State: RUN_FINALIZED

Approvals:
  apv_w1x2y3  web.search   accepted  2026-02-23 10:30:30  by=user:alice
  apv_p4q5r6  file.write   accepted  2026-02-23 10:30:32  by=user:alice

Tool Executions:
  web.search   apv_w1x2y3  OK   3 results
  file.write   apv_p4q5r6  OK   report.md (1.2KB)

원시 승인 로그 파일:

cat .euleragent/runs/a1b2c3d4e5f6/approvals.jsonl
{"id": "apv_w1x2y3", "tool_name": "web.search", "status": "accepted", "executed": true, "executed_at": "2026-02-23T10:30:30Z"}
{"id": "apv_p4q5r6", "tool_name": "file.write", "status": "accepted", "executed": true, "executed_at": "2026-02-23T10:30:32Z"}

전체 승인 워크플로우 다이어그램

euleragent run <agent> --task "..." --mode plan
            │
            ▼
    [LLM이 도구 호출 제안]
            │
            ├─ 일반 도구 호출 ─────────────────┐
            │                                   │
            ├─ web.search 호출 시 ──┐           │
            │                       ▼           │
            │              [SearchRouter]        │
            │              소스 평가 & 선택      │
            │                       │           │
            │              새 소스 활성화?       │
            │               ├─ 예 ──▶ [kind: source_enable 승인 생성]
            │               └─ 아니오           │
            │                       │           │
            ▼                       ▼           ▼
    [승인 레코드 생성] → .euleragent/approvals/ 저장
    {
      "id": "apv_xxx",
      "kind": "tool_call | source_enable",
      "tool_name": "web.search",
      "params": {...},
      "resolved_source_id": "brave",        ← source_enable 시
      "candidate_sources": ["brave", ...],  ← source_enable 시
      "routing_reason": "...",              ← source_enable 시
      "risk_level": "medium",
      "status": "pending"
    }
            │
            ▼
    [사람이 검토] euleragent approve list / show
            │
     ┌──────┴──────┐
     │             │
  수락           거부
     │             │
  --edit-params  --reason
  (선택)           │
     │             ▼
     ▼        [rejected] → 감사 로그 기록
  --execute
  (선택)
     │
     ▼
  [도구 실행] → tool_calls.jsonl 기록
     │
     ▼
  [결과를 컨텍스트에 추가]
     │
     ▼
  [다음 루프 or 완료]

예상 출력 요약

# 승인 목록
$ euleragent approve list
Pending approvals (2):
  apv_w1x2y3  web.search  medium  a1b2c3d4e5f6  pending
  apv_p4q5r6  file.write  medium  a1b2c3d4e5f6  pending

# 개별 수락 + 실행
$ euleragent approve accept apv_w1x2y3 --actor "user:you" --execute
Accepted and executed: apv_w1x2y3 (web.search)
  Result: 5 search results retrieved

# 파라미터 수정 + 수락
$ euleragent approve accept apv_w1x2y3 --actor "user:you" --edit-params '{"query": "..."}' --execute
Accepted with modified params: apv_w1x2y3 (web.search)

# 거부
$ euleragent approve reject apv_p4q5r6 --actor "user:you" --reason "경로 안전하지 않음"
Rejected: apv_p4q5r6 (file.write)

# 일괄 수락 + 실행
$ euleragent approve accept-all --run-id a1b2c3d4e5f6 --actor "user:you" --execute
Accepted and executed 2/2 successfully.

자주 묻는 질문 / 흔한 오류

Q: 승인 레코드가 JSONL 파일로 저장되는 위치는 어디인가요?

승인 레코드는 두 곳에 저장됩니다: 1. .euleragent/approvals/ — 전체 승인 큐 (런 ID와 무관하게 통합 관리) 2. .euleragent/runs/<run-id>/approvals.jsonl — 특정 런의 승인 기록

# 전체 승인 큐
ls .euleragent/approvals/

# 특정 런의 승인 기록
cat .euleragent/runs/a1b2c3d4e5f6/approvals.jsonl

Q: 승인한 도구가 실행에 실패하면 어떻게 되나요?

실행 실패 시 에러가 tool_calls.jsonl에 기록되고, 승인 상태는 executed_failed로 변경됩니다. 실행은 계속되지만 해당 도구의 결과가 없으므로 에이전트의 다음 출력이 불완전할 수 있습니다.

# 실패한 도구 확인
cat .euleragent/runs/<run-id>/tool_calls.jsonl | grep "status.*failed"

Q: --edit-params에서 JSON을 작성할 때 따옴표 처리가 어떻게 되나요?

셸에서 JSON을 전달할 때 작은따옴표로 전체를 감싸고 내부는 큰따옴표를 사용합니다:

euleragent approve accept apv_xxx \
  --actor "user:you" \
  --edit-params '{"query": "LangChain vs CrewAI 2025", "top_k": 3}'

Windows PowerShell에서는 이스케이프가 다릅니다:

euleragent approve accept apv_xxx `
  --actor "user:you" `
  --edit-params '{\"query\": \"LangChain vs CrewAI 2025\", \"top_k\": 3}'

Q: 실수로 잘못된 파라미터로 승인하고 실행해버렸습니다. 되돌릴 수 있나요?

실행된 도구의 결과는 되돌릴 수 없습니다(특히 file.writeshell.exec). 이것이 Plan 모드와 --edit-params를 권장하는 이유입니다. 파일 시스템 변경의 경우 diffs/ 디렉토리에 변경 내역이 기록됩니다:

ls .euleragent/runs/<run-id>/diffs/
cat .euleragent/runs/<run-id>/diffs/report.md.diff

Q: euleragent approve accept-all을 실행했는데 일부 승인이 처리되지 않았습니다.

--run-id--tool 필터가 적용되어 해당 조건에 맞는 승인만 처리됐을 수 있습니다. 또는 이미 다른 상태(accepted/rejected)인 승인은 처리 대상에서 제외됩니다.

# 모든 pending 승인 확인 (필터 없음)
euleragent approve list

# 특정 런의 모든 pending 승인 확인
euleragent approve list --run-id a1b2c3d4e5f6

Q: 고위험 도구를 특정 에이전트에서 완전히 차단하려면?

에이전트의 tools.yaml에서 denylist에 추가합니다:

# .euleragent/agents/my-assistant/tools.yaml
policy: deny-all
denylist:
  - shell.exec
  - file.delete
  - email.send
  - payment.charge

또는 agent.yaml에서:

# .euleragent/agents/my-assistant/agent.yaml
tools_denylist:
  - shell.exec
  - file.delete

Q: --actor를 매번 입력하기 번거롭습니다.

EULERAGENT_ACTOR 환경변수를 설정하면 --actor 플래그 없이도 자동으로 actor 정보가 기록됩니다:

# 쉘 프로필에 추가 (~/.bashrc, ~/.zshrc 등)
export EULERAGENT_ACTOR="user:$(whoami)"

# 또는 CI/CD 파이프라인에서
export EULERAGENT_ACTOR="ci:${CI_PIPELINE_ID}"

--actor 플래그가 명시적으로 제공되면 환경변수보다 우선합니다.


자주 하는 실수 (순서 어긋남)

증상 원인 복구
Error: Approval 'X' not found. 잘못된 approval ID euleragent approve list
Error: Approval 'X' is already accepted. 이미 수락된 승인 재수락 시도 euleragent approve list --run-id <id>
Error: Actor identity is required --actor 누락 --actor "user:name" 또는 export EULERAGENT_ACTOR="..."
Next: euleragent workflow resume <id> accept-all 후 자동 힌트 힌트에 표시된 명령어 실행

다음 단계: 04_task_file_and_batch.md — 태스크 파일, 변수 치환, 배치 리서치를 학습합니다.

← 이전 목록으로 다음 →