콘텐츠로 이동

에이전트 오케스트레이션

분류: Layer 12 - AI 시스템 & LLM 애플리케이션 | 선수지식: L12-20 (Prompt), L12-40 (RAG), L12-45 (Fan-out), L12-50 (Tool calling)

에이전트 오케스트레이션 — State, Planner, Tool Loop, Multi-Agent

섹션 제목: “에이전트 오케스트레이션 — State, Planner, Tool Loop, Multi-Agent”

에이전트 오케스트레이션은 LLM, 도구, 상태, 메모리, 종료 조건을 묶어 여러 단계의 작업을 제한된 자율성 안에서 실행하게 만드는 설계다. 핵심은 “모델이 알아서 다 하게 한다”가 아니라, 어떤 단계는 코드로 고정하고 어떤 단계만 LLM에게 선택하게 할지 정하는 것이다.

LLM 에이전트는 보통 LLM + tools + memory/state + planning + termination의 조합이다. 여기서 오케스트레이션은 각 부품을 이어 붙이는 배선이 아니라, 다음 행동을 누가 결정하는가, 중간 상태를 어디에 저장하는가, 실패했을 때 어디로 되돌아가는가, 언제 멈추는가를 정하는 실행 모델이다.

앞 문서들에서 배운 LLM API, prompt, RAG, fan-out, tool calling은 모두 중요한 부품이다. 특히 현재 의존 그래프는 Fan-out -> Tool calling -> Agent이므로 fan-out도 정상 읽기 경로에 포함된다. 다만 첫 회독에서는 독립 작업을 병렬로 실행하고 결과를 합친다는 핵심만 잡고, branch 상한·timeout·fan-in 같은 고급 분기 설계는 훑어봐도 된다. 제품의 실제 요청은 종종 한 번의 prompt나 한 번의 tool call로 끝나지 않는다.

예를 들어 “이 PR을 리뷰하고, 관련 문서를 찾아 근거를 붙이고, 테스트 실패 원인을 요약해줘”라는 요청은 최소한 다음 상태를 거친다.

요청 이해
-> 관련 파일/문서 검색
-> diff 읽기
-> 테스트 로그 확인
-> 문제 후보 정리
-> 필요하면 다시 검색
-> 최종 리뷰 작성

이 흐름에서 RAG는 근거를 찾는 단계, tool calling은 파일·로그·테스트를 조회하는 단계다. 그러나 검색 결과가 비었을 때 다시 검색할지, 테스트가 실패했을 때 어느 파일을 볼지, 충분한 근거를 얻었는지, 비용 한도에 도달했는지는 별도의 실행 구조가 필요하다. 이 구조가 agent orchestration이다.

처음 공부할 때 붙잡을 질문은 다섯 가지다.

  • Workflow vs Agent 결정: 코드가 허용할 제어 그래프와 경계를 미리 정할 수 있으면 workflow가 기본값이다. LLM router가 그 그래프 안의 분기를 고를 수는 있지만, 허용되지 않은 step이나 tool을 새로 만들지는 못한다.
  • Planner와 Executor 분리: 계획을 세우는 역할과 실제 tool을 호출하는 역할을 분리하면 비용·권한·디버깅 경계가 선명해진다.
  • Tool loop 제어: 생각 -> 도구 호출 -> 관찰 -> 상태 갱신이 반복되므로 max step, budget, 중복 호출 방지가 필요하다.
  • Memory와 State 관리: 현재 실행 상태, 세션 기록, 장기 기억은 서로 다른 저장소와 수명 주기를 가진다.
  • Graph와 Handoff 설계: 여러 agent나 노드가 있다면 누가 누구에게 넘기고, 어떤 조건에서 되돌아오고, 어떤 trace를 남길지 정해야 한다.

잘 설계된 오케스트레이션은 모델 능력을 키우는 장치가 아니라 불확실한 모델 행동을 제품 시스템 안에 가두는 장치다. 잘못 설계하면 비용·latency·권한 사고·무한 루프가 prompt 하나보다 훨씬 크게 터진다.

3.0 선행 기술의 한계 — 단일 LLM·도구 호출만으로 부족했던 이유

섹션 제목: “3.0 선행 기술의 한계 — 단일 LLM·도구 호출만으로 부족했던 이유”

L12-20(Prompt), L12-40(RAG), L12-45(Fan-out), L12-50(Tool calling)의 패턴은 모두 LLM 호출의 입출력과 외부 연결을 강하게 만든다. 이 가운데 fan-out은 tool calling이 활용하는 병렬 실행 감각을 제공하므로 정상 선수 경로다. 다만 bounded branch·timeout·fan-in의 세부 설계는 첫 회독에서 전부 숙달하지 않아도 된다. 그래도 한계가 남는다. 이유는 이 패턴들이 주로 한 번의 요청을 어떻게 더 잘 처리할까에 초점을 두기 때문이다.

한계는 세 지점에서 드러난다.

  • 단계 수가 사전에 정해지지 않는다. “버그 리포트 확인 -> 관련 파일 탐색 -> 수정 후보 생성 -> 테스트 실행 -> 실패하면 다시 탐색”처럼 몇 번 반복해야 할지 입력마다 다르다. Tool calling은 특정 도구를 호출하는 계약을 제공하지만, 다음 step을 계속 고르는 실행 정책까지 제공하지 않는다.
  • 중간 관찰에 따라 분기해야 한다. RAG의 기본형은 retrieve -> answer다. 검색 결과가 비었거나 서로 충돌하거나 권한 필터에 걸렸을 때 query rewrite, 추가 source 검색, refusal 중 무엇을 할지 결정해야 한다.
  • 상태가 호출 사이를 건너 살아남아야 한다. context window 안의 메시지는 working memory로 쓸 수 있지만, 장기 사용자 선호, 이전 승인, 이미 호출한 tool argument, retry 횟수, 비용 잔액은 별도 state로 관리해야 한다.

Agent orchestration은 이 문제를 네 가지 메커니즘으로 푼다.

  • 자율 결정 loop (while not done: thought → action → observation → state update)으로 단계 수를 LLM이 동적으로 결정 (§3.3).
  • Planner/Executor 분리로 “무엇을 할지”와 “어떻게 안전하게 실행할지”를 나눈다 (§3.5).
  • State machine 또는 orchestration graph로 중간 분기·재시도·checkpoint를 명시한다 (§3.8).
  • Short/Long-term memory 분리로 현재 작업 상태와 세션 간 기억을 다른 정책으로 관리한다 (§3.4).

단, 이 자율성은 단계 수와 비용으로 직결된다. 가령 한 step마다 모델을 한 번 호출하는 가상 loop가 8 step을 돌면 모델 호출도 8회 누적된다. tool latency, trace, error handling도 각 step에 따라 늘어난다. 그래서 “workflow가 default, agent는 신중히”라는 권고가 중요하다. 출처: Anthropic, Building effective agents (2024-12).

용어첫 정의헷갈리기 쉬운 경계
Orchestration여러 LLM/tool step을 상태와 조건에 따라 실행·분기·종료하는 설계단순 SDK 호출 묶음이 아니라 실행 정책이다
Workflow코드가 허용 node·edge·종료 경계를 미리 정의한 흐름LLM router가 허용된 branch를 골라도 제어 그래프가 코드에 닫혀 있으면 workflow다
AgentLLM이 관찰을 보고 허용 범위 안에서 다음 행동·tool을 동적으로 고르는 반복 실행자권한·예산·종료 경계까지 LLM에게 맡긴다는 뜻은 아니다
Planner목표를 하위 작업과 순서로 나누는 구성요소plan은 가설이므로 실행 중 관찰에 따라 바뀔 수 있다
Executor계획 또는 LLM 행동을 실제 tool call로 옮기는 구성요소schema·권한·멱등성(idempotency, 같은 요청을 여러 번 처리해도 효과가 한 번과 같게 하는 성질)은 executor나 앱 레이어가 강제한다
Tool loopLLM decision -> tool call -> observation -> state update 반복tool calling 1회가 아니라 반복과 종료 조건까지 포함한다
State현재 실행의 사실: step count, tool history, budget, status장기 memory와 다르다. state는 실행 제어용이고 더 엄격히 재현되어야 한다
Memory세션 내/세션 간 기억: 대화 요약, 사용자 선호, 과거 결정틀린 기억이 들어가면 다음 실행을 오염시키므로 검증·삭제 정책이 필요하다
Handoff한 agent나 노드가 다른 agent·사람·workflow로 작업을 넘기는 행위”대화만 넘김”이 아니라 goal, state, 권한, 완료 기준을 함께 넘겨야 한다
Orchestration graphnode와 edge로 agent 실행 경로를 표현한 구조그림이 아니라 복구·관측·테스트 가능한 실행 모델이어야 한다

3.1 Workflow vs Agent — Anthropic 분류 (2024-12)

섹션 제목: “3.1 Workflow vs Agent — Anthropic 분류 (2024-12)”

Anthropic의 “Building effective agents”가 정착시킨 분류.

LLM과 tools를 코드가 사전 정의한 제어 그래프와 경계 안에서 오케스트레이션한다. routing처럼 LLM이 분기를 선택해도, 선택 가능한 branch, 각 branch의 tool 권한, 합류점, 종료 조건을 코드가 미리 제한하면 workflow다. 따라서 workflow는 “LLM이 결정을 전혀 하지 않는 흐름”이 아니라 LLM의 결정 공간이 코드로 닫힌 흐름이다.

  • 결정성·재현성·디버깅 좋음
  • 대부분의 production 작업에 충분
  • 패턴: prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer

여기서 말하는 workflow는 “LLM application pattern”이다. Temporal, Step Functions 같은 일반 workflow engine은 결제·승인·배치처럼 긴 비즈니스 절차를 실행하고 복구하는 인프라 계층이고, durable execution은 그 engine이 장애 후에도 이어 달릴 수 있게 만드는 실행 의미론이다. Agent orchestration은 그 위나 옆에서 LLM이 어떤 step·tool·worker를 선택할지를 다룬다. 즉 같은 “오케스트레이션”이라도 관심사가 다르다.

Workflow 비교

일반 workflow engine

주 질문: 오래 걸리는 업무 절차를 어떻게 실행할까?

대표 도구/패턴: Temporal, Step Functions, Airflow

Durable execution

주 질문: worker crash 후 어디서 어떻게 재개할까?

대표 도구/패턴: Temporal history/replay, checkpoint

LLM workflow pattern

