AEGLOSLAB ChatGPT 같은 AI를 만들고 서비스하기

제6부 응용: 프롬프트·RAG·에이전트

6.6 에이전트 I — ReAct·계획·메모리

6.5에서 모델은 도구를 쥐었다 — 함수 스키마를 보고 "이 함수를 이런 인자로 불러라"라고 구조화된 호출을 내면, 앱이 실행해 결과를 되돌려 준다. 그 그림은 대체로 한 번의 호출 → 한 번의 결과 → 한 번의 답이었다. 호출·실행·관찰이 한 왕복으로 끝나거나, 길어야 우리가 코드로 미리 작성해 둔 두세 단계로 끝났다. 이 장은 그 왕복을 루프로 감는다. 모델이 관찰을 보고 다음에 무엇을 할지 스스로 정하고, 그 행동의 결과를 다시 관찰하고, 목표에 닿을 때까지 반복하게 만드는 것 — 이것이 에이전트(agent)다. 6.5의 도구 호출이 원자라면, 에이전트는 그 원자를 여러 개, 스스로 순서를 정해 가며 구성한 분자다.

에이전트는 지금 이 분야에서 가장 뜨겁고 동시에 가장 부풀려진 주제다. 그래서 이 장은 두 가지를 나란히 간다. 하나는 원리 — 무엇이 루프를 에이전트로 만드는가, ReAct가 왜 사고와 행동을 결합하는가, 계획과 메모리가 어디서 붙고 무엇이 관찰을 누적시켜 터뜨리는가. 다른 하나는 정직성 — 에이전트는 취약하다. 스텝마다의 작은 오류가 곱해지고, 데모에서 되던 것이 프로덕션에서 붕괴하며, 자율성은 무비용이 아니고 늘 이득도 아니다. 이 두 번째를 빼면 이 장은 판촉물이 된다. 나오는 트레이스·성공률·스텝 수는 규모를 가늠하기 위한 개략값이고, 무게는 특정 숫자가 아니라 그 숫자가 가리키는 방향에 있다.

6.6.1 제어 흐름을 누가 쥐는가 — 단발에서 루프로

6.5까지의 LLM 호출은 상태 없는 함수였다. 프롬프트를 넣으면 답이 나온다. 도구를 붙여도 전형적 흐름은 "모델이 도구를 부르고 → 앱이 실행하고 → 결과를 넣어 한 번 더 부르면 → 최종 답"으로 미리 정해진 몇 단계였다. 여기서 결정적인 질문 하나를 던져 보자. 누가 "언제 멈출지", "다음에 무슨 도구를 쓸지"를 정했나? 우리 코드가 정했다. 몇 번 왕복할지, 어떤 순서로 부를지, 언제 답을 낼지가 전부 우리가 작성한 제어 흐름(control flow) 안에 고정되어 있었다.

에이전트의 한 가지 결정적 차이는 이 제어 흐름을 모델에게 넘긴다는 것이다. 몇 번 도구를 부를지, 어떤 순서로 부를지, 언제 답을 낼지를 모델이 매 스텝 관찰을 보고 스스로 정한다. 코드는 루프를 실행하고 안전장치를 걸 뿐, "무엇을 다음에 할지"는 모델의 출력이 결정한다. 이 한 문장이 워크플로우와 에이전트를 가른다. 도구를 두 번 부르든 스무 번 부르든, 부르는 횟수와 순서를 사람이 코드로 정했으면 그것은 워크플로우이고, 모델이 관찰을 보고 매번 정했으면 에이전트다.

이 넘겨받은 제어 흐름을 실행하려면 다섯 가지가 있어야 한다. LLM(추론 엔진)는 관찰을 보고 다음 행동이나 최종 답을 생성한다. 도구(손발)는 외부 세계에 작용하거나 정보를 가져온다 — 검색·DB 조회·코드 실행·API 호출. 루프(순환)는 관찰→사고→행동을 목표에 닿을 때까지 반복한다. 메모리(상태)는 스텝과 스텝 사이, 세션과 세션 사이에 정보를 잇는다. 목표(방향)는 무엇을 이루려는가와 함께 "언제 끝났나"의 기준을 준다. 가장 짧은 정의로 압축하면, 에이전트는 목표를 향해 도구를 쓰며 관찰에 따라 스스로 다음 행동을 정하는 루프 안의 LLM이다. 도구만 있고 루프가 없으면 도구 쓰는 한 번의 호출이고(6.5), 루프만 있고 분기·판단이 없으면 정해진 파이프라인이다.

아니오 — 더 필요 예 — 목표 달성 목표 / 사용자 요청 LLM (추론 엔진) 다음 행동 결정 충분한가? 행동: 도구 선택·호출 환경 / 도구 실행 검색·DB·코드·API 관찰: 결과 또는 오류 메모리 갱신 (단기·장기) 최종 답변 / 종료

이 그림에서 에이전트의 정체는 피드백 화살표 — 관찰에서 메모리를 거쳐 LLM으로 돌아가는 고리 — 다. 6.5의 그림에는 이 피드백이 없었다. 관찰이 다시 판단의 입력이 되고, 그 판단이 또 관찰을 낳는 순환이 없으면 아무리 도구를 많이 붙여도 에이전트가 아니다. 반대로 이 순환이 있으면, 그리고 그 순환 안에서 다음 행동을 모델이 고르면, 도구가 하나뿐이어도 그것은 에이전트다.

6.6.2 자율성의 스펙트럼 — 워크플로우와 에이전트

"에이전트냐 아니냐"는 켜고 끄는 스위치가 아니라 자율성의 스펙트럼이다. 한쪽 끝은 고정 워크플로우다 — 사람이 작성한 코드가 단계·분기·도구를 다 정해 두고, LLM은 각 노드에서 "분류하라·요약하라·추출하라" 같은 국소 작업만 맡는다. 다른 쪽 끝은 자율 에이전트다 — LLM이 제어 흐름을 쥐고 계획·도구선택·종료를 스스로 정한다. 실전 시스템의 대부분은 그 사이 어딘가에 산다. 그리고 어디에 사는지가 그 시스템의 신뢰성·비용·디버깅 난이도를 거의 다 결정한다.

자율성·유연성 ↑ · 예측가능성·신뢰성 ↓ ① 단발 호출 (프롬프트→답) ② 도구 있는 호출 (정해진 몇 스텝) ③ 고정 워크플로우 (코드가 분기·순서 결정) ④ 제약된 에이전트 (LLM이 도구 선택, 좁은 범위·가드레일) ⑤ 자율 에이전트 (LLM이 계획·흐름·종료 전부)

