Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
27901d9
Feat(실험): 에이전트 질문 중계 — AskUserQuestion을 다른 역할 담당에게 묻고 답을 받아 실행을 이어 간다
oyeonghun Oct 6, 2026
b986f4c
Test(실험): 질문 중계 E2E — 진짜 헤드리스 Claude로 FE 질문 → BE 답 → 같은 실행에서 이어서 구현 (…
oyeonghun Oct 6, 2026
5d080ab
Test(실험): 질문 중계 BE → FE 방향 — 대상 FRONTEND, FE 담당만 답, BE 에이전트가 답을 읽는다
oyeonghun Oct 6, 2026
5be227b
Feat(실험): 질문 라우터 인터페이스 — "누구 소관인가"를 바꿔 끼우는 자리, SELF 판정, 판정 기록
oyeonghun Oct 6, 2026
2045f13
Feat(실험): 질문 라우터 오프라인 평가 장치 — 데이터셋 20문항, 규칙·Claude·HTTP(외부 결정 모델) 라우터 비교
oyeonghun Oct 6, 2026
8291539
Feat(실험): 라우터 평가 데이터셋을 팀원이 넣을 수 있게 — --data 경로, 형식 검사(--check), 여러 질문…
oyeonghun Oct 6, 2026
7d75bc1
Feat(실험): 질문 C안 서버 — 상담 초안(전부 정해진 것이면 곧 답), 기다림 포기 시 BLOCKED, 답이 오면 재개
oyeonghun Oct 6, 2026
f00dd11
Feat(실험): 질문 C안 노트북 쪽 — 상대 역할 상담 실행 자동화, 실행 안 3분 대기 후 내려놓기, 재개 프롬프트
oyeonghun Oct 6, 2026
51b452b
Test(실험): C안 E2E — 진짜 Claude로 ① 상담 실행이 코드에서 찾아 바로 답 ② 미정이면 멈춤 → 담당자 답…
oyeonghun Oct 6, 2026
21a3404
Feat(실험): NOMOS_HOME — 로컬 상태 위치를 바꿔 한 PC에서 에이전트 둘 이상, 실물 Executor 두 개…
oyeonghun Oct 6, 2026
31cd62b
Chore: 테스트 시간 제한 20초 → 60초 — 부하가 있을 때 준비 단계가 간헐적으로 넘쳤다
oyeonghun Oct 6, 2026
4604cdb
Fix: pm-relay 테스트 정리 단계 경합 — 늦게 올라온 중계 작업이 10분 대기로 남아 hook 시간 제한에 걸리던 것
oyeonghun Oct 6, 2026
291c56b
Merge: dev를 실험 브랜치에 합침 — 질문 중계 마이그레이션 017 → 018, 룸·멈춤과 연결
oyeonghun Oct 8, 2026
30792b5
Feat: 질문 중계 운영 준비 — 프로젝트 스위치(기본 켜짐), 내가 답할 질문 API, 만료 주기 정리 (CLI 0.1.5)
oyeonghun Oct 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ docs/TASK.md
docs/imple.md
docs/api-spec.md
docs/phase2-requirements.md
scripts/data/router-runs/
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,25 @@ PM_REVIEW 반려·피드백 분류·이의 설명·보고서는 아직 없다.
- 결정 뒤 `tasksChanged` — 승인이면 뒤 태스크가 풀리고 반려면 이 태스크가 다시 READY다.
- 015는 이미 `AWAITING_APPROVAL`에 멈춰 있던 태스크에 **지금 DB 값으로** 카드를 만든다(`payload.backfilled: true`, 단계 결과 없음).

### 질문 중계 (`domain/question`, 018) — 실험 브랜치

에이전트가 다른 역할이 정할 결정을 AskUserQuestion으로 묻고, 답을 받아 같은 실행에서 이어 간다. 근거는 헤드리스 실험(E1~E6, 메모리 `headless-askuserquestion`).

- **AskUserQuestion은 `--permission-prompt-tool`을 붙여야 생긴다**(그냥 `-p`에는 없다). 권한 도구(`bridge/permission.ts`)는 **AskUserQuestion만 처리하고 나머지는 전부 거부**한다 —
도구가 허용하면 셸·폴더 밖 쓰기가 실제로 실행된다(E2). 오류·종료도 거부다(fail closed). settings.json deny는 도구까지 오지도 않는다.
- 답은 `{질문 문장: 답}`이고 **키가 글자까지 같아야** 한다(E3) — 사람 답 API가 422로 막는다. 답이 없을 때의 거부 문구는 "커밋하지 말고 멈춰라"를 담는다(E4: 지시가 없으면 추측해 커밋한다).
- 실행 안에서 `NOMOS_QUESTION_INLINE_WAIT_MS`(3분)만 기다리고, 넘으면 태스크를 내려놓는다(BLOCKED·`QUESTION`, 담당 비움). 답이 다 오면 READY → 다시 가져가며 브리핑의 `answeredQuestions`로 받는다.
30분 MCP 무응답 한도(E1)를 실행이 붙잡고 기다리지 않는 이유다.
- **상담 실행**: 답할 역할의 Executor가 자기 작업공간을 `--tools Read,Grep,Glob` + 빈 MCP + `--strict-mcp-config`로 읽어 초안을 낸다(쓰기·셸·웹 없음).
질문 전부가 코드·명세에 정해져 있으면(decided) 초안이 곧 답(`agent_answered`, 사람 미확인) — 아니면 사람이 답한다(대상 역할 담당 또는 대표). 상담은 읽은 순간의 스냅샷이다.
- **만료 3일** — 읽을 때(settleExpiry)와 **주기 정리**(`startQuestionExpirySweep`, `startServer`에서만)가 만료시키고 그 질문으로 멈춘 태스크를 ESCALATED로 올린다.
주기 정리가 없으면 아무도 읽지 않은 질문 때문에 태스크가 영원히 기다린다.
- **프로젝트 스위치 `projects.question_relay`(기본 켜짐)**: 끄면 서버가 질문을 409 `QUESTION_RELAY_OFF`로 거부하고, 브리핑의 `questionRelay=false`를 보고 Executor가 권한 도구를 붙이지 않으며
프롬프트는 "가정하고 GOTCHA로 남겨라"가 된다(옛 서버라 필드가 없으면 꺼짐으로 본다). **시작(G1) 전에만** 바꾼다(`PATCH /projects/:id/settings`, `PROJECT_SETTINGS_UPDATED`) —
끄는 이유: 대조군 실험, 답할 사람이 없을 때, 상담 실행 비용(답할 쪽 구독), 문제 시 되돌리기.
- **다른 역할 소관 계약은 DECIDED로 남기지 않는다**(스위치와 무관, E6: 지어낸 BE 계약을 DECIDED로 발행했다) — 가정했다면 GOTCHA "가정함 — <역할> 확인 필요".
- **`GET /me/questions`**: 내가 답할 질문(대표는 전부, 팀원은 자기 역할이 대상인 것) — 알림 배지용. 답할 자격 정의는 `answerQuestion`과 같아야 한다.

### 룸 (`domain/room`, 017) — 보기 전용

룸 = **프로젝트 × 역할**. 테이블이 없다(역할당 에이전트가 하나라 구성이 저절로 정해진다). 피드는 세 가지를 시각순으로 섞은 한 줄씩이고 **문장은 서버가 만든다**(`room/render.ts`).
Expand All @@ -484,6 +503,9 @@ PM_REVIEW 반려·피드백 분류·이의 설명·보고서는 아직 없다.
`task_dispatches`의 resumes가 달라져 "다시 실행해 주세요 (재개)"가 다시 나간다. Executor가 꺼져 종료 보고조차 없으면 감시(`startStallWatchdog`, `startServer`에서만)가
45분 동안 실행 보고·도구 사용이 없는 수령을 같은 방식으로 멈춘다(`system:watchdog`). `tasks.claimed_at`(마지막 수령 시각)이 그 기준이자 화면의 "수령 후 경과"다.
- Executor의 룸 보고(`executor/activity.ts`)는 **실패해도 실행을 멈추지 않는다.** 옛 서버에는 API가 없으니 시작 보고가 실패하면 그 실행의 활동·종료 보고를 건너뛴다.
- **질문 중계(018, 실험 브랜치)와의 관계**: 답을 기다리느라 태스크를 내려놓은 실행(BLOCKED·`QUESTION`)의 종료는 멈춤(`AGENT_STOPPED`)이 아니다(`waitingQuestion`).
답이 와서 READY가 되면 "다시 실행해 주세요 (재개)"가 나가도록 `task_dispatches.resumes`에 `TASK_BLOCKED_ON_QUESTION`도 센다.
질문 줄(`QUESTION_*`)은 묻는 쪽 태스크의 룸과 **답할 역할(`targetRole`)의 룸** 둘 다에 나온다.
- 사람 메시지(룸 채팅)는 아직 없다. 넣을 때도 **실행 중인 모델에 끼워 넣지 않는다**(인계 노트와 같은 이유 — 리플레이가 깨지고 노트가 채널이 된다).

### 프로젝트와 멤버
Expand Down
40 changes: 39 additions & 1 deletion docs/erd.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -954,4 +954,42 @@ Ref: events.project_id > projects.id
Ref: events.actor_agent_id > agents.id

Ref: agent_tokens.agent_id > agents.id [delete: cascade]
Ref: oauth_sessions.user_id > users.id [delete: cascade]
Ref: oauth_sessions.user_id > users.id [delete: cascade]
// ★ 018 (실험 — exp/question-relay)
Table agent_questions {
id uuid [pk, default: `gen_random_uuid()`]
project_id uuid [not null, ref: > projects.id]
task_id uuid [not null, ref: > tasks.id]
asked_by_agent uuid [not null, ref: > agents.id]
asker_role text [not null, note: 'FRONTEND | BACKEND']
target_role text [not null, note: '답할 역할 — 서버의 질문 라우터가 정한다']
routed_by text [not null, note: "라우터 구현(domain/question/router.ts). 기본 'role_rule:opposite'"]
routing jsonb [not null, default: '{}', note: '라우터 판정 상세 {confidence, reason, latencyMs, fallback}']
questions jsonb [not null, note: 'Claude Code AskUserQuestion 형식 그대로(1~4개)']
status text [not null, default: 'pending', note: 'pending | agent_answered | answered | expired | self_owned']
draft jsonb [note: '상담 실행 초안 {answers, decided, basis}']
drafted_by_agent uuid [ref: > agents.id]
drafted_at timestamptz
answers jsonb [note: '키 = 질문 문장 그대로']
answer_source text [note: 'agent | human | agent_confirmed | human_override']
answered_by uuid [ref: > users.id, note: '사람이 답했을 때만']
answered_at timestamptz
expires_at timestamptz [not null, note: '기본 3일 — 지나면 expired, 그 질문으로 멈춘 태스크는 ESCALATED']
created_at timestamptz [not null, default: `now()`]

Note: '''
실행 중인 에이전트가 다른 역할 소관 결정을 AskUserQuestion으로 물으면 브릿지 권한 도구가 올린다(C안).
① 대상 역할 에이전트의 상담 실행이 초안 — 전부 decided면 곧 답(agent_answered, 사람 미확인) ② 아니면 대상 역할 담당·대표가 답(answered).
묻는 쪽은 몇 분만 기다리고 태스크를 BLOCKED(QUESTION)로 내려놓는다 — 답이 다 오면 READY.
CHECK agent_questions_status_chk: status IN ('pending','agent_answered','answered','expired','self_owned')
CHECK agent_questions_roles_chk: asker_role·target_role IN ('FRONTEND','BACKEND')
CHECK agent_questions_questions_chk: jsonb_typeof(questions)='array' AND 길이 1~4
CHECK agent_questions_source_chk: answer_source IN ('agent','human','agent_confirmed','human_override')
CHECK agent_questions_answer_chk: (status IN ('agent_answered','answered')) = (answers·answer_source·answered_at 모두 NOT NULL)
CHECK agent_questions_answered_by_chk: (status='answered') = (answered_by IS NOT NULL)
CHECK agent_questions_agent_answer_chk: status<>'agent_answered' OR (answer_source='agent' AND drafted_by_agent IS NOT NULL)
CHECK agent_questions_draft_chk: (draft IS NULL) = (drafted_by_agent IS NULL AND drafted_at IS NULL)
INDEX idx_agent_questions_pending ON (project_id) WHERE status = 'pending'
INDEX idx_agent_questions_task ON (task_id)
'''
}
Loading
Loading