주 질문: 정해진 LLM/tool 단계를 어떻게 조합할까?

대표 도구/패턴: chaining, routing, evaluator loop

Agent orchestration

주 질문: 다음 행동을 LLM이 언제 자율 결정하게 할까?

대표 도구/패턴: ReAct loop, planner, multi-agent

분산 시스템 관점에서는 이 구분이 중요하다. workflow engine은 queue, retry, timer, idempotency, consistency 같은 L6~L9 문제를 숨기지 않고 실행 모델로 묶어준다. agent orchestration은 그 실행 모델 위에서 모델의 자율성을 얼마나 허용할지 정한다. CAP·일관성·네트워크 장애 직관은 L9 분산 시스템 기초를 참조하면 된다.

LLM이 관찰 결과에 따라 다음 행동이나 tool 사용 순서를 동적으로 결정한다. 코드가 여전히 tool allowlist, 예산, 승인, 종료 상한을 강제하지만, 정상 경로의 step 수와 행동 순서를 모든 입력에 대해 미리 열거하기 어렵다는 점이 workflow와 다르다.

  • 동적·유연하나 비용·실패 ↑
  • 도구·환경 피드백을 받아 동작 조정 (loop)
  • production은 신중하게 (Anthropic: “agents shine for ill-defined, open-ended problems”)
Workflow가 적합:
- 입력·출력과 허용 node·edge를 코드로 열거할 수 있음
- LLM router가 골라도 선택지가 사전 정의된 branch 안에 닫힘
- 결정성·디버깅 중요
- 비용·latency 예측 필요
Agent가 적합:
- 관찰 전에는 필요한 단계 수나 tool 순서를 열거하기 어려움
- 새 관찰에 따라 다음 행동 후보 자체가 달라짐
- 이 동적 판단의 가치가 추가 비용·실패면보다 큼
- production에서는 sandboxing·human review 결합

3.2 Workflow 5대 패턴 (Anthropic 분류)

섹션 제목: “3.2 Workflow 5대 패턴 (Anthropic 분류)”

작업을 N개 step으로 분해, 각 step LLM 호출 결과를 다음 step 입력으로.

Step 1: 마케팅 카피 작성
Step 2: 톤 검사 (gate) → 통과 못하면 Step 1로
Step 3: 다국어 번역

입력 분류 후 적절한 핸들러로.

사용자 질문 → Router (작은 LLM)
├─ 일반 → cheap model
├─ 코드 → code-specialized
└─ 의료 → expert workflow

여러 LLM 호출을 동시에 → 결과 합산.

  • Sectioning: 작업을 독립 sub-task로 분할
  • Voting: 같은 작업 N번 후 majority

검색 fan-out과 같은 계열이다. Fan-out 검색 패턴은 이 문서까지 이어지는 정상 선수 경로이며, 여기서는 독립 작업을 동시에 실행하고 결과를 합친다는 핵심을 사용한다. bounded branch, timeout, fan-in 같은 고급 분기 설계는 첫 회독에서 훑어보고 나중에 다시 확인해도 된다.

중앙 LLM(orchestrator)이 sub-task 동적 결정·worker LLM에 위임.

사용자 요청 → Orchestrator
├─ Plan: "검색 + 분석 + 글쓰기 필요"
├─ Worker A: 검색
├─ Worker B: 분석
└─ Worker C: 글쓰기 → Orchestrator 통합

한 LLM이 응답 생성, 다른 LLM이 평가·피드백 → 반복.

  • 번역·코드 리뷰·창작에서 효과적
  • 명확한 평가 기준이 있을 때

Evaluator-Optimizer는 넓게 보면 critic loop다. 초안 생성자(generator)가 산출물을 만들고, 비평자(critic/evaluator)가 기준에 맞춰 결함을 찾은 뒤, 수정자(optimizer/reviser)가 다시 고친다.

draft = Generator(input)
for i in range(max_rounds):
critique = Critic(draft, rubric)
if critique.pass:
break
draft = Reviser(draft, critique)
return draft

대표 변형:

  • Evaluator-Optimizer: 하나의 산출물을 rubric으로 평가하고 반복 개선. 코드 리뷰·번역·요약 품질 개선에 적합
  • Debate → Review → Revise: 서로 다른 관점의 agent가 찬반·대안을 만든 뒤, judge가 쟁점을 정리하고 reviser가 최종안 작성
  • Self-Refine: 같은 모델이 자기 산출물을 비판·수정. 저렴하지만 같은 편향을 반복할 위험
  • Cross-Model Critic: generator와 critic을 다른 모델로 분리. 비용은 늘지만 blind spot이 줄어듦

적용 전제는 “비평 기준이 코드나 체크리스트로 표현 가능해야 한다”는 점이다. 기준이 모호하면 critic은 실제 품질보다 말투·길이·자신감 같은 표면 신호에 끌린다.

Agent loop는 tool calling을 감싼 반복 실행 구조다. 모델은 한 번에 최종 답을 만들 수도 있고, 현재 state를 보고 tool을 호출하자고 제안할 수도 있다. 애플리케이션은 그 호출을 검증·실행하고, observation을 state에 붙여 다시 모델에게 준다.

while not done:
decision = LLM(state) # 다음 행동 결정
append(state.messages, decision)
calls = extract_tool_calls(decision) # final 판단보다 먼저 추출
if has_final_text(decision) and calls is empty:
done = "normal" # final text만 있으면 정상 종료
break
# final text와 calls가 함께 있어도 final로 인정하지 않고 tool 경로로 보낸다.
reason = duplicate_guard(calls, state) or cap_guard(state)
if reason:
for call in calls:
state = append(state, matching_tool_response(call.id, skipped=reason))
state = append(state, final_assistant_without_tool_calls(reason))
done = reason # 모든 pending call을 닫은 뒤 guard 종료
break
actions = parse_actions(calls)
observations = execute(actions) # 정상 tool 실행
state = update(state, actions, observations)
continue # observation을 넣고 다음 decision으로
  • LLM: 사고·결정 엔진
  • Tools (L12-50): 외부 세계 액션
  • State: 이번 실행의 현재 위치·관찰·예산·종료 여부
  • Memory: 단·장기 정보 저장
  • Planning: 작업 분해
  • Termination: 종료 조건 (max_iter, success signal)

작은 worked example을 보자. 사용자가 “이 장애 리포트를 보고 원인을 좁혀줘”라고 요청했고, agent에게 search_logs, get_deploys, read_runbook 세 tool만 허용했다고 하자.

stepstate에서 중요한 사실LLM이 고른 actionobservation 이후 다음 판단
1goal=장애 원인 후보 찾기, step=0search_logs(service="checkout")에러가 14:05부터 늘었다
2error_start=14:05get_deploys(service="checkout")14:02 배포가 있었다
3deploy_id=d-42, error=timeoutread_runbook(topic="downstream timeout")DB timeout과 외부 결제 API timeout을 구분해야 한다
4이미 로그/배포/runbook 조회 완료final answer원인 후보, 근거, 다음 사람이 확인할 항목으로 종료한다

이 예제에서 agent의 가치는 tool을 많이 쓰는 데 있지 않다. 관찰값에 따라 다음 조회를 바꾸고, 이미 확인한 사실을 state에 남기고, 더 조회할 가치가 없을 때 멈추는 것이 가치다. 같은 search_logs를 같은 인자로 5번 반복한다면 agent가 아니라 실패한 loop다.

Memory를 보기 전에 state와 memory를 분리해야 한다. 둘 다 “기억”처럼 보이지만 운영 성격이 다르다.

구분수명예시깨지면 보이는 증상
Execution state한 요청 또는 한 workflow 실행step_count, 마지막 tool 결과, 사용한 budget, 현재 noderollback·재시도·trace replay가 안 되고 같은 tool을 반복
Short-term memory현재 대화/세션최근 메시지, tool observation 요약, 현재 작업 맥락context가 길어져 비용이 늘고 오래된 오류를 계속 참조
Long-term memory여러 세션사용자 선호, 과거 승인, 프로젝트 규칙, 도메인 사실틀린 기억이 계속 검색되어 다른 요청까지 오염

첫 구현에서는 long-term memory보다 execution state를 먼저 안정화하는 편이 낫다. state가 불안정하면 같은 요청 안에서도 디버깅이 안 되는데, 여기에 장기 memory까지 붙이면 실패 원인을 분리하기 어렵다.

현재 conversation·tool 호출 history. context window 안.

  • 너무 길어지면 비용·lost-in-middle
  • summarization으로 압축
  • relevant 부분만 retrieve해 prompt에 (selective)

여러 session 사이에 유지되는 정보. 외부 store (vector DB, key-value store).

  • 사용자 프로필·선호 (예: “사용자는 한국어 답변 선호”)
  • 과거 대화 요약
  • 학습한 도메인 지식
  • 표준 도구: Mem0, Letta (formerly MemGPT), Zep
  • Episodic: 특정 사건 (when/where/who/what)
  • Semantic: 추상화된 지식 (“Python은 동적 타입”)

Planning은 “모델이 생각을 길게 한다”가 아니라 목표를 실행 가능한 하위 작업으로 나누고, 관찰 결과에 따라 계획을 갱신하는 정책이다. 구현에서는 planner와 executor를 분리해서 생각하면 이해가 쉽다.

Planner:
goal을 읽고 하위 작업 후보를 만든다.
어떤 tool이나 worker가 필요한지 고른다.
성공 조건과 중단 조건을 제안한다.
Executor:
planner가 제안한 step을 실제 tool call로 실행한다.
schema, 권한, budget, idempotency를 검사한다.
observation을 state에 기록하고 planner에게 돌려준다.

Planner가 항상 별도 모델일 필요는 없다. 작은 agent에서는 같은 LLM 호출 안에서 다음 action을 고르는 것으로 충분하다. 중요한 경계는 “계획이 틀릴 수 있다”는 점이다. 따라서 좋은 planning은 처음 plan을 길게 쓰는 능력보다, 관찰 후 plan을 버리거나 줄이는 능력에 가깝다.

1. LLM이 전체 plan 생성 (high-level steps)
2. 각 step을 sequential로 실행
3. 실패 시 replanning

Plan-and-Execute는 작업 목표가 비교적 선명하고, 실행 중 새 정보가 조금만 들어오는 경우에 좋다. 예를 들어 “주어진 문서 3개를 비교 요약”은 먼저 읽기 -> 공통점 추출 -> 차이점 추출 -> 표 작성으로 나눌 수 있다. 반대로 “테스트 실패 원인을 찾아 수정”처럼 중간 관찰이 다음 행동을 크게 바꾸는 작업은 plan이 자주 낡는다.