이 축을 조금 더 분해하면 "어디까지를 모델에 맡길지"가 명확해진다. 제어 흐름 — 다음에 무엇을 할지 — 을 코드가 정하면 워크플로우, 모델이 매 스텝 정하면 에이전트다. 스텝 수가 고정이고 상한이 명확하면 워크플로우, 모델이 종료를 판단해 가변이면 에이전트다. 도구 선택이 노드마다 고정되어 있으면 워크플로우, 모델이 도구 집합에서 고르면 에이전트다. 계획을 사람이 설계했으면 워크플로우, 모델이 분해·재계획하면 에이전트다. 오류를 코드가 처리하면 워크플로우, 모델이 관찰을 보고 적응하면 에이전트다. 실제 시스템은 이 축들 위에서 제각각인 값을 가진다 — 도구는 모델이 고르되 스텝 상한은 코드가 고정하고, 계획은 사람이 리뷰하되 오류 복구는 모델에 맡기는 식이다. 그래서 "이건 에이전트인가"보다 "이 축들에서 각각 얼마나 자율적인가"가 더 유용한 질문이다.

자율성 축낮음(워크플로우)높음(자율 에이전트)
제어 흐름(다음에 무엇을)코드가 결정LLM이 매 스텝 결정
스텝 수고정·상한 명확가변(모델이 종료 판단)
도구 선택노드마다 고정모델이 도구 집합에서 고름
계획사람이 설계모델이 분해·재계획
오류 대응코드가 처리모델이 관찰 보고 적응

여기서 이 장 전체를 관통하는 견해 하나를 미리 명시해 둔다. 자율성은 미덕이 아니라 비용이다. 스펙트럼의 오른쪽으로 갈수록 유연하지만, 그 대가로 예측이 불가능해지고 디버깅이 어려워지며 실패면이 넓어진다. 옳은 질문은 "이걸 에이전트로 만들까"가 아니라 "이 과제에 필요한 최소 자율성은 얼마인가"다 — 필요한 만큼만 주는 것이 대개 정답이다. 화려하게 소개되는 성공 사례의 상당수는 사실 잘 설계된 워크플로우이지 자율적으로 동작하는 에이전트가 아니며, 이 관점은 6.7에서 정면으로 다룬다. 그리고 이 실질 기준 — 루프 안에서 모델이 제어 흐름을 쥐는가 — 을 쥐고 있으면, "AI 에이전트"라는 라벨이 붙은 것 대부분이 실은 함수를 두 번 부르는 파이프라인이거나 RAG라는 사실이 곧 눈에 들어온다. 라벨이 아니라 누가 다음 행동을 정하는가를 보면 된다.

6.6.3 ReAct — 사고와 행동을 관찰로 결합하다

루프를 어떻게 실행할지의 가장 영향력 있는 골격은 ReAct(Reasoning + Acting)다. 이름 그대로 추론(사고)과 행동을 한 흐름에서 번갈아 계획하는 것인데, 왜 굳이 결합해야 하는지는 두 극단을 보면 분명해진다.

행동 없이 추론만 하는 쪽부터 보자. 모델이 머릿속으로만 단계를 밟는 사고사슬(6.1)은 강력하지만, 모델은 새 정보를 가져올 수 없고 자기 지식이 틀렸거나 노후화됐어도 그럴듯하게 지어낸다. "2024년 3분기 우리 회사 매출은?" 같은 질문에 사고사슬은 사실을 날조한다 — 아무리 논리를 잘 밟아도 전제가 가중치 안에 없기 때문이다. 반대로 추론 없이 행동만 하는 쪽, 즉 모델이 생각을 건너뛰고 도구를 마구 부르면, 왜 이 도구인지와 결과를 어떻게 해석할지가 없어 헛발질하고, 오류가 나도 적응하지 못한다.

ReAct의 통찰은 이 둘이 서로의 약점을 정확히 메운다는 것이다. 추론이 어떤 행동을 할지 이끈다 — 지금 무엇이 부족한지 판단해 그 빈칸을 채울 도구를 고른다. 행동의 관찰이 추론을 현실에 붙든다 — 외부에서 온 사실로 다음 생각을 교정한다. 사고는 행동을 조종하고, 관찰은 사고를 접지(grounding)한다. 이 상호 교정이 핵심이고, 한쪽만 있으면 불완전하다.

한 스텝은 세 부분으로 이루어진다. 사고(Thought)는 "지금 무엇을 알고, 무엇이 부족하고, 다음에 무엇을 해야 하나"를 말로 푼다. 행동(Action)은 도구 하나를 인자와 함께 호출하거나, "최종 답변" 행동으로 루프를 끝낸다. 관찰(Observation)은 도구가 돌려준 결과 또는 오류이며, 이것이 다음 사고의 입력이 된다.

아니오 예 목표 사고 (Thought) 무엇이 필요한가 답할 수 있나? 행동 (Action) 도구 호출 도구 실행 (환경) 관찰 (Observation) 결과 · 오류 최종 답변 (종료 행동)

이 루프는 6.6.1의 일반 그림을 구체화한 것이다. 달라진 결정적 한 가지는 사고가 명시적으로 들어갔다는 점이다. 모델이 "왜 이 행동인지"를 말로 풀어 쓰면 세 가지가 따라온다. 첫째, 도구 선택이 나아진다 — 무엇이 부족한지 스스로 규정하고 나서 고르니 잘못된 도구를 덜 부른다. 둘째, 우리가 트레이스를 읽어 디버깅할 수 있다 — 모델이 어디서 왜 잘못 판단했는지가 사고 텍스트에 남는다. 셋째, 관찰을 해석하는 다음 사고가 앞의 사고에 이어져 궤적이 하나의 논증으로 흐른다. 사고를 생략하고 도구 호출만 나열하는 루프도 동작하긴 하지만, 이 세 가지를 잃는다.

6.6.4 프롬프트 원형에서 네이티브 함수 호출 루프로

원조 ReAct는 이 루프를 프롬프트 텍스트 형식으로 구현했다. 모델이 Thought: / Action: / Action Input: / Observation: 을 정해진 문법으로 출력하게 하고, 파서가 Action 을 추출해 도구를 실행한 뒤 그 결과를 Observation: 으로 이어 붙여 다시 모델에 입력한다. 모델이 다음 Thought: 를 쓰기 시작하면 루프가 1회 반복된 것이다.

Thought: 사용자는 지난 분기 매출 성장률을 원한다. 먼저 두 분기 매출이 필요하다.
Action: sql_query
Action Input: {"q": "SELECT quarter, revenue FROM sales WHERE quarter IN ('2024Q2','2024Q3')"}
Observation: [{"quarter":"2024Q2","revenue":1200000},{"quarter":"2024Q3","revenue":1380000}]
Thought: 성장률 = (1380000-1200000)/1200000. 계산기로 확인하자.
Action: calculator
...

