도구를 붙인 순간부터 문제는 더 이상 "어떻게 잘 답하게 할까"에 머물지 않는다. 이제 중요한 것은 어떻게 헤매지 않게 할까다. 한 번의 답변은 그럴듯할 수 있다. 하지만 여러 단계를 거쳐 검색하고, 판단하고, 실행하고, 다시 수정해야 하는 순간부터 프롬프트는 문장 꾸미기가 아니라 흐름 설계가 된다.
에이전트 아키텍처의 핵심은 의외로 단순하다. 모델에게 더 많은 지시를 몰아넣는 것이 아니라, 누가 무엇을 책임지는지 나누는 것이다. 잘 만든 에이전트는 "매우 영리한 하나의 프롬프트"가 아니라, 계획과 실행과 검토가 서로 다른 책임을 갖는 작은 시스템이다.

하나의 거대한 프롬프트가 자주 실패하는 이유
처음 에이전트를 만들 때 흔히 하는 실수가 있다. 하나의 모델에게 이런 일을 한꺼번에 시키는 것이다. 먼저 큰 목표를 이해하고, 다음 행동을 정하고, 도구를 고르고, 도구 결과를 해석하고, 실패하면 복구하고, 마지막 답변까지 예쁘게 써 달라고 한다. 얼핏 보면 효율적이다. 하지만 실제로는 이 구조가 가장 쉽게 흔들린다.
이유는 간단하다. 모델이 매 순간 너무 많은 역할을 동시에 떠안기 때문이다. 전략가이면서 작업자여야 하고, 현장 기록원이어야 하며, 때로는 감사 담당자처럼 위험도 판단해야 한다. 이렇게 되면 지금 무엇에 집중해야 하는지가 흐려진다. 어떤 단계에서는 장기 계획이 필요하고, 어떤 단계에서는 API 인자 하나를 정확히 채우는 일이 더 중요하다. 그런데 둘을 한 프롬프트 안에 섞어 두면, 모델은 종종 큰 그림을 잃거나 반대로 사소한 실행을 과하게 일반화한다.
이때 자주 나타나는 증상도 비슷하다. 계획은 그럴듯하지만 실행이 엉성해지고, 도구는 잘 불렀는데 왜 그 도구를 불렀는지 설명이 바뀌고, 한 번 실패한 뒤 같은 시도를 반복한다. 사람으로 치면 기획 회의와 현장 작업과 사후 보고를 동시에 하려다 모두 어설퍼지는 셈이다.
planner와 executor를 분리하면 무엇이 달라지는가
그래서 고급 에이전트는 보통 planner-executor 구조로 출발한다. planner는 "무엇을 할지"를 정하고, executor는 "정해진 일을 실제로 수행"한다. 이 분리는 단순한 취향 차이가 아니다. plan-then-execute 방식은 반응형 루프만으로 계속 움직이는 구조보다 더 예측 가능하고, 비용을 통제하기 쉬우며, 긴 작업에서 전략적 실수를 줄이는 데 유리하다.1
여기서 중요한 것은 planner의 산출물이 단순한 메모가 아니라는 점이다. 계획은 planner와 executor 사이의 계약이다. 이 계획을 구조화된 산출물로 남기면, 시스템은 지금 무엇을 하려는지 감사할 수 있고, 중간에 멈췄다가 다시 이어서 실행할 수도 있다.2
또 하나의 장점은 모델 배치가 유연해진다는 점이다. 계획을 세우는 단계에는 더 강한 모델을 쓰고, 각 단계를 실제로 수행하는 실행 단계에는 더 빠르고 저렴한 모델이나 결정적 프로그램을 붙일 수 있다. 그러면 매번 큰 모델을 다시 호출하는 ReAct식 반복보다 비용과 지연을 낮추기 쉬워진다.3
즉, planner-executor 구조의 본질은 "더 복잡한 에이전트"가 아니라 "역할이 나뉜 에이전트"다. 계획이 흔들리면 planner를 고치고, 도구 실행이 불안정하면 executor를 고치면 된다. 책임이 분리되어 있으니 문제도 분리해서 다룰 수 있다.
에이전트 프롬프트는 레이어로 설계해야 한다
에이전트를 하나의 프롬프트로 보지 말고, 여러 레이어가 겹쳐 있는 구조로 보면 설계가 훨씬 또렷해진다. 실무적으로는 다음 다섯 레이어를 먼저 구분해 두는 것이 좋다.
정책 레이어: 금지 작업, 승인 필요 조건, 민감정보 처리 규칙, 허용 도구 범위를 정의한다.
계획 레이어: 목표를 단계로 나누고, 각 단계의 의존 관계와 성공 조건을 만든다.
실행 레이어: 현재 단계 하나만 받아 도구를 호출하거나 결과를 산출한다.
관측 레이어: 실행 결과를 다음 단계가 읽기 쉬운 상태 정보로 정규화한다.
반성·재계획 레이어: 실패 원인을 짧게 진단하고, 계속할지 수정할지 멈출지 결정한다.
이 구조가 중요한 이유는 각 레이어의 질문이 다르기 때문이다. 정책 레이어는 "해도 되는가"를 묻고, 계획 레이어는 "어떤 순서가 맞는가"를 묻고, 실행 레이어는 "지금 이 한 단계를 어떻게 끝낼까"를 묻는다. 관측 레이어는 "무슨 일이 실제로 일어났나"를 기록하고, 반성 레이어는 "다음엔 무엇을 바꿔야 하나"를 다룬다.
이 다섯 질문을 한 프롬프트 안에서 동시에 처리하려고 하면 구조가 금방 흐려진다. 반대로 레이어를 나누면 프롬프트가 길어지는 대신, 시스템은 오히려 단순해진다. 각 레이어가 자기 역할만 제대로 하면 되기 때문이다.
planner는 생각을 길게 쓰는 모델이 아니라 계획 계약서를 만드는 모델이다
planner에게 가장 흔히 잘못 시키는 일은 "잘 생각해서 알아서 해 봐"다. 이렇게 하면 계획이 아니라 산문이 나온다. 좋은 planner는 멋진 설명문을 쓰는 모델이 아니라, executor가 바로 읽고 움직일 수 있는 계획 계약서를 만드는 모델이다.
계획은 보통 다음 정보를 포함해야 한다.
목표와 최종 산출물
단계 목록과 순서
각 단계의 입력과 기대 결과
단계 간 의존 관계
허용된 도구 또는 필요한 데이터
실패 시 재계획 조건
예산과 중단 규칙
예를 들어 planner가 남겨야 할 산출물은 이런 모양에 가깝다.
{
"goal": "사내 VPN 장애 문의를 진단하고 필요한 경우 티켓 초안을 만든다",
"steps": [
{
"id": "s1",
"task": "사용자 신원 확인에 필요한 정보가 충분한지 점검한다",
"depends_on": [],
"allowed_tools": [],
"success": "필수 식별 정보가 확보되었거나 추가 질문이 정의됨"
},
{
"id": "s2",
"task": "계정 상태를 조회한다",
"depends_on": ["s1"],
"allowed_tools": ["get_account_status"],
"success": "잠금 여부와 최근 인증 실패 기록을 확보함"
},
{
"id": "s3",
"task": "최근 장애 공지를 검색한다",
"depends_on": ["s1"],
"allowed_tools": ["search_incidents"],
"success": "관련 공지 유무를 판별함"
},
{
"id": "s4",
"task": "원인을 요약하고 티켓 초안 필요 여부를 결정한다",
"depends_on": ["s2", "s3"],
"allowed_tools": [],
"success": "사용자에게 제시할 진단 또는 다음 행동이 정리됨"
}
]
}이 예시에서 핵심은 화려한 JSON이 아니다. 각 단계가 책임, 입력, 성공 기준을 갖고 있다는 점이다. executor는 더 이상 전체 문제를 다시 해석할 필요가 없다. 그냥 현재 단계의 책임만 수행하면 된다.
planner 프롬프트를 만들 때도 이 점을 분명히 해 두는 편이 좋다. planner는 도구를 직접 호출하지 않고, 가능한 한 불필요한 단계를 만들지 않으며, 이미 완료한 단계를 다시 포함하지 않고, 부족한 정보가 있으면 "실행"이 아니라 "질문"을 단계로 남겨야 한다. 이 네 가지만 지켜도 에이전트가 훨씬 덜 방황한다.
executor는 똑똑한 비서가 아니라 집중력이 좋은 현장 작업자여야 한다
executor의 실패는 대부분 능력 부족이 아니라 범위 과다에서 나온다. planner가 만든 전체 계획과 사용자의 원문과 온갖 도구 설명과 긴 대화 기록을 executor에게 다 밀어 넣으면, executor는 다시 전략가가 되려고 한다. 그러면 planner와 역할이 겹친다.
좋은 executor는 현재 단계 하나에 집중한다. 지금 수행할 작업, 사용할 수 있는 도구, 필요한 입력값, 성공 조건, 실패 시 보고 형식만 알면 된다. 다시 말해 executor 프롬프트는 넓어야 하는 것이 아니라 좁아야 한다.
실무에서는 executor에게 다음 규칙을 주는 것이 효과적이다.
현재 단계의 목표를 벗어나지 말 것
허용된 도구만 사용할 것
모르면 추가 정보 필요 또는 실패 상태를 반환할 것
사용자에게 말하기 전에 먼저 단계 결과를 구조화해서 남길 것
위험한 쓰기 작업은 승인 플래그 없이는 실행하지 말 것
이렇게 하면 executor는 "무엇을 할까"를 고민하는 모델이 아니라, "정해진 한 일을 제대로 끝내는" 모델이 된다. 에이전트가 안정되는 순간은 보통 여기서 온다. 많이 생각하게 해서가 아니라, 덜 고민하게 만들어서다.
action budget은 에이전트의 지갑이자 브레이크다
사람은 시간이 없으면 일을 줄인다. 예산이 부족하면 출장 범위를 줄인다. 그런데 많은 에이전트는 아무 제한 없이 움직이게 만든다. 검색을 몇 번 해도 되는지, 재시도를 몇 번까지 허용할지, 어느 정도 비용을 넘기면 멈춰야 하는지 정하지 않는다. 그 결과는 익숙하다. 끝없는 도구 호출, 느린 응답, 예산 초과, 그리고 설명하기 어려운 실패다.
그래서 에이전트에는 반드시 action budget이 있어야 한다. action budget은 "할 수 있는 행동의 총량"을 정의하는 규칙이다. 단순히 토큰 수만 뜻하지 않는다.
단계 예산: 최대 몇 단계까지 진행할 수 있는가
도구 호출 예산: 외부 도구를 몇 번까지 호출할 수 있는가
시간 예산: 몇 초 안에 끝내야 하는가
비용 예산: 어떤 모델과 도구를 써도 되는가
위험 예산: 쓰기 작업이나 승인 필요 작업을 몇 번까지 시도할 수 있는가
중요한 점은 budget이 사후 모니터링 지표가 아니라, planner가 처음부터 보아야 하는 제약이라는 것이다. 예를 들어 최대 4번의 도구 호출만 허용된다면 planner는 애초에 열 단계짜리 장대한 계획을 만들면 안 된다. 먼저 사용자에게 필수 정보를 물어서 검색 범위를 줄이거나, 읽기 작업만으로 충분한 답변을 먼저 제시해야 한다.
실무에서는 budget을 숫자 하나로 두지 말고, 정책과 연결하는 편이 좋다. 예를 들어 "읽기 전용 도구 호출 5회, 쓰기 도구 0회, 사용자 승인 후 쓰기 도구 1회 허용"처럼 설계하면 훨씬 안전하다. 그러면 budget은 단순한 비용 장치가 아니라 권한 장치가 된다.
stop rule은 멈춤을 우연에 맡기지 않는 규칙이다
에이전트가 위험해지는 순간은 틀린 답을 할 때만이 아니다. 멈추지 못할 때도 위험하다. 정보가 부족한데 계속 추측하거나, 도구 오류가 반복되는데 같은 호출을 계속하거나, 이미 충분한 증거가 있는데도 괜히 한 번 더 검색하는 식이다.
이런 문제를 막으려면 stop rule을 명시해야 한다. stop rule은 "언제 끝났다고 볼 것인가"와 "언제 중단하고 사람에게 넘길 것인가"를 정의한다. 보통 다음 조건을 함께 둔다.
목표 달성에 필요한 근거와 결과가 확보되었을 때
필수 정보가 없어 더 진행하면 추측이 될 때
승인 없는 고위험 작업만 남았을 때
같은 실패가 반복되어 진행률이 없을 때
시간, 도구 호출, 비용 budget을 초과했을 때
근거가 서로 충돌해 자동 판단이 부적절할 때
좋은 stop rule은 끝내는 방법도 하나가 아니다. 어떤 경우에는 최종 답변으로 끝나고, 어떤 경우에는 추가 질문으로 끝나며, 어떤 경우에는 안전한 실패 응답으로 끝난다. 중요한 것은 "계속 시도할지"가 모델의 기분에 달려 있지 않다는 점이다.
예를 들어 내부 업무 에이전트라면 이런 규칙이 자연스럽다. 읽기 작업만으로 결론이 나면 답변하고 종료한다. 고객 데이터 수정이 필요하면 승인 요청 후 대기한다. 도구가 두 번 연속 같은 인증 오류를 내면 중단하고 사람 검토로 넘긴다. 이 정도만 있어도 무한 루프와 과잉 자동화가 크게 줄어든다.
observation은 '무슨 일이 있었는가'를 다음 단계가 읽을 수 있게 바꾸는 과정이다
에이전트는 도구를 호출한 뒤 반드시 관측을 남겨야 한다. observation은 행동의 결과로 환경에서 돌아온 피드백이다. API 응답, 상태 코드, 오류 메시지, 데이터 변화, 시스템 로그 같은 것이 모두 observation에 해당한다.4
중요한 것은 observation이 그냥 원본 덤프가 되어서는 안 된다는 점이다. 원본 로그를 통째로 다음 프롬프트에 넣으면 컨텍스트만 비대해지고, executor나 replanner가 핵심을 놓치기 쉽다. observation은 정규화된 상태 정보여야 한다.
예를 들어 이런 형태가 실무적으로 유용하다.
{
"step_id": "s2",
"status": "success",
"summary": "계정이 잠금 상태이며 최근 30분 내 인증 실패 5회가 확인됨",
"evidence_refs": ["acct.locked=true", "auth.fail_count=5"],
"error_type": null,
"retryable": false,
"cost": {
"latency_ms": 420,
"tool_calls": 1
}
}이 관측 구조가 좋은 이유는 세 가지다. 첫째, 다음 단계가 긴 로그를 다시 읽지 않아도 된다. 둘째, reflection이나 replanning이 진행률을 판단하기 쉽다. 셋째, 나중에 사람이 실패 로그를 볼 때도 무엇이 실제로 일어났는지 빠르게 파악할 수 있다.
관측 레이어에서 특히 조심할 점은 요약과 해석을 섞지 않는 것이다. observation은 가능하면 "발생 사실"에 가깝게 기록하고, "그 의미가 무엇인지"는 reflection이나 최종 답변 단계로 넘기는 편이 좋다. 그래야 한 번의 잘못된 해석이 다음 루프 전체를 오염시키지 않는다.
reflection은 매번 장황하게 생각하는 기능이 아니라, 실패를 짧게 교정하는 장치다
observation이 사실 기록이라면, reflection은 그 기록을 보고 무엇을 바꿔야 하는지 짧게 판단하는 단계다. reflection을 넣으면 에이전트 품질이 좋아질 수 있지만, 그만큼 계산량과 지연도 늘어난다.5 그래서 모든 단계 뒤에 무조건 reflection을 붙이는 것은 대개 과하다.
reflection이 특히 유용한 순간은 따로 있다. 첫째, 도구 실패가 발생했을 때다. 둘째, 여러 관측이 서로 충돌할 때다. 셋째, 마지막 답변을 내기 전에 근거 누락 가능성을 점검할 때다. 넷째, 같은 행동을 반복하려는 조짐이 보일 때다.
좋은 reflection 프롬프트는 길 필요가 없다. 오히려 짧아야 한다. 보통 아래 세 질문이면 충분하다.
방금 실패나 불확실성의 원인은 무엇인가
기존 계획에서 무엇을 유지하고 무엇을 바꿔야 하는가
다음 한 단계로 가장 안전한 선택은 무엇인가
여기서 중요한 것은 reflection이 planner를 대체하지 않는다는 점이다. reflection은 수리공에 가깝다. 무너진 전체 계획을 새로 설계하는 것이 아니라, 현재 경로가 왜 삐끗했는지 진단하고 재계획이 필요한지 여부를 알려 준다. 큰 방향 수정이 필요하면 planner로 되돌리고, 작은 교정이면 executor가 다음 단계에서 바로 반영하게 하면 된다.
재계획은 매번 하는 것이 아니라, 이벤트가 생겼을 때 호출하는 편이 낫다
초기 설계에서는 "매 단계마다 다시 계획하면 더 똑똑하지 않을까?"라는 생각을 하기 쉽다. 하지만 그렇게 하면 planner가 사실상 executor 위에 계속 덮여 있는 셈이어서 비용과 지연이 급격히 늘어난다. 계획이 지나치게 자주 바뀌면 시스템은 오히려 일관성을 잃는다.
그래서 재계획은 보통 이벤트 기반으로 거는 편이 좋다. 예를 들어 다음 조건에서만 replanner를 호출하는 식이다.
현재 단계가 실패했고 재시도 가치가 낮을 때
새로 얻은 정보가 기존 계획의 전제를 깨뜨렸을 때
사용자 요청이 중간에 바뀌었을 때
남은 budget으로는 기존 계획을 끝낼 수 없을 때
이 방식의 장점은 planner의 권위를 지키면서도, 현실 변화에 적응할 수 있다는 데 있다. 계획은 쉽게 흔들리지 않지만, 틀렸을 때는 고칠 수 있다. 바로 그 균형이 운영 가능한 에이전트의 특징이다.
작은 예시: 헬프데스크 에이전트를 레이어로 설계해 보기
사용자가 이렇게 요청했다고 해보자.
"VPN 접속이 안 됩니다. 제 계정 상태도 확인해 주시고, 필요하면 티켓도 만들어 주세요."
이 요청을 한 번에 처리하는 거대한 프롬프트를 만들 수도 있다. 하지만 운영 가능한 구조는 다음처럼 나뉜다.
정책 레이어는 먼저 선을 긋는다. 계정 잠금 해제나 실제 티켓 생성은 승인 없이는 실행하지 않는다. 개인정보는 필요한 범위만 조회한다. 장애 진단이 불확실하면 추측하지 않고 추가 질문 또는 사람 검토로 넘긴다.
계획 레이어는 요청을 단계로 바꾼다. 신원 확인 정보 점검, 계정 상태 조회, 최근 장애 공지 검색, 결과 비교, 해결 안내 또는 티켓 초안 작성, 승인 요청 순으로 만든다. 이때 planner는 create_ticket를 마지막 단계에 두되, 승인 플래그가 없으면 실행 단계가 아니라 "초안 준비"까지만 허용한다.
실행 레이어는 현재 단계만 본다. 예를 들어 계정 상태 조회 단계에서는 get_account_status만 허용하고, 최근 장애 공지 검색 단계에서는 search_incidents만 허용한다. 한 단계가 끝날 때마다 observation이 남는다.
관측 레이어는 계정 잠금 여부, 실패 횟수, 관련 공지 유무를 구조화해 기록한다. 예를 들어 "잠금=true", "실패횟수=5", "관련 공지 없음" 같은 식이다.
반성 레이어는 필요한 순간에만 동작한다. 만약 계정은 정상인데 공지에는 대규모 장애가 있다고 나오면, "개별 계정 문제가 아니라 공용 장애 가능성이 크므로 티켓 초안 대신 장애 안내를 우선 제시" 같은 교정을 수행할 수 있다.
action budget도 이 구조에 붙는다. 읽기 도구 3회, 쓰기 도구 0회, 승인 후 쓰기 도구 1회, 최대 15초라고 두면 planner는 쓸데없는 검색을 남발하지 못한다. stop rule도 분명하다. 공용 장애가 확인되면 안내 후 종료한다. 계정 문제로 판단되지만 승인이 없으면 티켓 초안만 만들고 대기한다. 같은 인증 오류가 반복되면 사람 검토로 넘긴다.
이 예시에서 눈여겨볼 점은 에이전트가 특별히 더 "똑똑한" 척하지 않는다는 것이다. 대신 어느 단계가 전략이고, 어느 단계가 실행이고, 어느 단계가 멈춤인지가 분명하다. 운영에서는 이 차이가 품질 차이보다 더 중요할 때가 많다.
설계 원칙을 한 문장으로 줄이면
좋은 에이전트 프롬프트 아키텍처는 모델에게 긴 지시를 주는 방식이 아니다. 계획, 실행, 관측, 교정을 서로 다른 책임으로 분리하고, 각 책임 사이에 budget과 stop rule을 심는 방식이다.
planner는 전체 길을 잡고, executor는 한 걸음을 정확히 디디고, observation은 실제로 무슨 일이 있었는지 기록하고, reflection은 꼭 필요할 때만 방향을 바로잡는다. 이 네 가지가 분리될수록 에이전트는 덜 화려해 보일 수 있다. 하지만 바로 그때부터 비로소 운영 가능한 시스템이 된다.
다음 장에서는 이런 구조를 실제 운영에서 무너뜨리는 요인들, 이를테면 무한 루프, 불필요한 도구 호출, 권한 경계 침범 같은 실패를 어떻게 줄일지 본격적으로 다룬다.
참고
1Architecting Resilient LLM Agents: A Guide to Secure Plan-then-Execute Implementations
2Planner-Executor architecture | AI Summary Hub
4Observe: Integrating Feedback to Reflect and Adapt · Hugging Face
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.