LAXworkspace
알림 · 보고서
오프라인입력 0 (캐시 0%) · 출력 0
새 대화
세션 목록
⚠ 로컬 데몬이 오프라인입니다 — 메시지는 큐에 쌓였다가 데몬이 켜지면 처리됩니다.
박보검의 요청: [빈 세션 양산 원인 진단 요청] 증상: LAX 워크스페이스 에이전트 "최근 세션" 목록에 "제목 없음 · 입력 0 (캐시 0%) · 출력 0"인 빈 세션이 에이전트마다 대량으로 쌓이고 있어. 메시지를 한 번도 주고받지 않은(토큰 0) 세션 레코드가 계속 생기는 상황이야. daemon/런타임 하니스 관점에서 묻는다: 1. 세션(conversation) 레코드가 DB에 생성되는 트리거가 뭐야? "+ 새 세션" 클릭 즉시 INSERT인지, 첫 메시지 전송 때인지? 2. daemon이 헬스체크/하트비트/브리지 연결테스트/스케줄러 등으로 빈 세션을 자동 생성하는 경로가 있어? (스샷에 "[브리지 연결 테스트]" 세션도 보임) 3. 최근에 세션 생성 로직이나 daemon 동작에 바뀐 게 있어? 4. 너가 보기에 이 빈 세션들의 진짜 원인과, 가장 깔끔한 해결책(생성 자체를 막기 vs 목록에서 0토큰 세션 숨기기 vs 정리 잡)이 뭐라고 봐? 진행상황 동봉: 나는 LAX-site 코드에서 세션 생성 경로를 병렬로 추적 중이야. 너는 daemon/런타임 쪽 원인을 봐줘.

결론부터. 빈 세션은 daemon이 만드는 게 아니다. 원인은 worker(LAX-site) 쪽 상태를 바꾸는 GET 엔드포인트 하나다. daemon은 chat_sessions에 INSERT를 하지 않는다(폴링·하트비트·ingest·complete만 한다). 추적 결과를 정리한다.


1) 세션 레코드 생성 트리거 — "클릭 즉시 INSERT"가 맞다

chat_sessions INSERT는 전부 createChatSession()(src/shared/chat.ts:33)을 거친다. "최근 세션" 목록에 뜨는 "제목 없음 · 0토큰" 의 발원지는 명확하다:

GET /chat/sessions/new?agent_id=<id>   (src/app/chat/routes.tsx:852)
  → createChatSession(... title 없음 ...)   ← 즉시 INSERT (line 864)
  → c.redirect(/chat/sessions/<id>)          ← 빈 방으로 이동 (line 870)
  • "+ 새 세션"은 <a href="/chat/sessions/new?..."> 링크다 (UI 3곳: line 212, 228, 361).
  • 즉, 첫 메시지 전송과 무관하게, 그 URL에 도달하는 순간 빈 행이 박힌다. title을 안 넘기므로 NULL → "제목 없음", 메시지가 없으니 daemon이 claude를 돌릴 일도 없어 input/output/cache 0.
  • 이게 핵심 안티패턴이다: GET이 DB를 변이시킨다(safe/idempotent 위반). 그래서 링크 클릭뿐 아니라 브라우저 prefetch/speculative navigation, 봇 크롤, 더블클릭, 뒤로가기 재검증 어느 것이든 빈 세션을 양산한다. 목록에 마구 쌓이는 양상과 정확히 맞는다.

2) daemon이 빈 세션을 자동 생성하나 — 아니오

daemon은 chat_sessions전혀 INSERT하지 않는다. 확인한 daemon 경로:

  • heartbeat() = GET /bridge/heartbeat (last_seen 갱신만, INSERT 없음, daemon.js:1483)
  • poll/ingest/complete = 이미 존재하는 task/session에 쓰기만
  • 압축(compaction)은 새 claude CLI 세션 id만 만들고 기존 chat_sessions 행을 재사용 — 새 행 INSERT 아님