오늘날 프로덕션에서는 이 텍스트 문법을 직접 파싱하지 않는다. 벤더의 네이티브 함수 호출(6.5)이 같은 루프를 구조화된 형식으로 실행한다 — 모델이 도구 호출을 구조화 필드로 내면, 런타임이 실행하고 결과를 도구 결과 메시지로 환류시키고, 모델이 다시 판단한다. "사고"는 별도 텍스트로 나오기도 하고, 추론 모델(3.7)의 내부 사고 토큰으로 흡수되기도 한다. 차이는 사소해 보이지만 실무에서는 크다. 텍스트 문법은 모델이 Action Input: 을 약간 다르게 쓰거나 JSON에 후행 쉼표를 넣기만 해도 파서가 깨지고 루프가 멈춘다. 구조화된 함수 호출은 이 취약한 문자열 파싱 단계 전체를 제거한다 — 인자가 스키마에 맞는 필드로 오고, 제약 디코딩(6.9)으로 형식을 강제할 수도 있다.

그러니 원조 ReAct의 정확한 Thought:/Action: 텍스트 포맷은 대체로 구식이 됐지만, 그 아이디어 — 추론과 행동을 관찰로 결합하는 루프 — 는 현행 에이전트의 기본 골격으로 그대로 살아 있다. "우리 시스템은 ReAct다"라는 말은 대개 이 패턴을 쓴다는 뜻이지 특정 프롬프트 문법을 쓴다는 뜻이 아니다. 개념은 그대로이고 형식만 더 견고해졌다고 보면 된다. 그리고 특정 프레임워크(LangChain·LangGraph·AutoGPT·CrewAI 등)의 정확한 API·기본동작은 저마다 다르고 자주 바뀌므로, 실제로 쓸 때는 각 공식 문서를 확인해야 한다.

6.6.5 워크스루 — ReAct 트레이스 한 판

식과 그림만으로는 미끄러진다. 작은 과제를 끝까지 실행해 보자. 과제는 "지난주에 결제가 실패한 프리미엄 고객이 몇 명이고, 그중 자동 재시도로 결제에 성공한 비율은?" 이고, 도구는 둘 — sql_query(DB 조회)와 calculator(산술)다. 모든 수치·결과는 규모를 가늠하기 위한 개략값이다.

[스텝 1]
Thought: 두 숫자가 필요하다. (a) 지난주 프리미엄 결제 실패 고객 수, (b) 그중 재시도 성공 수.
        먼저 (a)를 세자.
Action:  sql_query
Input:   SELECT COUNT(DISTINCT user_id) FROM payments
         WHERE plan='premium' AND status='failed' AND ts >= now()-interval '7 day'
Observation: [{"count": 42}]

[스텝 2]
Thought: (a)=42. 이제 (b): 그 42명 중 재시도로 성공한 수. retry_success 컬럼을 쓰자.
Action:  sql_query
Input:   SELECT COUNT(DISTINCT user_id) FROM payments
         WHERE plan='premium' AND status='failed' AND retry_success=true AND ts >= now()-interval '7 day'
Observation: ERROR: column "retry_success" does not exist   ← 도구 오류(관찰)

[스텝 3]  ← 오류 복구
Thought: 그런 컬럼이 없다. 스키마를 잘못 알았다. 성공한 재시도는 별도 row(status='retry_ok')일 수 있다.
        재시도 성공을 나타내는 값으로 다시 시도하자.
Action:  sql_query
Input:   SELECT COUNT(DISTINCT user_id) FROM payments
         WHERE plan='premium' AND status='retry_ok' AND ts >= now()-interval '7 day'
Observation: [{"count": 18}]

[스텝 4]
Thought: (b)=18. 비율 = 18/42. 정확히 계산기로.
Action:  calculator
Input:   18 / 42
Observation: 0.4285714

[스텝 5]
Thought: 답을 낼 수 있다. 42명 중 18명, 약 42.9%.
Action:  final_answer
Input:   "지난주 프리미엄 결제 실패 고객은 42명이며, 그중 18명(약 42.9%)이 자동 재시도로 결제에 성공했습니다."
→ 종료

이 다섯 스텝에 이 장의 개념이 거의 다 들어 있다. 하나의 질문을 (a)·(b)·비율의 하위목표로 분해했고, 각 사고가 다음 행동을 정하는 방식으로 추론이 행동을 이끌었다. 스텝 1의 42가 스텝 2·4의 계산 근거가 됐다는 점에서 관찰이 추론을 접지했다 — 모델이 지어낸 것이 아니라 DB가 준 사실이다. 스텝 2의 ERROR 를 스텝 3의 사고가 읽고 가정을 고쳐 재시도한 것은 오류 복구이고, 스텝 5에서 "답할 수 있다"고 판단해 종료 행동을 부른 것은 종료 판단이다. 그리고 이 답 하나를 위해 LLM 추론이 5번, 도구 실행이 4번 들었다 — 스텝이 진행될수록 앞의 모든 사고·행동·관찰이 컨텍스트에 쌓였다는 사실과 함께.

주의 깊게 볼 취약점이 하나 숨어 있다. 스텝 3의 사고는 "status='retry_ok' 일 것"이라고 추측했다. 이번엔 맞았지만, 만약 실제 스키마가 달랐다면 이 쿼리는 오류를 던지지 않고 조용히 0을 세거나 잘못된 수를 셌을 것이고, 그 잘못된 수가 스텝 4의 비율로, 다시 스텝 5의 최종 답으로 아무 경고 없이 흘러갔을 것이다. 던지는 오류(스텝 2)는 모델이 보고 고칠 수 있지만, 조용히 틀린 관찰은 끝까지 간다. 에이전트의 각 스텝은 이런 작은 추측을 품고 있고, 스텝이 늘수록 그것들이 곱해진다는 것이 이 장 후반의 주제다.

6.6.6 계획 — 분해와 계획-실행 대 반응형

ReAct는 "다음 한 걸음"을 매번 정하는 반응형(reactive)이다. 목표가 짧으면 이것으로 충분하지만, 목표가 길고 여러 단계로 얽히면 한 걸음씩만 보는 근시안이 문제가 된다 — 열 걸음 뒤를 못 보고 초반에 잘못된 방향으로 벗어나면 그 뒤의 모든 스텝이 헛수고가 된다. 여기서 계획(planning)이 등장한다.

계획의 밑바탕은 분해(decomposition)다. "분기 보고서를 작성하라" 같은 큰 목표를 실행 가능한 작은 단위로 분할한다 — 데이터 수집 → 지표 계산 → 표 생성 → 초안 작성 → 검토. 분해가 잘 되면 각 단위는 한 번의 도구 호출이나 짧은 서브루프로 풀리고, 각 단위의 성공·실패를 따로 판정할 수 있어 전체가 관측 가능해진다. 분해 자체를 LLM에 시키는 것이 흔하다("이 목표를 실행 가능한 단계로 나눠라"). 좋은 분해는 단위들이 서로 독립이거나 의존 관계가 명확해서, 독립인 것은 병렬로 실행하고(6.6.12) 의존하는 것만 직렬로 잇게 해 준다.

