Field Log · Entry
Agent Planning 패턴: ReAct·Plan/Execute·ReWOO·Tree Search (4/10)
오늘의 결론
- Planning은 장문의 reasoning을 출력하는 일이 아니라 미래 action과 dependency·완료 조건을 명시하는 일입니다.
- 불확실성이 높으면 ReAct처럼 짧게 행동하고 관찰하며, 구조가 명확하면 plan DAG로 병렬화합니다.
- 계획은 observation이 바뀌면 낡습니다. 끝까지 고집하지 말고 replan trigger와 plan version을 둡니다.
- Tree search는 모든 질문의 기본값이 아니라 simulation·검증·backtracking 가치가 큰 제한된 문제에 씁니다.
- 간단한 요청은 계획하지 않는 것이 가장 좋은 계획일 수 있습니다.
앞 글에서 질문마다 검색 깊이를 선택했습니다. ITERATIVE_SEARCH route가 정해졌다고 해서 action 순서가 자동으로 생기는 것은 아닙니다.
예를 들어 이런 목표가 있습니다.
현재 환불 정책과 6월 개정판을 비교하고, 변경된 기한이 어떤 FAQ와 지원 매뉴얼에 영향을 주는지 근거와 함께 설명해 줘.
필요한 action은 여러 개입니다.
현재 revision 찾기
6월 revision 찾기
두 문서 읽기
차이 추출하기
FAQ 찾기
지원 매뉴얼 찾기
근거 연결하기
답변 검증하기
어떤 것은 병렬로 실행할 수 있고, 어떤 것은 앞 결과가 있어야 시작할 수 있습니다. 이 관계를 설계하는 것이 planning입니다.
그림 1. 관찰 불확실성, dependency의 명확성, branch 검증 가능성에 따라 planning 패턴을 고른다. 더 복잡한 패턴이 항상 더 좋은 것은 아니다.
Reasoning trace와 Plan을 구분하자
다음은 계획처럼 보이지만 실행 계약으로 쓰기 어렵습니다.
먼저 정책을 잘 살펴보고 관련 내용을 생각한 다음,
필요하다면 더 검색하고 정확한 답을 작성하겠다.
무엇을 호출하고, 무엇이 성공이며, 어느 결과가 다음 단계에 필요한지 알 수 없습니다.
실행 가능한 plan은 최소한 다음을 가집니다.
{
"plan_id": "plan_004",
"version": 1,
"goal": "refund policy revisions and downstream documents comparison",
"steps": [
{
"id": "s1",
"tool": "get_policy_revision",
"arguments": {"policy": "refund", "revision": "current"},
"depends_on": [],
"success": "one authorized revision returned",
"on_failure": "FAIL_SOURCE_NOT_FOUND"
},
{
"id": "s2",
"tool": "get_policy_revision",
"arguments": {"policy": "refund", "revision": "2026-06"},
"depends_on": [],
"success": "one authorized revision returned",
"on_failure": "FINISH_COMPARISON_UNAVAILABLE"
},
{
"id": "s3",
"tool": "compare_evidence",
"arguments": {
"left": {"$from": "s2.data.evidence_ids"},
"right": {"$from": "s1.data.evidence_ids"}
},
"depends_on": ["s1", "s2"],
"success": "differences carry evidence spans",
"on_failure": "REPLAN"
}
]
}
Plan은 hidden chain-of-thought가 아니라 외부에서 검사할 수 있는 action graph입니다.
Pattern 0: 계획하지 않는 Direct execution
먼저 이 선택을 넣어야 합니다.
query: DOC-4821을 열어 줘
route: DIRECT_LOOKUP
action: get_document_by_id(DOC-4821)
이 요청에 plan 생성 model call을 추가하면 품질은 거의 그대로이고 latency와 failure point만 늘어납니다.
Direct가 맞는 조건:
- action이 한두 개로 확정적
- dependency가 없음
- 실패 fallback이 단순함
- tool이 read-only
- outcome을 바로 검증 가능
Planning의 첫 gate는 needs_plan: true|false입니다.
Pattern 1: ReAct — 보고 다음 행동을 정한다
ReAct의 핵심 흐름은 reasoning과 action, observation을 번갈아 사용하는 것입니다.
state₀
→ decide action₁
→ observation₁
→ decide action₂
→ observation₂
→ ...
RAG 예:
Action: search current refund policy
Observation: revision 2026-07-01 발견
Action: read revision header and refund window
Observation: 14일, effective_at 2026-07-15
Action: search previous revision
Observation: 2026-06-01 발견
장점
- observation을 즉시 반영
- 처음부터 전체 경로를 알 필요가 없음
- 잘못된 가정을 빠르게 수정
- open-ended environment에 적합
단점
- action마다 model call이 생길 수 있음
- 전체 dependency를 보지 못해 병렬화가 약함
- 같은 검색을 반복하거나 loop에 빠질 수 있음
- 긴 trajectory에서 context와 비용 증가
적합한 경우
- tool 결과가 다음 action을 크게 바꿈
- source가 존재하는지 모름
- failure가 흔하고 다양한 fallback이 있음
- step 수가 작고 hard budget이 있음
운영에서는 private reasoning 전체보다 action, reason_code, expected_observation을 기록합니다.
Pattern 2: Plan-and-Execute — 먼저 나누고 실행한다
Planner → ordered plan
Executor → step₁, step₂, step₃
Replanner → 필요할 때 수정
Plan-and-Solve는 먼저 문제를 분해한 뒤 해결하는 prompting 전략을 다룹니다. Agent에서는 이를 tool action plan과 executor 분리로 확장할 수 있습니다.
장점
- 목표 누락을 줄임
- progress를 사용자에게 설명하기 쉬움
- plan validation과 사전 budget 계산 가능
- planner와 executor model을 다르게 선택 가능
단점
- 첫 observation 전에 만든 가정이 틀릴 수 있음
- 너무 세세한 plan은 곧 낡음
- planner failure가 모든 step에 전파
- 실행 중 새 evidence를 반영하려면 replan 필요
적합한 경우
- subgoal이 비교적 명확함
- 긴 작업의 progress와 audit가 중요함
- 사용자 승인 지점을 미리 보여 줘야 함
- tool 결과가 구조를 완전히 바꾸지는 않음
Pattern 3: ReWOO — Dependency를 먼저 표현한다
ReWOO는 reasoning을 observation과 분리하고 tool result를 symbolic variable로 참조하는 구조를 제안합니다.
E1 = get current revision
E2 = get previous revision
E3 = compare #E1 #E2
E4 = search affected FAQ using #E3.changed_terms
핵심은 결과를 미리 지어내는 것이 아니라 결과 자리와 dependency를 미리 정의하는 것입니다.
장점
- 독립 step 병렬 실행
- dependency graph 검증 가능
- 반복 model call 감소 가능
- executor가 symbolic result를 안전하게 bind
단점
- 예상하지 못한 observation에 덜 유연함
- variable binding과 schema compatibility가 필요
- plan이 틀리면 downstream step이 한꺼번에 invalid
- side effect 충돌을 별도 검사해야 함
적합한 경우
- 데이터 수집 step이 독립적
- tool output schema가 안정적
- latency를 병렬화로 줄일 가치가 큼
- 실행 전 전체 비용을 계산해야 함
Pattern 4: Tree Search — 여러 경로를 탐색한다
Tree of Thoughts는 여러 중간 reasoning 경로를 탐색하고 평가하는 framework를 제시합니다. LATS는 reasoning, acting, planning을 tree search로 연결합니다.
state₀
/ | \
action A action B action C
/ \ | / \
... ... ... ... ...
장점
- 한 경로의 초기 실수에 덜 묶임
- backtracking과 alternative exploration
- simulator나 verifier가 있으면 후보 비교 가능
단점
- branch 수만큼 model·tool 비용 증가
- 실제 side effect를 branch마다 실행할 수 없음
- self-evaluation이 부정확하면 search도 잘못됨
- trace와 state 관리가 복잡
적합한 경우
- action이 read-only 또는 simulation 가능
- candidate의 quality를 자동 검증할 수 있음
- 잘못된 초기 선택의 비용이 큼
- 작은 branch factor와 depth로 제한 가능
Production RAG에서 tree search를 매 질문의 기본값으로 쓰기보다, 복잡한 query decomposition이나 계획 후보를 offline 또는 좁은 high-value route에서 비교하는 편이 현실적입니다.
네 패턴을 한 표로 비교하기
| 패턴 | Model 호출 | Observation 적응 | 병렬화 | 비용 | 잘 맞는 문제 |
|---|---|---|---|---|---|
| Direct | 최소 | 불필요 | 불필요 | 낮음 | 확정적 1-step |
| ReAct | step마다 가능 | 높음 | 낮음 | 중간~높음 | 불확실한 탐색 |
| Plan/Execute | planner + executor | 중간 | 중간 | 중간 | 명확한 subgoal |
| ReWOO/DAG | plan + execution | 중간 | 높음 | 중간 | 안정된 tool graph |
| Tree Search | branch별 반복 | 높음 | 가능 | 매우 높음 | 검증 가능한 탐색 |
Plan schema의 필수 필드
Goal과 success criteria
Plan이 무엇을 완료해야 하는지 정의합니다.
Step ID
재시도·trace·result binding에 쓰는 stable ID입니다.
Tool과 arguments
등록된 tool version과 input schema에 맞아야 합니다.
Dependency
어떤 step이 완료돼야 시작할 수 있는지 나타냅니다.
Result reference
$from: s1.data.evidence_ids처럼 구조화된 field를 참조합니다. 자유 문자열 치환을 피합니다.
Success condition
HTTP 성공이 아니라 이 step이 필요한 결과를 얻었는지 정의합니다.
Failure policy
RETRY_SAME
REPAIR_ARGUMENTS
SKIP_OPTIONAL
REPLAN
ASK_USER
FAIL_SAFE
Effect와 approval boundary
Write step은 계획됐다는 이유로 승인된 것이 아닙니다.
Plan validator 만들기
Model plan을 실행하기 전에 deterministic validation을 수행합니다.
def validate_plan(plan, registry, route_policy):
issues = []
step_ids = {step.id for step in plan.steps}
if len(step_ids) != len(plan.steps):
issues.append("DUPLICATE_STEP_ID")
for step in plan.steps:
if not registry.exists(step.tool, step.tool_version):
issues.append(f"UNKNOWN_TOOL:{step.id}")
missing = set(step.depends_on) - step_ids
if missing:
issues.append(f"MISSING_DEPENDENCY:{step.id}:{sorted(missing)}")
if step.tool not in route_policy.allowed_tools:
issues.append(f"TOOL_NOT_ALLOWED_BY_ROUTE:{step.id}")
registry.validate_arguments(step.tool, step.arguments)
if has_cycle(plan.steps):
issues.append("CYCLIC_PLAN")
if estimate_cost(plan) > route_policy.max_cost_usd:
issues.append("PLAN_COST_EXCEEDS_BUDGET")
if critical_path_ms(plan) > route_policy.deadline_ms:
issues.append("PLAN_DEADLINE_INFEASIBLE")
return issues
추가 검사:
- result reference가 dependency 안의 step만 가리키는가
- producer output field와 consumer input type이 맞는가
- 병렬 write가 같은 resource를 수정하지 않는가
- approval 없이 irreversible step이 runnable한가
- optional step 실패가 required success criterion을 깨지 않는가
- 최대 node와 depth를 넘지 않는가
DAG 실행과 병렬화
s1 current revision ─┐
├→ s3 compare ─┬→ s5 affected FAQ ─┐
s2 previous revision ┘ └→ s6 manual search ├→ s7 answer
s4 terminology map ──────────────────────────────────────┘
s1, s2, s4는 동시에 실행할 수 있습니다. 하지만 단순히 dependency가 없다고 모든 step을 병렬 실행하면 안 됩니다.
병렬 실행 전 확인:
- global rate limit
- shared cost budget reservation
- 동일 resource write conflict
- result ordering requirement
- cancellation propagation
- 한 branch 실패 시 다른 branch 결과를 보존할지
Budget은 완료 뒤 차감하는 대신 시작 전에 reserve합니다.
async def run_ready_steps(state, plan, budget):
ready = find_ready_steps(plan, state.completed_steps)
approved = []
for step in ready:
reservation = budget.try_reserve(estimate(step))
if reservation:
approved.append((step, reservation))
return await gather_with_limit(
[execute(step, reservation) for step, reservation in approved],
concurrency=3,
)
Plan은 언제 낡는가
Plan을 만든 시점의 가정이 깨지면 replan합니다.
명시적인 replan trigger:
- required source가 없음
- tool output schema는 맞지만 cardinality가 예상과 다름
- entity가 여러 개라 ambiguity 발생
- resource revision이 plan 이후 변경
- permission이나 capability 변경
- cost·time estimate가 threshold 초과
- success criterion이 이미 충족되어 남은 step 불필요
- 같은 failure code가 반복
모든 step 뒤 무조건 replan하면 ReAct와 비슷한 비용이 됩니다. 반대로 실패해도 처음 plan을 고집하면 stale plan이 됩니다.
def needs_replan(state, plan, event):
return any((
event.code in plan.replan_on,
event.invalidated_assumptions,
event.resource_revision_changed,
state.remaining_budget < plan.estimated_remaining_cost,
state.goal_revision != plan.goal_revision,
))
Receding horizon: 가까운 것만 자세히 계획한다
긴 작업 전체를 세부 tool call까지 계획하면 뒤쪽 step은 높은 확률로 낡습니다.
전체 goal
├─ 가까운 2~3 step: 실행 가능한 상세 plan
└─ 먼 단계: milestone 수준의 coarse plan
예:
Milestone 1: 두 정책 revision 확보
s1 get current
s2 get previous
Milestone 2: 변경점과 영향 문서 찾기
revision 확보 뒤 상세 planning
Milestone 3: 답변과 citation 검증
로봇 제어와 최적화의 receding horizon처럼, 몇 step 실행한 뒤 observation을 반영해 다음 구간을 계획합니다.
Stop rule은 Planning의 일부다
Agent가 더 좋은 evidence를 찾을 가능성은 항상 남아 있습니다. 그래서 완료 조건을 명시해야 합니다.
STOP_SUCCESS
required evidence groups covered
contradictions resolved or disclosed
citation validator passed
STOP_DIMINISHING_RETURN
last 2 searches added no new evidence group
STOP_BUDGET
search/read/time/cost limit reached
STOP_AMBIGUOUS
critical slot missing → ask user
STOP_UNSAFE
required action exceeds capability or approval
“충분히 조사해”라는 prompt는 stop rule이 아닙니다.
Planning 실패를 분류하기
| Failure | 의미 | 처방 |
|---|---|---|
| Missing step | success criterion에 필요한 action 누락 | goal coverage validator |
| Invalid dependency | 결과 전에 consumer 실행 | DAG validation |
| Hallucinated tool | registry에 없는 tool | pinned registry |
| Bad binding | output field와 input type 불일치 | schema compatibility |
| Stale plan | observation 뒤 가정이 깨짐 | replan trigger |
| Over-planning | 쉬운 요청에 너무 많은 step | needs-plan gate |
| Under-planning | multi-hop을 즉흥 1-step 처리 | complexity route |
| Loop | 같은 state-action 반복 | state fingerprint + stop |
| Unsafe effect | 승인 전 write | effect gate at execution |
State fingerprint로 loop 잡기
같은 상태에서 같은 action을 반복하는지 검사합니다.
import hashlib
import json
def action_fingerprint(state, action):
payload = {
"goal_revision": state.goal_revision,
"evidence_ids": sorted(state.evidence_ids),
"action": action.name,
"arguments": action.arguments,
}
raw = json.dumps(payload, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(raw.encode()).hexdigest()
같은 fingerprint가 반복되면:
- 첫 반복: model에게 structured duplicate feedback
- 둘째 반복: replan 또는 alternative action
- 한계 도달:
NO_PROGRESS_LOOP로 안전 종료
Planning 평가
Plan-level
- valid plan rate
- success criteria coverage
- unknown tool·cycle·bad binding rate
- estimated vs actual cost/latency error
- plan depth와 critical path
Execution-level
- task success
- step success와 first failure step
- unnecessary tool calls
- parallel speedup
- replan rate와 replan success
- stale plan execution rate
- loop·budget termination rate
비교 실험
같은 task에서 다음을 비교합니다.
Direct baseline
ReAct with max 8 steps
Plan/Execute with max 1 replan
DAG with concurrency 3
Tree search는 비용이 크게 다르므로 branch·depth·rollout budget을 함께 보고합니다.
흔한 실패 패턴
1. 모든 요청에 planner 호출
쉬운 lookup까지 지연됩니다.
처방: needs-plan gate와 direct baseline을 둡니다.
2. 자연어 bullet을 plan으로 저장
dependency, tool version, 성공 조건을 검증할 수 없습니다.
처방: typed plan DAG를 사용합니다.
3. 실행 결과를 plan에 미리 채움
모델이 아직 보지 않은 source ID나 사실을 지어냅니다.
처방: symbolic result reference만 허용합니다.
4. 실패할 때마다 전체 재계획
일시적 timeout에도 이미 완료된 step을 잃습니다.
처방: error taxonomy와 checkpoint를 사용해 필요한 subgraph만 갱신합니다.
5. Plan approval을 실행 approval로 재사용
실행 시점에 recipient, revision, authority가 바뀔 수 있습니다.
처방: side effect는 commit 직전에 다시 authorize합니다.
Production 체크리스트
- reasoning trace와 executable plan을 구분한다.
- 계획하지 않는 direct path가 있다.
- 불확실성·dependency·검증 가능성으로 planning 패턴을 고른다.
- plan에 goal, step ID, tool version, dependency, result reference가 있다.
- 각 step에 success condition과 failure policy가 있다.
- cycle, unknown tool, schema binding, budget, deadline을 사전 검증한다.
- 병렬 step은 budget과 resource conflict를 먼저 검사한다.
- plan version과 가정, replan trigger를 기록한다.
- receding horizon으로 먼 단계를 과도하게 세분화하지 않는다.
- success, no-progress, budget, ambiguous, unsafe stop rule이 있다.
- 같은 state-action fingerprint 반복을 감지한다.
- task quality와 step 효율·replan·비용을 함께 평가한다.
스스로 확인하기
- ReAct와 plan-and-execute는 observation을 반영하는 시점이 어떻게 다른가?
- ReWOO식 symbolic result reference가 사실을 미리 생성하는 것과 다른 이유는 무엇인가?
- Tree search가 write tool이 있는 production agent의 기본값이 되기 어려운 이유는 무엇인가?
- 여러분의 RAG task에서 병렬 실행할 수 있는 독립 step은 무엇인가?
- 어떤 observation이 현재 plan을 invalidate해야 하는가?
다음 글에서는 검색 결과가 관련 있어 보이는지를 넘어 답에 필요한 근거가 충분한지 판정하고, 부족하면 Self-RAG·CRAG·FLARE식으로 재검색하거나 안전하게 멈추는 loop를 만듭니다.
참고자료
- ReAct: Synergizing Reasoning and Acting in Language Models
- Plan-and-Solve Prompting
- ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models
- Tree of Thoughts: Deliberate Problem Solving with Large Language Models
- Language Agent Tree Search Unifies Reasoning, Acting, and Planning in Language Models