콘텐츠로 이동

Dual-Mode 실행

에이전트가 계획을 세우면서 동시에 파일을 수정하거나 명령을 실행하면 위험합니다. 탐색 중에 의도치 않은 부작용이 발생하고, 불완전한 계획이 즉시 실행될 수 있습니다.

Dual-Mode 실행은 에이전트의 상태를 명시적으로 두 가지로 구분합니다:

  • Plan Mode: 읽기 전용. 정보를 수집하고 계획을 수립합니다.
  • Normal Mode (Execute Mode): 전체 접근. 실제 변경을 수행합니다.
도구Plan ModeNormal Mode
read_file허용허용
search_code허용허용
list_directory허용허용
write_file차단허용
run_command차단허용
delete_file차단허용
call_api차단허용

핵심은 “하지 말라”는 지시가 아니라 스키마 수준에서 물리적으로 불가능하게 만드는 것입니다.

from enum import Enum
from typing import Callable
class AgentMode(Enum):
PLAN = "plan"
EXECUTE = "execute"
# 도구에 모드 메타데이터 부착
TOOL_REGISTRY = {
"read_file": {"fn": read_file, "modes": [AgentMode.PLAN, AgentMode.EXECUTE]},
"search_code": {"fn": search_code, "modes": [AgentMode.PLAN, AgentMode.EXECUTE]},
"list_directory": {"fn": list_directory, "modes": [AgentMode.PLAN, AgentMode.EXECUTE]},
"write_file": {"fn": write_file, "modes": [AgentMode.EXECUTE]},
"run_command": {"fn": run_command, "modes": [AgentMode.EXECUTE]},
"delete_file": {"fn": delete_file, "modes": [AgentMode.EXECUTE]},
}
def get_available_tools(mode: AgentMode) -> list[dict]:
"""현재 모드에서 사용 가능한 도구만 LLM에 노출"""
return [
{"name": name, "fn": tool["fn"]}
for name, tool in TOOL_REGISTRY.items()
if mode in tool["modes"]
]
def run_agent(goal: str):
# Plan Mode: 읽기 전용 도구만 제공
plan_tools = get_available_tools(AgentMode.PLAN)
plan = llm.complete(f"계획 수립: {goal}", tools=plan_tools)
# Execute Mode: 전체 도구 제공
exec_tools = get_available_tools(AgentMode.EXECUTE)
result = llm.complete(f"실행: {plan}", tools=exec_tools)
return result

OpenDev 논문은 에이전트의 각 반복(iteration)이 다음 6단계로 구성됨을 제안합니다:

반복(iteration) N:
1. Pre-check & Compaction
└── 컨텍스트 길이 확인, 오래된 히스토리 압축
2. Optional Thinking Phase
└── 복잡한 태스크의 경우 extended thinking 활성화
3. Optional Self-Critique
└── 이전 단계 출력 검토, Reflection 트리거 여부 결정
4. Action (LLM Call)
└── 실제 LLM 호출, 도구 선택 및 파라미터 결정
5. Tool Execution with Approval
└── 도구 실행, 필요시 인간 승인 요청
6. Post-processing & Termination Decision
└── 결과 처리, 목표 달성 여부 판단

1단계: Pre-check & Compaction

def pre_check(context: Context) -> Context:
if context.token_count > COMPACTION_THRESHOLD:
# 오래된 히스토리를 요약으로 교체
summary = llm.summarize(context.old_messages)
return context.replace_old_with_summary(summary)
return context

5단계: Tool Execution with Approval

모든 도구 실행에 승인 레이어를 추가할 수 있습니다. 특히 파괴적 작업(파일 삭제, 배포 등)은 인간 승인이 권장됩니다.

APPROVAL_REQUIRED = {"delete_file", "deploy", "send_email"}
def execute_tool(tool_name: str, params: dict) -> str:
if tool_name in APPROVAL_REQUIRED:
approved = request_human_approval(tool_name, params)
if not approved:
return f"사용자가 {tool_name} 실행을 거부했습니다"
return TOOL_REGISTRY[tool_name]["fn"](**params)

6단계: Termination Decision

def should_terminate(state: AgentState) -> bool:
return (
state.goal_achieved
or state.step_count >= MAX_STEPS
or state.consecutive_failures >= MAX_FAILURES
or state.explicitly_stopped
)

Claude Code, Cursor 등 실제 코딩 에이전트 도구들은 이미 이 패턴을 채택하고 있습니다.

도구Plan Mode 명칭특징
Claude CodePlan Mode/plan 명령으로 활성화, 읽기만 가능
CursorAgent Preview변경 사항을 미리 보여주고 확인 요청
GitHub Copilot WorkspacePlanning Canvas계획을 시각화하고 편집 후 실행

Dual-Mode 실행은 계획과 실행을 명확히 분리하여 안전성과 예측 가능성을 높입니다. 스키마 수준에서 도구 접근을 제한하면 런타임 오류 없이 권한을 강제할 수 있습니다. OpenDev의 6단계 구조는 각 반복을 체계화하여 에이전트 루프의 신뢰성을 높입니다. 다음 챕터에서는 여러 에이전트를 조율하는 Multi-Agent 오케스트레이션을 다룹니다.