계획을 언제 하느냐로 두 접근이 갈린다. 계획-실행(plan-and-execute)은 플래너가 처음에 전체 계획(단계 목록)을 한 번에 세우고, 실행기가 그 단계를 차례로 수행한다. 계획 수립에 LLM 호출을 몰아 쓰고, 실행 단계는 더 저렴하고 작은 모델로 실행할 수 있다. 반응형(ReAct)은 앞을 멀리 계획하지 않고 매 스텝 관찰을 보고 다음 한 걸음만 정한다. 세계가 바뀌거나 예상 못 한 관찰이 와도 곧장 적응한다.

계획-실행 (plan-and-execute) 반응형 (reactive · ReAct) 아니오 예 예 아니오 플래너: 전체 계획 수립 1) 수집 2) 계산 3) 작성 실행기: 단계 순차 실행 이탈·단계 실패? 완료 다음 한 걸음만 결정 행동 · 관찰 끝? 완료

두 접근의 성격은 정반대다. 계획-실행은 전체를 앞서 보므로 장기 일관성이 좋지만, 초기 계획이 굳어 있어 변화에 느리고 계획이 틀리면 전부 어긋난다. 반응형은 매 관찰에 즉시 적응하지만 한 스텝만 보므로 근시안적이고, 긴 과제에서 방향을 잃기 쉽다. 계획 LLM 호출도 다르게 든다 — 계획-실행은 앞에 몰아 한 번(또는 재계획 때마다) 쓰고, 반응형은 매 스텝 판단에 쓴다.

계획-실행반응형(ReAct)
앞을 보는 범위전체(장기 일관성 ↑)한 스텝(근시안)
계획 LLM 호출앞에 몰림매 스텝 판단
변화 적응느림(계획이 굳음)빠름(즉시 적응)
초기 계획이 틀리면전부 어긋남 → 재계획 필요스텝마다 교정
잘 맞는 곳안정적·예측가능·다단계 과제불확실·탐색적·환경 변동 큰 과제

실무는 대개 둘을 섞는다 — 큰 계획을 세우되 각 단계는 반응형 서브루프로 풀고, 관찰이 계획을 무효화하면 재계획한다. 순수한 한쪽 끝을 고집하는 시스템은 드물다. 단계가 뚜렷하고 안정적인 정형 리포트나 ETL형 다단계 과제라면 계획-실행에 무게를 싣는 편이 낫다 — 계획을 사람이 리뷰할 수도 있어 예측가능하고 감사가 쉽다. 반면 디버깅·리서치·웹 탐색처럼 환경이 불확실하고 탐색적이라면 반응형에 무게를 싣고 필요할 때 재계획한다 — 매 관찰에 적응하는 편이 이득이기 때문이다.

6.6.7 반성과 재계획 — 궤적을 스스로 고치기

계획을 세우고 실행하는 것만으로는 부족하다. 실행 중에 나온 결과가 계획의 전제를 위협할 때, 그리고 방금 낸 출력 자체에 결함이 있을 때, 이를 스스로 검출해 고치는 두 메커니즘이 반성과 재계획이다.

반성(reflection)은 모델이 자기 출력이나 궤적을 스스로 비판하고 고치는 단계다. "방금 계획이나 답에 빠진 것·틀린 것은 없나?"를 모델에게 묻고, 그 비판을 다음 시도의 재료로 쓴다. 코드 생성처럼 검증 신호가 있는 과제 — 테스트 실패 메시지를 관찰로 환류시키는 경우 — 에서 특히 효과가 보고된다(Self-Refine·Reflexion 계열의 연구가 대표적이다).

통과·상한 도달 시도 (초안·행동) 실행·검증 (테스트·규칙·관찰) 반성: 무엇이 틀렸나 자기비판 개선된 재시도 채택

반성은 범용 해법이 아니다. 가장 중요한 한계는, 모델이 자기를 비판할 때 그 비판이 원래 출력과 같은 맹점을 공유한다는 것이다. 스스로 틀린 줄 모르는 오류는 아무리 반성해도 못 검출한다 — 잘못된 사실을 확신하는 모델은 그 사실을 다시 검토해도 여전히 확신한다. 반성이 실제로 도움 되는 경우는 대개 외부 검증 신호가 반성의 재료로 들어올 때다. 테스트 결과·도구 오류·규칙 위반 같은, 모델 바깥에서 온 판정이 있어야 반성이 붙들 조정 변수가 생긴다. "자기 성찰만으로 정답에 수렴한다"는 주장은 과제와 설정에 크게 의존하며 반박하는 결과도 있으니, 검증 신호 없이 반성 루프만 실행하는 설계는 조심해야 한다. 게다가 반성은 시도를 한 번 더 하는 것이므로 스텝 수를 늘려 비용과 지연을 키운다(6.6.12) — 그래서 반성 루프에는 반드시 반복 상한을 건다.

재계획(replan)은 다른 문제를 다룬다. 계획은 결국 가정이다. 실행 중 관찰이 그 가정을 깨면 — 쓰려던 도구가 없어졌거나, 중간 결과가 예상과 다르거나, 새 정보가 나오면 — 옛 계획을 붙들지 말고 다시 구성해야 한다. 재계획을 부르는 트리거는 단계의 반복 실패, 관찰과 계획의 모순, 진전 없음이다. 여기서 중요한 것은 재계획을 언제 할지의 임계값이다. 너무 자주 재계획하면 매 관찰마다 계획을 전면 수정해 계획이 굳지 못하고 발산하고, 너무 안 하면 이미 무효가 된 계획을 끝까지 고집한다. 이 임계값을 설정하는 것이 계획형 에이전트 설계의 실질적 어려움이다.

검증 신호가 있는 반복 개선 과제 — 코드·수식·구조화 출력처럼 통과·실패를 기계가 판정할 수 있는 것 — 이라면 반성 루프에 상한을 걸어 쓰는 것이 잘 맞는다. 검증기가 반성의 조정 변수가 되어 주고, 상한이 비용 폭주를 막는다. 반면 검증 신호가 없는 열린 과제에서 반성 루프에만 의존하는 것은 위험하다 — 조정할 변수가 없으니 스텝만 늘고 품질은 안 오를 수 있다.

6.6.8 메모리 — 단기·스크래치패드·장기·에피소드

LLM은 상태가 없다. 호출과 호출 사이에 아무것도 기억하지 못한다. 그런데 에이전트 루프는 상태를 이어야 한다 — 스텝 1의 관찰을 스텝 5가 써야 하고, 어제의 대화를 오늘 이어야 한다. 이 상태를 담는 것이 메모리이고, 메모리는 하나가 아니라 계층이다.

