02. Plan 모드 vs Execute 모드 — 언제 무엇을 쓸까
학습 목표
이 튜토리얼을 마치면 다음을 할 수 있습니다:
- Plan 모드와 Execute 모드의 내부 동작 방식을 설명한다
- 동일한 태스크를 두 모드로 실행하고 결과를 비교한다
- 에이전틱 루프(agentic loop)가 무엇인지 이해하고
--max-loops로 제어한다 --min-proposals로 최소 도구 제안 수를 강제한다--artifact-name으로 출력 파일명을 커스터마이징한다--source-set으로 검색 소스 그룹을 지정한다--llm-plan/--llm-final으로 스코프별 LLM 프로필을 선택한다- 실제 시나리오에서 어떤 모드를 선택할지 판단한다
사전 준비
- 워크스페이스 초기화 완료 (
euleragent init) code-agent에이전트 생성:
euleragent new code-agent --template code-assistant
Plan 모드와 Execute 모드란?
euleragent의 실행 모드는 사람의 개입 시점에 따라 두 가지로 나뉩니다.
Plan 모드 (기본값)
Plan 모드에서는 에이전트가 어떤 행동을 취할지 제안하고 실제 실행 전에 멈춥니다.
[사용자] ──태스크──▶ [LLM] ──제안──▶ [승인 큐]
↓
[사람이 검토]
↓
[승인 후 실행]
이 방식은: - 위험한 작업(파일 쓰기, 웹 검색, 셸 실행)을 실행 전에 검토할 수 있습니다 - 에이전트의 의도를 파악하고 파라미터를 수정할 수 있습니다 - 실수를 미연에 방지할 수 있습니다
Execute 모드
Execute 모드에서는 require_approval 목록에 없는 도구는 즉시 실행하고, 목록에 있는 도구는 승인을 큐에 넣습니다.
[사용자] ──태스크──▶ [LLM] ──제안──▶ [즉시 실행] (require_approval 아닌 경우)
└──제안──▶ [승인 큐] (require_approval 경우)
이 방식은: - 신뢰할 수 있는 저위험 태스크를 빠르게 처리할 수 있습니다 - 읽기 전용 작업이나 이미 검증된 파이프라인에 적합합니다
단계별 실습
Step 1: 같은 태스크를 두 모드로 실행
Plan 모드로 실행
euleragent run code-agent \
--task "fibonacci.py 파일을 만들고 피보나치 수열을 반환하는 Python 함수를 작성해줘" \
--mode plan
예상 출력:
Run c3d4e5f6a1b2 started (agent: code-agent, mode: plan)
[loop 1/5] Generating plan...
→ Proposed: file.write (risk: medium)
path: fibonacci.py
content: "def fibonacci(n): ..."
Run c3d4e5f6a1b2 completed (state: PENDING_APPROVAL)
Artifacts:
.euleragent/runs/c3d4e5f6a1b2/artifacts/plan.md
1 approval(s) pending.
Use: euleragent approve list --run-id c3d4e5f6a1b2
에이전트가 file.write를 제안했지만 아직 파일은 생성되지 않았습니다. fibonacci.py는 존재하지 않습니다:
ls fibonacci.py # No such file or directory
Execute 모드로 실행
이번에는 --mode execute로 실행합니다. 단, file.write는 policy.py의 HIGH_RISK_TOOLS 집합에 속하므로 여전히 승인이 필요합니다. HIGH_RISK_TOOLS에 속하지 않는 file.read 같은 도구는 즉시 실행됩니다.
euleragent run code-agent \
--task "fibonacci.py 파일을 만들고 피보나치 수열을 반환하는 Python 함수를 작성해줘" \
--mode execute
예상 출력:
Run d4e5f6a1b2c3 started (agent: code-agent, mode: execute)
[loop 1/5] Generating plan...
→ Proposed: file.write (risk: medium) — queued for approval
Run d4e5f6a1b2c3 completed (state: PENDING_APPROVAL)
1 approval(s) pending.
왜 execute 모드도 승인이 필요한가요?
file.write는policy.py의HIGH_RISK_TOOLS집합에 속하기 때문입니다.resolve_tool_permission()은 도구가allowlist에 있고HIGH_RISK_TOOLS에도 속하면"require_approval"을 반환합니다. Execute 모드는 "승인이 필요 없는 저위험 도구만 즉시 실행"하는 것이지, 모든 도구를 자동 실행하는 것이 아닙니다.
두 모드의 핵심 차이
| 항목 | Plan 모드 | Execute 모드 |
|---|---|---|
| 도구 제안 | 제안 후 중단 | 즉시 실행 (가능한 경우) |
require_approval 도구 |
승인 큐에 추가 후 중단 | 승인 큐에 추가 후 계속 진행 |
| 아티팩트 | plan.md (제안 내용) |
result.md (실행 결과) |
| 실행 속도 | 항상 승인 대기 | 저위험 도구는 즉시 처리 |
| 권장 사용 | 위험한 작업, 초기 개발 | 신뢰된 파이프라인, 읽기 전용 |
Step 2: 에이전틱 루프와 --max-loops
에이전틱 루프는 LLM이 태스크를 완료할 때까지 반복적으로 도구를 사용하는 과정입니다:
루프 시작
↓
LLM이 다음 행동 결정 (도구 호출 or 텍스트 응답)
↓
도구 호출이 있으면 → 승인 큐 추가 or 즉시 실행
↓
결과를 컨텍스트에 추가
↓
태스크 완료? → 아니면 루프 반복
기본 최대 루프 수는 5회입니다. 복잡한 태스크나 단순한 태스크에 맞게 조정할 수 있습니다:
# 단순 태스크: 루프 1회로 제한
euleragent run code-agent \
--task "hello_world.py 파일에 Hello, World! 출력 코드를 작성해줘" \
--mode plan \
--max-loops 1
# 복잡한 태스크: 루프 10회 허용
euleragent run code-agent \
--task "프로젝트 전체 코드를 분석하고 개선점을 찾아 리포트를 작성해줘" \
--mode plan \
--max-loops 10
--max-loops를 사용해야 하는 경우:
- 줄이기: 에이전트가 너무 많은 도구 호출을 시도하거나 무한 루프에 빠질 위험이 있을 때
- 늘리기: 여러 단계가 필요한 복잡한 리서치나 분석 태스크
Step 3: --min-proposals로 배치 수집 강제
--min-proposals N은 에이전트가 최소 N개의 도구 호출을 제안하기 전에는 종료하지 않도록 강제합니다. 배치 정보 수집이 필요할 때 유용합니다.
euleragent run code-agent \
--task "프로젝트의 Python 파일들을 읽고 각 파일의 주요 기능을 요약해줘" \
--mode plan \
--min-proposals 3 \
--max-loops 5
예상 출력:
Run e5f6a1b2c3d4 started (agent: code-agent, mode: plan)
[loop 1/5] Generating plan...
→ Proposed: file.read (path: main.py)
[loop 2/5] Continuing... (proposals so far: 1, min required: 3)
→ Proposed: file.read (path: utils.py)
[loop 3/5] Continuing... (proposals so far: 2, min required: 3)
→ Proposed: file.read (path: config.py)
[loop 4/5] Continuing... (proposals so far: 3, min required: 3)
→ Min proposals reached. Generating summary...
Run e5f6a1b2c3d4 completed (state: PENDING_APPROVAL)
3 approval(s) pending.
팁:
--min-proposals는--mode plan과 함께 쓸 때 가장 효과적입니다. 에이전트가 충분한 정보를 수집하도록 강제한 후, 사람이 모든 제안을 한 번에 검토하고 승인할 수 있습니다.
Step 4: --artifact-name으로 출력 파일명 지정
기본적으로 아티팩트 파일명은 모드에 따라 plan.md 또는 result.md입니다. --artifact-name으로 커스터마이징할 수 있습니다:
euleragent run code-agent \
--task "fibonacci.py 파일을 만들고 피보나치 함수를 구현해줘" \
--mode execute \
--artifact-name fibonacci_report.md
예상 출력:
Run f6a1b2c3d4e5 completed (state: RUN_FINALIZED)
Artifacts:
.euleragent/runs/f6a1b2c3d4e5/artifacts/fibonacci_report.md
--artifact-name은 다음 상황에서 유용합니다:
- 여러 실행의 결과를 의미 있는 이름으로 구분할 때
- 자동화 파이프라인에서 예측 가능한 파일명이 필요할 때
- CI/CD 시스템에서 아티팩트를 수집할 때
Step 4-1: --source-set으로 검색 소스 그룹 지정
--source-set은 web.search 도구가 사용할 검색 소스 그룹을 지정합니다. workspace.yaml의 mcp.sources에 정의된 소스들을 그룹으로 묶어 태스크별로 다른 검색 전략을 적용할 수 있습니다:
euleragent run code-agent \
--task "최신 Python 패키지 동향을 조사해줘" \
--mode plan \
--source-set research
--source-set을 지정하면 내부적으로 SearchRouter가 해당 그룹의 후보 소스(예: tavily, brave, local_kb) 중 최적의 소스를 선택하여 web.search 호출을 라우팅합니다. 소스를 지정하지 않으면 기본 소스 그룹이 사용됩니다.
참고:
web.search는 내부적으로 SearchRouter를 통해 라우팅됩니다. 사용자가 직접 호출하는 인터페이스는 동일하지만, 뒤에서는mcp.sources에 정의된 후보 소스들이 평가되어 최적의 소스가 선택됩니다. 소스 활성화에는 HITL 승인이 필요할 수 있습니다 — 자세한 내용은 03_hitl_approval.md를 참조하세요.
Step 4-2: --llm-plan / --llm-final으로 스코프별 LLM 프로필 지정
태스크의 계획 단계와 최종 결과물 생성 단계에서 서로 다른 LLM을 사용할 수 있습니다:
# 계획은 로컬 LLM으로, 최종 결과물은 외부 LLM으로
euleragent run code-agent \
--task "Python 유틸리티 설계 및 작성" \
--mode execute \
--llm-plan ollama_local \
--llm-final openai_main
workspace.yaml에 is_external: true로 표시된 프로필은 HITL 승인이 필요합니다:
llm_profiles:
ollama_local:
provider: ollama
base_url: http://localhost:11434
model: qwen3:32b
is_external: false # 로컬 → 승인 불필요
openai_main:
provider: openai
api_key: ${OPENAI_API_KEY}
model: gpt-4o-mini
base_url: https://api.openai.com/v1
is_external: true # 외부 → 승인 필요
default_llm_profile: ollama_local
폴백 동작: 외부 프로필이 아직 승인되지 않았다면 로컬 기본 제공자로 폴백하며, 런은 중단되지 않습니다. kind: llm_profile_enable 승인 레코드가 생성되며, 승인 후 다음 런부터 외부 프로필이 적용됩니다. 폴백→승인→재실행 전체 사이클의 상세 실습은 09_scoped_llm_profile.md를 참조하세요.
# 승인 대기 목록 확인
euleragent approve list --tool llm.external_call
# 개별 승인
euleragent approve accept <approval-id> --actor "user:you"
# 일괄 승인
euleragent approve accept-all --actor "user:you" --tool llm.external_call
Step 5: 전체 시나리오 — 코드 어시스턴트 실전 활용
실제 업무와 유사한 시나리오로 두 모드를 모두 경험합니다.
시나리오: Python 유틸리티 스크립트 작성
Step 5-1: Plan 모드로 초안 검토
euleragent run code-agent \
--task "data_processor.py 파일을 만들어줘. CSV 파일을 읽어서 각 행의 합계를 계산하고 결과를 output.csv에 저장하는 Python 스크립트야. pandas 사용하고 타입 힌트 포함해줘." \
--mode plan \
--max-loops 2
예상 출력:
Run a1b2c3d4e5f6 started (agent: code-agent, mode: plan)
[loop 1/2] Generating plan...
→ Proposed: file.write (risk: medium)
path: data_processor.py
content: [python code with pandas...]
Run a1b2c3d4e5f6 completed (state: PENDING_APPROVAL)
1 approval(s) pending.
제안된 코드를 먼저 확인합니다:
euleragent approve show apv_p1q2r3
{
"id": "apv_p1q2r3",
"tool_name": "file.write",
"params": {
"path": "data_processor.py",
"content": "import pandas as pd\nfrom pathlib import Path\nfrom typing import Optional\n\ndef process_csv(input_path: str, output_path: str = 'output.csv') -> None:\n ..."
},
"risk_level": "medium",
"status": "pending"
}
코드가 마음에 들면 승인하고 실행합니다:
euleragent approve accept apv_p1q2r3 --actor "user:you" --execute
Step 5-2: Execute 모드로 후속 작업
파일이 생성된 후, 간단한 테스트 파일 생성은 execute 모드로 빠르게 처리합니다:
euleragent run code-agent \
--task "test_data.csv 샘플 파일을 만들어줘. 이름, 점수1, 점수2 컬럼에 5개 행을 포함해줘." \
--mode execute \
--artifact-name test_setup_report.md
test_data.csv 생성도 file.write이므로 승인이 필요하지만, execute 모드는 루프를 멈추지 않고 다음 도구 호출로 계속 진행하므로 더 빠르게 완료됩니다.
언제 어떤 모드를 써야 하나?
| 상황 | 권장 모드 | 이유 |
|---|---|---|
| 처음 보는 태스크, 결과가 불확실 | Plan | 에이전트 의도를 먼저 확인 |
| 프로덕션 파일 수정 | Plan | 실수 시 복구 어려움 |
| 외부 서비스 호출 (웹, 이메일) | Plan | 의도치 않은 외부 전송 방지 |
| 읽기 전용 분석 | Execute | 즉각 처리 가능 |
| 이미 검증된 파이프라인 | Execute | 속도 우선 |
| 초기 개발/프로토타이핑 | Plan | 에이전트 동작 학습 |
| 배치 정보 수집 | Plan + --min-proposals |
충분한 데이터 수집 보장 |
| CI/CD 자동화 | Execute + 사전 허용 목록 관리 | 자동화 요구 |
하이브리드 접근법
실제 운영에서는 두 모드를 조합하는 것이 가장 안전합니다:
- 리서치 단계:
--mode plan --min-proposals 5— 충분한 정보 수집 제안을 생성 - 검토 단계:
euleragent approve list→euleragent approve show— 각 제안 검토 - 실행 단계:
euleragent approve accept-all --actor "user:you" --execute— 승인 후 일괄 실행 - 결과 확인:
euleragent logs <run-id>— 실행 내역 감사
예상 출력 요약
# Plan 모드 — 파일 쓰기 제안 있는 경우
Run a1b2c3... completed (state: PENDING_APPROVAL)
1 approval(s) pending.
# Execute 모드 — 즉시 실행 가능한 도구 있는 경우
Run b2c3d4... completed (state: RUN_FINALIZED)
[executed] file.read — OK
[queued] file.write — pending approval
# max-loops 초과 시
Run c3d4e5... completed (state: RUN_FINALIZED)
[warn] Max loops (3) reached. Task may be incomplete.
# min-proposals 충족 시
Run d4e5f6... completed (state: PENDING_APPROVAL)
[info] min-proposals (3) satisfied after loop 3.
3 approval(s) pending.
자주 묻는 질문 / 흔한 오류
Q: execute 모드인데 왜 여전히 승인을 요청하나요?
file.write, web.search, shell.exec 등 policy.py의 HIGH_RISK_TOOLS 집합에 속하는 도구는 execute 모드에서도 반드시 승인이 필요합니다. Execute 모드는 고위험이 아닌 도구(예: file.read, git.diff)를 즉시 실행하는 것입니다. 승인 판단은 resolve_tool_permission()의 반환값 기준입니다: "allow" → 즉시 실행, "require_approval" → 승인 필요, "deny" → 차단.
Q: --max-loops를 초과하면 어떻게 되나요?
에이전트가 태스크를 완료하지 못한 상태로 종료됩니다. 아티팩트는 생성되지만 불완전할 수 있습니다. 경고 메시지가 출력됩니다. 더 많은 루프가 필요하면 --max-loops를 늘리세요.
# 루프 부족 시 증상 확인
euleragent logs <run-id>
# [warn] Max loops (5) reached. Task may be incomplete.
Q: --min-proposals를 설정했는데 에이전트가 도구를 충분히 제안하지 않으면 어떻게 되나요?
--max-loops에 도달하면 에이전트가 종료됩니다. 이 경우 --max-loops를 늘리거나 태스크 설명을 더 구체적으로 작성하세요. 예를 들어 "5개의 파일을 각각 읽어서 분석해줘"처럼 도구 호출 횟수를 명시하면 효과적입니다.
Q: plan.md와 result.md의 차이는 무엇인가요?
plan.md: Plan 모드 실행 결과. 에이전트가 제안한 행동과 그에 대한 설명이 포함됩니다. 실제 실행 결과가 아닙니다.result.md: Execute 모드 실행 결과. 실제로 수행된 작업과 그 결과가 포함됩니다.--artifact-name을 지정하면 모드에 관계없이 지정한 이름으로 저장됩니다.
Q: --mode를 지정하지 않으면 기본값이 무엇인가요?
기본값은 plan입니다. euleragent는 안전 우선 원칙에 따라 기본적으로 실행 전 사람의 검토를 요구합니다.
자주 하는 실수 (순서 어긋남)
| 증상 | 원인 | 복구 |
|---|---|---|
Error: No task provided. |
--resume-run만 지정, --task 누락 |
자동으로 이전 run의 task 로드됨 |
Info: --max-loops auto-raised from 2 to 7 |
--min-proposals가 --max-loops보다 큼 |
정상 — 자동 조정 안내 |
| Plan 결과가 placeholder 텍스트 | FakeProvider 사용 중 | workspace.yaml의 default_llm_profile을 실제 LLM으로 변경 |
다음 단계: 03_hitl_approval.md — HITL 승인 워크플로우를 심층적으로 학습합니다.