virtual-insanity
← 리포트 목록

needs_evidence 반려 67건 반려사유별 개선안 제안서

2026-10-22 claude [needs_evidence, 완료보고, 증거게이트, 재발행루프, orchestrator, 제안서, cowork]

needs_evidence 반려 67건 반려사유별 개선안

작성: Cowork(다듬) / CMD#42324 / 조회 2026-10-22 03:52~04:05 KST / 코드·DB·크론 수정 없음(제안만)

[권고]

1순위(codex): 반려 집계 태스크가 스스로 반려를 늘리는 자기증식 고리를 "입력 쪽"에서 끊는다. - 무엇을: scripts/orchestrator.py gather_system_context()(886행)가 Ron LLM에 넘기는 context["queue"]["counts"]에서 needs_evidence 키를 빼거나, "과거 반려 누계이므로 집계 태스크 생성 금지" 주석 키로 바꾼다. - 왜: 라이브 큐 API 응답 counts가 {'claimed': 1, 'done': 4, 'failed': 1, 'needs_evidence': 67}이다(조회 03:52). get_queue_status()(2192행)가 이 값을 그대로 Ron LLM에 준다. 그래서 LLM이 "needs_evidence 65건…", "66건…", "67건…"처럼 건수를 제목에 넣은 집계 태스크를 계속 새로 만든다. 10-19 21:02 이후 guardian 14건, cowork 1건이 이 계열로 발행됐다(아래 쿼리 Q7). 제목 숫자가 바뀌므로 정확 제목 중복 가드(15분)를 통과한다. - 기대효과: 고리 입력이 사라지면 신규 집계 태스크는 0건/일에 가까워진다. 현재 이 계열이 일 평균 약 4.7건(14건÷3일) 발행되고 거의 전부 반려된다. 필요한 최종 집계는 사람이 요청할 때만 만든다.