컨텍스트 창 (모델이 매 스텝 실제로 보는 것 · 유한) 기록·요약(write) 검색(retrieve) 단기 (working) — 진행 중 트레이스 사고·행동·관찰의 누적 대화 · 최근 스텝들 스크래치패드 — 에이전트가 쓴 메모 중간 계산·계획·할 일 목록 · 자기가 다시 읽음 검색되어 들어온 것 (retrieved-in) 장기 메모리에서 이번 스텝에 필요한 조각만 장기 메모리 (외부 저장 · 사실상 무한) · 벡터 DB (의미 검색, 6.2·6.3) · 요약·압축된 과거 대화 · 키-값 사실 저장 (프로필·설정) · 에피소드 로그 (과거 궤적) 유한한 창에 다 못 넣음 → 필요할 때만 검색
에이전트 메모리 계층 — 무엇이 컨텍스트에 있고, 무엇이 밖에 있나. 규칙: 컨텍스트 창은 유한하고 비용이 크다(6.6.11·7.5). 장기 메모리는 밖에 두고, 매 스텝 "필요한 조각만" 창에 끌어온다. 이 "밖에 두고 필요할 때 검색"이 바로 RAG의 기계(6.3)와 같다 — 메모리 = 자기 자신의 과거에 대한 RAG.

단기(short-term / working) 메모리는 컨텍스트 창 그 자체다. 진행 중인 사고·행동·관찰의 누적 대화이며, 빠르고 정확하지만 유한하다 — 컨텍스트 길이와 비용에 갇힌다(6.6.11). 루프가 길어지면 여기서 가장 먼저 문제가 생긴다. 스크래치패드(scratchpad)는 에이전트가 스스로 쓰는 메모다 — 중간 계산, 계획, 할 일 목록. 보통 컨텍스트 안에 유지하거나 외부 파일·노트에 써 두고 필요할 때 다시 읽는다. 생각을 외부화해 컨텍스트를 정돈하고 긴 과제의 실마리를 붙드는 장치다. 장기(long-term) 메모리는 창 밖의 외부 저장소이고 형태가 여럿이다 — 벡터 DB(의미 검색, 6.2·6.3), 요약(옛 대화를 압축), 키-값 사실(사용자 프로필·설정), 에피소드 로그(과거 궤적). 무한히 쌓되 필요할 때만 검색해 창에 끌어온다. 에피소드(episodic) 메모리는 인지과학에서 빌린 구분으로, 무슨 일이 있었나(과거 상호작용·궤적)를 담는 에피소드, 세상 사실을 담는 의미(semantic), 하는 법을 담는 절차(procedural)를 나눈다. 실무에서 "에이전트가 지난번 실수를 기억한다"는 대개 에피소드 메모리를 검색해 다시 컨텍스트에 넣는 것이다.

메모리는 저장만 하면 되는 것이 아니라 관리해야 한다. 무엇을 쓸지(write)를 골라야 한다 — 모든 것을 저장하면 검색이 잡음투성이가 되니 중요한 사실·결정·실패만 요약해 넣는다. 언제 요약할지(compact)를 정해야 한다 — 단기 메모리가 차기 전에 옛 스텝을 압축한다. 어떻게 검색할지(retrieve)는 벡터 검색으로 "지금 질의와 의미가 가까운" 기억을 끌어오는 것인데, 이것이 바로 6.2의 임베딩·근사최근접(ANN)과 6.3의 RAG가 하는 그 기계다. 그리고 반성으로 여러 에피소드에서 교훈을 뽑아 절차·규칙으로 승격하는 응고(consolidate)는 아직 성숙도가 낮은 연구 영역이다.

여기서 조용히 살펴봐야 할 것이 있다. "장기 메모리를 가진 에이전트"는 판촉에서 화려하게 들리지만, 속을 열면 대개 과거 대화에 대한 벡터 검색이다 — 새 기계가 아니라 6.3의 RAG다. 그리고 그렇기 때문에 RAG의 모든 실패 양식을 그대로 물려받는다. 관련 기억을 못 찾는 검색 실패, 오래되고 모순된 기억, 잘못 검색된 기억이 현재 판단을 오염시키는 일이 똑같이 일어난다. "기억한다"가 "정확히 적시에 떠올린다"를 뜻하지 않는다. 메모리는 능력이자 또 하나의 오류원이며, 에이전트에 장기 메모리를 붙이는 것은 신뢰성을 추가 비용 없이 얻는 것이 아니라 RAG 파이프라인 하나를 더 운영하기로 하는 결정이다.

6.6.9 언제 멈추나 — 종료 조건과 헛돔 방지

루프의 가장 실무적인 두 문제는 언제 멈추나와 깨졌을 때 어떻게 하나다. 이것을 소홀히 하면 에이전트는 무한히 돌거나, 비용을 낭비하거나, 조용히 틀린 답을 낸다. 먼저 멈추는 문제부터 보자.

에이전트는 여러 이유로 멈추고, 그 이유를 전부 명시적으로 걸어야 한다. 정상 종료는 모델이 "다 됐다"며 최종 답변 행동을 부르는 것과, 검증기·테스트·규칙이 목표 달성을 확인하는 것 두 가지다. 안전장치로서의 종료는 스텝 상한(최대 반복 횟수 도달), 예산 상한(토큰·비용·시간 한도 초과), 진전 없음 감지(반복·정체), 그리고 사람에게 인계(확신이 낮거나 고위험이면 에스컬레이션)다.

종료 조건무엇성격
최종 답변 행동모델이 "다 됐다"며 종료 행동 호출정상 종료(모델 판단)
성공 검증 통과검증기·테스트·규칙이 목표 달성 확인정상 종료(외부 신호)
스텝 상한최대 반복 횟수 도달안전장치(무한 루프 차단)
예산 상한토큰·비용·시간 한도 초과안전장치
진전 없음 감지반복·정체 감지헛돔 차단
사람에게 인계확신 낮음·고위험 → 에스컬레이션안전한 항복

여기서 명시할 원칙 하나. 모델의 "다 됐다"만 믿으면 안 된다. 모델은 틀렸는데도 끝났다고 선언할 수 있다 — 조용한 오류(6.6.5의 취약점)를 사실로 받아들여 자신 있게 종료하는 경우가 대표적이다. 그래서 정상 종료 신호가 와도 가능하면 검증을 한 번 더 통과시키고, 스텝 상한과 예산 상한은 모델의 판단과 무관하게 코드가 강제해야 한다. 이것은 협상 대상이 아니라 하드 가드다.

예 예 아니오 아니오 예 아니오 예 아니오 루프 반복 시작 최종 답변 신호? 검증 통과? 종료: 성공 스텝·토큰·시간 예산 초과? 강제 종료 부분결과· 사람 인계 진전 없음 반복 감지? 헛돔 차단 대안·재계획 ·중단 한 스텝 실행
자율 루프의 종료 조건 판정