ReAct(Reasoning and Acting, 추론과 행동) (L12-20 §5.5)

섹션 제목: “ReAct(Reasoning and Acting, 추론과 행동) (L12-20 §5.5)”

생각·행동·관찰 명시 루프.

ReAct는 plan을 한 번에 길게 쓰기보다 매 step마다 thought/action/observation을 반복한다. tool 결과가 중요한 작업에 적합하지만, 종료 조건과 중복 tool 방지가 없으면 같은 관찰을 계속 반복하기 쉽다.

여러 경로 동시 탐색·평가.

Tree-of-Thoughts는 여러 후보 경로를 만들고 평가하면서 탐색한다. 수학 퍼즐이나 설계 대안 비교처럼 후보 간 차이가 크고 평가 기준이 있을 때 의미가 있다. 단순 조회나 짧은 분류에 쓰면 비용만 늘어난다.

작업 후 자기 비판 → 개선 (Reflexion).

Self-Reflect는 산출물을 만든 뒤 “무엇이 부족했는가”를 다시 보게 하는 패턴이다. 코드 리뷰, 번역, 설명문 개선처럼 평가 기준이 있는 산출물에는 도움이 된다. 그러나 같은 모델이 같은 blind spot을 반복할 수 있으므로 고위험 판단에서는 독립 critic이나 human review가 필요하다.

작업 특징먼저 시도할 전략피해야 할 오해
단계가 고정되고 입력·출력이 명확함Workflow / prompt chainingagent loop를 붙여도 품질보다 비용이 먼저 늘 수 있다
중간 tool observation이 다음 행동을 바꿈ReAct / state machineobservation을 state에 남기지 않으면 같은 실수를 반복
전체 목표는 명확하지만 하위 작업이 큼Plan-and-Execute첫 plan을 절대적인 계약으로 취급하지 않는다
후보 경로를 비교해야 함Tree-of-Thoughts / evaluator평가 기준 없이 후보를 늘리면 noise만 커진다
산출물 품질을 반복 개선해야 함Evaluator-Optimizer / Self-Reflectcritic도 평가 대상이며 종료 조건이 필요하다

Multi-agent는 “여러 모델을 쓰면 더 똑똑하다”가 아니라 작업을 여러 독립 역할로 나눠도 통합 비용보다 이득이 클 때 쓰는 패턴이다. 여기서 핵심 개념이 handoff다.

Handoff는 한 agent가 다른 agent에게 단순히 메시지를 전달하는 일이 아니다. 최소한 다음 다섯 가지가 같이 넘어가야 한다.

goal: 넘기는 작업의 목표
context: 지금까지 확인한 근거와 제외한 후보
state: step, budget, tool history, current artifact
authority: 다음 agent가 실행할 수 있는 tool과 금지된 action
done criteria: 어떤 상태면 다시 supervisor나 사람에게 돌려줄지

handoff가 부실하면 subagent는 같은 검색을 반복하거나, 이전 agent의 암묵적 결정을 모른 채 충돌하는 산출물을 만든다. 이 실패는 모델 성능 문제가 아니라 orchestration graph의 계약 문제다.

[Supervisor agent]
├─ [Researcher] (검색)
├─ [Coder] (코드 작성)
└─ [Reviewer] (검토)
  • supervisor가 작업 분배·통합
  • 가장 일반적인 패턴

여러 agent가 동등 관계로 자유 통신. 복잡·비용 ↑.

agent A → agent B → agent C 단방향. workflow에 가깝지만 각 agent가 자율 결정.

이 패턴은 handoff 계약이 가장 단순하다. researcher -> writer -> reviewer처럼 산출물이 한 방향으로 흐른다. 다만 reviewer가 문제를 발견했을 때 writer로 되돌아가는 edge가 있으면 이미 graph가 된다. 이때는 “몇 번 되돌릴지”, “무엇이 pass인지”, “비용 상한은 얼마인지”를 정해야 한다.

Anthropic의 공개 가이드와 multi-agent research system 사례는 “먼저 단순한 single agent 또는 workflow를 시도하고, 관측된 병목이 있을 때 multi-agent를 추가한다”는 방향을 보인다. 이는 모든 제품에 적용되는 규격이 아니라, communication overhead와 debugging difficulty가 생긴다는 구조적 경고로 읽는다.

다음 조건이 여러 개 동시에 맞을 때만 multi-agent를 고려한다.

  • 작업 분해가 자연스럽다: researcher, planner, coder, reviewer처럼 역할별 산출물이 분리된다.
  • 병렬 탐색 이득이 크다: 서로 다른 검색 경로·가설·설계안을 동시에 비교해야 한다.
  • 컨텍스트가 너무 넓다: 단일 agent context window에 모든 근거·파일·대화가 안정적으로 들어가지 않는다.
  • 검토 관점이 독립적이다: 보안 리뷰, UX 리뷰, 테스트 리뷰처럼 failure mode가 다르다.
  • 추가 비용을 정당화할 task value가 있다: 작은 시간 절약보다, 독립 탐색이 결과 가치에 직접 기여하는 고가치 리서치·장기 작업에 적합하다.

반대로 다음 조건이면 쓰지 않는다.

  • 작업이 선형적이다: 한 파일 수정, 짧은 요약, 단순 분류는 workflow 또는 single agent가 낫다.
  • 공유 컨텍스트 의존이 크다: 모든 참여자가 같은 세부 상태를 알아야 하면 message passing 비용이 폭증한다.
  • 통합 기준이 모호하다: 여러 agent 산출물을 합칠 judge 기준이 없으면 최종 품질이 운에 가까워진다.
  • latency·비용 SLO(Service Level Objective, 내부 성능 목표)가 빡빡하다: agent 간 대화 turn 수가 예측 불가능해진다.
  • 권한 있는 action이 많다: 여러 agent가 파일·DB·외부 API를 동시에 만지면 audit·rollback이 어려워진다.

Anthropic 자체 research system 운영 보고 (Claude Opus 4 lead + Sonnet 4 subagents):

  • 토큰 사용: agent는 chat 대비 약 , multi-agent는 약 15×. 비용이 task value를 정당화할 때만 손익이 맞는다.
  • 성능: 같은 multi-agent 구성이 single-agent Opus 4 대비 +90.2% (breadth-first 연구 query에서).
  • 적합한 작업: 병렬화 가능·single context window 초과·서로 독립 방향 탐색.
  • 부적합한 작업: 모든 agent가 같은 컨텍스트를 공유해야 하거나 의존 관계가 많은 경우 — 대표적으로 코딩 작업. 출처: Anthropic, Multi-Agent Research System (2025).

깨지는 시나리오 — Flappy Bird 사례 (Cognition)

섹션 제목: “깨지는 시나리오 — Flappy Bird 사례 (Cognition)”

“Flappy Bird clone 만들기”를 두 subagent로 분할했을 때 실제로 관찰된 실패:

  • Subagent 1은 작업을 오해해 Super Mario Bros 풍 배경을 만들었고, Subagent 2가 만든 새 asset과 시각적 스타일이 충돌.
  • 최종 통합 agent가 incompatible output을 합치지 못해 작업 실패.

원인: subagent들이 서로의 결정·암묵적 가정을 보지 못함. Cognition의 2025년 에세이는 이 사례를 바탕으로 single-threaded linear agent + 대화 압축 모델을 default로 권고하고, multi-agent는 question-answering처럼 좁고 잘 정의된 sub-task에 한정하자고 주장했다. 이는 한 조직의 경험적 주장이지 모든 agent 시스템의 보편 결론은 아니다. 출처: Cognition, Don’t Build Multi-Agents (2025).

이 두 보고가 §3.13·§3.14의 “Multi-agent: 통신 overhead 多 — single agent 우선” 권고의 정량적·사례적 근거다.

프레임워크 생태계는 빠르게 바뀐다. 교과서적으로 외울 대상은 이름 목록보다 선택 기준이다.

선택 기준확인할 질문
명시적 상태·전이agent loop를 graph/state machine으로 볼 수 있는가
Tool 권한·격리allowlist, confirmation, audit log를 넣기 쉬운가
관측성step trace, token, latency, retry를 남기는가
UI·런타임 통합streaming, cancellation, human approval과 맞는가
모델·벤더 독립성provider 교체와 fallback이 가능한가

예시는 LangGraph, CrewAI, AutoGen, OpenAI/Claude SDK, Pydantic AI, Mastra, Vercel AI SDK, DSPy 등이지만 특정 이름은 참고일 뿐이다.

agent loop를 명시적 finite state machine으로 표현한다. 특정 프레임워크 이름보다 중요한 점은 상태와 전이를 코드로 볼 수 있어야 한다는 것이다.

States: [planning] → [tool_calling] → [observing] → [reflecting] → [done]
Transitions: 명시적 조건
Error: error state로 분기, retry/escalate
  • 디버깅·재현·복구 표준화

Orchestration graph는 이 state machine을 node와 edge로 표현한 것이다.

[plan]
-> [retrieve_docs]
-> [call_tool]
-> [judge_observation]
├─ enough evidence -> [final_answer]
├─ missing evidence -> [retrieve_docs]
├─ risky action -> [human_approval]
└─ error/budget exceeded -> [fail_safe]

graph로 만들면 좋은 이유는 “agent가 무엇을 했는지”가 아니라 무엇을 할 수 없게 막았는지가 보이기 때문이다. 예를 들어 human_approval edge 없이는 결제·이메일·배포 같은 비가역 action이 바로 실행될 수 있다. budget exceeded -> fail_safe edge 없이는 비용이 늘어도 멈출 경로가 없다. missing evidence -> retrieve_docs edge는 useful하지만, 횟수 제한이 없으면 stuck loop가 된다.

작은 상태 설계 예:

state 필드왜 필요한가실패 신호
original_goalgoal drift를 감지하기 위한 기준점최종 답이 초기 요청과 다른 문제를 풀고 있음
step_count종료 조건과 비용 상한 계산같은 tool을 계속 호출하고도 멈추지 않음
tool_history중복 호출과 idempotency 확인같은 인자로 같은 tool을 반복
evidence_ids답변 근거와 citation 추적근거 없는 확신 또는 오래된 문서 인용
budget_remaining요청당 비용·시간 한도 관리고가 모델·multi-agent 호출이 누적됨
approval_status비가역 action gate승인 전 이메일·DB write·배포가 실행됨