2순위(codex): 10-19에 넣은 재발행 루프 차단 게이트가 실제로는 작동하지 않는 이유 두 가지를 고친다. - 이유 A. 라이브 오케스트레이터(PID 46446, 가동 24일 19시간, 즉 09-27 07:55 기동)는 orchestrator.py 수정 시각(10-19 21:01) 이전부터 떠 있다. logs/ 전체에서 NEEDS_EVIDENCE_REISSUE_LOOP_BLOCKED 로그는 0건이다. 새 코드를 읽었다는 증거가 없다. 재시작은 해리 승인 사항이다. - 이유 B. 코드를 다시 읽는다 해도 못 잡는다. 실측으로 _reissue_family_key()를 guardian 15건(#42111~#42322)에 돌렸더니 15개 제목이 15개 서로 다른 계열로 갈렸다. 같은 계열 3회 임계에 도달하는 묶음이 0개다. 연속 body 유사도도 0.18~0.42로 임계 0.9에 한참 못 미친다. 제목 문구와 body가 매번 달라서다. 이 게이트는 문구가 같은 재발행만 막는 설계라 이번 패턴에는 구조적으로 무력하다. 1순위가 이를 대체하는 근본 처방이다.

3순위(guardian/cowork 지시문): 정책 문서 신설 없이, 수신 에이전트 제출 규율을 "제출 직전 3줄 체크"로 못박는다(아래 3절). 규칙은 이미 있고 어긴 것이다.

[현황]

1. 집계 파일 대조 (Q1)

  • 대상 파일: /Users/ron/.hermes/workspace/memory/guardian_needs_evidence_breakdown_20261022.md(1999바이트, 03:04 저장). ~/knowledge-agent/400-reports/에는 오늘자 guardian 집계 파일이 없다. search_files 도구가 오류(No such process)를 내서 ls -lt·find -newermt로 대신 찾았다.
  • 집계 파일 합계는 70건(needs_evidence 67 + guardian failed 3). 이 제안서 대상은 DB의 needs_evidence 67건이다. 집계 시점 이후 DB는 needs_evidence 67, failed 2다(Q0). 파일의 "failed 3"과 DB "failed 2"는 차이가 있어, failed는 이 제안서에서 다루지 않는다(미확인).
  • 집계 파일 3절의 상위 3라벨: llm_infra_failure 16건 / invalid_verification_exit_code 9건 / verification_command_unverifiable 5건. 4절에 "라벨 없음(결과 확인 보류 문구)" 39건이 가장 많다.
  • agent별 건수(DB 재조회, Q2): guardian 49, cowork 8, analyst-macro 5, analyst-pm 4, ron 1 = 67.

2. 실제 사례 대조 — 집계 파일이 놓친 사실

Q3(result_note의 "차단 사유:" 구간만 따로 파싱해 재집계)로 확인한 결과:

집계 파일 라벨 파일 건수 실제 차단 사유 구간에 있는 건수 판정
llm_infra_failure 16 0 오분류
invalid_verification_exit_code 9 guardian 5 + cowork 2 + ron 1 = 8 대체로 일치
verification_command_unverifiable 5 guardian 3 + cowork 1 = 4 대체로 일치
missing_verification_exit_code 표에 없음 guardian 18 (전체 21건, Q4) 집계 파일이 놓침
  • llm_infra_failure 16건 전부가 차단 사유가 아니라 "원본 요약" 본문 안에 그 단어가 쓰인 경우다. 사례(Q5): #41902 "llm_infra_failure 계열 실제 원인은 0건", #42042 "llm_infra_failure 계열은 1건(1.8%)", #41628 "llm_infra_failure가 반려 사유인 건은 0건". 즉 guardian이 "인프라 때문이 아니다"라고 쓴 문장을 단순 문자열 검색이 인프라 반려로 센 것이다. 이 숫자를 믿고 "인프라 지연 재등록"을 1순위로 삼으면 방향이 틀린다. 실제 인프라 반려는 사실상 0건이다.
  • 상위 실제 사유(Q3): guardian "결과 확인 보류" 24(템플릿 문구 자체), missing_verification_exit_code 18, "검증 근거 없음" 14, missing_verification_command 5, invalid_verification_exit_code 5, execution_artifact_path_not_found 3. analyst-macro 5건·analyst-pm 4건은 전부 missing_result_or_verification 한 가지다.

3. 사례 CMD result_note 직접 조회 (Q6, 라벨당 3건 이상)

  • invalid_verification_exit_code: #41917 cowork(10-17 14:53), #41348 cowork, #41504 guardian, #40297 ron. 모두 "검증:" 줄에 exit code가 없거나 0이 아니다. #40297은 ron이 본문에 "exit code 1"(지정 스크립트 실패)을 그대로 적어 invalid가 났다. 이건 작업 자체가 실패했는데 done으로 제출한 경우라 정직한 반려다.
  • missing_verification_exit_code / missing_verification_command / execution_artifact_path_not_found: #42314 guardian("근거: …보조 스크립트는 memory/tmp_ne_agg.py와 tmp_ne_agg2.py입니다" → 해당 파일 없음, 검증 줄 자체 없음), #42062 guardian("/Users/ron/.hermes/workspace/ne_agg.py"를 인용했으나 실제는 /tmp/ne_agg.py), #42307 guardian("결론: 작업 결과가 정리됨" — 결론이 시스템 기본 문구). 세 건 모두 지운 임시 스크립트나 틀린 경로를 근거로 인용했다.
  • verification_command_unverifiable: #42316 guardian, #40825 guardian, #40691 cowork. "검증:" 값 안에 워크스페이스 경로 토큰이 없어서 난다(규칙은 completion_evidence_policy.md 933~945행에 있음).
  • missing_result_or_verification(analyst-macro 5, analyst-pm 4): #41900, #41067 등. 본문에는 84bp·325bp 같은 실제 계산 결과와 파일 경로까지 있다. 그런데 게이트 판정이 "결과 확인 보류"다. #41900 result_note는 382자로 잘려 있어 어떤 줄이 모자랐는지 DB만으로는 확인되지 않는다(미확인). 같은 사유로 9건 연속이고 모두 같은 유형(IG/HY OAS 파일 판독)이다.

4. 규칙 부재 vs 제출 규율 구분 (Q8, 정책 문서 대조)

반려 유형 건수(대략) 기존 규칙 위치 판정
exit code 누락·비0 guardian 23 + cowork 2 + ron 1 completion_evidence_policy.md 1796행 "있어도 0이 아니면 invalid_verification_exit_code로 반려한다", agent_completion_submission_standard.md §0 TL;DR 2번 규칙 있음 → 제출 규율
경로 인용 오류·삭제된 임시 스크립트 guardian 3~5 agent_completion_submission_standard.md §0 TL;DR 3번("근거파일에 적은 경로를 지금 직접 확인했는가"), §9-9 규칙 있음 → 제출 규율. 단 "임시 스크립트를 쓰면 지우지 말고 저장 경로를 근거로 인용"이라는 한 줄은 없음(부분 공백)
verification_command_unverifiable 4 completion_evidence_policy.md 933~945행 규칙 있음 → 제출 규율
analyst-macro/pm missing_result_or_verification 9 agent_completion_submission_standard.md §9-2, §9-4, 신설 조항 5(805행) 규칙 있음. 다만 실제 어느 줄이 모자란지 DB에서 못 봄 → 진단 정보 부족
"반려 집계가 반려를 늘림" guardian 44 + cowork 8 없음. agent_completion_submission_standard.md §10(1038행)은 재발행 시 인프라 vs 근거미비 구분만 다룸 규칙·코드 모두 공백 (1순위)

결론: 문서 규칙이 비어서 생긴 문제는 "자기증식 집계 고리" 한 가지다. 나머지는 이미 있는 규칙을 수신 에이전트(대부분 guardian)가 제출 시점에 어긴 것이다. 또 이 정책 문서는 4,697행과 1,162행이라 에이전트가 실제로 읽고 따를 가능성이 낮다. 문서를 더 늘리는 것은 처방이 아니다.

5. 반려 알림이 다시 태스크가 되는 경로 (Q7, bus_messages·DB)

  • 경로 확인됨(입력 쪽): 위 1순위의 큐 counts 경로. 근거 메시지: bus/messages.jsonl(UTC 표기 10-21 15:02~18:01, KST 10-22 00:02~03:01)에서 orchestrator→guardian 태스크 #42310, #42311, #42312, #42314, #42315, #42316, #42321, #42322가 모두 "needs_evidence N건 …" 제목이다.
  • 경로 차단됨(출력 쪽): 반려 알림 메시지 자체("결과 확인 보류")는 completion_note_should_not_requeue()(scripts/shared/public_result_formatter.py 3729행)와 orchestrator.py 1961행 필터로 Ron LLM의 "최근 완료 태스크" 컨텍스트에서 제외된다. _retry_failed_commands()(3438행)도 같은 필터로 건너뛴다. 즉 반려 알림 → 재처리 경로는 막혀 있고, 실제 순환은 "건수 숫자"를 통해 돈다.
  • 2차 증폭: 반려된 집계 태스크의 알림은 guardian→ron failed 메시지로 오는데(예 #42311, #42312, #42315, #42321), 이 failed 건은 다시 재시도 후보가 되지 않는다(필터). 따라서 "failed 4건"이라는 새 숫자가 또 다른 제목("needs_evidence 67건·failed 4건")을 만든다.
  • 완료됨으로 저장된 반려 알림도 있다. #42322는 DB status=done인데 bus 메시지는 "결과 확인 보류"로 나갔다(10-21 18:04 메시지). 상태와 알림이 불일치한 사례로, 원인은 확인하지 못했다(미확인).

codex용 항목 (코드 수정 필요, 직접 수정하지 않음)

C1. 큐 건수가 집계 태스크를 낳는 입력 차단 (최우선)

  • 수정 대상: scripts/orchestrator.py gather_system_context() 886행 부근, context["queue"]["counts"]를 채우는 부분(get_queue_status() 2192행의 반환값).
  • 내용: counts에서 needs_evidence·failed의 과거 누계를 Ron LLM에 노출하지 않는다. 필요하면 "최근 1시간 신규 반려 수"만 준다. RON_SYSTEM_PROMPT(427행)에 "반려 집계·분류 태스크를 새로 만들지 말 것(이미 존재하는 집계 파일을 참조)" 한 줄을 추가한다.
  • 검증 명령: python3 -m py_compile scripts/orchestrator.py → exit 0. python3 -c "import json,orchestrator as o; c=json.loads(o.gather_system_context()); print(c['queue']['counts'])"에서 needs_evidence 키가 사라졌는지 확인. 신규 pytest 1개 추가 후 python3 -m pytest tests/ -q -k needs_evidence. 반영 후 24시간 동안 sqlite3 -readonly …ops_multiagent.db "SELECT count(*) FROM bus_commands WHERE target_agent IN ('guardian','cowork') AND title LIKE '%needs_evidence%' AND created_at>=datetime('now','localtime','-1 day')" 기대값 0.
  • 롤백 조건: 24시간 내 Ron이 실제 장애성 반려(예: 인프라 반려 급증)를 놓쳤다는 증거가 나오면 counts 노출을 복구하고 신규 반려 수만 따로 노출.

C2. 집계 파일의 llm_infra_failure 오분류 방지

  • 수정 대상: guardian이 쓰는 집계 로직이 사람 손 스크립트라 소스 파일은 확인하지 못했다(미확인). 대신 DB에 이미 있는 문서화 기준을 코드로 재사용한다. scripts/agent_queue_worker.py::_is_llm_infra_failure_note()(2349행)와 같은 판정(노트 결론 줄이 needs_evidence: llm_infra_failure_retry_deferred로 시작하는가)을 쓰는 집계용 읽기 전용 스크립트 scripts/needs_evidence_summary.py를 신설해, guardian 태스크 지시문이 "이 스크립트 출력만 인용"하도록 한다.
  • 검증 명령: python3 scripts/needs_evidence_summary.py → llm_infra_failure 건수 0(현재 Q3 기준 실제 0건), missing_verification_exit_code 21건(Q4), 합계 67건과 DB SELECT count(*) … status='needs_evidence'가 일치. exit code 0.

C3. 게이트 반려 진단 정보 보존 (analyst-macro/pm 9건 원인 확정용)

  • 수정 대상: scripts/agent_queue_worker.py 결과 저장부(guarded_completion_result_note 계열, 4205행 부근 "결론: …결과 확인 보류" 조립 구간)와 scripts/server.py 3091행 부근 동일 템플릿. result_note가 900자([:900])에서 잘리고 "원본 요약"이 220자(raw_excerpt[:220])라서 어느 줄이 모자랐는지 DB만으로는 알 수 없다.
  • 내용: bus_commands가 아니라 별도 로그 파일에 원본 제출문 전체와 판정 사유 코드를 남긴다(저장 위치 신설은 codex가 결정). DB 컬럼 추가는 하지 않는다.
  • 검증 명령: 새 반려 1건을 재현한 뒤 로그에서 원본 4블록 전체가 읽히는지 확인, python3 -m pytest tests/ -q -k completion 통과.

C4. 재발행 루프 게이트(10-19) 적용 확인 및 보강

  • 수정 대상: 코드 수정이 아니라 적용 확인이 먼저다. 재시작은 해리 승인 후 launchctl로 하고, 이 에이전트는 하지 않는다. 확인 명령: ps -o lstart= -p $(pgrep -f 'orchestrator.py --loop')가 stat -f '%Sm' scripts/orchestrator.py(10-19 21:01)보다 늦은지 확인.
  • 보강안: _reissue_family_key()(4301행 이후)는 제목에서 숫자를 지워도 문구가 달라 못 묶는다. needs_evidence·반려가 제목에 있고 대상이 guardian/cowork이면 계열 키를 "needs_evidence_aggregate" 하나로 통일하는 좁은 규칙을 추가한다(오탐 범위가 이 키워드로 한정됨). 이 보강은 C1이 안 들어갔을 때만 필요하다.
  • 검증 명령: python3 -m pytest tests/test_orchestrator_needs_evidence_reissue_loop.py -q 기존 8개 통과 + 신규 케이스(실제 제목 15개 → 같은 계열). 기대: Q 재현 스크립트(/tmp/ne_q3.py) 출력이 "15 rows 1 families".

[리스크]

  • 집계 파일 오분류(llm_infra_failure 16건)를 근거로 인프라 대책을 세우면 효과가 없다. 지난 제안서(261019_claude_needs_evidence_reissue_loop_policy.md)가 "인프라 지연형 25.5%(14건)"를 2순위로 올렸던 것도 같은 문자열 검색 오분류에서 나왔을 가능성이 높다. 이 문서의 Q3·Q5 재집계가 그 가정을 뒤집는다. 같은 집계 방법이 이번에도 쓰였기 때문이다.
  • 단일 장애점: 집계 태스크의 수신자가 guardian 한 에이전트에 몰려 있다(49/67=73.1%). guardian 한 곳의 제출 규율 문제가 전체 반려 수치를 결정한다.
  • 재시작 지연: 라이브 오케스트레이터가 24일 넘게 재시작되지 않았다. 코드 수정이 쌓여도 반영 시점이 불명확하다. C4의 ps 확인 없이는 "적용됐다"고 보고할 수 없다.
  • 제안서 한계: #41900류 analyst-macro/pm 9건의 정확한 부족 줄은 확인하지 못했다. C3 없이는 확정 불가다.

[검증방법]

  • KPI 1: 신규 집계성 태스크. SELECT count(*) FROM bus_commands WHERE target_agent IN ('guardian','cowork') AND title LIKE '%needs_evidence%' AND created_at >= <C1 반영 시각> → 기대 0건/일(현재 약 4.7건/일).
  • KPI 2: needs_evidence 총량. SELECT count(*) FROM bus_commands WHERE status='needs_evidence' → 현재 67에서 24시간 동안 증가폭 0 또는 감소(10-19 제안서 시점 55건 → 10-22 03:52 현재 67건, 3일간 +12건, 하루 약 +4건).
  • KPI 3: 오분류 해소. C2 스크립트가 보고하는 llm_infra_failure 건수가 DB 실제 결론줄 기준과 일치.
  • 롤백: C1 반영 후 24시간 안에 비집계성 반려(analyst-macro/pm 등)가 오히려 증가하거나, Ron이 실제 장애 알림을 놓친 정황이 있으면 counts 노출을 복구한다.

부록. 실행한 조회 목록 (모두 읽기 전용)

  • Q0: sqlite3 -readonly ops_multiagent.db "SELECT status,count(*) FROM bus_commands WHERE status IN ('needs_evidence','failed') GROUP BY status" → 2행(failed 2, needs_evidence 67)
  • Q2: SELECT claimed_by,count(*) FROM bus_commands WHERE status='needs_evidence' GROUP BY 1 → 5행(guardian 49, cowork 8, analyst-macro 5, analyst-pm 4, ron 1)
  • Q3: python sqlite3 mode=ro로 needs_evidence 67행 result_note의 "차단 사유:" 구간 재집계(/tmp/ne_q4.py) → 67행
  • Q4: SELECT count(*) … status='needs_evidence' AND result_note LIKE '%missing_verification_exit_code%' → 21
  • Q5: … result_note LIKE '%llm_infra_failure%' → 16행(전부 본문 인용, 차단 사유 구간 0건)
  • Q6: 사례 CMD 12건(42316, 42310, 42314, 41917, 41504, 41348, 42083, 41900, 41067, 40297, 42307, 42062) result_note 조회 → 12행
  • Q7: SELECT … requested_by='orchestrator' AND target_agent='guardian' AND created_at>='2026-10-19 21:02' AND (title LIKE '%needs_evidence%' OR title LIKE '%반려%') → 14; cowork 동일 조건 → 1. 큐 API counts {'needs_evidence': 67, …}
  • Q8: 정책 문서 줄 확인: agent_completion_submission_standard.md 7~20(§0 TL;DR), 130(§3-1-1), 1038(§10); completion_evidence_policy.md 933~945, 1796
  • 보조: /tmp/ne_q3.py로 _reissue_family_key/_reissue_body_similarity를 guardian 15건에 적용 → 15 rows 15 families, 연속 유사도 0.18~0.42