"[브리지 연결 테스트]" 세션은 daemon이 아니라 bridge 위임 엔드포인트(/bridge/..., src/app/bridge/routes.ts:1148)를 외부에서 호출한 흔적이다. 이 엔드포인트는 세션+메시지+task를 한 번에 만든다. 어떤 테스트 스크립트/에이전트가 이 경로를 두드리면 세션이 생긴다. 정확한 title 문자열의 출처(누가 "[브리지 연결 테스트]"를 넣었는지)는 호출자 코드를 봐야 100% 확정된다 — 여기까진 추측이라 못 박지 않는다. 다만 daemon 발원이 아닌 건 확실하다.

이 외 worker 측 자동 생성기(참고): 자기반성 세션(routes.ts:307, task 완료마다 "[자기반성]"), 스케줄 리포트(schedules.ts:122), brain/draft/reports/a2a/voice. 이들은 title이 있고 메시지/task가 붙는다 — "제목 없음·0토큰" 패턴과는 다르다.

3) 최근 변경 영향 — 빈 세션 양산은 최근 daemon 변경 탓이 아니다

  • /sessions/new 엔드포인트는 신규가 아니다(스냅샷 커밋 f37c706부터 존재). 메커니즘은 이전부터 있던 구조적 결함.
  • 최근 daemon 커밋(d9fdb96 하트비트→activity, 13ce02d throttle 분리, 99dca83 스킬 시딩 등)은 어느 것도 chat_sessions를 만들지 않는다.
  • 따라서 "최근에 뭐가 바뀌어서 터졌다"면 daemon이 아니라 링크 노출 증가/프리페치·봇 트래픽 쪽을 의심해야 한다.

4) 진짜 원인과 가장 깔끔한 해결책

진짜 원인: 변이를 일으키는 GET(/chat/sessions/new)이 메시지 한 통 오기도 전에 행을 물질화한다. 안전하지 않은 GET이라 사람 클릭 외의 트래픽에도 반응한다.

권장 — 두 가지를 함께:

우선 조치 효과 비용/리스크
본질 수정 세션을 "입장 시"가 아니라 "첫 메시지 전송 시" 생성. "새 세션"은 DB 행 없는 작성(compose) 화면으로 보내고, 첫 메시지가 실제 전송될 때 INSERT. 빈 세션 클래스 자체 소멸 chat 작성 플로우 수정 필요
(동등한 경량안) 위가 부담이면 최소한 GET→POST 전환(prefetch/봇 차단) + dedup: 해당 에이전트에 "메시지 없는 빈 세션"이 이미 있으면 새로 만들지 말고 그 행으로 redirect 신규 생성 거의 차단 작음
즉시 완화 "최근 세션" 목록 쿼리(listChatSessions)에서 메시지 0건 & 0토큰 세션 숨김(pinned 제외) 이미 쌓인 빈 세션 + 신규가 화면에서 사라짐 작음, 단 orphan 행은 DB에 잔존
보조 메시지 없는 N분 경과 세션 주기적 삭제 잡 청소 작성 중 세션과 레이스 위험 → 단독 의존 금지

내 결론: "생성을 막기 + 목록에서 숨기기"를 같이 간다. 본질 수정(첫 메시지 시 생성, 안되면 POST+dedup)으로 발생을 끊고, 목록 필터로 기존 누적분을 즉시 가린다. 정리 잡 단독은 비권장(레이스).


분담 메모

  • 수정 위치는 전부 LAX-site worker(src/app/chat/routes.tsx, src/shared/chat.ts)다 — daemon 변경 불필요. 네가 병렬로 보는 site 코드 트랙이 맞다.
  • 정확한 라인 핸드오프: 생성부 chat/routes.tsx:852(GET→변이), 목록부 shared/chat.ts:57 listChatSessions(여기에 메시지/토큰 필터), dedup 후보 SELECT 기준 agent_id + 메시지 0건.
  • 미확정 1건: "[브리지 연결 테스트]" title의 정확한 호출자 — bridge 엔드포인트(bridge/routes.ts:1148)를 누가 호출했는지 그 caller를 봐야 확정된다. daemon 발원은 배제 확정.

daemon/런타임은 이 건에서 clean이다. 비가역 변경 없음, 롤백 대상 없음.