Discord 비동기 인바운드 워커 계획

목표

Discord 리스너 타임아웃을 사용자 대면 실패 모드에서 제거하기 위해 인바운드 Discord 턴을 비동기로 만듭니다:

  1. 게이트웨이 리스너가 인바운드 이벤트를 빠르게 수용하고 정규화.
  2. Discord 실행 큐가 현재 사용하는 것과 동일한 순서 경계로 키가 지정된 직렬화된 작업을 저장.
  3. 워커가 Carbon 리스너 수명 외부에서 실제 에이전트 턴을 실행.
  4. 실행 완료 후 원본 채널이나 스레드로 응답 전달.

이것은 에이전트 실행 자체가 진행 중인데도 channels.discord.eventQueue.listenerTimeout에서 큐에 대기 중인 Discord 실행이 타임아웃되는 문제의 장기 수정입니다.

현재 상태

이 계획은 부분적으로 구현되었습니다.

완료된 사항:

  • Discord 리스너 타임아웃과 Discord 실행 타임아웃이 별도 설정으로 분리.
  • 수용된 인바운드 Discord 턴이 src/discord/monitor/inbound-worker.ts에 큐잉.
  • 워커가 Carbon 리스너 대신 장시간 실행 턴을 소유.
  • 큐 키에 의한 기존 경로별 순서 유지.
  • Discord 워커 경로에 대한 타임아웃 회귀 커버리지 존재.

쉬운 말로:

  • 프로덕션 타임아웃 버그가 수정됨
  • 장시간 실행 턴이 더 이상 Discord 리스너 예산 만료만으로 죽지 않음
  • 워커 아키텍처는 아직 완성되지 않음

여전히 부족한 사항:

  • DiscordInboundJob이 아직 부분적으로만 정규화되었고 실시간 런타임 참조를 포함
  • 명령 의미론 (stop, new, reset, 미래 세션 제어)이 아직 워커 네이티브로 완전히 전환되지 않음
  • 워커 관측성과 운영자 상태가 아직 최소
  • 재시작 내구성이 아직 없음

이 문서가 필요한 이유

현재 동작은 전체 에이전트 턴을 리스너 수명에 연결:

  • src/discord/monitor/listeners.ts가 타임아웃과 중단 경계를 적용.
  • src/discord/monitor/message-handler.ts가 큐에 대기 중인 실행을 해당 경계 내에 유지.
  • src/discord/monitor/message-handler.process.ts가 미디어 로딩, 라우팅, 디스패치, 타이핑, 드래프트 스트리밍, 최종 응답 전달을 인라인으로 수행.

이 아키텍처에는 두 가지 나쁜 속성이 있습니다:

  • 정상적이지만 긴 턴이 리스너 감시독에 의해 중단될 수 있음
  • 다운스트림 런타임이 응답을 생성했음에도 사용자에게 응답이 표시되지 않을 수 있음

타임아웃을 높이면 도움이 되지만 실패 모드를 바꾸지 않습니다.

비목표

  • 이번 패스에서 비-Discord 채널을 재설계하지 않음.
  • 첫 구현에서 범용 전채널 워커 프레임워크로 확장하지 않음.
  • 공유 크로스 채널 인바운드 워커 추상화를 아직 추출하지 않음; 중복이 명백할 때만 저수준 프리미티브를 공유.
  • 안전하게 출시하기 위해 필요하지 않는 한 첫 패스에서 내구성 있는 충돌 복구를 추가하지 않음.
  • 이 계획에서 라우트 선택, 바인딩 의미론, ACP 정책을 변경하지 않음.

목표 아키텍처

1. 리스너 단계

DiscordMessageListener가 진입점으로 유지되지만, 역할이 다음으로 변경:

  • 사전 검사 및 정책 확인 실행
  • 수용된 입력을 직렬화 가능한 DiscordInboundJob으로 정규화
  • 세션별 또는 채널별 비동기 큐에 작업 큐잉
  • 큐잉 성공 후 즉시 Carbon에 반환

리스너는 더 이상 종단 간 LLM 턴 수명을 소유하지 않아야 합니다.

