MINARA

에이전트 루프

단일 턴이 사용자 메시지에서 최종 답변까지 흐르는 방식, 그리고 이를 지탱하는 상태 관리

에이전트 루프는 턴 오케스트레이터입니다. 사용자 메시지를 받아 최종 답변을 생성하고, 그 과정에서 발생한 모든 내용을 저장하는 단일 역할을 담당합니다. 가격 조회, 거래, 딥 리서치 보고서 등 Minara의 모든 기능은 이 루프를 통해 실행됩니다. 루프는 apps/agent/src/agent/runtime.tsAgentRuntime 클래스에 구현되어 있습니다.

Vercel AI SDK를 직접 구현한 루프 대신 사용하는 이유는 무엇인가요? 턴은 짧고 범위가 명확한 시퀀스입니다. 컨텍스트를 구성하고, 모델을 호출하고, 요청된 도구를 실행하고, 결과를 다시 전달한 뒤, 중단될 때까지 반복합니다. AI SDK의 streamText는 이미 해당 다단계 도구 루프를 Anthropic, OpenAI, xAI, OpenRouter에 걸친 단일 공급자 비종속 인터페이스로 정확하게 모델링합니다. Minara는 streamText가 호출하는 모듈에 핵심 정책(허용 도구, 게이트 실행 조건, 캐시 대상, 컨텍스트 압축 방식)을 유지하고, 기계적인 도구 호출 반복 처리는 SDK에 위임합니다.

agent loop diagram

실제 동작 확인: REPLHTTP 게이트웨이의 모든 채팅 턴이 이 루프를 구동합니다. 첫 거래 안내서는 이 루프를 처음부터 끝까지 한 번 통과하는 과정을 보여줍니다.

턴 파이프라인

AgentRuntime.run()은 턴 형태의 파이프라인입니다. 아래 단계를 순서대로 실행하고, 게이트웨이 SSE와 REPL 양측이 소비하는 단일 AsyncGenerator<AgentEvent>를 방출합니다. 분산된 콜백 대신 단일 이벤트 스트림을 사용합니다.

1. 사전 턴

모델을 호출하기 전, 런타임은 선호도 졸업과 입력 스캐너를 실행한 뒤 buildSystemPromptBlocks()로 시스템 프롬프트를 조립합니다. 프롬프트는 문자열 연결이 아닌 선언된 블록으로 구성됩니다. 이를 통해 캐시 가능한 접두 부분(아이덴티티와 스킬 카탈로그)은 턴 간 바이트 단위로 동일하게 유지되어 Anthropic의 프롬프트 캐시를 활용합니다. 동적 꼬리 부분(활성 스킬, 시그널 컨텍스트, 대기 중인 확인)은 매 턴마다 재구성됩니다. 블록 순서는 실질적 경계를 결정합니다. 캐시된 접두 부분을 변경하면 웜 호출이 캐시 미스로 전환됩니다. 워크스페이스 마크다운(SOUL.md, MEMORY.md, HEARTBEAT.md)이 동적 블록 앞에 위치하므로, 운영자가 관리하는 기준 정보가 추론된 레이어보다 우선합니다(워크스페이스 참조).

활성 스킬 결정은 라우터가 먼저 수행합니다. 라우터는 키워드, 라이프사이클 단계, 자산 유형, 동시 활성화 여부로 모든 스킬을 점수화하고, 활성 세트가 모델에 노출할 툴 세트를 결정합니다.

2. 압축 단계

대화가 길어지면, 런타임은 모델 호출 비용을 소비하기 전에 윈도우를 맞추기 위해 압축 단계(경계 슬라이스, 붕괴, 자동압축, 상위 N 재주입)를 실행합니다. 압축은 유료 요약보다 LLM 없는 가지치기를 우선합니다. 전체 계약은 docs-src/context-management.md에 정의되어 있습니다.

3. streamText

런타임은 활성 스킬이 허용하는 도구와 해당 턴의 허용 툴 세트를 교집합으로 구성하여 streamText를 호출합니다. SDK는 내부적으로 도구 호출-관찰 루프를 구동하며 stopWhen: stepCountIs(maxSteps) 조건에서 중단합니다. 각 스텝에서 수행되는 작업은 다음과 같습니다.

  • prepareStep은 호출 전 누적 token 예산을 조정합니다.
  • 모델은 텍스트와 도구 호출을 방출하고, 도구 호출은 레지스트리를 통해 디스패치됩니다(다음 섹션 참조).
  • onStepFinish는 사용량을 기록하고 스트림에 스텝 이벤트를 방출합니다.