자율 루프의 고질병은 진전 없이 실행되는 것이다. 대표적인 양상이 몇 가지 있다. 진동(oscillation)은 같은 두세 행동을 번갈아 무한 반복하는 것이다(도구 A→B→A→B…). 동일 행동 반복은 실패한 도구 호출을 똑같은 인자로 계속 재시도하는 것이다. 헛돔(spinning)은 새 정보 없이 사고만 맴도는 것이다("생각해 보니… 다시 생각해 보니…"). 목표 표류(goal drift)는 원래 목표에서 벗어나 곁가지에 빠지는 것이다. 이것들을 탐지하는 신호는 동일·유사 행동의 반복(행동을 해시해 비교), 새 정보 없는 관찰(관찰이 이전과 중복), 예산은 소모되는데 목표 지표는 정체하는 것이다. 탐지되면 강제로 개입한다 — 온도를 높여 다른 행동을 유도하거나(1.7), 재계획을 부르거나, 사람에게 넘긴다. 그리고 이 모든 탐지가 실패해도 스텝 상한이 최후의 안전망으로 남는다.

6.6.10 깨졌을 때 — 오류 복구

도구는 실패한다(6.5) — 네트워크 오류, 잘못된 인자, 빈 결과, 권한 없음. 에이전트의 강점 하나는 이 오류를 관찰로 받아 적응할 수 있다는 것이다. 6.6.5의 스텝 3에서 봤듯, 오류 메시지를 읽고 가정을 고쳐 다시 시도하는 능력이 에이전트를 워크플로우보다 낫게 만드는 대목이다. 하지만 이 능력은 저절로 생기지 않고 설계해야 한다.

핵심은 오류를 무시하지 않고 관찰로 환류시키는 것이다. 예외를 조용히 처리하지 말고 오류 메시지 자체를 관찰로 모델에 준다. 좋은 오류 메시지가 좋은 복구를 부른다 — "column X does not exist"는 모델이 무엇을 고쳐야 할지 알려 주지만, 밋밋한 "쿼리 실패"는 모델을 눈멀게 한다. 그래서 도구의 오류 메시지를 설계하는 것 자체가 에이전트 신뢰성의 일부다.

오류를 일시적인 것과 영구적인 것으로 나눠 다루는 것도 중요하다. 일시적 오류(타임아웃·rate limit)는 백오프 후 재시도하면 풀린다. 영구적 오류(스키마 오류·권한 없음)는 재시도해 봐야 소용없으니 접근을 바꾸거나 다른 도구를 써야 한다. 이 둘을 구분하지 않으면, 영구적 오류를 무한 재시도하며 예산을 소진하거나(6.6.9의 동일 행동 반복), 반대로 일시적 오류에 곧장 포기하는 실수를 한다. 어느 쪽이든 재시도 횟수 상한을 걸어 같은 실패를 무한히 되풀이하지 않게 막는다. 한 도구가 막히면 다른 도구·다른 질의로 우회하고, 계획 자체가 틀렸으면 재계획한다(6.6.7).

그리고 다 안 될 때는 우아하게 실패해야 한다. 부분 결과와 함께 "여기까지 됐고 이건 확실치 않다"를 반환하거나 사람에게 인계하는 것이, 틀린 답을 자신 있게 내는 것보다 낫다. 실제로 가장 위험한 실패는 오류를 던지는 것이 아니라 조용히 틀리는 것이다 — 도구가 그럴듯하지만 잘못된 값을 돌려주고, 모델이 그것을 사실로 받아 전체를 오염시키는 경우다. 명시적 오류는 모델이 보고 복구할 수 있지만, 침묵하는 오류는 아무 신호 없이 최종 답까지 흘러간다. 그래서 관찰의 타당성을 확인하는 검증 단계가 종료 조건만큼 중요하다 — 결과가 상식적 범위 안에 있는지, 빈 결과가 실제 0인지 쿼리가 잘못된 것인지, 형식이 기대와 맞는지를 코드가 점검하면 조용한 오류의 상당수를 관찰 단계에서 검출할 수 있다.

6.6.11 누적되는 관찰 — 컨텍스트 관리와 프리픽스 캐시

에이전트 루프의 조용한 시한폭탄은 컨텍스트가 계속 자란다는 것이다. 매 스텝마다 사고·행동·관찰이 대화에 누적된다. 스텝 10에서 모델은 스텝 1부터 9까지의 모든 트레이스를 다시 본다. 관찰이 큰 도구 — 웹페이지 덤프, 긴 SQL 결과, 파일 내용 — 를 여러 번 부르면 컨텍스트는 순식간에 창을 채우고 비용은 폭증한다.

이 누적이 세 가지를 동시에 망가뜨린다. 첫째는 비용과 지연이다 — 매 스텝 입력 토큰이 늘어 추론이 비용이 커지고 느려진다(6.6.12). 둘째는 창 초과다 — 결국 컨텍스트 길이 한계에 부딪히면 옛것을 버리거나 실패한다. 셋째는 품질 저하다 — 창이 길어지면 중간에 놓인 정보가 묻혀 정확도가 떨어지는 "lost in the middle" 현상이 나타난다(5.6). 옛 관찰이 신호를 익사시키는 셈이라, 컨텍스트를 방치하면 길이 한계에 닿기 전에 이미 품질이 저하되기 시작한다.

핵심 전략은 창에 든 것을 능동적으로 관리하는 것이다. 옛 스텝들을 짧은 요약으로 대체하는 압축(compaction)은 정보 손실 위험과 요약용 LLM 호출 비용을 대가로 창을 비운다. 최근 N스텝만 유지하고 옛것을 버리는 절단(truncation)은 저렴하지만 초반 맥락을 잃는다. 관련된 관찰만 남기고 잡음을 제거하는 선택적 보존은 "무엇이 관련인가"를 판단할 근거가 필요하다. 관찰을 외부 메모리에 넣고 필요할 때만 검색해 오는 외부화(6.3)는 창을 작게 유지하지만 검색이 실패하면 정보를 못 본다. 원문 트레이스 대신 요약된 상태 객체를 유지하는 구조화 상태는 명료하지만 설계 부담이 크다. 그리고 가장 흔하고 값싼 처방 하나 — 50KB 크기의 도구 결과 전체를 넣지 말고 필요한 필드만 넣는 도구 결과 절단은, 잘못 자르면 핵심을 놓치지만 제대로 하면 컨텍스트 폭증의 상당 부분을 원천에서 막는다.

전략무엇트레이드오프
요약·압축(compaction)옛 스텝들을 짧은 요약으로 대체정보 손실 위험, 요약에 LLM 호출
윈도잉·절단(truncation)최근 N스텝만 유지, 옛것 버림초반 맥락 상실
선택적 보존관련된 관찰만 남기고 잡음 제거"관련"을 무엇이 판단하나
외부화 + 검색관찰을 메모리에 넣고 필요할 때만 검색검색 실패 시 정보 못 봄
구조화 상태원문 트레이스 대신 요약된 상태 객체 유지설계 부담, 유연성↓
도구 결과 절단큰 덤프 전체를 넣지 말고 필요한 필드만잘못 자르면 핵심 누락

