AI 실행 구조 기술 검토 보고서
갱신: 2026-08-18
요약 및 결론
HeyMoa의 AI 실행은 사용자와 대화를 이어 가는 채팅과 회의 종료 뒤 결과물을 만드는 분석으로 분리돼 있다. 채팅에는 한 오케스트레이터와 두 담당 Agent가 참여한다. 회의 분석은 세 모델 작업을 병렬로 실행하지만 작업 사이의 위임이나 협의가 없어 multi-agent로 분류하지 않는다.
| 검토 항목 | 결론 | 판단 근거 |
|---|---|---|
| 채팅 구조 | 계층형 multi-agent | 오케스트레이터가 대화를 소유하고 Linear·GitHub Agent에 작업을 위임한 뒤 결과를 회수한다. |
| Agent 범위 | Linear와 GitHub만 독립 Agent | 각각 별도 상태, 제한된 도구, 반복 실행과 종료 조건을 가진다. |
| 실행 선택 | 한 모델 출력마다 한 갈래 | 내부 조회, Linear, GitHub를 한 호출 묶음에서 섞지 않는다. |
| 외부 쓰기 | 사용자 승인 뒤 실행 | PostgreSQL checkpoint에 상태를 보존하고 승인 이후 같은 thread에서 재개한다. |
| 회의 분석 | 채팅과 분리된 병렬 workflow | 세 section이 입력만 공유하고 독립적으로 실행된 뒤 reducer에서 합쳐진다. |
책임 경계는 분명하다. 오케스트레이터가 최종 응답을 전담하고, 담당 Agent에는 이번 작업만 전달하며, 외부 쓰기는 승인 노드와 실행 노드를 분리했다. 반면 운영 배포 commit과 조사 대상 checkout의 일치 여부, 분석 callback의 연속 실패 정책, 일부 실행 상한의 운영값은 확인되지 않았다.
조사 범위와 판정 기준
| 구분 | 내용 |
|---|---|
| 조사 기준일 | 2026-08-17 |
| 조사 대상 | 당시 로컬 heymoa-ai checkout의 채팅·위임·분석 실행 코드 |
| 포함 범위 | 프로필 선택, 오케스트레이터 routing, Linear·GitHub 위임, 쓰기 승인, 상태 저장, 회의 분석, 멱등 처리 |
| 제외 범위 | 모델 답변 품질, 비용, 부하 시험, 운영 장애 기록 |
| 미확인 | 조사 checkout과 운영 배포 commit의 일치 여부 |
모델 호출 여부만으로 Agent를 판정하지 않았다. 아래 네 조건을 함께 만족하는 실행 단위를 Agent로 보았다.
- 부모 대화와 구분되는 작업 상태를 가진다.
- 맡은 업무에 맞게 제한된 도구를 사용한다.
- 도구 결과를 보고 다음 행동을 스스로 선택한다.
- 완료·반복·실패·단계 상한에 따른 종료 조건을 가진다.
이 기준에 따르면 프로필은 prompt·context·tool 구성을 고르는 규칙이고, 회의 분석의 section은 정해진 결과물을 만드는 병렬 작업이다.
현행 구조
채팅은 중앙 오케스트레이터가 요청을 판단하고 직접 조회 또는 담당 Agent 위임을 선택한다. Linear와 GitHub Agent는 부모 대화 전체가 아니라 정리된 작업 한 건을 받아 실행한다. 회의 분석은 별도 진입점에서 시작하며 채팅의 checkpoint와 상태를 공유하지 않는다.
| 실행 단위 | 입력 | 소유 상태 | 도구와 처리 | 종료 결과 |
|---|---|---|---|---|
| 채팅 오케스트레이터 | 사용자 대화와 선택된 프로필 | 부모 messages, 전송 상태 | HeyMoa 읽기 또는 담당 Agent 위임 | 사용자 답변 |
| Linear Agent | Linear 작업 한 건 | provider_messages, 승인, 단계 수 | 허용된 Linear 도구 | 완료·중단 요약 |
| GitHub Agent | GitHub 작업 한 건 | provider_messages, 승인, 단계 수 | 허용된 GitHub 도구 | 완료·중단 요약 |
| 회의 분석 workflow | 확정된 회의 전사 | section 결과 | 개요·할 일·결정 사항 분석 | 병합 결과와 callback |
담당 Agent끼리는 직접 메시지를 교환하지 않는다. 한 모델 출력에서 Linear와 GitHub를 동시에 실행하지도 않는다. Linear 결과를 받은 오케스트레이터가 다음 판단에서 GitHub 작업을 새로 위임할 수는 있다.
핵심 아키텍처 판단
| 판단 | 코드에서 확인한 근거 | 구조에 미치는 영향 |
|---|---|---|
| 대화 소유자는 오케스트레이터 하나다. | 직접 도구와 담당 Agent의 결과가 모두 부모 messages로 돌아간다. | 최종 응답과 대화 이력을 한곳에서 관리한다. |
| 호출 묶음은 한 갈래가 맡는다. | routing 결과가 두 갈래 이상이면 refusal이 모든 tool call에 오류 결과를 붙인다. | 미완성 tool call로 다음 모델 호출이 깨지는 일을 막지만 한 차례에 여러 provider를 병렬 실행하지 못한다. |
| 담당 Agent는 작업 한 건만 본다. | prep이 provider 상태를 비우고 위임받은 task만 새 HumanMessage로 넣는다. | 이전 위임의 결과가 섞이지 않고 모델 입력이 불필요하게 커지지 않는다. |
| 승인과 외부 쓰기를 분리한다. | approval은 interrupt()만 수행하고 실제 호출은 다음 tools가 맡는다. | 재개 과정에서 승인 노드가 다시 실행돼도 외부 쓰기가 중복되지 않는다. |
| 회의 분석은 Agent 협업이 아니다. | 세 section은 서로의 상태를 읽거나 일을 위임하지 않고 reducer에서 결과만 합친다. | 채팅과 다른 병렬 workflow로 운영·실패 정책을 나눠야 한다. |
구조의 핵심 절충은 명확하다. 한 번에 한 실행 갈래만 허용해 대화 이력의 완결성을 우선했고, 담당 Agent의 입력을 좁혀 상태 격리를 택했다. 대신 여러 외부 서비스를 한 차례에 병렬 처리하는 구조는 아니다.
채팅 요청 처리
대화 위치가 입력 구성을 정한다
ChatPolicy.select()는 요청의 scope와 kind를 비교해 프로필을 고른다. 프로필 선택은 실행자 선택보다 앞서 일어나며, 같은 오케스트레이터에 어떤 맥락과 내부 도구를 제공할지 정한다.
| 요청 위치 | 프로필 | 처음 제공하는 맥락 | HeyMoa 내부 도구 |
|---|---|---|---|
| 공유 회의록 | meeting | 현재 회의록 전체 | 회의록 읽기, 전사 검색 |
| 개인 회의록 | note | 현재 회의록 전체 | 없음 |
| 워크스페이스 | workspace | 회의록 목록 | 회의록 읽기, 전사 검색 |
어느 프로필에서도 Linear와 GitHub Agent를 구성할 수 있다. 실제 모델에는 사용자가 연결한 서비스의 위임 도구만 노출한다. 회의 중 채팅도 별도 그래프를 만들지 않고 meeting 프로필로 같은 채팅 그래프를 실행한다.
호출 묶음의 소유자를 하나로 정한다
| 모델 출력 | 다음 실행 |
|---|---|
| 도구 호출 없음 | 답변을 사용자에게 보내고 종료 |
| HeyMoa 읽기만 포함 | direct_tools에서 직접 실행 |
| Linear 위임만 포함 | Linear Agent 실행 |
| GitHub 위임만 포함 | GitHub Agent 실행 |
| 서로 다른 갈래가 섞이거나 호출이 잘못됨 | refusal에서 호출 전체를 오류로 완결 |
모델이 만든 각 tool call에는 대응하는 ToolMessage가 필요하다. 호출 일부만 실행하면 부모 대화 이력이 미완성 상태로 남을 수 있다. 그래서 혼합 호출을 갈래별로 나누지 않고 모두 거절한다. 실행 결과는 다시 오케스트레이터로 들어가며, 최종 사용자 문장도 오케스트레이터가 만든다.
채팅 오케스트레이터는 gpt-5.6-luna, 추론 강도 low를 사용한다.
담당 Agent는 준비·실행·회수 경계를 가진다
위임은 세 단계로 나뉜다.
prep이provider,intent,task, 원래tool_call_id를 저장하고 이전 provider 상태를 지운다.- 담당 Agent가 허용된 도구 안에서 판단과 실행을 반복한다.
collect가 완료·중단 요약을 원래 tool call의ToolMessage로 바꿔 부모 대화에 추가한다.
담당 모델은 gpt-5.6-luna, 추론 강도 medium이며 parallel_tool_calls=False로 실행한다. 도구 호출이 없으면 완료하고, 같은 도구와 인자를 되풀이하면 중단한다. 쓰기가 포함되면 승인을 기다리고, 읽기만 있으면 도구 검사와 실행으로 넘어간다. 연속 실패가 max_failures에 닿거나 모델 호출이 max_steps에 닿아도 중단 요약을 만든다.
| 상태 범위 | 주요 필드 | 책임 |
|---|---|---|
| 부모 대화 | messages, message_id, turn_content | 사용자와 이어지는 대화와 이미 전송한 문장 |
| 위임 경계 | handoff, tool_call_id | 담당 서비스, intent, task와 원래 호출 연결 |
| 담당 Agent | provider_messages, provider_approvals, provider_steps | 이번 작업의 이력, 승인, 실행 단계 |
| 결과 회수 | provider_summary | 완료·중단 결과를 부모 대화로 반환 |
상태·승인·안정성 통제
확인된 통제 장치는 외부 쓰기 중복, 위임 사이의 상태 오염, 반복 호출, 장시간 실행을 각각 다른 경계에서 막는다.
쓰기 승인은 checkpoint에서 멈추고 이어 간다
쓰기 호출이 생기면 approval이 interrupt()로 사용자 결정을 요청한다. AsyncPostgresSaver가 중단 시점의 상태를 저장하며 chat_id를 checkpoint의 thread_id로 사용한다. 승인이나 거절 뒤에는 같은 thread에 Command를 보내 실행을 재개한다.
재개할 때 approval이 다시 실행될 수 있으므로 이 노드에서는 외부 쓰기와 감사 기록을 만들지 않는다. 승인된 외부 호출은 다음 tools에서만 실행한다. 거절한 호출도 결과 메시지를 남겨 담당 Agent의 이력을 완결한다.
재사용 장치는 수명과 목적이 다르다
| 통제 장치 | 유효 범위 | 통제하는 위험 |
|---|---|---|
prep | 위임 한 번 | 이전 provider 상태가 새 작업에 섞임 |
prefill() | 모델 호출 한 번 | 지침·근거·대화의 입력 순서가 흔들림 |
| PostgreSQL checkpoint | 같은 chat_id | 승인 대기 중 상태 유실 |
| 프로필 그래프 레지스트리 | 프로세스 | 같은 그래프의 반복 구성 |
| 모델 레지스트리 | 모델·추론 강도 조합 | 같은 모델 클라이언트의 반복 생성 |
| Langfuse 프롬프트 대체본 | 프롬프트별 | 원격 프롬프트 조회 실패 |
| 도구 fingerprint | 현재 사용자 차례 | 성공한 HeyMoa 읽기의 즉시 반복 |
max_failures, max_steps | 담당 Agent 작업 | 연속 실패와 무한 실행 |
prefill()은 그래프 노드나 실행 결과 cache가 아니다. 모델 입력을 instructions → grounding → history 순서로 조립해 변하지 않는 접두부를 유지한다. 대화가 길어지면 모델에 보내는 사본만 줄이고 PostgreSQL checkpoint의 원본 messages는 보존한다.
회의 후 분석
세 결과물을 독립적으로 만든 뒤 합친다
회의 분석은 채팅 오케스트레이터와 담당 Agent를 거치지 않는다. sections.yml의 overview, action_items, decisions 사이에 의존관계가 없어 세 노드를 함께 시작하고 sections reducer에서 결과를 합친다.
세 작업은 gpt-5.6-luna, 추론 강도 low를 사용한다. 노드별 제한 시간은 55초이며 한 번 재시도한다. checkpointer가 없어 채팅 승인처럼 중단 지점에서 재개하지 않는다.
analysis_id로 중복 실행을 막는다
| 접수 상태 | 처리 |
|---|---|
같은 analysis_id가 실행 중 | 새 작업을 만들지 않고 기존 실행을 기다림 |
| 같은 ID의 결과가 보관됨 | 모델을 다시 호출하지 않고 기존 결과를 callback으로 전송 |
| 결과 없음 | 세 section을 새로 실행 |
전사 색인은 분석과 함께 시작하는 별도 작업이다. embedding에는 text-embedding-3-small을 사용한다. 분석 결과의 재사용 기준은 내용의 유사도가 아니라 정확히 같은 analysis_id다.
| 비교 항목 | 채팅 | 회의 분석 |
|---|---|---|
| 실행 시작 | 사용자 메시지 | 회의 분석 요청 |
| 작업 선택 | 오케스트레이터의 조건부 routing | 고정된 세 section 병렬 실행 |
| 상태 보존 | PostgreSQL checkpoint | 실행 중 checkpointer 없음 |
| 사용자 승인 | 외부 쓰기에 사용 | 없음 |
| 재사용 기준 | 같은 chat_id의 대화 상태 | 같은 analysis_id의 실행·보관 결과 |
한계와 확인사항
| 상태 | 항목 | 확인이 필요한 이유 |
|---|---|---|
| 미확인 | 운영 배포 commit과 조사 checkout의 일치 여부 | 보고서의 코드 근거가 실제 운영 경로와 같은지 판정할 수 없다. |
| 미확인 | 운영 환경의 max_steps, max_failures 값과 산정 근거 | 담당 Agent의 최악 실행 시간과 호출 비용을 계산할 수 없다. |
| 미확인 | 분석 callback 연속 실패 정책 | 분석 결과를 만들고도 업무 서버에 전달하지 못했을 때의 복구 책임이 불분명하다. |
| 미확인 | 회의 분석 timeout과 retry 소진 뒤 최종 처리 | 실패 상태 저장, 재발행, 운영자 경보의 경계가 확인되지 않았다. |
| 미확인 | 모델·추론 강도 변경의 품질·비용 평가 기준 | 현재 모델 설정을 바꿀 때 회귀 여부를 판단할 기준이 없다. |
| 확인된 범위 밖 | 회의 중 자동 개입, 주제 이탈 경고, 결정 충돌 알림 | 조사한 코드에는 전담 실행 그래프가 없다. 다른 저장소의 구현 여부는 확인하지 못했다. |
한 번의 모델 출력에서 여러 provider를 병렬 실행하지 않는 점과 담당 Agent끼리 직접 협업하지 않는 점은 현행 설계의 제약이다. 이를 문제로 볼지는 제품이 한 사용자 차례에 복수 외부 작업을 요구하는지 확인한 뒤 판단해야 한다.
구현 근거
| 검토 항목 | heymoa-ai 경로 |
|---|---|
| 프로필 선택과 모델·도구 설정 | src/heymoa/application/policies/chat.py, src/heymoa/infrastructure/config/profiles.yml |
| 최상위 채팅 그래프 | src/heymoa/infrastructure/agent/builder.py |
| 오케스트레이터와 조건 분기 | src/heymoa/infrastructure/agent/orchestrator/ |
| 위임 준비와 결과 회수 | src/heymoa/infrastructure/agent/branches/handoff/ |
| Linear·GitHub Agent 실행 loop | src/heymoa/infrastructure/agent/providers/ |
| 부모·담당 Agent 상태 | src/heymoa/infrastructure/agent/state.py |
| 모델 입력 조립 | src/heymoa/infrastructure/llm/caching.py |