streamText 전체 호출은 스텝이 컨텍스트 윈도우를 초과하는 경우 트리밍 후 재시도하는 오버플로 복구 가드로 감싸져 있습니다.

4. 도구 디스패치 및 게이팅

모델이 방출하는 각 도구 호출은 툴 레지스트리를 통해 디스패치됩니다. 두 가지 게이트가 적용됩니다.

  • 권한 등급. 모든 도구는 PermissionTier(READ_ONLYCONFIRM_ONCEALWAYS_CONFIRMMANUAL_ONLY)를 가집니다. 등급은 권고가 아닌 강제 사항입니다.
  • 안전 스택. 자금 이동 핸들러는 공유 미리 보기-확인 헬퍼에서 게이팅되고, 거래 경로는 추가적으로 훅 파이프라인(token 안전성, 노출, 슬리피지, 리스크 매니저 한도, 감사 로그)을 통과합니다. 차단된 호출은 모델이 일반 도구 결과로 인식하는 구조화된 오류를 반환하며, 감사 로그는 이를 별도로 기록합니다. 6단계 전체 스택은 안전 및 샌드박스에 문서화되어 있습니다.

턴 상태(소스, 실행 중인 사용 도구 세트, 위험 한도)는 ToolCallContext에 전달되므로, 서브 에이전트 호출 및 스킬 실행 시퀀스를 포함한 모든 도구 호출이 동일한 턴별 상태를 읽습니다.

5. 턴 종료

streamText가 중단되면, 런타임은 post_turn 이벤트를 방출합니다. 이 이벤트는 의사결정 캡처, 채팅 턴 기록, 워크스페이스 상태 업데이트, 대시보드 캐시 무효화, 관찰 가능성으로 팬 아웃됩니다. 이 기록은 관찰 가능성 툴링이 읽는 대상이자, 학습 시스템이 반성하는 소스입니다.

턴 상태 관리

루프를 안전하게 확장하기 위한 두 가지 불변 조건이 있습니다.

  • 턴당 하나의 컨텍스트. 위험 한도, 시그널 컨텍스트, 사용 도구 세트는 ToolCallContext에 존재하며, 모듈 수준 상태에는 존재하지 않습니다. 동시 턴(REPL과 게이트웨이는 동시에 실행될 수 있음)은 서로의 상태를 공유하지 않습니다.
  • 캐시된 접두 부분은 불변입니다. 턴 중간의 스킬 활성화는 시스템 프롬프트를 재구성하지 않고 노트를 추가합니다. 접두 부분을 재구성하면 프롬프트 캐시가 깨지기 때문입니다. 로딩 또는 압축에 대한 모든 변경은 캐시된 접두 부분을 수정하지 않아야 합니다.

턴 외부의 결정적 실행

모든 도구 호출이 streamText를 통과하는 것은 아닙니다. 스케줄 및 이벤트 기반 워크플로는 모델 라운드트립 없이 레지스트리를 통해 등록된 도구 핸들러를 직접 호출합니다. tool_call 워크플로 스텝은 모델 호출 없이, 루프가 실행할 것과 동일한 핸들러를 동일한 권한 등급과 안전 게이트 하에서 실행합니다.

두 가지 결과가 나타납니다.

  • 루틴 자동화(알림, 브리핑, 모니터링)는 비용이 낮게 유지됩니다. 엔진은 모델 노드를 명시적으로 포함하는 스텝에서만 token을 소비합니다.
  • 실행 의미론은 모델의 스키마 해석이 아닌 도구 구현을 따릅니다.

루프와 워크플로 엔진은 하나의 툴 레지스트리에 대한 두 개의 진입점입니다. 이 때문에 자금 이동 스텝은 실행 위치에 관계없이 2단계 확인을 통과합니다.

참고 항목

  • 스킬 시스템, 루프가 호출하는 라우팅, 활성화, 도구 허용 목록.
  • LLM 통합, streamText 뒤에 있는 공급자 추상화 및 모델 라우팅.
  • 안전 및 샌드박스, 디스패치 단계가 적용하는 권한 등급과 자금 안전 스택.
  • 워크스페이스, 동적 프롬프트 블록의 앞에 위치하는 기준 마크다운.

목차