Page History
...
1.2 실측 데이터 — 하네스 로그에서 확인된 증상
증상 | 수치 | 근본 원인 |
|---|---|---|
term_read 폴링 폭주 | 55초간 12회 동일 데이터 | 비동기 완료를 기다릴 수 없어서 반복 읽기 |
턴 완료 시그널 미응답 → 사회자 교착 | E2E 미팅 교착 | 루프 종료 후 시그널 수신 불가 |
컨텍스트 폭증 | 15분에 10K→30K chars | 폴링 반복으로 히스토리 누적 |
잔여 작업 미진행 | 계획의 60%만 완료 | 루프 종료 = LLM 비활성 |
이 증상들은 모두 하나의 구조적 결함을 가리킨다: “완료 시그널 부재(Completion Signal Absence)”. LLM은 명령을 보내놓고 결과를 기다릴 수 없으며, 기다리려고 폴링하면 컨텍스트가 폭발한다.
1.3 에이전트와 LLM의 차이
특성 | 일반 LLM (펑션콜) | 워크 에이전트 |
|---|---|---|
실행 모델 | 동기 루프, 라운드 제한 | 이벤트 기반, 시그널 반응 |
비동기 작업 | 폴링으로 대체 | 완료 시그널 대기 |
실패 복구 | 재시도 없음 (라운드 소진) | 자동 재시도 / 에스컬레이션 |
상태 추적 | 히스토리 의존 (윈도우 제한) | 명시적 상태 머신 |
타임아웃 | 전체 루프 단위 | 단계별 세밀한 제어 |
결론: 일반 LLM에 “기다림”과 “깨어남”을 가르치는 것이 에이전트화의 핵심이다.
...
핵심은 Action 2의 “대기”다. 기존 동기 루프에서는 이 대기가 불가능하다. ReAct의 논문 원문은 관찰이 즉시 반환되는 것을 가정하지만, 실제 시스템에서는 npm test가 30초 걸릴 수 있다. 이 간극을 메우는 것이 Akka 상태 머신이다.
2.2 다른 플래닝 패턴과의 비교
패턴 | 전략 | 장점 | 한계 |
|---|---|---|---|
ReAct | 생각→행동→관찰 반복 | 관찰에 근거한 추론, 환각 감소 | 비동기 대기 미지원 (원본) |
Plan-and-Execute | 전체 계획 수립 후 순차 실행 | 예측 가능한 작업에 효율적 | 계획 수정이 어려움 |
Tree-of-Thought | 여러 추론 경로 탐색, 가지치기 | 복잡한 문제 해결력 | LLM 호출 비용 높음 |
LATS | 몬테카를로 트리 탐색 + LLM | 최적 경로 탐색 | 실시간 시스템에 부적합 |
Reflexion | 실패 후 자기 성찰, 교훈 기억 | 반복 학습 가능 | 추가 메모리 관리 필요 |
AgentWin이 ReAct를 선택한 이유: 실시간 도구 실행 + 비동기 환경에서는 “한 번에 전체 계획”보다 “한 걸음씩 관찰하며 진행”이 현실적이다. 다만 원본 ReAct에 없는 Waiting 상태를 추가해야 한다.
...
3.2 왜 switch/case가 아닌 Become인가
관점 | switch/case FSM | Akka Become() |
|---|---|---|
상태 추가 | case 문 추가, 모든 메시지에 상태 체크 | 메서드 하나 추가, 해당 상태 메시지만 처리 |
스레드 안전 | lock 필요 | 액터 모델이 보장 (단일 스레드 처리) |
잘못된 상태의 메시지 | 무시 또는 예외 | Stash()로 보관, 나중에 Unstash() |
타임아웃 | 별도 타이머 관리 | ReceiveTimeout 내장 |
실패 복구 | try/catch 중첩 | Supervision Strategy |
테스트 | 전체 FSM 초기화 필요 | TestKit으로 상태별 메시지 주입 |
3.3 핵심: 액터는 죽지 않는다
동기 루프와 액터의 결정적 차이:
...
| Code Block | ||
|---|---|---|
| ||
UserInput / CompletionSignal
│
┌──────▼──────┐
┌────────│ Thinking │←────────────┐
│ │ (LLM 호출) │ │
│ └──────┬──────┘ │
│ │ │
│ ToolCalls? │ No ToolCalls? │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ │
┌────┴────┐ ┌───────────┐ │
│ Acting │ │ Complete │ │
│(도구실행) │ │ (최종응답) │ │
└────┬────┘ └───────────┘ │
│ │
│ 즉시 결과? 비동기 대기? │
├─────────────┬───────────────────────┤
▼ ▼ │
Become(Thinking) ┌──────────┐ │
│ Waiting │──completion──┘
│(시그널대기)│
└────┬─────┘
│ timeout (30s)
▼
Become(Thinking)
"타임아웃됨, 현재 상태 확인" |
4.2 상태별 동작 명세
상태 | 진입 조건 | 수신 메시지 | 전이 |
|---|---|---|---|
Thinking | StartReAct / CompletionSignal / 도구결과 / timeout | — | LLM 호출 → ToolCalls 유무로 분기 |
Acting | Thinking에서 ToolCalls 있음 | — | 도구 실행 → 즉시결과면 Thinking, 비동기면 Waiting |
Waiting | Acting에서 비동기 도구 | CompletionSignal, TaskComplete, ReceiveTimeout | 시그널 → Thinking, 타임아웃 → Thinking |
Complete | Thinking에서 ToolCalls 없음 | — | 부모에게 결과 전송, 액터 대기 |
4.3 Waiting 상태가 풀어내는 것
Before (동기 루프):
...
ReActActor가 Acting → Waiting 전환 여부를 결정하는 기준:
도구 | 동작 | 판별 |
|---|---|---|
| 터미널에 명령 전송, 결과는 나중에 | 비동기 → Waiting |
| 버퍼 즉시 반환 | 동기 → Thinking |
| 액터 메시지 전송, 응답은 나중에 | 비동기 → Waiting |
| 파일 쓰기 후 완료 대기 | 비동기 → Waiting |
| 즉시 반환 | 동기 → Thinking |
...
6. 보조 장치 — 세션 메모리와 진단 로깅
ReAct 상태 머신만으로는 부족하다. 특히 온디바이스 소형 모델을 사회자로 쓸 때, 두 가지 보조 장치가 필수다.
...
ReAct 루프는 비결정적(non-deterministic)이다. LLM이 왜 그 행동을 선택했는지 추적하려면 전체 경로를 기록해야 한다.
로그 태그 | 기록 내용 | 용도 |
|---|---|---|
| LLM 요청 내용 + 응답 | 추론 트레이스 추적 |
| 도구 호출명 + 인자 + 결과 | 행동 검증 |
| 대기 시작 + 시그널 수신/타임아웃 | 비동기 흐름 추적 |
| Become() 전환 기록 | 상태 전이 검증 |
이 로그는 하네스(Harness) 평가에 그대로 연결된다. 비결정적 시스템에서 “관찰 가능성(observability)”은 선택이 아니라 필수다.
...
2025–2026년 현재, 주요 LLM 에이전트 프레임워크들은 다음과 같이 플래닝과 상태를 관리한다:
프레임워크 | 플래닝 방식 | 상태 관리 | 비동기 모델 | 장애 복구 |
|---|---|---|---|---|
LangGraph | 그래프 기반 워크플로우 | 체크포인트된 dict | Python asyncio | 수동 재시도 |
OpenAI Agents SDK | 에이전트 간 핸드오프 | Runner 내부 관리 | Python async | 제한적 |
CrewAI | 역할 기반 멀티에이전트 | 공유 메모리 | 프로세스 기반 | 없음 |
AutoGen | 멀티에이전트 대화 | 대화 히스토리 | Python async | 제한적 |
Semantic Kernel | Planner + Plugins | Kernel 컨텍스트 | C#/.NET async | 제한적 |
그리고 Akka 액터 모델:
역량 | 대부분의 프레임워크 | Akka 액터 |
|---|---|---|
타입 안전 상태 전이 | dict 기반, 런타임 에러 | Become() — 컴파일 타임 메서드 분리 |
장애 복구 | try/catch, 수동 재시도 | Supervision Strategy 자동 복구 |
동시성 | 스레드/async 직접 관리 | 액터 = 단일 스레드 보장, 자연 격리 |
분산 실행 | 별도 인프라 구축 | Akka.Cluster 내장 |
이벤트 소싱 | 없음 (외부 DB) | Akka.Persistence 내장 |
역압(Backpressure) | 없음 | Akka Streams 통합 |
| Note |
|---|
트레이드오프: Akka는 Python 스크립트 한 줄로 시작할 수 없다. 학습 곡선이 있고, 인프라 설정이 필요하다. 하지만 프로덕션급 에이전트 시스템 — 신뢰성, 동시성, 확장성이 필요한 환경 — 에서는 액터 모델이 Python async 프레임워크보다 견고한 기반이 된다. |
...
에이전트에게 자율성을 주면 반드시 고삐가 필요하다:
장치 | 설명 | 값 |
|---|---|---|
MaxRounds | ReAct 루프 최대 반복 | 10 |
ReceiveTimeout | Waiting 상태 타임아웃 | 30초 |
동일 호출 제한 | 같은 도구 연속 호출 차단 | 3회 |
MaxCallsPerRound | 라운드당 도구 호출 상한 | 30 |
CancelReAct | 사용자 수동 취소 | 즉시 중단 |
특히 ReceiveTimeout은 Akka에 내장된 기능이다. Waiting 상태에서 30초 내에 시그널이 없으면 자동으로 Thinking으로 돌아가 “타임아웃됐다, 현재 상태를 확인하겠다”는 판단을 LLM에게 넘긴다. 교착 상태가 구조적으로 불가능해진다.
...
이번 2편에서 다룬 핵심 기술과, 이후 활용될 기술을 정리한다:
기술 | 역할 | 상태 |
|---|---|---|
ReAct Pattern | LLM의 추론-행동-관찰 루프 구조화 | 설계 완료, Phase 2 구현 예정 |
Akka.NET Become() | 상태 머신 런타임 전환, 스레드 안전 | Phase 2 핵심 |
Akka ReceiveTimeout | 비동기 대기 타임아웃, 교착 방지 | Phase 2 핵심 |
Akka Supervision | 자식 액터 장애 자동 복구 | 기존 적용, 확장 예정 |
세션 메모리 (Actor State) | 온디바이스 모델의 컨텍스트 보존 | 설계 완료, Phase 2와 통합 |
LM-Kit.NET | 온디바이스 추론 엔진 (gemma-4) | NuGet 설치 완료, Phase 4 |
ILlmProvider 추상화 | 원격/온디바이스 LLM 전환 투명화 | Phase 4 |
Akka.Persistence | 이벤트 소싱으로 상태 재생/복구 | 향후 검토 |
Akka Streams | LLM API 역압(backpressure) 제어 | 향후 검토 |
...
11. 마무리 — LLM에게 기다림을 가르치다
...
Phase 2 구현이 끝나면, 그 결과를 가지고 3편에서 만나자.
| Info |
|---|
구현하고 나면 테스팅및 작동에 확신있는 클코드에게.. 정말 자신있어? |
...
참고
- ReAct: Synergizing Reasoning and Acting in Language Models — Yao et al. (2022)
- Tree of Thoughts: Deliberate Problem Solving with Large Language Models — Yao et al. (2023)
- LATS: Language Agent Tree Search — Zhou et al. (2023)
- Akka.NET — Switchable Behaviors (Become)
- Akka.NET — Finite State Machine
- LangGraph — GitHub
- OpenAI Agents SDK
- AgentWin Tech/DOC — ReAct-ActorPlanning.md, OnDevice-SessionMemory.md, AiMode-DiagnosticLogging.md
...


