02-types-thread

Dec 7, 2025

ThreadNode

📁 관련 코드: lib/types/nodes/thread.ts

이메일 **스레드(대화)**를 나타냅니다. 관련된 이메일들을 그룹화합니다.

핵심 개념: Thread vs Email vs Memory

┌─────────────────────────────────────────────────────────────┐
Thread: "Re: 프로젝트 일정 논의"                             
threadId: "abc123"                                         
├─────────────────────────────────────────────────────────────┤

┌─────────────────────────────────────────────────────┐   
Email 1 (1/15): "1월 22일에 미팅하죠"                 
messageId: "msg-001"                                
└─────────────────────────────────────────────────────┘   

LLM 추출                                     
┌─────────────────────────────────────────────────────┐   
Memory: "1/22 미팅 예정"                             
└─────────────────────────────────────────────────────┘   

┌─────────────────────────────────────────────────────┐   
Email 2 (1/18): "3시로 변경됐습니다"                  
messageId: "msg-002"                                
└─────────────────────────────────────────────────────┘   

LLM 추출 (기존 Memory 업데이트)               
┌─────────────────────────────────────────────────────┐   
Memory v2: "1/22 3시 미팅 예정"                      
└─────────────────────────────────────────────────────┘   

└─────────────────────────────────────────────────────────────┘

계층 구조

레벨

설명

예시

Thread

대화 컨테이너

"프로젝트 일정 논의"

Email

개별 메시지

"1월 22일에 미팅하죠"

Memory

추출된 정보

"1/22 미팅"

Gmail의 threadId

threadId: string;  // Gmail API에서 제공

Gmail이 Thread를 결정하는 방식

  1. Subject 기반: "Re:", "Fwd:" 제거 후 같은 제목

  2. References 헤더: 이메일 헤더의 In-Reply-To, References

  3. 시간 제한: 일정 기간 내의 관련 이메일만

왜 Gmail threadId를 그대로 사용하는가?

대안

문제점

자체 스레드 ID 생성

Gmail과 매핑 필요, 복잡성 증가

Subject로 그룹화

"Re: Re: Re:" 처리, 동일 제목 다른 대화

Gmail threadId 사용

Gmail과 1:1 매핑, 신뢰성 높음

ThreadStatus

enum ThreadStatus {
  ACTIVE = 'active',      // 활성 대화
  ARCHIVED = 'archived',  // 비활성 (오래된)
  RESOLVED = 'resolved',  // 주제 해결됨
}

상태 전이

                    ┌────────────────────┐
                    
                    
              ┌──────────┐               
    스레드 →│  ACTIVE  │← 메시지 도착
              └────┬─────┘               
                   
      30경과    사용자/LLM 판단   
          ┌────────┴────────┐            
          
   ┌──────────────┐  ┌──────────────┐    
   ARCHIVED   RESOLVED   
   └──────────────┘  └──────┬───────┘    
                           
                           └─────────────┘
                           재개 ACTIVE로

isResolved vs status

필드

용도

isResolved

빠른 boolean 체크

status

상세 상태 (ARCHIVED 등)

// 빠른 필터링
const unresolvedThreads = threads.filter(t => !t.isResolved);

// 상세 필터링
const activeUnresolved = threads.filter(
  t => t.status === ThreadStatus.ACTIVE && !t.isResolved
);

시간 필드

startedAt: Date;      // 스레드 시작 (첫 이메일)
lastMessageAt: Date;  // 마지막 메시지

왜 둘 다 필요한가?

시나리오 1: "오래된 대화 정리하기"

// 30일 이상 비활성 스레드
const staleThreads = threads.filter(
  t => daysSince(t.lastMessageAt) > 30
);

시나리오 2: "이번 달 시작된 대화"

// 이번 달 시작된 스레드
const thisMonthThreads = threads.filter(
  t => t.startedAt >= startOfMonth
);

Helper Functions

addMessageToThread

새 이메일이 기존 스레드에 추가될 때:

const updatedThread = addMessageToThread(
  existingThread,
  'new-message-id',
  new Date()
);

// 결과:
// - messageIds에 추가 (중복 방지)
// - messageCount 증가
// - lastMessageAt 업데이트 (더 최근이면)

shouldArchiveThread

비활성 스레드 자동 감지:

if (shouldArchiveThread(thread, 30)) {
  thread.status = ThreadStatus.ARCHIVED;
}

그래프에서의 관계

[ThreadNode: 프로젝트 일정 논의]
        
        ├── CONTAINS ────→ [MemoryNode: 1/22 미팅]
        
        ├── CONTAINS ────→ [MemoryNode: 보고서 요청]
        
        ├── HAS_PARTICIPANT ──→ [PersonNode: 김철수]
        
        └── HAS_PARTICIPANT ──→ [PersonNode: 이영희]

암시적 관계 (Implicit)

Thread를 통해 자동으로 추론되는 관계:

[Memory A] ←── SAME_THREAD ──→ [Memory B]
     
     └── 같은 threadId ─────────────┘

Factory 사용 예시

const thread = createThreadNode({
  id: generateUUID(),
  threadId: 'gmail-thread-abc123',
  subject: '프로젝트 일정 논의',
  participantCount: 3,
  messageCount: 5,
  startedAt: new Date('2024-01-15'),
  lastMessageAt: new Date('2024-01-18'),
  sourceType: SourceType.BOOTSTRAPPED,
  confidence: 1.0,  // Thread는 Gmail에서 직접 오므로 신뢰도 1.0
});

다음 문서

  • → EntityNode: 회사/제품 엔티티 스키마

  • → MemoryNode: Memory와 Thread의 관계