에이전트 루프 내부
핵심 오케스트레이션 엔진은 run_agent.py의 AIAgent 클래스입니다. 이 클래스는 프롬프트 어셈블리부터 도구 디스패치, 제공자 장애 조치까지 모든 것을 처리하는 대용량 파일(15k+ 라인)입니다.
핵심 책임
AIAgent은 다음을 담당합니다.
prompt_builder.py을 통해 효과적인 시스템 프롬프트 및 도구 스키마 조립- 올바른 제공자/API 모드 선택(chat_completions, codex_responses, anthropic_messages)
- 취소 지원을 통해 중단 가능한 모델 호출하기
- 도구 호출 실행(스레드 풀을 통해 순차적으로 또는 동시에)
- OpenAI 메시지 형식으로 대화 기록 유지
- 압축, 재시도 및 대체 모델 전환 처리
- 상위 및 하위 에이전트 전체에서 반복 예산 추적
- 컨텍스트가 손실되기 전에 영구 메모리 플러시
두 개의 진입점
# Simple interface — returns final response string
response = agent.chat("Fix the bug in main.py")
# Full interface — returns dict with messages, metadata, usage stats
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # auto-built if omitted
conversation_history=None, # auto-loaded from session if omitted
task_id="task_abc123"
)
``chat()`은 결과 dict에서 `final_response` 필드를 추출하는 `run_conversation()` 주변의 얇은 래퍼입니다.
## API 모드 \{#api-modes}
Hermes는 공급자 선택, 명시적 인수 및 기본 URL 경험적 방법을 통해 해결되는 세 가지 API 실행 모드를 지원합니다.
| API 모드 | 용도 | 클라이언트 유형 |
|----------|----------|-------------|
| `chat_completions` | OpenAI 호환 엔드포인트(OpenRouter, 맞춤형, 대부분의 공급자) | `openai.OpenAI` |
| `codex_responses` | OpenAI 코덱스/응답 API | `openai.OpenAI`(응답 형식 포함) |
| `anthropic_messages` | 기본 Anthropic 메시지 API | `anthropic.Anthropic` 어댑터를 통해 |
모드는 메시지 형식, 도구 호출 구조, 응답 구문 분석 방식, 캐싱/스트리밍 작동 방식을 결정합니다. 세 가지 모두 API 호출 전후에 동일한 내부 메시지 형식(OpenAI 스타일 `role`/`content`/`tool_calls` dicts)으로 수렴됩니다.
**모드 해결 순서:**
1. 명시적 `api_mode` 생성자 인수(가장 높은 우선순위)
2. 제공자별 감지(예: `anthropic` 제공자 → `anthropic_messages`)
3. 기본 URL 추론(예: `api.anthropic.com` → `anthropic_messages`)
4. 기본값: `chat_completions`
## 수명주기 전환 \{#turn-lifecycle}
에이전트 루프의 각 반복은 다음 순서를 따릅니다.
```text
run_conversation()
1. Generate task_id if not provided
2. Append user message to conversation history
3. Build or reuse cached system prompt (prompt_builder.py)
4. Check if preflight compression is needed (>50% 컨텍스트)
5. Build API messages from conversation history
- chat_completions: OpenAI format as-is
- codex_responses: convert to Responses API input items
- anthropic_messages: convert via anthropic_adapter.py
6. Inject ephemeral prompt layers (budget warnings, 컨텍스트 pressure)
7. Apply prompt caching markers if on Anthropic
8. Make interruptible API call (_interruptible_api_call)
9. Parse response:
- If tool_calls: execute them, append results, loop back to step 5
- If text response: persist session, flush memory if needed, return
메시지 형식
모든 메시지는 내부적으로 OpenAI 호환 형식을 사용합니다.
{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}
확장된 사고를 지원하는 모델의 추론 콘텐츠는 assistant_msg["reasoning"]에 저장되며 선택적으로 reasoning_callback을 통해 표시됩니다.
메시지 대체 규칙
에이전트 루프는 엄격한 메시지 역할 교체를 시행합니다.
- 시스템 메시지 뒤:
User → Assistant → User → Assistant →... - 도구 호출 중:
Assistant (with tool_calls) → Tool → Tool →... → Assistant - 절대로 보조 메시지 2개를 연속으로 사용하지 마세요.
- 절대로 두 개의 사용자 메시지가 연속으로 표시되지 않음
- 만
tool역할은 연속 항목을 가질 수 있습니다(병렬 도구 결과).
공급자는 이러한 시퀀스의 유효성을 검사하고 잘못된 기록을 거부합니다.
중단 가능한 API 호출
API 요청은 인터럽트 이벤트를 모니터링하는 동안 백그라운드 스레드에서 실제 HTTP 호출을 실행하는 _interruptible_api_call()에 래핑됩니다.
┌────────────────────────────────────────────────────┐
│ Main thread API thread │
│ │
│ wait on: HTTP POST │
│ - response ready ───▶ to provider │
│ - interrupt event │
│ - timeout │
└────────────────────────────────────────────────────┘
중단된 경우(사용자가 새 메시지, /stop 명령 또는 신호를 보냄):
- API 스레드가 중단되었습니다(응답이 삭제되었습니다).
- 에이전트는 새로운 입력을 처리하거나 완전히 종료할 수 있습니다.
- 대화 기록에 부분 응답이 삽입되지 않습니다.
도구 실행
순차 대 동시
모델이 도구 호출을 반환하는 경우:
- 단일 도구 호출 → 메인 스레드에서 직접 실행
- 다중 도구 호출 →
ThreadPoolExecutor을 통해 동시에 실행됨- 예외: 대화형으로 표시된 도구(예:
clarify)는 순차적 실행을 강제합니다. - 완료 순서에 관계없이 원래 도구 호출 순서에 결과가 다시 삽입됩니다.
- 예외: 대화형으로 표시된 도구(예:
실행 흐름
for each tool_call in response.tool_calls:
1. Resolve handler from tools/registry.py
2. Fire pre_tool_call plugin hook
3. Check if dangerous command (tools/approval.py)
- If dangerous: invoke approval_callback, wait for user
4. Execute handler with args + task_id
5. Fire post_tool_call plugin hook
6. Append {"role": "tool", "content": result} to history
상담원 수준 도구
일부 도구는 handle_function_call()에 도달하기 전에 run_agent.py에 의해 차단됩니다.
| 도구 | 가로채는 이유 |
|---|---|
todo | 에이전트-로컬 작업 상태 읽기/쓰기 |
memory | 문자 제한이 있는 영구 메모리 파일에 씁니다. |
session_search | 상담원의 세션 DB를 통해 세션 내역을 조회합니다. |
delegate_task | 격리된 컨텍스트로 하위 에이전트를 생성합니다. |
이러한 도구는 에이전트 상태를 직접 수정하고 레지스트리를 거치지 않고 합성 도구 결과를 반환합니다.
콜백 표면
AIAgent은 CLI, 게이트웨이 및 ACP 통합에서 실시간 진행을 가능하게 하는 플랫폼별 콜백을 지원합니다.
| 콜백 | 해고되었을 때 | 사용처 |
|---|---|---|
tool_progress_callback | 각 도구 실행 전/후 | CLI 스피너, 게이트웨이 진행 메시지 |
thinking_callback | 모델이 생각을 시작하거나 멈출 때 | CLI "생각 중..." 표시기 |
reasoning_callback | 모델이 추론 콘텐츠를 반환하는 경우 | CLI 추론 디스플레이, 게이트웨이 추론 블록 |
clarify_callback | clarify 도구가 호출될 때 | CLI 입력 프롬프트, 게이트웨이 대화형 메시지 |
step_callback | 에이전트 차례가 완료될 때마다 | 게이트웨이 단계 추적, ACP 진행 |
stream_delta_callback | 각 스트리밍 토큰(활성화된 경우) | CLI 스트리밍 디스플레이 |
tool_gen_callback | 도구 호출이 스트림에서 구문 분석되는 경우 | 스피너의 CLI 도구 미리보기 |
status_callback | 상태 변화(생각, 실행 등) | ACP 상태 업데이트 |
예산 및 대체 동작
Iteration Budget
에이전트는 IterationBudget을 통해 반복을 추적합니다.
- 기본값: 90회 반복(
agent.max_turns을 통해 구성 가능) - 각 에이전트는 자체 예산을 갖습니다. 하위 에이전트는
delegation.max_iterations(기본값 50)으로 제한된 독립 예산을 얻습니다. — 상위 + 하위 에이전트 전체의 총 반복은 상위의 한도를 초과할 수 있습니다. - 100%에서는 에이전트가 중지되고 완료된 작업 요약을 반환합니다.
폴백 모델
기본 모델이 실패하는 경우(429 비율 제한, 5xx 서버 오류, 401/403 인증 오류):
- 구성에서
fallback_providers목록을 확인하세요. - 각 대체를 순서대로 시도해 보세요.
- 성공하면 새 공급자와 대화를 계속합니다.
- 401/403에서는 장애 조치 전에 자격 증명 새로 고침을 시도합니다.
폴백 시스템은 보조 작업도 독립적으로 처리합니다. 비전, 압축, 웹 추출 및 세션 검색에는 각각 auxiliary.* 구성 섹션을 통해 구성할 수 있는 자체 폴백 체인이 있습니다.
압축 및 지속성
압축이 트리거되는 경우
- 프리플라이트(API 호출 전): 대화가 모델 컨텍스트 창의 50%를 초과하는 경우
- 게이트웨이 자동 압축: 대화가 85%를 초과하는 경우(더 공격적, 턴 간 실행)
압축 중에 어떤 일이 발생합니까?
- 메모리가 먼저 디스크로 플러시됩니다(데이터 손실 방지).
- 중간 대화 차례는 간결한 요약으로 요약됩니다.
- 마지막 N개의 메시지는 그대로 유지됩니다(
compression.protect_last_n, 기본값: 20). - 도구 호출/결과 메시지 쌍은 함께 유지됩니다(분할되지 않음).
- 새 세션 계보 ID가 생성됩니다(압축하면 "하위" 세션이 생성됨)
세션 지속성
매 턴 이후:
- 메시지는 세션 저장소에 저장됩니다(
hermes_state.py을 통한 SQLite). - 메모리 변경 사항은
MEMORY.md/USER.md로 플러시됩니다. - 세션은 나중에
/resume또는hermes chat --resume을 통해 재개될 수 있습니다.
주요 소스 파일
| 파일 | 목적 |
|---|---|
run_agent.py | AIAgent 클래스 — 완전한 에이전트 루프 |
agent/prompt_builder.py | 메모리, 기술, 컨텍스트 파일, 성격을 바탕으로 시스템 프롬프트 조립 |
agent/context_engine.py | ContextEngine ABC — 플러그형 컨텍스트 관리 |
agent/context_compressor.py | 기본 엔진 — 손실 요약 알고리즘 |
agent/prompt_caching.py | 인류 프롬프트 캐싱 마커 및 캐시 측정항목 |
agent/auxiliary_client.py | 부가 작업(비전, 요약)을 위한 보조 LLM 클라이언트 |
model_tools.py | 도구 스키마 수집, handle_function_call() 디스패치 |