03. HITL 승인 워크플로우 — 제안 검토, 수정, 실행
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다:
- HITL(Human-In-The-Loop) 승인의 목적과 구조를 이해한다
- 승인 레코드의 JSON 구조를 분석한다
- 개별 승인을 수락·수정·거부할 수 있다
- 도구 파라미터를 수정하여 더 안전한 실행을 할 수 있다
- 일괄 승인 시 도구 필터와 런 ID 필터를 활용한다
- 위험 수준(low/medium/high)에 따른 처리 방침을 적용한다
- MCP 소스 활성화 승인(
kind: source_enable)을 이해하고 소스를 오버라이드한다 - 승인 감사 추적(audit trail)을 읽는다
사전 준비
- 워크스페이스 초기화 완료 (
euleragent init) - 에이전트 하나 이상 생성되어 있을 것:
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-final로is_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" --executeEULERAGENT_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.yaml의 mcp.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-set과workspace.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.write나 shell.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 — 태스크 파일, 변수 치환, 배치 리서치를 학습합니다.