2. 정규화된 작업 페이로드

나중에 턴을 실행하는 데 필요한 데이터만 포함하는 직렬화 가능한 작업 디스크립터를 도입합니다.

최소 형태:

  • 라우트 식별
    • agentId
    • sessionKey
    • accountId
    • channel
  • 전달 식별
    • 대상 채널 ID
    • 응답 대상 메시지 ID
    • 스레드 ID (있을 경우)
  • 발신자 식별
    • 발신자 ID, 라벨, 사용자명, 태그
  • 채널 컨텍스트
    • 길드 ID
    • 채널 이름 또는 슬러그
    • 스레드 메타데이터
    • 해석된 시스템 프롬프트 오버라이드
  • 정규화된 메시지 본문
    • 기본 텍스트
    • 유효 메시지 텍스트
    • 첨부 파일 디스크립터 또는 해석된 미디어 참조
  • 게이팅 결정
    • 멘션 요구 결과
    • 명령 인증 결과
    • 바인딩된 세션 또는 에이전트 메타데이터 (해당 시)

작업 페이로드는 실시간 Carbon 객체나 변경 가능한 클로저를 포함하지 않아야 합니다.

3. 워커 단계

다음을 담당하는 Discord 전용 워커 러너 추가:

  • DiscordInboundJob에서 턴 컨텍스트 재구성
  • 실행에 필요한 미디어 및 추가 채널 메타데이터 로딩
  • 에이전트 턴 디스패치
  • 최종 응답 페이로드 전달
  • 상태 및 진단 업데이트

4. 순서 모델

주어진 라우트 경계에 대해 오늘과 동일한 순서를 유지해야 합니다.

권장 키:

  • resolveDiscordRunQueueKey(...)와 동일한 큐 키 로직 사용

5. 타임아웃 모델

전환 후 두 가지 별도 타임아웃 클래스가 있습니다:

  • 리스너 타임아웃
    • 정규화와 큐잉만 커버
    • 짧아야 함
  • 실행 타임아웃
    • 선택적, 워커 소유, 명시적, 사용자에게 표시
    • Carbon 리스너 설정에서 우연히 상속되지 않아야 함

테스트 계획

기존 타임아웃 재현 커버리지 유지:

  • src/discord/monitor/message-handler.queue.test.ts

새 테스트 추가:

  1. 전체 턴을 기다리지 않고 큐잉 후 리스너 반환
  2. 경로별 순서 유지
  3. 다른 채널은 동시 실행 가능
  4. 응답이 원본 메시지 대상으로 전달
  5. stop이 워커 소유 활성 실행을 취소
  6. 워커 실패가 이후 작업을 차단하지 않고 가시적 진단 생성
  7. ACP 바인딩된 Discord 채널이 워커 실행에서 올바르게 라우팅

위험 및 완화

  • 위험: 현재 동기 동작에서 명령 의미론 드리프트 완화: 동일 전환에서 명령 상태 배관을 함께 출시
  • 위험: 응답 전달이 스레드 또는 reply-to 컨텍스트를 잃음 완화: DiscordInboundJob에서 전달 식별을 일급으로
  • 위험: 재시도 또는 큐 재시작 중 중복 전송 완화: 첫 패스는 인메모리만, 또는 지속성 전에 명시적 전달 멱등성 추가
  • 위험: 마이그레이션 중 message-handler.process.ts가 추론하기 어려워짐 완화: 워커 전환 전이나 도중에 정규화, 실행, 전달 헬퍼로 분리

수락 기준

계획은 다음이 완료될 때 완료:

  1. Discord 리스너 타임아웃이 정상적인 장시간 실행 턴을 더 이상 중단하지 않음.
  2. 리스너 수명과 에이전트 턴 수명이 코드에서 별도 개념.
  3. 기존 세션별 순서 유지.
  4. ACP 바인딩된 Discord 채널이 동일한 워커 경로를 통해 동작.
  5. stop이 이전 리스너 소유 호출 스택 대신 워커 소유 실행을 대상으로.
  6. 타임아웃과 전달 실패가 조용한 리스너 드롭이 아닌 명시적 워커 결과가 됨.