여기서 서빙 쪽 수단 하나가 크게 작동한다. 에이전트 트레이스는 앞부분이 매 스텝 거의 그대로 반복되는 성질이 있다 — 시스템 프롬프트, 도구 정의, 초반 스텝은 루프가 반복해도 바뀌지 않는다. 이 공통 프리픽스를 프리픽스 캐시로 재사용하면(5.6), 매 스텝 같은 앞부분의 KV를 다시 계산하고 다시 과금하는 낭비를 크게 줄인다 — 이것이 에이전트 비용 최적화의 가장 큰 수단이다. 그런데 여기에 함정이 하나 숨어 있다. 앞부분을 자주 바꾸면 — 예컨대 요약으로 프리픽스가 달라지면 — 캐시 전체가 무효화돼 오히려 불리하다. 그래서 요약·압축은 트레이스의 뒤에서 하고 안정된 앞부분은 변경하지 않는 것이 유리하다. 관리를 열심히 한다면서 프리픽스를 변경하면, 절약하려던 것보다 더 큰 비용을 캐시 미스로 치를 수 있다.

실무 에이전트의 신뢰성 상당 부분은 "얼마나 똑똑한 계획"이 아니라 "컨텍스트를 얼마나 깔끔하게 관리하는가"에서 온다는 것이 내 견해다. 관찰을 무절제하게 쌓으면 긴 과제는 비용과 품질 양쪽에서 붕괴한다. 도구 결과를 컨텍스트에 넣기 전에 요약하고 필터하는 습관이, 화려한 계획 전략보다 실전에서 더 크게 남는다.

6.6.12 비용과 지연 — 루프는 곧 여러 번의 추론

에이전트의 값은 여기서 결정된다. 한 번의 최종 답을 위해 LLM 추론을 여러 번 실행한다. 6.6.5의 예는 답 하나에 추론 5번이 들었다. 스텝이 $k$ 번이면 추론도 대략 $k$ 번이고, 앞서 봤듯 각 스텝의 입력이 누적돼 점점 커진다. 이 두 효과가 겹치면 비용이 스텝 수에 초선형으로 는다.

스텝마다 컨텍스트가 대략 일정량 $\Delta$ 씩 늘어난다고 하자(사고+행동+관찰). 고정 프리픽스가 $C_0$ 이면 스텝 $i$ 의 입력 토큰은 대략 $C_0 + i\,\Delta$ 이고, $k$ 스텝 전체의 입력 토큰 합은

$$T_{\text{in}}(k) \approx \sum_{i=0}^{k-1}(C_0 + i\,\Delta) = k\,C_0 + \Delta\,\frac{k(k-1)}{2} \;=\; O(k^2).$$

숫자를 넣어 보자. 시스템 프롬프트와 도구 정의를 합친 고정 프리픽스가 $C_0 = 1{,}500$ 토큰, 한 스텝이 컨텍스트에 더하는 양이 $\Delta = 800$ 토큰(사고 150 + 행동 50 + 관찰 600가량)이라 하자. $k = 8$ 스텝이면 총 입력 토큰은 $8\times1500 + 800\times\frac{8\times7}{2} = 12{,}000 + 22{,}400 = 34{,}400$ 토큰이다. 만약 매 스텝 입력이 프리픽스 $1{,}500$ 으로 고정이었다면 $12{,}000$ 이었을 텐데, 누적이 이를 세 배 가까이 부풀렸다. 그리고 이것은 스텝이 늘수록 급해진다 — $k=20$ 이면 $20\times1500 + 800\times190 = 30{,}000 + 152{,}000 = 182{,}000$ 토큰으로, 스텝이 2.5배(8→20) 되는 사이 입력 토큰은 5.3배가 된다. 이 $34{,}400$ 토큰을 입력 100만 토큰당 몇 달러라는 개략 단가(정확한 값은 벤더·모델·시점마다 다르니 공식 가격표를 확인해야 한다)로 환산하면, 답 하나의 입력 비용만 해도 단발 호출의 수십 배가 된다. 여기에 스텝마다의 출력 토큰과 도구 실행 비용이 더 추가된다. 단발 호출 대비 에이전트가 10배·100배 비용이 클 수 있는 근본 이유가 이 제곱 항이다. 6.6.11의 요약·압축과 프리픽스 캐시가 하는 일이 바로 이 $O(k^2)$ 를 눌러 $O(k)$ 에 가깝게 만드는 것이다 — 캐시가 앞부분 재계산을 없애면 실효 입력이 누적분에서 증분분으로 줄어든다.

더 문제가 되는 것은 지연이다. 루프는 직렬이다 — 스텝 $i$ 의 행동은 스텝 $i-1$ 의 관찰에 의존하므로 병렬화할 수 없다. 사용자가 기다리는 시간은 대략 모든 스텝의 추론 지연과 도구 지연의 합이다.

$$L_{\text{total}} \approx \sum_{i=1}^{k}\big(L^{\text{LLM}}_i + L^{\text{tool}}_i\big).$$

역시 숫자를 넣어 보자. 스텝당 LLM 추론이 대략 2초(프리필 + 200토큰가량의 디코드, 5.1), 도구 실행이 대략 1.5초라 하면, $k=8$ 스텝 길이의 과제는 $8\times(2 + 1.5) = 28$ 초다. 단발 챗의 2초 남짓 즉답과 완전히 다른 UX다. 게다가 스텝이 진행될수록 입력이 커져 프리필이 부하가 커지므로 후반 스텝의 $L^{\text{LLM}}_i$ 는 초반보다 길어진다 — 위 계산은 오히려 낙관적이다. 사용자는 화면 앞에서 30초를 기다리는데, 그동안 아무 피드백이 없으면 실패로 느낀다.

그래서 완화책은 두 방향이다. 총 시간을 실제로 줄이는 쪽으로는, 더 나은 계획·분해로 왕복 자체를 줄이는 것이 가장 크다. 쉬운 스텝은 저렴하고 빠른 모델에, 어려운 판단만 큰 모델에 보내는 모델 라우팅(5.6)으로 루프 전체를 최상위 모델로 보내는 낭비를 막는다. 프리픽스 캐시로 반복되는 앞부분의 재계산을 없애 스텝당 추론을 저렴하고 빠르게 한다. 서로 독립인 도구는 한 스텝에서 동시에 부른다 — 세 소스를 병렬로 검색하는 식인데, 의존이 없는 곳에서만 가능하다. 총 시간을 못 줄이는 경우엔 체감이라도 낫게 한다 — 중간 진행(사고·부분 결과)을 스트리밍해 "일하고 있음"을 보이면(7.1), 같은 28초라도 사용자가 견디는 시간이 달라진다.