각 agent step을 이벤트(Kafka, NATS, AWS EventBridge)로 발행. 비동기·confirmable.

Event-driven 패턴은 agent step이 오래 걸리거나 외부 시스템 응답을 기다릴 때 유용하다. 다만 LLM이 만든 이벤트도 결국 시스템 이벤트이므로 idempotency key, retry policy, dead-letter queue(DLQ, 정해진 재시도 후에도 처리하지 못한 이벤트를 격리하는 큐) 같은 일반 분산 시스템 원칙이 필요하다. agent라는 이름이 붙어도 중복 결제, 중복 이메일, 순서 역전 문제는 사라지지 않는다.

비가역 action(이메일·결제) 전 사용자 승인.

Human-in-the-loop는 “모델이 불안하니 사람이 보자”보다 좁게 설계해야 한다. 좋은 승인 요청은 무엇을 하려는지, 왜 해야 한다고 판단했는지, 어떤 근거를 봤는지, 취소하면 어떤 대안이 있는지를 포함한다. 사람이 매번 모든 trace를 읽어야 한다면 approval UI가 아니라 책임 전가가 된다.

agent 권한은 “무엇을 할 수 있는가”보다 “실패했을 때 어디까지 피해가 번지는가”로 설계한다.

정책 영역기본값운영 기준
Tool allowlist작업별 최소 tool만 노출read/write/delete·외부 발송 tool을 분리
Confirmation비가역 action 전 승인결제, 이메일, 배포, DB write는 human gate
Sandbox파일·네트워크·secret 범위 제한repo/path allowlist, 임시 credential, egress 제한
Prompt/tool 방어untrusted input을 명령과 분리검색 결과·웹 페이지·사용자 첨부는 tool 지시로 불인정
Audit logstep·tool·argument·결과 기록rollback과 책임 추적이 필요한 action은 trace 필수

매 step state 저장. 실패 시 마지막 checkpoint부터 재개.

Checkpoint는 long-term memory가 아니다. checkpoint는 같은 실행을 복구하거나 재현하기 위한 스냅샷이고, memory는 다음 실행에서도 참고할 수 있는 지식이다. 둘을 섞으면 “오염된 실행 상태”가 장기 기억처럼 검색되어 다른 요청까지 망칠 수 있다.

agent loop에서는 **전체 작업량(total work)**과 **사용자가 기다리는 임계 경로(critical path)**를 구분해야 한다. 병렬 worker를 늘리면 wall-clock latency가 줄 수 있어도 모든 branch의 model·tool 호출은 비용에 남는다. 반대로 순차 tool loop, 재시도, 사람 승인은 그대로 critical path에 이어져 지연을 늘린다. 즉 “빨리 끝났다”와 “적은 일을 했다”는 같은 말이 아니다.

에이전트에서 비용과 지연이 특히 증폭되는 이유는 step 수를 입력 전에 확정하기 어렵고, 매 step에 이전 observation이 누적되며, 실패한 시도와 sub-agent 통신도 실행 그래프에 추가되기 때문이다. 일반 token·tool 비용식과 latency 계산법은 LLM 비용·지연을 기준으로 삼고, 이 문서에서는 아래 증폭 원인과 §3.14의 workflow-vs-agent 비교에 집중한다.

  • Loop 무한 반복: max_iter 누락 → context 폭증
  • 동일 tool 반복: stuck → memory에 history 명시
  • 불필요한 sub-agent: workflow로 충분한데 agent 사용
  • Reasoning 모델 + Multi-agent: 호출당 token과 호출 수가 함께 늘 수 있으므로 조합별 실측 필요
  • Long context: 매 step prompt가 누적되어 비싸짐
  • Workflow 우선: 가능하면 workflow로
  • Smaller model 라우팅: 단순 단계는 작은 모델 (L12-70)
  • Prompt caching: 시스템 prompt·tool definition은 caching
  • Tool result 압축: 큰 tool output을 summarize
  • Termination 명시: 명확한 종료 조건
  • Parallel tool calling (L12-50 §3.7)
  • SWE-Bench Verified (500): GitHub 이슈 해결 — 코드 agent 평가에 자주 인용
  • τ-bench / tau-bench: multi-turn tool agent (retail·airline)
  • BFCL v3: tool calling 정확도
  • WebArena, OSWorld, GAIA: web/OS 자동화
  • AgentBench: 다양 도메인
  • Trajectory eval: agent의 step-by-step trace 검토
  • Trace observability: LangSmith, Langfuse, Arize, OpenLLMetry, Weights & Biases
  • Cost·latency dashboard: 평균 step 수, 평균 비용/요청
  • Success rate: 작업 완료 여부
  • Recovery rate: 오류 후 복구 성공

critic loop는 “평가자가 있으니 안전하다”가 아니라, 평가자 자체도 eval 대상이다.

  • Rubric anchoring: critic prompt에 평가 기준·반례·통과/실패 예시를 고정한다.
  • Judge bias 측정: 길고 자신감 있는 답변, 자기 모델이 만든 답변, 먼저 제시된 답변을 과대평가하는지 샘플링한다.
  • Gold dataset: 사람이 검토한 기대 결과와 실패 사례를 버전 관리하는 집합이다. 작은 집합부터 시작해 critic의 precision/recall을 보고, 관찰된 failure mode에 맞춰 늘린다.
  • Disagreement log: generator pass, critic fail, human pass 같은 불일치를 따로 모아 rubric을 고친다.
  • Final human gate: 법무·보안·비용 집행처럼 고위험 action은 critic loop 후에도 사람이 승인한다.
  • Stuck loop: 같은 tool·argument 반복 → step/budget 한도, 호출 중복 감지
  • Wrong tool 선택: tool description 또는 routing eval 부족 → description·선택 eval 보강
  • Wrong argument: 선택한 tool의 인자가 계약을 어김 → schema·business validation 강화
  • Memory contamination: long-term memory에 잘못된 정보 → memory 검증·삭제 정책
  • Goal drift: 처음 목표에서 멀어짐 → 주기적으로 원본 query 재확인
  • Cost runaway: budget alert 누락 → 호출당·요청당 한도 설정
  • Observation overflow: tool output이 매우 길 → summarization
  • Permission abuse: agent가 의도하지 않은 작업 수행 → allowlist + confirmation

아래 두 진단은 특정 framework API보다 증상과 저장 경계를 먼저 분리하는 법을 다룬다. 첫 회독에서는 오염된 memory는 다른 요청까지 망칠 수 있고, goal drift는 원래 목표를 실행 state에 고정하지 않을 때 생긴다는 차이를 잡는다.

선택 진단: Memory Contamination

Memory contamination은 검증되지 않은 과거 정보가 현재 요청의 사실처럼 재사용되는 실패다. 같은 thread에서 checkpoint를 이어 쓰는 것 자체는 정상이다. 문제는 namespace가 섞이거나, 오래된 실행 state와 long-term memory를 출처·유효기간 확인 없이 다시 읽는 데 있다.

관찰 증상먼저 의심할 경계trace/checkpoint에서 찾을 증거
다른 사용자·세션의 이름이나 결정을 반복 인용한다tenant/user와 thread namespace 충돌, 장기 memory 검색 filter 누락서로 다른 tenant가 같은 namespace를 썼는지, memory hit의 owner·source가 현재 요청과 같은지
이전 turn의 잘못된 tool 결과를 계속 사실로 쓴다checkpoint message 누적, 요약·검색 시 오래된 오류 재선택잘못된 observation이 처음 들어온 step, 이후 model input마다 그 observation이 재주입되는지
message 수가 매 turn 계속 늘고 오래된 오류가 사라지지 않는다보존 state와 model input view를 같은 것으로 취급저장된 전체 history와 실제 model에 전달된 bounded input의 길이 차이

Checkpoint는 같은 실행·대화를 재현하고 재개할 state snapshot이고, long-term memory는 다른 세션에서도 검색할 지식이다. checkpoint를 되돌려도 별도 memory store의 오염 항목은 남을 수 있고, memory 항목을 삭제해도 이미 저장된 checkpoint history는 바뀌지 않는다. 두 저장소는 출처, 보존 기간, 삭제 경로를 따로 가져야 한다.

모델 입력을 제한할 때는 임의의 최근 메시지 N개가 아니라 turn 경계를 단위로 자른다. 조립 결과에는 system message, 최근 완결 turn, 현재 active turn 전체가 모두 들어가야 한다.

bounded_input(system, completed_turns, active_turn, max_completed_turns):
require max_completed_turns >= 0
require every completed turn is closed
require active_turn starts with the current user message
require every tool call in active_turn has its matching response at model boundary
recent = last(completed_turns, max_completed_turns)
return system + flatten(recent) + active_turn

완결 turn은 user -> assistant tool call -> matching tool response(s) -> final assistant까지 닫힌 묶음이다. Active turn은 현재 user message부터 아직 final answer가 나오지 않은 지점까지이며, 다음 model 호출 시점에는 그 안의 모든 tool call이 matching response를 가져야 한다. Token budget이 부족하면 오래된 완결 turn부터 제외하고, active turn만으로 초과하면 tool output을 state에 넣기 전에 cap·요약하거나 실행을 중단한다. 완결 turn과 active turn 어느 쪽도 중간에서 잘라 AI tool call과 대응 response를 떼지 않는다. 이 방식은 checkpoint를 삭제하지 않고 모델이 보는 view만 제한한다는 점도 중요하다.

복구는 고정된 “세 번째 이전 checkpoint”를 고르는 일이 아니다.

  1. trace에서 잘못된 사실이 최초로 state 또는 memory hit에 들어온 step을 찾는다.
  2. 그 직전 checkpoint가 현재 tenant·thread에 속하고 pending tool call 없이 닫혀 있는지 확인한다.
  3. checkpoint 오염이면 그 지점에서 branch/replay하고, long-term memory 오염이면 별도 store에서 해당 source를 격리·삭제한다.
  4. 같은 fixture를 다시 실행해 cross-tenant 정보가 0건이고, 잘못된 evidence가 model input에 없으며, terminal history가 닫혔는지 확인한다.

주의할 점은 checkpoint replay/fork가 선택 지점 이후의 node를 다시 실행하므로 LLM·외부 API·write call도 반복될 수 있다는 것이다. Write node에는 tenant·resource·논리 작업을 묶은 business idempotency key를 강제하고, 되돌릴 수 없거나 고위험인 action은 새 human approval 뒤에서만 재개하며, 실제 반영 전 dry-run 경계를 둔다.

