ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 에이전트 루프의 나침반, 계획(Planning) — 복잡함을 나눌 때 시작되는 문제 해결
    IT 2026. 6. 6. 23:00
    에이전트 루프의 나침반, 계획(Planning) — 복잡함을 나눌 때 시작되는 문제 해결

    사용자가 에이전트에게 "이 프로젝트 전체를 리팩터링해서 성능을 올려줘"라고 명령한다. 이는 한 번에 수행할 수 있는 단일 작업이 아니다. 에이전트가 이처럼 크고 모호한 목표를 받았을 때 길을 잃지 않고 차근차근 나아갈 수 있게 돕는 나침반이 바로 계획(Planning) 단계다.

    아무리 똑똑한 AI 모델이라도 계획을 세우지 않고 코딩부터 시작하면 백전백패다. 이 글에서는 에이전트 루프의 중추인 계획 단계의 중요성과 작동 기법을 알아본다.

    ① 계획(Planning) 단계에서 수행하는 일

    계획은 관찰을 통해 수집한 '현재의 환경 정보'와 사용자가 전달한 '최종 목표'를 융합하여, 실행 가능한 로드맵을 설계하는 과정이다. 이 단계에서 에이전트는 다음과 같은 구체적인 작업을 수행한다.

    • 목표 해체 (Goal Decomposition): 하나의 거대한 태스크를 여러 개의 독립적이고 순차적인 하위 태스크(Sub-task)로 분할한다.
    • 순서도 수립 및 우선순위 지정: 어떤 도구를 먼저 호출해야 하는지 인과관계와 우선순위를 매긴다. (예: 파일을 수정하기 전에 먼저 검색을 해서 참조 경로를 찾아야 한다.)
    • 동적 재계획 (Dynamic Re-planning): 행동 결과가 예상을 빗나가거나 에러가 났을 때, 기존 계획을 폐기하고 새로운 대안 경로를 탐색한다.

    diagram

    다이어그램 설명. 입력된 단일 목표를 '계획 엔진'이 단계별 하위 태스크(1, 2, 3단계)로 쪼갠다. 이 계획을 순서대로 행동으로 보낸다. 행동 중 실패가 발생하면 닫힌 피드백 선을 타고 올라와 계획 엔진에서 동적으로 우회 경로(Re-planning)를 다시 세운다.

    ② 계획 엔진의 실체와 프롬프트의 역할: 누가 계획을 세우는가?

    독자들이 가장 흔히 오해하는 것 중 하나가 '계획 엔진(Planning Engine)'이라는 독립된 프로그램이 별도로 존재하여 코드를 짜는 LLM에게 일방적으로 계획을 하달한다고 생각하는 것이다. 하지만 실상은 그렇지 않다.

    계획 엔진의 실체는 '계획 수립 역할을 부여받은 LLM 자신'이다. 호스트 프레임워크는 전체적인 에이전트 루프(관찰 → 계획 → 행동 → 검증)를 제어하는 껍데기(Orchestrator)일 뿐이며, 실제로 계획을 수립하고 쪼개는 고차원적 사유는 LLM의 뇌를 빌려 수행한다. 이때 프레임워크가 LLM에게 계획을 '의뢰'하게 되는데, 이 의뢰 과정에서 프롬프트 디자인(Prompt Design)이 계획의 성패를 가른다.

    1. 계획 의뢰용 프롬프트의 핵심 요소

    단순히 "계획을 세워줘"라고 요청하면 LLM은 모호하고 실행 불가능한 계획을 내놓기 쉽다. 따라서 호스트 프레임워크는 다음과 같은 제약 조건이 촘촘하게 설계된 프롬프트를 주입해야 한다.

    • 단계별 독립성 (Self-Containment): 각 하위 태스크는 이전 단계에 의존하되, 하나의 도구 호출(Tool Call)이나 명확히 닫힌 작업 단위로 완결되어야 한다.
    • 성공 기준 명시 (Success Criteria): "코드를 고친다"가 아니라 "코드를 고친 후 npm run build를 실행해 에러가 없는지 확인한다"처럼 각 단계의 완료 여부를 판단할 구체적 기준을 계획에 포함하게 한다.
    • 구조화된 형식 강제 (JSON/Schema): 계획의 상태(Pending, Completed, Failed)를 프레임워크가 추적하고 가공할 수 있도록 XML이나 JSON Schema 형태로 출력을 제한한다.

    2. 계획 수립 시점의 프롬프트 예시 (System Prompt)

    실제 에이전트 시스템에서 계획을 의뢰할 때 사용하는 프롬프트 템플릿의 간략한 모습은 다음과 같다.

    # 역할
    너는 복잡한 개발 목표를 안전하게 수행하기 위해 실행 계획을 설계하는 '계획 엔진'이다.
    
    # 임무
    사용자가 요구한 최종 목표와 현재 관찰된 시스템 상태를 바탕으로, 이를 5단계 이내의 하위 태스크(Sub-tasks)로 쪼개어 계획을 수립하라.
    
    # 제약 조건
    1. 각 단계는 구체적이고 검증 가능한 액션(예: 파일 읽기, grep 검색, 테스트 실행 등) 단위여야 한다.
    2. 각 단계마다 "이 단계가 성공했는지 판단할 검증 기준(verification)"을 반드시 작성하라.
    3. 결과를 반드시 지정된 JSON 포맷으로만 출력하라.
    

    이와 같이 구체적이고 정교한 프롬프트를 통해 LLM에게 계획 역할을 성공적으로 수행하도록 '의뢰'함으로써, 에이전트는 무모한 삽질 없이 안전하게 최종 목적지까지 도달할 수 있는 탄탄한 로드맵을 얻게 된다.

    ③ 왜 계획을 세워야 하는가?

    LLM은 글자나 코드를 생성할 때 한 번에 한 글자씩 출력하는 자동회귀(Autoregressive) 모델이다. 즉, "미리 전체 구조를 생각하지 않고 바로 다음 토큰을 생성"한다. 이 특성은 긴 코딩이나 논리적 추론에서 커다란 약점으로 작용한다.

    계획 단계가 필수적인 이유는 두 가지다.

    1. 추론 오차 감소 (System 2 Thinking): 계획을 먼저 쓰게 유도하면 LLM은 생각의 영역(Scratchpad)을 확보하게 된다. '생각하고 행동하기' 패러다임을 통해 논리 구조가 어긋나는 것을 미리 막는다.
    2. 분할 정복 (Divide and Conquer): 복잡한 리팩터링을 진행할 때, "어떤 모듈부터 먼저 수정하고 의존성을 해결할지"를 미리 쪼개놓아야 한 번에 처리할 인지 과부하(Cognitive Load)를 분산할 수 있다.
    3. 자원의 효율성: 계획이 꼬인 상태로 행동(Action) 도구를 마구 실행하면 디렉토리가 지저분해지고 불필요한 API 비용(토큰 비용)만 수십 배로 발생한다.

    ④ 앞단계 및 뒷단계와의 연결고리

    계획은 관찰(Observation)을 소화하여 행동(Action)으로 내보내는 루프의 핵심 브레인이다.

    이전 단계 (관찰) 현재 단계 (계획) 다음 단계 (행동)
    "현재 가상환경은 Poetry이며 requests 모듈이 없는 상태다"라는 맥락 인지 Poetry 규격에 따라 패키지를 설치하고 코드를 고칠 하위 태스크 리스트 작성 수립된 리스트의 첫 단계인 poetry add 명령어 도구 호출
    • 이전 단계(관찰)와 연결: 관찰이 수집해 온 파일 정보, 환경 타입, 에러 상태를 바탕으로 계획의 원재료를 삼는다. 관찰이 정확할수록 훌륭한 계획이 나온다.
    • 다음 단계(행동)와 연결: 계획이 완성되면 한 번에 하나의 계획 항목을 꺼내어 이를 수행하기 위한 구체적인 도구(Tool Call)를 행동 단계에 위임한다.

    ⑤ 실질적으로 어떻게 동작하는가? (사례 중심)

    사용자가 "사용하지 않는 유틸리티 함수들을 정리해줘"라는 요청을 했다고 가정해 보자.

    1. 정적 계획 (Static Planning)의 생성

    모델은 내부 Scratchpad(또는 프롬프트 맥락)에 다음과 같은 순차 계획을 JSON 포맷으로 기술하고 관리한다.

    {
      "goal": "unused_utilities_cleanup",
      "steps": [
        { "id": 1, "task": "Find all files in src/utils/", "status": "pending" },
        { "id": 2, "task": "Find references of each function in other files using grep", "status": "pending" },
        { "id": 3, "task": "Delete functions with zero references", "status": "pending" },
        { "id": 4, "task": "Run tests to verify changes", "status": "pending" }
      ],
      "current_step": 1
    }
    

    2. 동적 재계획 (Dynamic Re-planning)의 발동

    에이전트가 3단계를 수행하여 참조가 없는 calculate_old_tax() 함수를 삭제했다. 그런데 4단계에서 pytest를 돌려보았더니 테스트가 깨졌다! test_legacy.py 라는 오래된 테스트 파일에서 이 함수를 여전히 임포트하여 검사하고 있었던 것이다.

    이때 에이전트는 무너진 상황(에러 로그)을 관찰하고, 다음과 같이 즉시 계획을 동적으로 수정한다.

    {
      "goal": "unused_utilities_cleanup",
      "steps": [
        { "id": 1, "task": "Find all files in src/utils/", "status": "completed" },
        { "id": 2, "task": "Find references...", "status": "completed" },
        { "id": 3, "task": "Delete functions...", "status": "completed" },
        // ─── 여기서 동적으로 계획이 변경 및 삽입됨 ───
        { "id": 3.1, "task": "Fix imports or delete dead assertions in test_legacy.py", "status": "pending" },
        { "id": 4, "task": "Run tests to verify changes", "status": "pending" }
      ],
      "current_step": 3.1
    }
    

    에이전트는 뇌에 해당하는 계획 리스트를 스스로 변경하여, `test_legacy.py`로 달려가 임포트 라인을 지우고 다시 테스트를 돌리는 식으로 똑똑하게 장애를 극복한다.

    정리하며

    계획(Planning)은 무모한 실행을 영리한 탐색으로 바꾸어주는 에이전트의 전략실이다. 복잡한 코딩 작업을 잘 쪼개고, 예기치 못한 에러가 날 때 계획을 신속하게 롤백하여 플랜 B를 꺼내 드는 능력. 이것이 바로 단순 챗봇은 절대 흉내 낼 수 없는, 에이전트만의 차별화된 영리함이다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.