콘텐츠로 이동

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 등 실제 코딩 에이전트 도구들은 이미 이 패턴을 채택하고 있습니다.

계획 모드의 명칭과 활성화 방법은 제품마다 다릅니다. 현재 제품 문서에서 모드별 도구·파일·네트워크 권한을 확인하고, UI의 “계획” 표시만으로 읽기 전용이라고 판단하지 않습니다. 읽기 도구도 민감 파일 접근이나 외부 조회 비용을 발생시킬 수 있습니다.

Dual-Mode 실행은 계획과 실행을 명확히 분리하여 안전성과 예측 가능성을 높입니다. 스키마 필터링과 런타임 권한 검사를 함께 사용해야 합니다. OpenDev의 6단계 구조는 각 반복을 체계화하여 에이전트 루프의 신뢰성을 높입니다. 다음 챕터에서는 여러 에이전트를 조율하는 Multi-Agent 오케스트레이션을 다룹니다.