출처: LangGraph short-term memory 관리, LangGraph persistence

선택 진단: Goal Drift

Goal drift는 agent가 실행은 계속하지만 처음 요청과 다른 문제를 풀기 시작하는 실패다. 긴 tool output이나 중간 가설이 새로운 목표처럼 취급될 때 나타난다.

관찰 증상trace에서 비교할 것실패 판정
최종 답이 원본 질문과 무관한 주제로 수렴한다original_goal, 현재 plan, final answerfinal answer의 완료 조건이 원래 요청의 완료 조건과 다름
tool arguments가 초기 요청과 무관한 키워드로 바뀐다직전 observation과 다음 tool call arguments근거 수집이 아니라 observation의 부수 주제를 새 목표로 추적
step이 늘수록 “현재 작업” 설명이 달라진다각 model input의 stable instruction과 current task원본 목표가 사라졌거나 중간 요약이 이를 덮어씀

Goal anchor 원칙은 원본 목표와 완료 기준을 original_goal 같은 불변 state로 보존하고, 다음 model input의 안정된 instruction 영역에서 다시 보여 주는 것이다. 기존 message list를 제자리에서 고치거나 pending tool call과 response 사이에 anchor를 끼워 넣지 않는다. N=5 step마다 재주입 같은 값은 보편 규칙이 아니라 trace에서 drift가 나타나는 길이와 token budget으로 조정할 예시 정책이다.

짧은 worked example을 보자. 목표는 “PR의 테스트 실패 원인을 근거와 함께 리뷰”인데, runbook tool이 반환한 긴 문서에 “클라우드 이전 계획”이 포함되어 다음 call이 migration 문서만 검색하기 시작했다면 drift다. 마지막으로 PR diff·테스트 로그를 다루던 checkpoint가 복구 후보이고, runbook 결과를 읽은 뒤라도 여전히 테스트 실패를 좁히는 call이었다면 더 뒤 checkpoint를 고를 수 있다.

on each model step:
compare current_plan and proposed_action with original_goal + done_criteria
if trace/eval marks drift:
stop before executing the proposed action
resume from the last closed checkpoint that still pursued original_goal
inject original_goal as a stable instruction, then branch

복구 성공 기준은 단순히 답의 주제가 돌아온 것이 아니다. 새 branch가 원래 완료 기준을 만족하고, drift 이후의 stale pending action을 실행하지 않았으며, trace에서 original_goal -> evidence -> final answer의 연결이 다시 보일 때 성공이다.

출처: LangGraph time travel

reasoning 계열 모델은 일반 chat 모델보다 긴 내부 추론을 쓰도록 설계된 경우가 많아 복잡한 planning에서 이득이 날 수 있다. 다만 모델명·가격·지원 기능은 빠르게 바뀌므로, 여기서는 특정 최신 제품 목록보다 비용 구조를 보는 법에 초점을 둔다.

  • 동일 작업에 일반 모델 multi-step vs reasoning 모델 single-step 비용 비교
  • reasoning 모델은 호출당 비용이나 출력 토큰이 더 커질 수 있지만 step 수가 줄어 합산 비용이 낮아지는 경우도 있다 (경험적 추정; 작업 복잡도에 따라 다름)
  • L11-90 §3.13의 RLVR(Reinforcement Learning with Verifiable Rewards, 검증 가능한 보상으로 하는 강화학습) 계열 접근은 코드·수학처럼 정답을 검사하기 쉬운 작업에서 이점이 보고되었다. 모든 agent 작업의 보장은 아니다.

3.13 깨지는 조건 정량 표 (운영 결정용)

섹션 제목: “3.13 깨지는 조건 정량 표 (운영 결정용)”
기법효과 발휘 범위깨지는 조건
Workflow명확한 입력·출력과 허용된 분기step 수가 가변이어도 bounded state machine으로 표현 가능하면 agent가 자동 선택은 아니다
Agent (single)관찰에 따라 tool 선택이 달라지는 작업자기 traffic의 step·비용 baseline 없이 loop를 열면 runaway 위험
Multi-agent역할 경계가 명확하고 독립 병렬 탐색이 가능한 작업토큰 ~15× (Anthropic 보고). 공유 컨텍스트·의존 多·코딩 작업에서 깨짐 (Flappy Bird 사례, Cognition)
ReActtool 자주 호출단발 답변엔 오버헤드
Plan-and-Execute사전 계획 가능계획 변경 잦으면 replanning 비용
Tree-of-Thoughts복잡 puzzle평가 기준 없이 후보를 늘리면 비용과 noise가 함께 증가
Graph/state-machine framework상태·전이·checkpoint 명시가 필요한 작업승인·재개·trace 요구를 작은 spike로 검증하지 않고 framework 이름만으로 선택하면 경계가 흐려짐
Reasoning model 단발복잡 reasoning호출당 비용·출력 길이가 커질 수 있음 — 단순 작업엔 일반 모델 (경험적 추정)
Iteration cap예상 step 분포를 trace로 아는 loop전역 “표준” 값은 없으며 task별 p95(측정값의 95%가 이 값 이하가 되도록 잡은 백분위수)와 budget에서 정해야 함

정량 수치 주석: agent와 multi-agent의 비용 배율은 tool 수, context 길이, 평가 방식에 따라 크게 달라진다. Anthropic의 2025년 research system 보고의 약 4×/15×는 그 시스템과 workload의 사례다. 다른 벤치마크나 블로그 수치를 일반 운영 임계값으로 옮기지 말고, 같은 task set에서 step 수, input/output token, tool 비용, 성공률을 함께 재야 한다.

3.14 Cost·Latency 정량 비교 (운영 결정 표)

섹션 제목: “3.14 Cost·Latency 정량 비교 (운영 결정 표)”

절대 금액은 모델 버전·가격표·컨텍스트 길이에 따라 쉽게 낡는다. 운영 판단은 아래 식과 실측 dashboard로 한다.

llm_cost_i = input_tokens_i × input_price_i
+ output_tokens_i × output_price_i
total_cost = Σ_all_attempts(llm_cost_i + tool/API charge_i)
total_work_time = Σ_all_attempts(node_duration_i)
wall_clock_latency ~= critical_path(node_duration_i + human wait)
+ scheduling/communication overhead
  • retry 계산: 최초 호출과 retry를 각각 all_attempts에 한 번씩 넣는다. 이미 실제 token·tool 사용량을 합산했다면 별도 retry 항을 더하지 않는다.
  • 병렬 계산: 가상 예에서 세 branch가 각각 1초, 2초, 4초라면 total work는 7초지만, 서로 독립이고 동시에 시작했다면 fan-in 전 critical path는 약 4초다.
  • single agent: step 수와 retry가 비용을 지배한다.
  • multi-agent: 병렬 탐색 이득이 있어도 agent 간 message·merge 비용이 추가된다. Anthropic 자체 research system 보고처럼 chat 대비 agent는 약 4×, multi-agent는 약 15× 토큰을 쓸 수 있다.
  • reasoning model 단발: 호출 수는 줄지만 호출당 token 단가와 출력 길이가 커질 수 있다.
  • 반복 작업: 빈도가 높고 입력·출력이 안정되면 workflow, 캐시, smaller model routing을 먼저 검토한다. fine-tune은 데이터 품질, 재학습 비용, 모델 변경 시 평가까지 감당할 수 있을 때 별도로 비교한다.

가상 손계산 — 같은 PR 리뷰를 workflow와 agent로 실행

섹션 제목: “가상 손계산 — 같은 PR 리뷰를 workflow와 agent로 실행”

아래 값은 가격표 주장이 아니라 계산법을 익히기 위한 가상 시나리오다. 입력은 백만 token당 $2, 출력은 백만 token당 $8, 유료 tool은 호출당 $0.002라고 가정한다.

방식실제로 센 시도token·tool 비용 계산total work와 critical path
Workflowread-only 조회 3개 병렬 + LLM 2회(18,000 / 1M × $2) + (3,000 / 1M × $8) = $0.060조회 0.4+0.8+0.5초와 LLM 1.2+1.5초의 total work는 4.4초; critical path는 max(0.4, 0.8, 0.5)+1.2+1.5=3.5초
Single agentLLM 4회 + 유료 tool 5회. LLM 4회에는 실패 후 다시 시도한 1회가 이미 포함됨(30,000 / 1M × $2) + (4,000 / 1M × $8) + (5 × $0.002) = $0.102모두 순차라면 4×1.0 + 5×0.6 = 7.0초가 total work이자 critical path

workflow의 두 LLM 호출은 각각 (1) diff·테스트 로그에서 리뷰 후보를 구조화하는 호출, (2) 후보를 정책 문서와 대조해 최종 리뷰를 합성하는 호출이다. agent가 항상 더 비싸거나 느리다는 결론은 아니다. 이 가정에서는 동적 탐색의 이점이 없는 고정 작업이라 workflow가 유리할 뿐이며, 실제 결정은 같은 labelled PR set의 성공률·token·tool 비용·p95 latency를 함께 비교해야 한다.

결정: 입력·출력과 허용 분기가 안정적이면 workflow부터 측정한다. 관찰 결과에 따라 tool 선택을 동적으로 바꿔야 하고 그 추가 비용을 설명할 수 있을 때 reasoning model 또는 agent를 비교한다.

3.15 Agent Silent Failure 신호와 대응 방향

섹션 제목: “3.15 Agent Silent Failure 신호와 대응 방향”
증상정량 신호원인복구
Stuck loop같은 tool·argument 반복 또는 step 수가 자기 baseline을 초과종료 조건·중복 감지 부재step/budget cap, 호출 fingerprint 기록
Goal drift답이 원본 query와 무관컨텍스트 누적매 N step마다 원본 query 재확인
Memory contamination잘못된 정보 반복long-term memory 오염memory 검증·삭제 정책, fact-check
Cost runaway호출당 비용이 task baseline보다 급증budget 미설정·retry 누적per-request budget, alert
Observation overflowtool output 매우 김summarization 누락tool output cap·summarize
Permission abuseagent가 의도 외 작업allowlist 미적용tool allowlist + confirmation
Multi-agent stuck둘 사이 무한 대화termination 부재turn 한도, supervisor가 종료 결정
Judge biascritic이 그럴듯한 답만 통과rubric·gold dataset 부재blind eval, human spot check

