이전 메시지 불러오는 중…
리아의 요청:
[첫 임무 발부 — 게이트① 설계안만] 인프라/플랫폼 오너 손석구님, 리아입니다. 환영합니다. 첫 임무를 정식 발부합니다. **이번 단계는 설계안 제출까지이며, daemon.js 구현/수정은 하지 마세요.**
## 목표
LAX `/activity`의 **서브항목을 클릭했을 때 라이브 진행률·단계**가 보이게 한다. (지금은 라벨+상태만 보임)
## 현황 (리아·보검 조사)
- `daemon/daemon.js` 1069~1094: Task/Agent 툴콜을 가로채 `activityEmit()`→`/bridge/activity`로 emit. 내용 = `label`(Task description 80자 클립) + `status`(running→done/failed) + parent_task_id/session_id/model. **진행률 본문 채널 없음.**
- run_in_background 서브는 bgSubIds로 turn-end까지 running 유지(1083,1118).
- 과거 ReferenceError 결함(sub-agent emit 죽던 것)은 fix됨(`clip` 모듈레벨).
- 핵심 난점: 서브 실시간 진행("p1560/1730, 28893건")은 **서브에이전트 자신의 stdout/transcript에 있고 부모 daemon 스트림 파서엔 미유입**. 그래서 "emit 필드 추가"가 아니라 **진행상태를 어디서/어떻게 수집할지 경로 설계가 선행**.
## 산출물 (이번 게이트①에서 제출할 것)
1. **진행상태 수집경로 설계안** — 옵션 2~3개(예: 서브 stdout 파싱 추가 / 서브가 STORE_PROGRESS 같은 마커를 emit하면 부모가 포워드 / 별도 progress 파일 폴링)와 각 트레이드오프.
2. daemon.js의 어느 지점에 progress 채널을 끼울지(emit 스키마 변경안), `/activity` UI 렌더 변경 범위.
3. **안전계획**: 변경 전 커밋고정/백업, /activity 라이브 1건 검증 방법(자가 DONE 금지), revert(단일커밋) 보장, 변경 최소화.
4. 구현 시 예상 리스크(daemon은 전 에이전트 구동 핵심 — 회귀 시 전사 영향).
## 게이트
설계안 → 보검(구조)·Jason(승인) 리뷰 통과 후에야 구현(②) 착수. 지금은 **설계안만**. 착수 가능 여부와 설계안 ETA부터 회신 주세요.
리아님, 손석구입니다. 코드 직접 확인했고 게이트① 설계안 제출합니다. daemon.js는 한 줄도 건드리지 않았습니다(읽기만).
결론 먼저
- 착수 가능합니다. 설계안은 이 회신에 첨부(아래). 추가 산출물(스파이크 결과 반영본) ETA는 반나절(0.5d) — 단, 구현(②)이 아니라 "설계 확정에 필요한 읽기 전용 관찰 1회"가 선행돼야 옵션이 결정됩니다.
- 핵심 발견(설계를 통째로 바꾸는 사실 2개):
- 렌더 채널은 이미 끝까지 뚫려 있습니다.
agent_activity테이블에detail(TEXT 500자)·tool_calls(INT) 컬럼이 이미 존재 →/activity/data의mapSub가detail을 그대로 내려보냄 →monitor.tsx의subCard()가 라벨 밑줄에a.detail을 렌더하고🔧 tool_calls도 표시함. 즉 "진행률 본문 채널 없음"은 emit 쪽만 비어 있는 것이지, DB·UI는 이미 준비됨. → 마이그레이션 불필요, UI 변경도 (텍스트 진행이면) 0줄. - 그래서 진짜 문제는 리아님 지적대로 수집 경로 단 하나. 그리고 그 경로는 "서브 텍스트가 부모 daemon 스트림에 유입되는가?"라는 단일 사실에 따라 설계가 갈림.
- 렌더 채널은 이미 끝까지 뚫려 있습니다.
1. 진행상태 수집경로 — 옵션 3안 + 트레이드오프
전제: emit upsert 키는 external_id = ${task.task_id}:${subId} (이미 존재). recordActivity는 tool_calls=MAX(old,new), detail=COALESCE(new,old), finished_at=excluded(running이면 NULL 유지)로 이미 진행 업데이트 친화적. 즉 어떤 옵션이든 "같은 external_id로 running 재emit하며 detail/tool_calls만 갱신"하면 됨.
| 옵션 | 방식 | 장점 | 단점/리스크 | 신규 표면 |
|---|---|---|---|---|
| A. 부모 스트림 카운트 (서브 tool_use가 부모 stream-json에 보일 경우) | daemon 950~ 라인스캐너/블록 루프에서, 진행 중 서브의 tool_use를 세어 같은 external_id로 tool_calls·detail(최근 도구명) 갱신 emit |
신규 프로토콜 0, MCP 0, 마이그레이션 0, UI 0. 가장 작은 변경 | 서브 내부가 부모 스트림에 유입돼야만 성립 (리아님이 "미유입"으로 본 가정). 유입돼도 "p1560/1730" 같은 도메인 카운터는 못 봄(도구 호출 수만) | daemon 한 블록 |
B. 서브 self-report 마커 (STORE_PROGRESS: 직접 bridge POST) |
서브에게 report_progress 경로 부여 — 서브가 진행 줄을 emit하면 부모 라인스캐너가 가로채 /bridge/activity로 포워드(기존 STORE_CAREERWIKI_PROGRESS와 동일 패턴) |
도메인 진행("28893건 중 p1560") 정확히 표현 가능. 기존 마커 포워드 패턴 재사용 | 서브 출력이 부모 스캐너에 닿아야 함(A와 같은 미지수). 안 닿으면 서브가 bridge에 직접 POST해야 하는데 서브에 HMAC 시크릿/엔드포인트 노출 필요 → 표면↑. 서브 협조(마커 출력) 필요 | 마커 1종 + (경우에 따라) 서브용 emit 경로 |
| C. 서브 transcript 파일 테일링 | daemon이 running 서브의 ~/.claude/projects/**/*.jsonl 트랜스크립트를 subId로 매핑해 폴링·tail → tool_calls/최근활동 추출해 emit |
서브 협조 불필요, 진짜 실시간 내부 가시화 | subId→파일 매핑이 하니스 버전 의존·취약. 폴링 루프·파일 핸들 관리로 daemon 복잡도↑ = 전사 구동 코어에 회귀 위험 가장 큼 | 폴링 서브시스템 |
권고: A를 1순위로 두되, "서브 tool_use의 부모 스트림 유입 여부"를 결정짓는 읽기 전용 스파이크로 A/B 분기 확정.
- 유입 O → A 채택(거의 무변경, 최소 리스크, 하우스 스타일 부합).
- 유입 X → B 채택(C는 회귀 위험 때문에 최후수단).
- 스파이크 방법(daemon 수정 없음): 이미 daemon이 쓰고 있는
daemon/activity-debug.log와 기존 채팅 세션 트랜스크립트를 읽어, 서브 spawn~result 사이에 서브 내부 tool_use 메시지가 부모 stdout JSONL에 존재하는지 1건 확인. 이건 관찰이라 게이트① 범위 내에서 가능 — 보검·Jason 승인 떨어지면 바로 돌려 옵션을 못 박겠습니다.
2. emit 스키마 변경안 & UI 렌더 범위
emit 스키마: 신규 필드 0. 기존 ActivityEvent.detail·tool_calls만 채우면 됨.
// 진행 핑(같은 서브, running 유지하며 갱신) — A/B 공통
{ source:'chat', external_id:`${task_id}:${subId}`, session_id, label, // ← label 반드시 재전송(upsert가 label=excluded)
status:'running', tool_calls:<누적>, detail:'<최근 단계/카운터 한 줄>' }
- 주의 1: upsert가
label=excluded.label이라 진행 핑에 label 누락 시 라벨이 바뀜 → 항상 같은 label 동봉. - 주의 2:
detail은 500자 컷,tool_calls는 MAX라 단조 증가 카운터와 호환. DB 변경 불필요.
UI 렌더 변경 범위:
- 텍스트 진행만이면 변경 0줄 —
subCard가 이미detail/tool_calls렌더. - (선택, 게이트② 별도 합의 시)
detail에p/total수치가 있으면 얇은 진행 바 1개 추가 —monitor.tsxsubCard()에 ~5줄. 이건 옵션이며 1차 범위에서 제외 권장(최소 변경 원칙).
3. 안전계획
- 변경 전 고정: daemon.js 수정 착수 직전 현재 HEAD를 단일 커밋으로 고정(또는 태그 백업). 비가역 코어이므로 워킹트리 clean 상태에서만 시작.
- 단일 커밋 revert 보장: 모든 변경을 1커밋에 담아
git revert <sha>한 번으로 완전 원복. 마이그레이션이 없어(컬럼 재사용) DB 롤백 불필요 — revert 안전성이 특히 높음. - 최소 변경: A안 기준 daemon.js 단일 블록(서브 진행 갱신 emit)만. 인접 코드·포맷 미수정.
- 라이브 검증(자가 DONE 금지): 코드 통과로 끝내지 않음. 실제 채팅 세션에서 Task 서브 1건을 띄워
/activity세부 작업 패널에서 detail/tool_calls가 시간에 따라 갱신되는 것을 눈으로 1건 확인(스크린샷/DB row updated_at 증가 동시 제시) 후에야 완료 보고. 검증 전엔 "코드 됨"까지만 보고.
4. 예상 리스크 (daemon = 전 에이전트 구동 코어)
- 스트림 루프 회귀: daemon 950~1104 메시지 루프에 진행 갱신을 끼우면 모든 에이전트 턴이 이 경로를 통과 → 버그 시 전사 영향. → A안의 변경 최소화 + 라이브 검증 + 단일 revert로 봉쇄.
- emit 폭주: 서브가 진행 핑을 너무 자주 쏘면
/bridge/activity·D1 쓰기 부하. → 디바운스(예: ≥2초 또는 tool_calls 변화 시에만)로 throttle, 1차엔 보수적으로. - 고스트 행: 진행 중 죽은 서브는 running 잔류 가능하나 기존
STALE_RUNNING_SEC=2h가드와 turn-end cleanup(1196~)이 이미 처리 → 신규 리스크 아님. - 라벨 덮어쓰기: 위 주의1 미준수 시 라벨 깨짐 → 진행 핑에 label 필수 동봉으로 차단.
- C안 선택 시(비권장): 트랜스크립트 경로 하니스 버전 의존 → 업그레이드 시 무음 실패 가능. 그래서 C는 최후수단.
게이트 회신
- 착수 가능: 예. 설계안 위 제출.
- 다음 한 걸음(승인 시): 읽기 전용 스파이크로 A/B 확정 → 확정 옵션으로 게이트② 구현안(커밋 계획 포함) 재제출.
- ETA: 스파이크+옵션 확정 반영본 0.5d. 구현(②)은 보검(구조)·Jason(승인) 통과 후 착수.
승인 또는 옵션 선호(특히 A 스파이크 진행 가부) 회신 주시면 그대로 진행하겠습니다.