guardian 완료보고 종료코드 누락·허구 경로·양식문구 복사 반려 반복 — 원인과 방지안
guardian 완료보고 반려 반복 — 원인과 방지안
대상: bus_commands id 40260, 40275, 40276, 40282 (4건 모두 claimed_by=guardian, status=needs_evidence)
작성 원칙: 코드 수정 없음. 코드 수정은 §6 표로 codex에 넘긴다.
1. 결론 (먼저)
4건의 반려는 "guardian이 양식을 몰라서" 생긴 게 아니라 세 겹의 구조 문제가 겹친 결과다.
- 작은 대체모델이 답을 지어냈다. 4건 모두 1순위 모델(gpt-5.4)이 59초 만에 타임아웃됐고, 그다음 순서로 남은 모델은 로컬 소형모델(ollama qwen3.5:4b)뿐이었다(OpenRouter 2종은 키 미설정으로 건너뜀). 이 답변에 실제로 확인하지 않은 줄 번호·원문·종료코드가 들어갔다. 이를 막는 패치(최소 타임아웃 360초 + 게이트웨이 타임아웃 뒤 소형모델 답변 저장 차단)는 09-28 12:28에 파일로 반영됐지만, 워커 8개는 09-27 07:55:09에 시작된 프로세스 그대로라 아직 적용되지 않았다.
- 워커의 공개용 압축이 멀쩡한 근거까지 지웠다. 4건 모두
source_log_path:/verification_command:리터럴이 없어서, 워커가 원문을 그대로 넘기는 예외 조건을 못 탔다. 그 결과 압축 단계에서/Users/ron/...절대경로가 지워지고 "검증:" 줄이 빠졌다. 서버는 남은jobs.json같은 맨 파일명을 워크스페이스 기준으로 풀어/Users/ron/.hermes/workspace/jobs.json(실존 안 함)으로 판정했다 — 40275의 "허구 경로" 3개 중 2개는 이 압축의 부산물이다. - 자동 보강 안내문이 답안처럼 복사됐다. 오케스트레이터가 과제 본문 끝에 붙이는
[자동 보강]블록의 "결론: 수행한 일과 현재 상태를 완료형 한 문장으로 적는다." 같은 설명문을 소형모델이 그대로 결과에 복사했다(40276). 이 문구를 결과에서 걸러내는 검사는 코드에 0건이다.
guardian 역할 프롬프트에는 이 세 가지를 막을 문장이 없다(§4).
2. 4건 반려 패턴 분류
조회: sqlite3 -readonly /Users/ron/.hermes/data/ops_multiagent.db "SELECT id,claimed_by,status,title,result_note FROM bus_commands WHERE id IN (40260,40275,40276,40282) ORDER BY id;" → 4행, exit code 0 (2026-09-28 16:1x~16:37 KST)
원문(압축 전) 대조: /Users/ron/.hermes/logs/agent-queue-guardian.log 177057·177458·177464·177528행
서버 차단 기록: /Users/ron/.hermes/workspace/logs/completion_evidence_blocked.jsonl 18595·18606·18607·18611행
| id | 과제 | 서버 반려 사유 | 압축 전 원문의 실제 상태 | 분류 |
|---|---|---|---|---|
| 40260 | BAMLC0A0CM.json 갱신 주체 확인 | missing_verification_exit_code | 검증: "grep -n 'market_indicator\|macro' /Users/ron/.hermes/cron/jobs.json → exit code 1 (찾음 없음)". 실제로는 같은 grep이 14건 일치(grep -c 재실행). 근거파일 "jobs.json:23 "market-indicator-tracker": {"enabled": false}"도 실제 23행은 "state": "paused", |
A(지어낸 값) + B(압축으로 경로 소실: 서버 요약에 " 에서", ".hermes/workspace/scripts") |
| 40275 | market-indicator-tracker 08-16 정지 원인 | execution_artifact_path_not_found(workspace/jobs.json, executions.db, market_indicator_tracker.py) + missing_verification_exit_code | 원문 근거는 /Users/ron/.hermes/cron/jobs.json, /Users/ron/.hermes/cron/executions.db(둘 다 실존)였으나 서버에는 jobs.json, executions.db만 도달. 세 번째 /Users/ron/.hermes/scripts/pipeline/market_indicator_tracker.py는 정말 없는 경로(실제는 workspace/scripts/pipeline/). 검증 "health_check.py --check cron_status → exit code 0"은 존재하지 않는 옵션(실제 옵션은 --cron-check, --gateway-check 등) |
A + B |
| 40276 | 타임아웃 패치 워커 반영 여부 | missing_verification_exit_code | 결론·근거·검증·근거파일 4줄이 과제 본문의 [자동 보강] 블록과 글자 그대로 동일. 검증은 "pytest" 한 단어 |
C(안내문 복사) |
| 40282 | 같은 크론 비활성 원인 재진단 | verification_command_unverifiable + missing_verification_exit_code | 검증 "sqlite3 조회 결과 3회 이상 오류 기록 확인 … 행 수 3+ / exit code 0"(명령 리터럴 없음). 근거파일 "executions.db:5", "market_indicator_tracker.log:42 ERROR: ..."은 말줄임표 그대로의 가짜 인용. 과제 본문에 "(추정)"이라 스스로 적음 |
A |
분류 기준: A=실행 안 한 값을 지어냄, B=워커 압축이 실존 근거를 지움, C=양식 설명문 복사. 집계: A 3건(40260·40275·40282), B 2건(40260·40275), C 1건(40276). 종료코드 누락 4/4건(100%).
같은 날 guardian의 형제 과제도 동일 계열이다: 40257(경로 두 개를 공백으로 붙여 써 failed), 40264·40265·40280·40287(failed). 과제 재발행이 원인 해소 없이 반복되고 있다.
3. 모델 경로 실측
- 4건 직전 워커 로그:
[model-fallback] agent=guardian model=openai-codex/gpt-5.4 failed (timeout=59s, remaining_budget=120s)(177020~177057, 177430~177464, 177500~177528 구간). - 40276 직전에는 gpt-5.4가 쿨다운 중이라
[model-cooldown] … 스킵 (1133s 남음)→ 남은 후보는 ollama 소형모델뿐. - 모델 체인:
scripts/shared/llm.py151~156행DEFAULT_MODEL_CHAIN= gpt-5.4 → nemotron(OpenRouter) → minimax(OpenRouter) → ollama/qwen3.5:4b. OpenRouter 2종은 로그상 "API key 미설정"으로 매번 스킵. - 패치 파일:
scripts/agent_queue_worker.py2128행GATEWAY_MIN_MODEL_TIMEOUT_SEC = max(360, …), 2140행_model_timeout_for, 3015행 "게이트웨이 타임아웃 뒤 소형 로컬 답변을 완료로 저장하지 않는다". 파일 수정시각Sep 28 12:28:31 2026. - 실행 중 워커:
ps -eo pid,lstart,command | grep agent_queue_worker.py→ PID 46448~46462 8개 전부2026년 9월 27일 07시 55분 09초시작. 로그의timeout=59s가 360초가 아닌 것 자체가 옛 코드가 돌고 있다는 증거다.
4. 원인 — guardian 프롬프트와 제출 기준에서 빠진 것
guardian 역할 프롬프트: scripts/agent_registry.py 311~336행.
| 빠진 것 | 현재 문구(원문) | 결과 |
|---|---|---|
| 종료코드를 리터럴로 쓰라는 요구 | 333행 "검증: <실행한 검증 명령> → <실제 결과값/exit code>\n" — "결과값 또는 exit code"로 읽혀 선택사항이 됨 |
4/4건 missing_verification_exit_code. 제출 기준 문서는 이미 config/agent_completion_submission_standard.md 187행 "결과값에는 게이트가 읽는 리터럴 exit code 0을 그대로 쓴다"고 요구하지만 프롬프트에 없음 |
| 운영 장부의 정확한 절대경로 | 317~320행 "사용 가능 도구"에 health_check.py·ontology_core.py·curl 3개뿐. 크론 장부 경로 없음 | guardian이 /Users/ron/.hermes/scripts/pipeline/…처럼 경로를 추측. AGENTS.md 최상단은 canonical을 /Users/ron/.hermes/cron/jobs.json·executions.db로 못박았지만 guardian 프롬프트엔 전달 안 됨 |
source_log_path:/verification_command: 리터럴 |
없음 (ron·codex·cowork 프롬프트에도 없음) | 워커 _has_verifiable_four_block_completion_note()(agent_queue_worker.py 4207·4209행)가 두 리터럴을 요구 → 불충족 시 4324행 _public_result_text(… max_len=600) 압축 경로로 떨어져 절대경로·검증 줄 소실 |
[자동 보강] 설명문을 복사하지 말라는 금지 |
335행 "자기지시적 placeholder" 금지만 있음 — 어떤 문구가 placeholder인지 예시 없음 | 40276 복사 발생 |
| 부재 확인(0건) 시 grep 종료코드 규칙 | 없음 | 40260에서 "exit code 1 (찾음 없음)" 작성. 기준 문서 183~186행에 ! grep -rlq …; echo "exit code $?" 요령이 있으나 guardian에 미전달 |
| 실행할 수 없는 명령의 대체 수단 | 없음 | 과제는 stat -f, ps … \| grep, launchctl list \| grep, ls -lt … \| head를 요구했지만 워커 화이트리스트(agent_queue_worker.py 2213~2322행)에는 stat·ps·launchctl list(일반)가 없고, 2358행 dangerous_tokens가 \|를 전면 차단. guardian이 실행 못 한 명령을 "실행했다"고 서술 |
제출 기준 쪽 공백:
- config/agent_completion_submission_standard.md §4 반려표(244~248행)에 missing_verification_exit_code의 guardian 사례와 "작은 대체모델 답변" 계열이 없다. 248행은 self_evolve.py phantom path만 다룬다.
- 자동 보강 안내문 자체(scripts/orchestrator.py 1115·1491행, scripts/shared/agent_task_quality.py 1016행)가 "결론: …적는다." 형태로 답안 줄과 똑같은 라벨로 시작한다. 소형모델은 이를 채워야 할 빈칸이 아니라 완성된 답으로 인식한다.
5. 방지안 (우선순위)
1순위 — 워커 재시작으로 이미 만든 패치를 실제로 태운다 (운영 조치, 코드 수정 불필요)
- 무엇:
bash scripts/restart_agent_queue_workers.sh(화이트리스트 2306~2307행 등재) 또는 LaunchAgent kickstart로 8개 워커 재시작. 워커 재시작은 SOUL.md "확인 필요" 목록(삭제·배포·크론 비활성화 등)에 들지 않지만 운영 중 프로세스 교체이므로 ron이 수행하고 ops에 기록한다. - 왜: A분류(지어낸 값) 3건의 직접 조건인 "게이트웨이 59초 타임아웃 → 소형모델 답변"을 360초 최소 타임아웃 + 3015행 저장 차단이 막는다.
- 기대효과: 재시작 후 guardian 로그의
timeout=59s0건, 소형모델 답변의 done 시도 0건. guardian needs_evidence 중 A분류가 오늘 3/4 → 0 목표.
2순위 — guardian 프롬프트에 6줄 추가 (codex, §6 표 1행)
추가할 내용(문장 그대로 쓰는 것을 권장):
1. "검증: 줄 끝에는 반드시 exit code 0 리터럴을 적는다. exit=0, 통과, pytest 단어만으로는 반려된다."
2. "크론 장부는 /Users/ron/.hermes/cron/jobs.json, /Users/ron/.hermes/cron/executions.db만 쓴다. 파이프라인 스크립트는 /Users/ron/.hermes/workspace/scripts/pipeline/. /Users/ron/.hermes/scripts/pipeline/는 없다."
3. "모든 경로는 /Users/ron/로 시작하는 절대경로로, 한 줄에 하나씩 쓴다."
4. "완료 보고에 source_log_path: <절대경로>, verification_command: <실행한 명령> 두 줄을 넣는다."
5. "과제 본문의 [자동 보강] 블록은 작성 안내다. 그 문장을 결과에 복사하면 반려된다."
6. "실행하지 못한 명령(stat, ps, 파이프 |)은 실행했다고 쓰지 않는다. 대신 sqlite3, grep -n, ls, find로 확인하거나 '미실행'이라고 적고 done 대신 fail로 보고한다."
- 기대효과: B분류(압축 소실) 2건은 4번 한 줄로 원문 통과 조건을 충족 → 0 목표. 종료코드 누락 4/4 → 0 목표.
3순위 — 안내문 복사를 게이트가 잡게 한다 (codex, §6 표 2·3행)
- 결과 노트에 자동 보강 안내문(예: "수행한 일과 현재 상태를 완료형 한 문장으로 적는다", "각 경로 뒤에 실제로 연 줄 번호를 숫자로 붙이고")이 있으면 새 사유
completion_template_text_copied로 반려. - 안내문 자체를 답안 라벨과 겹치지 않게 바꾼다: "결론: …적는다." → "(작성 안내) 결론 줄에는 …". 라벨로 시작하지 않으면 복사해도 4블록 라벨 검사에서 먼저 걸린다.
- 기대효과: C분류 재발 시 원인이 "exit code 누락"이 아니라 "안내문 복사"로 정확히 표시돼 재발행 과제가 헛돌지 않음.
4순위 — 과제 발행 쪽: guardian이 실행 못 하는 명령을 과제에 넣지 않는다 (codex, §6 표 4행)
- 오케스트레이터가 guardian 과제 본문에
stat,ps,launchctl list |,| head,| grep를 넣으면 발행 전 경고하거나 허용 명령(sqlite3,grep -n,ls -la,find)으로 바꾼다. 또는 guardian 전용으로stat -f,ps -eo읽기전용 접두어를 화이트리스트에 추가(보안 검토 후). - 기대효과: "실행했다고 서술했지만 실행 흔적 없음" 유형 제거.
5순위 — 문서 반영 (cowork 후속, 코드 아님)
config/agent_completion_submission_standard.md§4 표에missing_verification_exit_code(guardian 40260·40275·40276·40282),completion_template_text_copied(신설 예정) 두 행 추가.
6. codex용 코드 수정 대상 표
| # | 파일 | 줄 | 현재 | 바꿀 내용 | 검증 방법 |
|---|---|---|---|---|---|
| 1 | /Users/ron/.hermes/workspace/scripts/agent_registry.py |
317~320, 329~335 | guardian 프롬프트: 도구 3개, 검증: <…/exit code> |
§5 2순위 6줄 추가. ron·analyst 프롬프트도 같은 <실제 결과값/exit code> 표현이므로 동일 적용 검토 |
grep -n "cron/executions.db" scripts/agent_registry.py 1건 이상, python3 -m py_compile scripts/agent_registry.py exit code 0 |
| 2 | /Users/ron/.hermes/workspace/scripts/shared/public_result_formatter.py |
3087 부근 completion_required_evidence_gaps() / 3063 completion_has_self_referential_placeholder_evidence() 옆 |
자동 보강 안내문 탐지 없음 | 안내문 고정 문구 목록 매칭 시 completion_template_text_copied 반환. scripts/server.py 740행 _completion_storage_required_result_gaps()와 worker 3812~3836행 라벨표에도 등록 |
40276 원문(guardian.log 177464행)을 입력한 회귀 테스트가 새 사유를 반환 |
| 3 | /Users/ron/.hermes/workspace/scripts/orchestrator.py 1115·1491행, /Users/ron/.hermes/workspace/scripts/shared/agent_task_quality.py 1016·1020·1025·1031행 |
안내문이 "결론: …적는다."처럼 답안 라벨로 시작 | "(작성 안내) 결론 줄: …" 형태로 라벨과 분리. scripts/tests/test_orchestrator_task_quality.py 456·487·763·1490행 기대값 동시 수정 |
python3 -m pytest scripts/tests/test_orchestrator_task_quality.py -q 전부 통과 |
|
| 4 | /Users/ron/.hermes/workspace/scripts/agent_queue_worker.py |
4187~4212 _has_verifiable_four_block_completion_note(), 4316~4326 압축 분기 |
source_log_path:/verification_command: 없으면 600자 압축 → 절대경로·검증 줄 소실 |
4블록 라벨 + 실존 절대경로 1개 이상 + exit code N이 있으면 두 리터럴 없어도 원문 보존. 최소한 압축 시 "검증:" 줄과 /Users/ron/ 경로는 보존 |
guardian.log 177458행(40275 원문)을 _sanitize_queue_update_payload("/complete", …)에 넣었을 때 /Users/ron/.hermes/cron/jobs.json과 "검증:" 줄이 남음 |
| 5 | /Users/ron/.hermes/workspace/scripts/orchestrator.py (guardian 과제 생성부) |
과제 본문 생성 경로 | guardian 과제에 stat/ps/파이프 명령 포함 |
발행 전 guardian 실행 불가 명령 탐지 → 허용 명령으로 치환 또는 경고 | 오늘 과제 40260·40276 본문을 넣은 테스트에서 경고 발생 |
| 6 | (코드 아님) 워커 재시작 | — | 8개 워커가 09-27 07:55 코드 | bash scripts/restart_agent_queue_workers.sh |
ps -eo pid,lstart,command \| grep agent_queue_worker.py 시작시각이 12:28 이후, guardian 로그 timeout= 값 ≥ 360 |
7. 리스크
- 단일 장애점: guardian·ron·analyst 모두 1순위 모델 하나(gpt-5.4)에 의존하고, 2·3순위(OpenRouter)는 키가 없어 사실상 빈 자리다. 1순위가 막히면 곧바로 4B 소형모델로 떨어진다. 1순위 패치가 적용돼도 "소형모델 답변을 저장 안 함 → 재시도 지연"으로 바뀔 뿐 처리량은 늘지 않는다. OpenRouter 키 설정 여부는 인증키 변경이라 해리 확인 사항이다.
- 압축 경로 완화(표 4행)는 공개용 정제를 약화할 수 있다. 해리에게 가는 메시지로 절대경로가 새지 않도록 bus 전달 경로는 기존 정제를 유지해야 한다.
- 재발행 과제가 원인 해소 없이 반복되면(오늘 guardian 형제 과제 최소 9건) 큐와 모델 예산만 소모한다. 표 1·6행 전에는 같은 주제 guardian 재발행을 멈추는 편이 낫다.
8. 검증방법
| 지표 | 명령 | 기대값 | 롤백 조건 |
|---|---|---|---|
| 워커 재시작 반영 | ps -eo pid,lstart,command \| grep agent_queue_worker.py |
8개 시작시각 > 2026-09-28 12:28 | 재시작 후 워커 수 < 8 |
| 게이트웨이 타임아웃 값 | grep -n "agent=guardian model=openai-codex" /Users/ron/.hermes/logs/agent-queue-guardian.log 최근 줄 |
timeout= ≥ 360 |
— |
| guardian 반려율 | sqlite3 -readonly /Users/ron/.hermes/data/ops_multiagent.db "SELECT status,count(*) FROM bus_commands WHERE claimed_by='guardian' AND created_at>=datetime('now','-7 days','localtime') GROUP BY status" |
needs_evidence+failed 비율이 오늘 기준보다 50% 이상 감소 | 7일 뒤 감소 없음 → 표 4행 재검토 |
| 종료코드 누락 재발 | grep -c '"agent": "guardian".*missing_verification_exit_code' /Users/ron/.hermes/workspace/logs/completion_evidence_blocked.jsonl 7일 증분 |
0 | — |
| 안내문 복사 | grep -c "수행한 일과 현재 상태를 완료형" /Users/ron/.hermes/workspace/logs/completion_evidence_blocked.jsonl 증분 |
표 2행 반영 후 새 사유로만 기록 | 정상 보고 오탐 1건 이상 → 문구 목록 축소 |