critic loop는 품질을 올리지만 종료 조건이 없으면 “더 비판 → 더 수정”만 반복한다. production에서는 아래 guardrail을 함께 둔다.

  • max_rounds: 예를 들어 1~3회. task의 개선 폭과 비용을 보고 정한다.
  • quality threshold: 예를 들어 rubric 점수 ≥ 0.85 또는 모든 must-have 항목 통과 시 종료한다. 점수 보정이 먼저다.
  • delta threshold: 예를 들어 이전 round 대비 점수 개선이 0.02 미만이면 수렴으로 판단한다.
  • budget cap: 요청당 최대 토큰·달러·wall-clock time을 선차감 방식으로 관리
  • no-new-issues rule: 수정 후 새 critical issue가 생기면 rollback 또는 human escalation
  • judge disagreement: 두 critic이 충돌하거나 critic confidence가 낮으면 반복 대신 사람에게 넘김

아래 수치는 계산 구조를 보여 주기 위한 가상 정책 예다.

max_rounds = 2
max_cost_per_request = $0.20
stop if score >= 0.85 and critical_issues == 0
stop if score_delta < 0.02
escalate if security_or_payment_action == true

3.16 Agent의 좁은 시스템 매핑 (Transferable Pattern)

섹션 제목: “3.16 Agent의 좁은 시스템 매핑 (Transferable Pattern)”

LLM agent를 다른 시스템과 비교할 때는 구조적 유사성만 가져오고 운영 의미론이 같다고 보지 않아야 한다.

Agent 구성요소좁게 가져올 수 있는 직관등가가 아닌 이유
State machine (LangGraph)허용 상태와 전이를 명시하는 workflow일반 workflow engine의 durable execution을 자동 제공하지 않는다
Agent loop결정 -> 행동 -> 관찰 -> 갱신의 bounded feedback loopPID 제어처럼 오차 함수와 제어 법칙이 정의된 loop가 아니다
Tool callschema가 있는 request/response 경계service mesh는 서비스 간 네트워크 정책 계층이지 tool call이 아니다
Memory (short/long)session-scoped state와 persistent store를 분리하는 직관기억의 신뢰도·삭제·검색 정책은 별도 agent 문제다
Multi-agent supervisor작업 분배와 결과 통합을 맡는 coordinatorworker 간 의미 충돌을 자동 해결하지 않는다
Checkpoint실행 상태를 다시 읽을 수 있는 snapshotDB transaction의 원자성·격리·rollback과 같지 않다
Trajectory eval여러 step의 인과 순서를 trace로 재생하는 관점분산 trace만으로 답의 의미적 정확성을 판정할 수 없다

§3.15의 stuck loop처럼 같은 tool name과 arguments가 다시 제안되는 현상은 trace에서 반복 호출로 관찰할 수 있다. 다만 §9 선택 실습의 fingerprint 검사는 현재 thread 안에서 이미 실행한 호출의 반복을 막는 local repeat-call guard일 뿐 circuit breaker가 아니다.

  • local repeat-call guard: 같은 thread의 tool name과 canonical arguments를 비교해 중복 실행을 건너뛰고 현재 turn을 종료한다.
  • circuit breaker: 여러 호출에서 관찰한 downstream 실패율·timeout을 바탕으로 호출을 차단하고, open/half-open 같은 복구 상태를 관리한다.
  • 경계: 중복 호출 guard는 정상 downstream에 대한 같은 요청의 반복을 막고, circuit breaker는 비정상 downstream으로의 호출 확산을 막는다. 둘은 함께 쓸 수 있지만 서로 대체하지 않는다.

운영 시나리오 — 사내 코드 리뷰 agent 도입 결정 (예시)

섹션 제목: “운영 시나리오 — 사내 코드 리뷰 agent 도입 결정 (예시)”
가상 상황: GitHub PR 자동 리뷰 도입 검토. 평균 PR 5개/일이며, diff·테스트 로그·정책 문서를 읽는 허용 경로는 이미 정해져 있다.
검증 집합: 사람이 finding을 판정한 labelled PR 50개. 성공률·input/output token·tool 비용·p95 latency를 같은 조건에서 기록한다.
선택지:
A. Workflow: read-only 조회 3개 → 후보 구조화 LLM → 정책 대조·최종 합성 LLM → comment
- 두 LLM 호출과 허용 branch가 코드 그래프에 고정됨
- router가 정책 종류를 골라도 허용 정책 문서와 합류점은 코드가 제한함
B. Single agent + tools (read_file, run_test, search_docs):
- 검색 결과에 따라 다음 조회를 바꿀 수 있지만, step cap와 중복 호출 검사가 필요
C. Multi-agent (reviewer + tester + security):
- 세 역할의 독립 병렬 탐색 이득과 message·merge 비용을 함께 측정해야 함
D. Reasoning model (single shot):
- 별도 loop를 줄일 수 있는지 같은 labelled PR set으로 확인해야 함
1차 선택: A (Workflow)를 baseline으로 측정.
근거:
- 현재 요구는 허용 control graph로 표현 가능하고 동적 tool 탐색이 필수가 아님
- §3.14의 두 LLM 호출과 read-only 조회를 trace에서 따로 계측할 수 있음
대안 비선택:
- B·C·D가 나빠서가 아니라, 추가 복잡도를 정당화하는 labelled 비교 결과가 아직 없음
전환 조건:
- labelled PR에서 고정 workflow가 놓치는 finding이 "관찰 후 새 tool 선택"으로 반복 복구됨
- 그 성공률 개선이 token·tool 비용과 p95 latency 증가를 감수할 만큼 큼
silent failure (§3.15):
- stuck loop, goal drift, cost runaway, multi-agent stuck

§3.1 Workflow vs Agent + §3.13 깨지는 조건 + §3.14 cost 비교 + §3.15 silent failure 모두 적용.

  • 코드 어시스턴트 (Cline·Cursor·Devin·Claude Code)
  • 데이터 분석 agent
  • 고객 지원 자동화 (Tier 1)
  • 사내 자동화 (Slack 봇이 다단계 작업)
  • web·OS 자동화 (Operator·Computer Use)
  • research agent (논문 검색·요약)

엔지니어가 agent를 운영할 때 다음에 도움된다.

  • Workflow vs Agent 결정: 허용 그래프를 코드로 닫을 수 있으면 workflow. 관찰에 따라 다음 행동 후보가 달라질 때 agent를 비교
  • State machine 패턴: LangGraph 또는 자체 FSM으로 agent loop 명시화 — 디버깅·재현·복구
  • Observability: 매 step trace를 LangSmith/Langfuse로. cost·latency·success rate dashboard
  • Cost runaway 방어: max_iter, budget per request, alert
  • Single agent 우선: Anthropic 권고대로 multi-agent는 마지막
  • Reasoning 모델 도입 결정: 호출당 비용 증가 가능성 vs step 수 절감의 비교
  • Human-in-the-loop: 비가역 action 사용자 승인
개념 A개념 B차이점
WorkflowAgent코드가 허용 그래프를 닫고 그 안에서 분기 vs LLM이 관찰에 따라 다음 행동 순서를 동적으로 선택
Workflow engineAgent frameworkengine은 실행·복구 인프라, framework는 LLM step·tool 선택 구조
Durable executionAgent checkpointdurable execution은 history/replay 의미론, checkpoint는 agent state 저장 수단
Prompt chainingRouting순차 vs 분기
Orchestrator-workersMulti-agentorchestrator-workers는 하나의 controller가 전문 worker에 일을 분배하는 구체 패턴, multi-agent는 supervisor·swarm·pipeline까지 포함하는 더 넓은 범주
Single agentMulti-agent1개 LLM 자율 vs N개 협업. multi는 비용·debug ↑
Short-term memLong-term memcontext 안 vs 외부 store (Mem0, Letta, Zep)
EpisodicSemantic memory사건·시간 vs 추상화된 지식
ReActPlan-and-Executestep별 thought·action·obs vs plan 후 실행
State machineFree agent loop명시 transitions vs LLM 매번 결정
Graph-oriented frameworkRole-oriented framework상태·checkpoint 표현과 역할·handoff 표현 중 무엇이 핵심인지 같은 작은 작업으로 검증
Reasoning model일반 + CoT(Chain of Thought, 단계적 추론 유도)모델의 내부 reasoning 예산 vs prompt로 유도한 다단계 출력
Critic loopAgent eval산출물 반복 개선 루프 vs 시스템 품질 측정 체계
CriticJudge결함 피드백 생성 vs 후보 간 판정·점수화

7. 체크리스트 체크

  • Workflow와 Agent의 차이를 결정성·비용·debug 관점에서 설명할 수 있다 (Anthropic 분류)
  • Workflow 5대 패턴(chaining/routing/parallelization/orchestrator-workers/evaluator-optimizer)을 작업에 매핑할 수 있다
  • Critic loop / evaluator-optimizer / debate-review-revise의 공통 구조(generator → critic → reviser)를 설명할 수 있다
  • ReAct·Plan-and-Execute·Tree-of-Thoughts·Self-Reflect 4가지 planning 전략의 차이를 말할 수 있다
  • Multi-agent를 써야 하는 조건(독립 병렬 탐색·역할 분리·컨텍스트 초과)과 쓰면 안 되는 조건(선형 작업·공유 컨텍스트 의존·SLO 제약)을 구분할 수 있다
  • State machine 패턴(LangGraph)이 production agent에서 가치 있는 이유를 설명할 수 있다
  • Agent의 cost·latency 폭증 원인 5종(loop·동일 tool·sub-agent·reasoning·long-context)을 식별할 수 있다
  • SWE-Bench Verified·τ-bench·BFCL 같은 대표 벤치마크의 측정 영역 차이를 구분할 수 있다
  • Stuck loop·goal drift·memory contamination·cost runaway 등 silent failure를 대응할 수 있다
  • Critic loop의 종료 조건(max_rounds·quality threshold·delta threshold)과 비용 상한을 설계할 수 있다
  • Judge bias를 gold dataset·blind eval·human spot check로 점검할 수 있다
  • Workflow 패턴: prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer
  • Critic loop: evaluator-optimizer, debate-review-revise, self-refine, Reflexion, LLM-as-a-judge, rubric anchoring, judge bias, gold dataset
  • Agent 패턴: ReAct, Plan-and-Execute, Tree-of-Thoughts, Reflexion, Self-Refine, Voyager
  • Memory: short-term, long-term, episodic, semantic, Mem0, Letta (MemGPT), Zep
  • Multi-agent: supervisor, swarm, sequential, hierarchical, role specialization, handoff, message passing, AutoGen, MetaGPT
  • Framework: LangGraph, CrewAI, AutoGen, OpenAI Agents SDK, Claude Agent SDK, Pydantic AI, Mastra, Vercel AI SDK
  • Observability: LangSmith, Langfuse, Arize Phoenix, OpenLLMetry, OpenAI tracing, Helicone, W&B Traces
  • Eval: SWE-Bench Verified, τ-bench, BFCL v3, WebArena, OSWorld, GAIA, AgentBench
  • Coding agents: Devin, Claude Code, Cline, Cursor, Aider, OpenHands