데모에서 "에이전트가 알아서 다 했다"는 이 비용·지연 부담을 숨긴다. 프로덕션에서 사용자당·과제당 수십 번의 추론은 유닛 이코노믹스를 붕괴시킬 수 있다(7.5). 에이전트를 추가하기 전에 "이 과제 하나에 추론 몇 번, 몇 초, 얼마가 드는가"를 반드시 견적해야 한다. 그 견적을 해 보면 단발 호출이나 얕은 워크플로우가 더 저렴하고 빠르고 신뢰할 만한 경우가 생각보다 많다는 것을 알게 된다.

6.6.13 에이전트는 취약하다 — 복합 실패와 데모-프로덕션 간극

이 절이 이 장의 양심이다. 앞의 모든 능력에도 불구하고 에이전트는 근본적으로 취약하다. 이것을 모르고 배포하면 데모의 인상이 프로덕션의 악몽이 된다.

에이전트 취약성의 수학적 핵심은 오류가 곱해진다는 것이다. 각 스텝이 독립적으로 확률 $p$ 로 올바르게 수행된다고 하면, $k$ 스텝을 전부 성공할 확률은

$$P_{\text{성공}}(k) \approx p^{\,k}.$$

스텝 성공률이 꽤 높아 보여도 곱하면 붕괴한다. 스텝당 0.95는 "꽤 괜찮은" 신뢰도처럼 들리지만, $0.95^{20} = 0.358$ 이다 — 20스텝 길이의 궤적의 성공률이 36%로 떨어진다. 뒤집어 말하면, 10스텝 과제에서 종단 성공률 90%를 원한다면 스텝당 성공률이 $0.90^{1/10} \approx 0.99$ 여야 한다. 스텝당 99% 정확도는 지금의 도구·모델로 쉽지 않은 목표이고, 이 한 줄이 "긴 자율 궤적은 왜 그렇게 잘 깨지는가"를 설명한다.

스텝당 성공률 $p$5스텝10스텝20스텝
0.990.950.900.82
0.950.770.600.36
0.900.590.350.12
0 5 10 15 20 0.0 0.2 0.4 0.6 0.8 1.0 스텝 수 k 성공률 p = 0.99 p = 0.95 p = 0.90
복합 신뢰도 — 전체 성공률 $\approx p^k$ (스텝당 $p$, $k$스텝)

이 $p^k$ 는 물론 단순화다. 실제로는 스텝이 독립이 아니고(앞 오류가 뒤를 좌우한다), 어떤 오류는 복구·자기교정되며(반성·재시도가 $p$ 를 부분적으로 되살린다), 어떤 스텝은 다른 스텝보다 치명적이다. 그래도 방향은 확고하다 — 스텝이 늘수록 신뢰도는 곱셈으로 깎이고, 자율성이 길어질수록 취약해진다. 6.6.7·6.6.10의 복구 메커니즘은 이 곡선을 완화하지 그 법칙을 없애지 못한다. 이것이 "짧고 제약된 워크플로우가 종종 긴 자율 에이전트를 이긴다"의 수학적 근거다.

복합 실패는 여러 양상으로 나타난다. 오류 누적·연쇄(cascade)는 초반의 잘못된 관찰 하나가 이후 모든 사고를 오염시키는 것으로, 특히 조용한 오류(6.6.10)가 위험하다 — 끝까지 발견되지 않고 자신 있게 틀린 답으로 나온다. 비결정성은 같은 입력이 매번 다른 궤적을 낳는 것으로(샘플링, 1.7), 버그를 재현·디버깅하기 어렵게 만들고 테스트를 불안정하게(flaky) 만든다. 보안 확대는 도구와 자율성이 곱해져 공격면이 넓어지는 것으로, 검색·도구가 가져온 내용에 숨은 간접 프롬프트 인젝션이 에이전트를 탈취해 위험한 도구(파일 삭제·송금·외부 전송)를 부르게 할 수 있다 — 자율 에이전트는 이 위험이 특히 크고, 도구 권한 최소화와 인간 승인 관문이 필수다(7.4). 그리고 평가의 함정이 있다 — 에이전트 벤치마크 점수는 과제 선택·하네스·데이터 누출에 민감해서, 화려한 성공률이 좁은 과제 분포에서 나온 것일 수 있다(4.1).

이 모든 것이 하나로 모이는 지점이 데모와 프로덕션의 간극이다. 에이전트 데모는 행복 경로(happy path)를 보인다 — 정제된 입력, 잘 작동하는 도구, 예상된 질문. 프로덕션은 다르다 — 모호하고 적대적인 입력, 도구 장애, 엣지케이스, 악의적 사용자. 데모에서 90%처럼 보인 성공률이 실사용에서 절반으로 떨어지는 일이 흔하다. 여기서 이 장과 다음 장을 잇는 핵심 견해가 나온다. 자율성은 그 자체로 목표가 아니다. 더 많은 자율성은 더 큰 실패면, 더 낮은 예측가능성, 더 높은 비용($p^k$)을 뜻한다. 많은 실전 문제는 좁고 제약된 워크플로우 — 고정된 단계, 명확한 분기, 국소 LLM 호출 — 로 풀 때 더 신뢰할 만하고 저렴하고 디버깅이 쉽다.

그래서 설계의 원칙은 밋밋하지만 확고하다. 가장 단순한 것부터 시작한다 — 단발 호출에서 얕은 워크플로우로, 그리고 필요할 때만 자율 루프로 올라가며, 자율성은 비용을 정당화할 때만 추가한다. 자율성을 주더라도 제약한다 — 도구 권한 최소화, 스텝 상한, 예산 상한, 고위험 행동엔 인간 승인 관문. 전 궤적(사고·행동·관찰)을 로깅·트레이싱해 관찰 가능하게 만든다 — 에이전트는 관찰 없이는 운영할 수 없다(7.6). 그리고 좁게 시작한다 — 한 도메인, 한 종류 과제로 좁혀 신뢰도를 확보한 뒤 넓힌다. "AGI로 가는 자율 에이전트"류의 서사는 현시점 프로덕션 현실과 거리가 있고, 실무의 진실은 소박하다 — 잘 제약된 좁은 에이전트·워크플로우는 잘 실행되고, 자유로운 범용 에이전트는 대체로 취약하다. 이것은 비관이 아니라 설계 지침이다. 취약성을 인정하고 그 둘레에 가드를 세우는 쪽이, 자율성을 신봉하는 쪽보다 더 나은 제품을 만든다.

이 장은 에이전트 하나를 어떻게 실행하는가 — 루프·ReAct·계획·메모리·종료·복구, 그리고 그것을 제약하는 비용과 취약성 — 를 세웠다. 6.7은 바로 이 취약성을 정면으로 받아, 언제 자율 대신 워크플로우인가를 따지고, 여러 에이전트로 역할을 나누고 오케스트레이션하는 패턴(오케스트레이터-워커·평가자-생성자·라우팅)을 펼치며, 도구와 컨텍스트를 표준으로 잇는 MCP(Model Context Protocol)를 다룬다. 이 장이 부품 하나였다면, 다음 장은 여러 부품을 어떻게 신뢰성 있게 결합하는가다.