needs_evidence 반려 상위 2개 사유별 재발 방지안 (실DB·게이트 재현 기반)
needs_evidence 반려 상위 2개 사유별 재발 방지안
조회 기준: /Users/ron/.hermes/data/ops_multiagent.db (sqlite3 -readonly), 2026-10-06 16:50 KST. 코드 수정 없음. 수정이 필요한 곳은 9절에 파일·함수명만 적는다.
0. 먼저 정정할 것 — 건수
- 지금 DB에서 status='needs_evidence'는 전체 16건이다(id 40276~40994, 생성 2026-09-28~10-06). 기간 제한 없이 조회한 값이다.
- guardian #41002 보고의 13건은 "최근 7일(datetime('now','-7 days'))" 창 값이다. 같은 DB에서 3일 창은 9건, 4일 창은 11건, 24시간 창은 4건이다. 즉 13건은 틀린 값이 아니라 창이 7일이라 나온 값이고, 지시문의 16건도 틀린 값이 아니라 전체 기간 값이다. 두 보고 모두 "창"을 같이 적지 않아 서로 어긋나 보인다.
- 에이전트별(전체 16건): guardian 9, analyst-macro 2, analyst-pm 2, cowork 2, ron 1. guardian #41002는 7일 창 기준 guardian 7, pm 2, macro 2, cowork 2, ron 0(합 13)으로 적었다. 같은 7일 조건을 16:50에
datetime('now','-7 days')(UTC 기준)로 돌리면 guardian 7, macro 2, pm 2, cowork 2(합 13)이고,datetime('now','localtime','-7 days')(KST 기준)로 돌리면 합 12다. 이유: created_at은 KST로 저장되는데 datetime('now')는 UTC라 창 경계가 9시간 어긋난다. 경계 부근 건 1건 차이일 뿐 에이전트별 분포는 같고, ron #40297은 09-28 건이라 두 창 모두 밖이다. 즉 13은 UTC 창 값, 12는 KST 창 값이다.
1. [권고]
1순위 — missing_verification_exit_code 계열(전체 16건 중 7건 + invalid 2건 = 9건, 56%): guardian의 "라벨 건수 집계형" 완료보고에서 반복된다. 이 유형은 "exit_code: 4" 같은 라벨 건수 문구를 쓰는 것이 그대로 게이트에 걸린다는 점을 지시문에서 막는다. 기대효과: guardian 9건 중 라벨 오독 유형 3건(40854, 40869, 40902) 제거, 목표 7일 내 0건.
2순위 — missing_result_or_verification 계열(5건, 31%): analyst-macro 2, analyst-pm 2, cowork 1. 원인은 "근거:"·"근거파일:" 인용 줄 안의 ...(생략 표시)와 닫히지 않는 중괄호다. 이 줄을 생략 없이 한 줄 인용으로 쓰게 하는 규칙을 analyst-* 지시문에 추가한다. 기대효과: analyst-macro/pm 4건 중 4건 재현 가능(아래 3절), 목표 7일 내 0건.
2. [현황] 사유별·에이전트별 분류 (16건, 직접 대조)
result_note의 "차단 사유:" 구간을 정규식으로 뽑아 집계했다(한 건에 사유가 여러 개일 수 있음).
| 사유 | 건수 | claimed_by | CMD |
|---|---|---|---|
| missing_verification_exit_code | 7 | guardian 7 | 40276, 40346, 40633, 40842, 40854, 40902, 40994 |
| missing_result_or_verification | 5 | analyst-macro 2, analyst-pm 2, cowork 1 | 40408, 40518, 40874, 40960, 40954 |
| invalid_verification_exit_code | 2 | ron 1, guardian 1 | 40297, 40869 |
| verification_command_unverifiable | 1 | cowork | 40691 |
| 라벨 없음("결과 확인 보류"만) | 1 | guardian | 40825 |
| missing_execution_artifact / missing_verification_command | 각 1 | guardian | 40842 (exit_code 라벨과 함께 3개 동시) |
| completion_result_note_forces_rework | 1 | cowork | 40954 (missing_result_or_verification과 동시) |
guardian #41002 분류와의 대조: - 상위 2개 사유(missing_result_or_verification 5건, missing_verification_exit_code 5건)와 CMD 번호는 7일 창 기준으로 DB와 일치한다. 전체 기간 기준으로는 exit_code 계열이 7건(+invalid 2)으로 더 크고, missing_result_or_verification은 5건이라 순위는 같다. - 불일치 1: #41002는 missing_verification_exit_code 대상으로 40633, 40842, 40854, 40902, 40994를 적었는데 40842의 DB 사유는 3개(artifact/command/exit_code) 동시이고 40869는 invalid라서 사유가 다르다. - 불일치 2: #41002는 원인을 "검증 줄에 exit code 숫자를 빠뜨린 것으로 보인다"고만 적었다. 아래 3절 재현에서 이 가설은 40994 1건에만 맞고 나머지는 다르다.
3. 반복 구조 — 게이트 코드를 읽기 전용으로 재현한 결과
원문은 agent-queue-*.log의 command_id별 note에서 가져왔다. 같은 note를 현재 게이트 함수에 넣어 봤다(scripts/server.py _completion_storage_required_result_gaps, scripts/shared/public_result_formatter.py completion_required_evidence_gaps). 일부는 작성 당시 코드와 달라 지금은 통과하므로 "재현됨/재현 안 됨"을 구분해 적는다.
사유 A: exit code 계열 (guardian) — 원인이 둘로 갈린다
- A-1 라벨 건수를 exit code로 오독: 40854, 40869, 40902. 이 노트들의 검증 줄에
exit_code: 4,exit_code 2같은 문구가 있다. 이는 반려 사유 라벨 건수를 적은 설명문이다. 서버 게이트 정규식(server.py_STORAGE_EXIT_CODE_RE, 733~736행exit\s*code|exit_code|...[::=]?\s*(?P<code>\d+))이 이걸 실제 exit code로 읽어 0이 아니라며 invalid_verification_exit_code를 낸다. 재현: 40854, 40869, 40902 세 건 모두 현재 서버 게이트에서['invalid_verification_exit_code']가 나왔다. - A-2 진짜 누락: 40994. 검증 줄에 grep/ls 결과는 있지만 "exit code N"이 없다. 재현: 서버 게이트가
['missing_verification_exit_code']를 냈다. guardian #41002의 "빠뜨렸다" 가설은 이 1건에만 정확하다. - A-3 작성 시점 코드에서만 막힌 건: 40276, 40346, 40633, 40825, 40842는 지금 서버 게이트에 넣으면
[]로 통과한다. 이후 패치(예: agent_queue_worker.py 4300~4330행 근처 검증/근거파일 원문 보존)로 해소된 것으로 보이며, 단정은 못 한다. 40276은 note에 "각 경로 뒤에 실제로 연 줄 번호를…"이라는 자동 보강 안내문이 그대로 근거파일로 들어가 있었다(안내 문구를 값으로 복사한 사례). - 에이전트·유형: guardian의 "반려 분류표 작성형"(40825, 40842, 40854, 40869, 40902 5건)과 "원인 분류 점검형"(40276, 40346, 40633, 40994 4건).
사유 B: missing_result_or_verification (analyst-macro, analyst-pm, cowork)
- 재현: analyst-macro 40408·40518은 공용 게이트에서
['truncated_unverifiable_evidence', 'missing_source_log_path']가 나왔다. analyst-pm 40874도 같은 값이다. 원인은completion_has_truncated_inline_evidence(public_result_formatter.py)가 "리터럴...이 있고{개수가}개수보다 많으면" 잘린 JSON 붙여넣기로 판정하기 때문이다. 실제 노트 대조: - 40408:
{7개·}4개, 본문에..., "value_bp": 97.0같은 생략 표시. - 40518:
{2개·}1개,1M ... 4,3M ... 7같은 표기. - 40874:
{2개·}0개, 근거파일 인용에"IG_SPREAD": {"series": ..., ...처럼 중괄호를 열고 생략. - 이 게이트는 "근거파일:" 한 줄 인용은 중괄호 검사에서 제외한다(public_result_formatter.py의 패치 2026-09-23 주석). 그런데 40874는 근거파일 줄이
-불릿으로 여러 줄이어서 첫 줄 제외 규칙만으로 빠지지 않는 부분이 있고, 40408·40518은 "근거:"·"검증:" 안에 파이썬 출력({'date': ..., 'value': ...})을 그대로 붙였다. - 40960은 지금 게이트에서
[]로 통과한다(재현 안 됨). 40874와 달리 이미 근거파일 줄 형식을 쓴 노트다. - 에이전트·유형: analyst-macro/pm의 "JSON 파일 직접 파싱 후 수치 판단형"(40408, 40518, 40874, 40960 4건), cowork의 분류표/방지안형 1건(40954).
- 추가 확인: 40874는 바로 다음 재제출 40875에서 done이 되었다. 같은 내용을 "생략 없이 인용"으로 고쳐 통과한 사례다(근거파일 줄을
파일:행 "date": "2026-10-01" 다음 줄 "value": 3.24처럼 닫힌 인용으로 바꿨다).
4. 기존 문서와의 중복 확인
대상: config/agent_completion_submission_standard.md(1162행), config/completion_evidence_policy.md(4697행).
이미 있는 것(방지안에서 제외):
- invalid_verification_exit_code 라벨 정의와 pytest failed 혼입 규칙: standard 179행, 245행.
- exit code 리터럴 요건(analyst-fundamental/codex 전용)과 rc=0 허용: standard 636~666행. 공용 4블록 템플릿과 "exit code <숫자>" 안내: agent_registry.py 프롬프트 및 standard §9-2.
- 잘린 인라인 JSON 일반 규칙: standard 244행 "truncated_unverifiable_evidence | 잘린 인라인 JSON/로그를 근거로 붙여넣음 | 요약 대신 실존 경로 인용".
- missing_verification_exit_code 일반 설명과 KPI 쿼리: policy 1347~1367행, 1836~1844행.
겹치지 않는 것(방지안 대상):
- (a) "반려 사유 라벨 이름과 건수를 검증 줄에 exit_code N 형태로 쓰면 게이트가 실제 exit code로 읽는다"는 함정. standard/policy에서 exit_code 4 형태 문구를 다룬 줄은 검색에서 0건이다(grep "exit_code 4|_STORAGE_EXIT_CODE_RE" 매치 0). 이 함정은 오늘자 별도 문서 261006_cowork_guardian-needs-evidence-prevention.md에만 있고 config에는 반영되지 않았다.
- (b) "... 생략 표시 + 닫히지 않는 {가 한 노트에 같이 있으면 잘린 JSON으로 판정된다"는 구체 트리거. standard 244행은 결과(라벨)만 적고 트리거 조건(리터럴 ...과 {>} 동시)은 적지 않았다. 그래서 에이전트가 파이썬 dict 출력이나 ... 생략 인용을 자연스럽게 쓴다.
- (c) 창(7일/전체/24시간) 명시 없이 건수를 보고해 16 vs 13이 어긋나는 문제. 두 문서에 "건수 보고 시 조회 창을 쓴다"는 규칙이 없다.
- (d) 자동 보강 안내문("각 경로 뒤에 실제로 연 줄 번호를 숫자로 붙이고…")을 근거파일 값으로 복사하는 실패(40276). 이 지시문 자체가 값으로 복사되는 사례는 standard의 self_referential_placeholder 규칙(public_result_formatter.py 3100행 부근)과 비슷하지만 문서에는 이 실제 문구가 적혀 있지 않다.
5. 방지안 (겹치지 않는 부분만)
규칙 1 (사유 A 대응, guardian 분류표형): 검증 줄에서 반려 사유 라벨과 건수를 쓸 때는 missing_verification_exit_code: 4건처럼 쓰지 말고 missing_verification_exit_code는 4건처럼 exit_code 뒤에 콜론·등호·숫자가 바로 붙지 않게 쓴다. 검증 줄의 끝에는 실제 명령의 종료 코드만 exit code 0 한 번 쓴다.
규칙 2 (사유 A 대응, 점검형): 명령 결과를 인용한 모든 검증 줄 끝에 , exit code 0을 붙인다. grep이 매치 0건이면 exit 1이므로 "매치 0건 확인"은 grep -c ... → 0, exit code 1이 아니라 다른 성공 명령(wc -l, ls)으로 대체한다.
규칙 3 (사유 B 대응, analyst-macro/pm): 노트 어디에도 ...·…·닫히지 않는 {를 쓰지 않는다. 숫자는 파이썬 dict 출력을 붙이지 말고 0.86 / 3.24 / 2.38처럼 풀어서 쓴다. 근거파일은 파일:행 "원문 한 구절"을 구절 안에 중괄호 없이 쓴다.
규칙 4 (보고 공통): 건수를 말할 때는 "조회 창"(전체/7일/24시간)과 SQL 조건을 같은 줄에 적는다.
규칙 5 (자동 보강 문구): 자동 보강 안내문은 지시이지 값이 아니므로 근거파일 값으로 복사하지 않는다. 실제 연 파일:행과 인용으로 대체한다.
6. [리스크]
- 단일 장애점: 게이트가 워커(agent_queue_worker.py)와 서버(server.py, public_result_formatter.py 호출) 두 곳이라 한쪽만 고치면 어긋난다. 재현 결과도 작성 시점과 지금 게이트 값이 달라 일부(40276 등)는 지금 통과한다. 문서 규칙만 늘리면 게이트와 어긋날 때 에이전트가 혼란을 겪는다.
- 규칙 1은 정규식(
_STORAGE_EXIT_CODE_RE)의 오탐을 에이전트가 우회하는 것이어서 근본 해결이 아니다. 근본 해결은 게이트가 "검증:" 줄의 맨 끝 exit code만 읽게 하는 코드 수정이다(9절). - 규칙 3은
...를 막아 에이전트 보고가 길어질 수 있다. 상한 길이는 문서에서 정하지 않았다(미확인). - 이번 재현은 로그에 남은 note 원문을 대상으로 했고, 로그에 없는 2건(40297 ron, 40874 일부 구간)은 DB 요약만 확인했다.
7. [검증방법]
- 지표: 반려 건수 중 위 두 사유 비중. 현재 전체 16건 중 exit code 계열 9건(56%), missing_result_or_verification 5건(31%).
- 검증 명령(규칙 반영 7일 뒤):
sqlite3 -readonly /Users/ron/.hermes/data/ops_multiagent.db "SELECT claimed_by,count(*) FROM bus_commands WHERE status='needs_evidence' AND created_at>=datetime('now','-7 days') GROUP BY 1". - 기대값: guardian 7일 needs_evidence 3건 이하, analyst-macro/pm 0건. 재현 가능 여부는 서버 게이트에 note를 넣어
[]가 나오는지로 본다. - 롤백 조건: 규칙 반영 후 7일 동안 같은 사유의 반려가 줄지 않거나,
...금지 때문에 다른 라벨(missing_source 등) 반려가 늘면 규칙 3을 철회한다.
8. 완료 보고 형식 메모
이 제안서는 정책 제안이며 config/*.md에는 아직 반영하지 않았다. 반영은 해리 확인 후 별도 작업으로 한다.
9. 코드 수정이 필요하면 (수정 대상 파일·함수만, 이번 작업에서는 수정 안 함)
- scripts/server.py
_completion_storage_required_result_gaps+_STORAGE_EXIT_CODE_RE: "검증:" 섹션의 마지막 exit code만 판정하도록 범위 한정(라벨 건수 문구 오탐 방지). - scripts/shared/public_result_formatter.py
completion_has_truncated_inline_evidence: "근거파일:" 불릿 여러 줄과 "근거:/검증:" 안의 파이썬 출력도 제외 범위로 볼지 검토. - scripts/agent_registry.py
AGENT_ROLE_PROMPTS의 guardian·analyst-macro·analyst-pm 항목: 규칙 1~3 한 줄씩 추가. - scripts/orchestrator.py 자동 보강 블록(완료보고 4블록 보충 문구 생성 함수): 안내문이 값으로 복사되지 않도록 문구에 "값이 아니라 지시" 표시.