목표: 고정 workflow와 bounded agent loop를 같은 작은 작업에 적용해, 동적 행동 선택이 언제 추가 제어 비용을 만드는지 확인한다.

예상 관찰: workflow는 정해진 두 tool을 바로 실행하지만 agent는 다음 action과 종료 여부를 반복해서 결정한다. agent 경로에는 checkpoint, step cap, 중복 호출 방지, 정상·실패 종료 사유가 추가되며, 같은 출력이라도 더 많은 policy decision이 생길 수 있다.

성공 기준: 아래 명령과 코드를 건너뛰어도 normal 종료가 cap보다 우선해야 한다는 점, 미응답 tool call 없이 terminal history가 닫혀야 한다는 점, deterministic 비교에서 같은 결과에 agent만 세 번의 policy decision을 쓴다는 결론을 설명할 수 있으면 된다.

선택 부록: bounded workflow·agent hands-on

이 실습은 provider나 framework API를 외우는 대신 같은 fixture에서 제어 정책이 어떻게 달라지는지 확인한다. 외부 API와 write tool은 쓰지 않는다. get_weather(city)get_news(topic)은 입력에 대해 항상 같은 문자열을 돌려주는 deterministic tool이라고 가정한다.

먼저 다음 두 workflow를 코드가 허용한 node·edge 안에서 구성한다.

실습고정할 제어 그래프확인할 것
Prompt chaining요청 정규화 -> 두 read tool -> 결과 합성세 단계와 종료점이 입력마다 같고, 실패 branch를 코드로 열거할 수 있는가
Routing분류 -> weather/news/both handler -> 합류LLM router가 분기를 골라도 허용 handler 밖의 tool을 만들 수 없는가

이 baseline은 agent보다 단순해서가 아니라, 동적 다음 행동이 필요하다는 증거를 찾기 위한 비교군이다.

아래 loop 하나면 hands-on의 핵심을 따라갈 수 있다. normal 판정을 step cap보다 먼저 두고, guard가 실행하지 않은 pending tool call도 같은 call id의 response로 닫는 순서에 주목한다.

run_turn(user_input, tenant_id, conversation_id):
namespace = encode(tenant_id, conversation_id)
require authenticated_tenant owns namespace
state = checkpoint.load(namespace) or {messages: []}
state.original_goal = user_input
state.guard = {step_count: 0, seen: set(), termination: "running"}
append(state.messages, user(user_input))
while true:
decision = model(bounded_input(state))
state.guard.step_count += 1
append(state.messages, decision)
calls = decision.tool_calls
if decision.has_final_answer and calls is empty:
state.guard.termination = "normal" # normal이 cap보다 우선
checkpoint.save(namespace, state)
return state
fingerprints = canonical(tool_name, arguments) for each call
if any fingerprint is already seen or duplicated in this batch:
reason = "duplicate_tool_call"
else if state.guard.step_count >= MAX_STEPS:
reason = "step_limit"
else:
reason = null
if reason:
for call in calls:
append(state.messages, tool_response(call.id, skipped=reason))
append(state.messages, assistant_final(reason))
state.guard.termination = reason
checkpoint.save(namespace, state)
return state
add all fingerprints to state.guard.seen
results = execute_and_correlate_by_call_id(calls)
append(state.messages, results)
checkpoint.save(namespace, state)

Final text와 tool call이 한 decision에 함께 있으면 그 text를 terminal final로 인정하지 않는다. call을 실행할 수 있으면 matching response를 붙인 뒤 다음 model decision을 받고, cap·duplicate guard가 막으면 모든 call id에 skipped response를 붙인 뒤 guard가 만든 final assistant로 닫는다. 어느 경우든 AI(mixed decision)만 terminal history에 저장하거나 matching response 없이 종료해서는 안 된다.

Fingerprint는 tool name + canonical arguments로 만들고 매번 달라질 수 있는 call id는 넣지 않는다. 여기서 canonical arguments는 JSON object key를 재귀적으로 정렬하고 불필요한 공백을 제거하되, array 순서와 string·number·boolean 타입은 보존한 직렬화 결과다. 따라서 {"city":"서울","units":"metric"}과 key 순서만 바꾼 object는 같지만, array 순서나 단위·대소문자가 다르면 다른 인자로 취급한다. schema default 적용이나 Seoul서울 같은 도메인 동치화가 필요하면 fingerprint 전에 별도 정규화 정책을 명시해야 한다.

같은 batch 안의 중복과 이전 step의 반복을 모두 검사한다. 그러나 이것은 현재 turn의 반복 실행을 막는 local guard다. write tool의 재시도에는 사용자·tenant·resource·논리 작업을 묶은 business idempotency key가 별도로 필요하다.

상태수명turn 시작 시 처리검증 포인트
messagesconversation checkpoint이전 완결 history를 이어 받음pending call 없이 닫힌 history인가
original_goal현재 user turn새 입력으로 교체하고 실행 중 불변final answer가 같은 완료 기준을 만족하는가
step_count, seen, termination현재 user turn guard반드시 0, 빈 집합, running으로 reset이전 실패가 다음 turn을 즉시 막지 않는가
checkpoint namespacetenant + conversation인증된 tenant 범위에서 load같은 conversation id라도 tenant가 다르면 분리되는가
long-term memory여러 conversation·process별도 검색·검증 정책 적용source·owner·유효기간·삭제 경로가 있는가

Namespace key가 다르다는 unit test만으로 authorization이 보장되지는 않는다. 실제 저장소 조회가 인증된 tenant filter를 강제하고, 다른 tenant의 같은 conversation id에 대한 read/write를 거절하는 integration test가 필요하다.

Provider 호출 없이 아래 case를 materialize한다. 각 행은 입력 state와 terminal history만으로 판정할 수 있어야 한다.

Case입력기대 결과
정상 완료와 cap 동시 도달step_count=MAX_STEPS에서 tool call 없는 final answertermination=normal. 정상 완료가 cap보다 우선
Final text + pending callstep_count=MAX_STEPS에서 text와 weather call이 함께 옴text만 저장해 정상 종료하지 않고, matching skipped response와 final assistant를 붙여 cap 종료
Step cap + 두 pending call서로 다른 call id의 weather/news 요청두 id 각각 정확히 한 matching response를 받고, 마지막 assistant는 tool call 없이 종료
Duplicate call이전과 같은 tool name·canonical arguments, 새 call id실행하지 않고 duplicate_tool_call. call id가 달라도 중복으로 판정
Batch 내부 duplicate한 decision에 같은 fingerprint 두 개둘 다 실행하지 않고 각 call id를 response로 닫음
다음 turn resetguarded failure 뒤 새 user input이전 messages만 계승하고 step_count=0, seen=[], termination=running에서 시작
Namespace 파생tenant A/B가 같은 conversation id 사용같은 입력에는 같은 namespace, tenant가 다르면 다른 namespace
Terminal history정상·cap·duplicate·두 pending 종료call id는 고유하고 각 call에 response가 정확히 하나이며, 마지막은 tool call 없는 assistant final

Terminal history 검사는 전체 turn을 앞에서부터 읽는다. 새 user message는 직전 turn이 final assistant로 끝난 뒤에만 허용하고, assistant가 낸 call id의 batch 내부·history 전체 고유성을 먼저 확인한 뒤 pending 집합에 넣는다. 각 tool response는 pending id를 정확히 한 번만 제거해야 한다. 마지막 message까지 읽었을 때 pending이 비어 있어도 AI(tool call) -> Tool(result)로 끝났다면 닫힌 history가 아니다. 마지막은 반드시 tool call 없는 final assistant여야 한다.

아래 코드를 python3로 실행해 assertion이 없으면 표의 결정적 fixture만 통과한 것이다. tenant 접근 제어, 재시작 persistence, 실제 trace payload는 검사하지 않는다.

import json
MAX_STEPS = 4
def canonical_args(arguments):
return json.dumps(arguments, sort_keys=True, ensure_ascii=False,
separators=(",", ":"), allow_nan=False)
def fingerprint(call):
return f'{call["name"]}:{canonical_args(call["args"])}'
def has_duplicate(calls, seen):
fingerprints = [fingerprint(call) for call in calls]
return any(value in seen for value in fingerprints) or len(fingerprints) != len(set(fingerprints))
def namespace(tenant_id, conversation_id):
return canonical_args([tenant_id, conversation_id])
def start_turn(messages, tenant_id, conversation_id):
return {"messages": list(messages), "namespace": namespace(tenant_id, conversation_id),
"guard": {"step_count": 0, "seen": [], "termination": "running"}}
def finish(step, *, final=False, calls=(), seen=()):
call_ids = tuple(call["id"] for call in calls)
history = [("user", None), ("assistant", call_ids)]
if final and not calls:
return "normal", history
reason = "duplicate_tool_call" if has_duplicate(calls, set(seen)) else "step_limit" if step >= MAX_STEPS else "running"
if reason != "running":
history.extend(("tool", call_id) for call_id in call_ids)
history.append(("assistant", ()))
return reason, history
def is_closed(history):
pending = set()
issued = set()
turn_open = False
final_assistant = False
for kind, value in history:
if kind == "user":
if pending or turn_open: return False
turn_open, final_assistant = True, False
elif kind == "assistant" and turn_open and not pending:
call_ids = tuple(value)
if len(call_ids) != len(set(call_ids)) or issued.intersection(call_ids):
return False
issued.update(call_ids)
pending.update(call_ids)
final_assistant = not call_ids
if final_assistant: turn_open = False
elif kind == "tool" and turn_open and value in pending:
pending.remove(value)
final_assistant = False
else:
return False
return not pending and not turn_open and final_assistant
def call(call_id, name, args):
return {"id": call_id, "name": name, "args": args}
old_call = call("old-1", "get_weather", {"city": "서울", "units": "metric"})
new_id_same_args = call("new-1", "get_weather", {"units": "metric", "city": "서울"})
batch_duplicate = (call("batch-1", "get_news", {"topic": "AI"}),
call("batch-2", "get_news", {"topic": "AI"}))
normal = finish(MAX_STEPS, final=True)
mixed = finish(MAX_STEPS, final=True,
calls=(call("mixed-1", "get_weather", {"city": "서울"}),))
cap = finish(MAX_STEPS, calls=(call("cap-1", "get_weather", {"city": "서울"}),))
duplicate = finish(MAX_STEPS, calls=(new_id_same_args,), seen=(fingerprint(old_call),))
two_pending = finish(MAX_STEPS, calls=(call("weather-1", "get_weather", {"city": "서울"}),
call("news-1", "get_news", {"topic": "AI"})))
fixtures = (normal, cap, duplicate, two_pending)
assert [reason for reason, _ in fixtures] == ["normal", "step_limit", "duplicate_tool_call", "step_limit"]
assert all(is_closed(history) for _, history in fixtures)
assert mixed[0] == "step_limit" and is_closed(mixed[1])
assert has_duplicate((new_id_same_args,), {fingerprint(old_call)})
assert has_duplicate(batch_duplicate, set())
batch = finish(1, calls=batch_duplicate)
assert batch[0] == "duplicate_tool_call" and is_closed(batch[1])
failed_guard = {"step_count": MAX_STEPS, "seen": [fingerprint(old_call)], "termination": "step_limit"}
next_turn = start_turn(normal[1], "tenant-a", "conversation-1")
assert failed_guard != next_turn["guard"]
assert next_turn["guard"] == {"step_count": 0, "seen": [], "termination": "running"}
assert next_turn["messages"] == normal[1]
assert namespace("tenant-a", "conversation-1") == namespace("tenant-a", "conversation-1")
assert namespace("tenant-a", "conversation-1") != namespace("tenant-b", "conversation-1")
assert is_closed([("user", None), ("assistant", ())])
assert not is_closed([("user", None), ("assistant", ("missing",))])
assert not is_closed([("user", None), ("tool", "orphan")])
assert not is_closed([("user", None), ("assistant", ("x",)), ("tool", "wrong")])
assert not is_closed([("user", None), ("assistant", ("x",)), ("tool", "x"), ("tool", "x"), ("assistant", ())])
assert not is_closed([("user", None), ("assistant", ("same", "same")), ("tool", "same"), ("assistant", ())])
assert not is_closed([("user", None), ("assistant", ("reused",)), ("tool", "reused"), ("assistant", ()),
("user", None), ("assistant", ("reused",)), ("tool", "reused"), ("assistant", ())])
assert not is_closed([("user", None), ("assistant", ("x",)), ("tool", "x")])

set은 pending lookup에만 쓰며, assistant call id를 넣기 전에 list 길이와 set 길이를 비교한다. 이 순서가 없으면 같은 id를 두 번 낸 잘못된 batch가 하나로 합쳐져 통과할 수 있다. issued는 이전 batch에서 이미 쓴 id의 재사용을 막고, pending에서 제거된 id에 다시 온 response는 duplicate 또는 unmatched response로 거부한다.

아래 항목은 실제 checkpointer·저장소·trace backend가 있어야 검증할 수 있으므로 위 snippet의 성공 범위와 분리한다.

CaseIntegration setup통과 조건
Tenant 실제 접근 거부tenant A가 저장한 뒤 tenant B credential로 같은 id 조회namespace 문자열이 다른 데 그치지 않고 B의 read·write가 저장소 계층에서 거부됨
Checkpointer 재시작process를 종료·재기동해 같은 namespace 조회in-memory 구현은 state 소실, persistent 구현은 retention 범위 안에서 closed state와 replay 유지
Trace field 완전성정상·cap·duplicate 실행을 trace backend로 전송goal, step, call id, fingerprint, result, token·cost·latency, termination reason을 연결해 조회

Worked example — 같은 step에서 종료 순서가 만드는 차이

섹션 제목: “Worked example — 같은 step에서 종료 순서가 만드는 차이”

MAX_STEPS=4이고 네 번째 model decision을 받았다고 하자. cap 숫자는 같아도 decision의 모양에 따라 terminal history가 달라진다.

네 번째 decision올바른 route닫힌 history잘못된 구현이 만드는 실패
tool call 없는 final answernormalAI(final)cap을 먼저 검사해 이미 끝난 작업을 step_limit으로 오판
weather와 news 두 callstep_limitAI(calls) -> Tool(skipped weather) -> Tool(skipped news) -> AI(final)첫 call만 닫아 다음 turn에서 unresolved news call 발생
이전 step과 같은 weather argumentsduplicate_tool_callAI(call) -> Tool(skipped duplicate) -> AI(final)새 call id만 보고 다시 실행해 stuck loop 지속

이 예제의 핵심은 종료 reason 문자열이 아니라 history가 다음 turn에 이어질 수 있는 상태인가다. Guard가 tool을 실행하지 않았더라도 모델이 이미 call을 제안했다면 각 call에 대응하는 관찰 결과를 남겨야 한다. 반대로 final answer에는 pending call이 없으므로 가짜 tool response를 만들지 않는다.

Fixture는 city=서울, topic=AI이고 두 방식 모두 weather와 news를 한 번씩 읽어 같은 결과를 만든다고 하자.

방식tool callspolicy decisions해석
고정 workflow20두 tool과 합류점이 코드에 이미 정해져 있다
scripted agent23weather 선택, news 선택, 종료 판단이 추가된다

결과와 tool 호출 수가 같은데 agent에만 세 번의 policy decision이 생긴다. 이 숫자는 외부 성능 주장이 아니라 fixture에서 결정적으로 나온다. 관찰에 따라 세 번째 tool이 필요해지는 labelled case가 반복되고, 그 성공률 증가가 token·tool 비용과 p95 latency 증가보다 클 때 agent의 동적 선택이 가치를 증명한다.

Checkpoint state를 이어 쓰는 두 번째 turn에서는 이전 messages가 남아도 guard는 새로 시작해야 한다. in-memory checkpointer는 process lifetime에만 존재하고, persistent checkpointer는 외부 storage와 retention 정책이 보장하는 범위에서 재시작 뒤에도 남는다. 어느 쪽도 사용자 선호나 도메인 사실의 정확성을 보장하는 long-term memory가 아니다.

관찰 목표최소 evidence실패 신호
정상 replay같은 namespace의 closed checkpoint에서 같은 fixture 재개checkpoint 위치를 배열 index로 추측하거나 pending call 뒤에서 재개
Memory contamination 복구잘못된 fact가 들어오기 전 checkpoint와 별도 memory hit sourcecheckpoint만 되돌렸는데 오염된 long-term memory가 다시 검색됨
Goal drift 복구마지막 on-goal checkpoint, immutable original goal, 새 branch tracedrift 이후 pending action을 실행하거나 새 답이 원래 done criteria를 벗어남
비용·지연 관측step별 token, tool latency, retry, critical path평균만 보고 p95·실패 retry·병렬 total work를 누락
Multi-agent 비교agent 간 message 수, merge 실패, 성공률통신 비용만 늘고 single-agent baseline보다 품질이 낮음

같은 짧은 기술 설명과 사람이 판정한 작은 gold dataset을 사용한다. Critic loop는 Generator -> Critic -> Reviser를 최대 2회 실행하고, 정확성·누락·구체성·톤·위험한 주장 5개 항목을 고정한다.

검증가상 정책관찰 지표
품질 종료score >= 0.85이고 critical issue 0score before/after, human override
수렴 종료score_delta < 0.02round별 개선 폭
비용 종료max_rounds=2, max_cost_per_request=$0.20round count, tokens/cost per round
Judge biassame-model critic과 cross-model critic 비교disagreement rate, 길이·순서 편향
고위험 actionsecurity/payment 판단 발생반복 대신 human escalation
Reasoning 비교일반 모델 multi-step vs reasoning 모델 single-step성공률, 총 token, step 수, p95 latency

Critic이 pass했다는 사실만으로 정답은 아니다. Gold dataset과 human override에서 critic의 precision/recall을 확인하고, 두 critic이 충돌하면 반복을 늘리기보다 사람에게 넘긴다.

증상먼저 확인할 계약교정 방향
loop가 멈추지 않는다normal/duplicate/cap route 순서, per-turn guard종료 reason을 terminal state와 trace에 함께 기록
필요한 tool이 호출되지 않는다해당 fixture에 tool이 정말 필요한지, 노출 schema·descriptionlabelled fixture와 tool 선택 eval을 먼저 고침
두 번째 turn이 즉시 cap에 걸린다guard reset과 checkpoint merge 규칙messages만 이어 받고 실행 guard는 reset
두 번째 turn에 history가 없다namespace 변경, in-memory saver 재생성·process restartpersistence 요구에 맞는 checkpointer 선택
multi-agent가 더 나쁘다공유 context 의존, message·merge overheadsingle agent나 고정 workflow로 축소
cost runaway가 난다step, retry, tool 내부 비용, long context요청별 budget, 중복 guard, result 압축, smaller model routing

실습 성공 기준은 두 층으로 나뉜다. 위 순수 snippet은 정상·중복·cap 우선순위, call-response 상관관계, canonical fingerprint, turn reset, namespace 파생 fixture만 통과시키면 된다. tenant 접근 거부·checkpointer 재시작 지속성·trace field 완전성은 실제 integration 환경에서 별도로 통과해야 한다. 그 결과를 바탕으로 같은 fixture의 workflow와 agent 제어 비용 차이, checkpoint state와 long-term memory의 차이를 설명할 수 있으면 된다.

  1. Workflow(코드가 허용 그래프·경계를 정의) 안에서도 LLM routing은 가능하며, Agent는 관찰에 따라 다음 행동 순서를 동적으로 고른다.
  2. Workflow 5대 패턴은 LLM 단계 조합이고, workflow engine/durable execution은 실행·복구 계층이다.
  3. State machine·observability·human-in-the-loop가 production agent의 핵심 패턴이다.
  4. Single agent 우선, multi-agent는 독립 병렬 탐색·역할 분리 이득이 비용·debug 난이도를 넘을 때만 쓴다.
  5. SWE-Bench Verified·τ-bench·BFCL은 대표 평가 축을 보여주며, cost runaway·goal drift·stuck loop·judge bias가 흔한 silent failure다.

최종 수정: 2026-07-10