Critic 리뷰 — stale 크론 복구 작업
승인 여부: REVISE
핵심 원인 진단(isolated 크론은 agentTurn만 실행 가능)은 코드와 맞다. 그러나 완료 보고의 검증은 수동 스크립트 실행 성공을 정규 Hermes cron 경로 성공으로 과대 해석했고, 같은 패턴/스키마 드리프트/부작용을 충분히 닫지 못했다. 원 담당(Codex C)은 아래 결함을 고친 뒤 재검증해야 한다.
검토 대상:
- /Users/ron/knowledge-agent/400-reports/260414_stale_cron_audit.md
- /Users/ron/knowledge-agent/400-reports/260414_pipeline_daily-context-snapshot.md
- /Users/ron/.openclaw/cron/jobs.json
- /Users/ron/.openclaw/workspace/src/cron/**
- /Users/ron/.openclaw/workspace/scripts/pipeline/**
- /Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/**
A~E 전수 점검
| 항목 | 판정 | 근거 |
|---|---|---|
| A1. 원인과 코드 일치 | ✅ | 보고서의 원인 주장: 260414_stale_cron_audit.md:12. 실제 코드도 src/cron/service/jobs.ts:77-83에서 isolated는 agentTurn만 허용하고, 런타임은 src/cron/service/timer.ts:773-775에서 아니면 skipped를 반환한다. 도구 설명도 src/agents/tools/cron-tool.ts:258-260와 일치한다. |
| A2. 같은 패턴 누락 여부 | ❌ | grep -rn 및 JSON 파싱 결과, 여전히 enabled + isolated + non-agentTurn가 남아 있다: cron-alert는 jobs.json:6529, payload script는 jobs.json:6542; memory-weekly-report는 jobs.json:9956, payload command는 jobs.json:9971. 비활성까지 포함하면 ops-auto-healer도 jobs.json:6079, payload shell은 jobs.json:6088. 중복 id도 blueprint-updater가 jobs.json:9389와 jobs.json:9422에 남아 있다. |
| A3. 과거 유사 버그 검색 | ✅ | git log --grep에서 e3a059a0f7 feat: add cron_validator for payload schema enforcement, 4e93941b63 fix: check agentTurn and shell kinds..., 53973f06b4 fix: isolated 세션 크론잡의 agentTurn→shell 잘못된 변환 방지 확인. 메모리/로그에도 2026-04-12 vault_architect silent failure detected: enabled cron not executing for 15 days, Root cause found: vault_architect cron jobs explicitly disabled... 기록이 있다. 다만 보고서에는 이 과거 패턴 반영이 약하다. |
| B4. 근본 해결 vs 증상 | ⚠️ | 대상 11개는 agentTurn으로 맞췄지만 전체 cron 품질 문제는 남았다. 현재 파싱 기준 enabled + isolated + non-agentTurn 2개가 남아 있고, agentTurn인데 message가 없는 활성 isolated 잡이 87개다. src/cron/types.ts:57-62는 message 필수, timer.ts:780-783은 job.payload.message만 넘긴다. |
| B5. 엣지 케이스 | ❌ | 빈/None id, 대량 stale, 동시 쓰기, 네트워크 단절, 의미적 실패(rc=0 but data fail) 처리가 불충분하다. 상세 엣지 케이스는 아래 별도 섹션. |
| B6. 실패 모드/새 실패 경로 | ❌ | china-macro-collector는 로그상 [PMI] FAIL - no data, [Housing] FAIL - no data인데 보고서와 상태는 ok로 처리했다(china-macro-collector.log:27-36). pipeline-self-healer 수동 실행은 수정 완료: 14건으로 범위 밖 상태를 변경했다(pipeline-self-healer.log:4-22). 정규 cron 경로가 아니라 “agent에게 쉘 명령 실행을 요청하는 프롬프트”라 명령 실행 보장이 약하다. |
| B7. 롤백 | ⚠️ | 백업은 있다: /Users/ron/.openclaw/cron/jobs.json.bak.260414-stale-cron-1776129106, self-healer 백업도 /Users/ron/.openclaw/cron/jobs.json.healer-bak.1776128971. 그러나 롤백 절차/검증은 보고서에 없다. |
| C8. 다른 호출자 영향 | ❌ | 웹 관리자 수동 실행 경로는 payload.command를 요구한다: scripts/pipeline/webapp/blueprints/admin.py:461-483, :692-696, :726-730. Codex C가 변환한 대상 예: daily-kpi-eval은 jobs.json:664-667에 message만 있고 command가 없다. 즉 Hermes cron 기준은 맞췄지만 웹 관리자 수동 실행은 깨질 수 있다. |
| C9. 성능 영향 | ⚠️ | 직접 shell 실행이 아니라 isolated agentTurn이므로 새 에이전트 세션/LLM 호출 비용이 추가된다. 기존 보고서 중 /Users/ron/knowledge-agent/400-reports/260405_claude_token_efficiency.md:31도 isolated agentTurn 133회를 비용 이슈로 기록한다. 이번 11개 전환의 latency/토큰/동시성 영향 측정은 없다. |
| C10. 보안 | ⚠️ | 새 payload는 “명령을 실행하라”는 자연어 프롬프트다. 외부 입력을 직접 넣은 정황은 없지만, shell command를 deterministic runner가 아니라 agent에게 맡기는 공격면/오동작면이 생긴다. 또한 web admin 경로는 여전히 command를 실행하는 별도 schema를 기대해 schema 혼재가 남았다. |
| D11. 수정 코드 실제 실행 | ⚠️ | 대상 스크립트들은 수동 실행됐다(summary.json, 각 .log). 하지만 변경된 cron spec이 Hermes ticker에서 실제 due-run으로 실행됐다는 증거는 없다. daily-context-snapshot은 lastManualRunLog가 실행 로그가 아니라 결과 JSON(jobs.json:5908-5915)이고, lastDurationMs가 0이다. Gateway pid 43519 생존은 memory_guardian.log:11153-11186에서 보이나, 내가 확인한 범위에서는 Cron ticker started 문자열은 찾지 못했다. |
| D12. Before/After 메트릭 | ❌ | 보고서는 “대상 11개 ok”만 제시한다. daily-context-snapshot.py:85가 stale 목록을 stale[:40]로 자르고, markdown도 len(cron['enabled_stale_over_24h'])만 출력한다(daily_context_snapshot.py:157). 현재 직접 계산한 활성 stale 실제값은 146개인데 스냅샷은 40개로 보인다. 전체 stale 개선 메트릭이 없다. |
| D13. 회귀 테스트 | ❌ | script/shell + isolated로 의도적으로 되돌렸을 때 skip이 재현되는 테스트, 또는 변환 후 due-run 성공을 잡는 테스트가 없다. 기존 TS 테스트는 있으나 이번 jobs.json 데이터 회귀를 검증하지 않았다. |
| E14. 재현성 | ⚠️ | 보고서에 증거 파일 경로는 있다(260414_stale_cron_audit.md:65-70). 하지만 적용 diff, 실행 명령, 검증 명령, “정규 cron 재실행을 어떻게 확인했는지”가 부족해 다음 사람이 동일 결론을 재현하기 어렵다. |
| E15. 접근 근거/대안 | ⚠️ | isolated + agentTurn 선택 근거는 코드로 뒷받침된다. 그러나 대안(직접 shell runner 유지, main + systemEvent, web admin schema와 통합, validator 우선 수리)은 검토되지 않았다. |
| E16. 플레이북 저장 | ❌ | find /Users/ron/knowledge-agent/500-signals -type f \( -iname '*cron*' -o -iname '*stale*' -o -iname '*playbook*' \) 결과 없음. 체크리스트 요구 미충족. |
결함 목록 — Codex C에게 넘길 수정 요구사항
- 남은 활성 스펙 불일치 크론 2개 처리 필요
- 파일/라인:
/Users/ron/.openclaw/cron/jobs.json:6529,:6542(cron-alert,payload.kind=script,sessionTarget=isolated) - 파일/라인:
/Users/ron/.openclaw/cron/jobs.json:9956,:9971(memory-weekly-report,payload.kind=command,sessionTarget=isolated) - 재현:
bash python3 - <<'PY' import json jobs=json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs'] print([(j.get('id'), (j.get('payload') or {}).get('kind')) for j in jobs if j.get('enabled', True) and j.get('sessionTarget')=='isolated' and (j.get('payload') or {}).get('kind')!='agentTurn']) PY -
기대: 빈 리스트.
-
agentTurn 스키마 drift:
message필수 vs 다수 잡의command사용 - 코어 타입/런타임:
src/cron/types.ts:57-62,src/cron/service/timer.ts:780-783. - 현재 데이터 예:
/Users/ron/.openclaw/cron/jobs.json:4(vault-note-atomizer) 등 활성 isolatedagentTurn인데message없는 잡 87개. - 관련 호출자 충돌: 웹 관리자 수동 실행은
command필수(scripts/pipeline/webapp/blueprints/admin.py:461-483,:692-696,:726-730). 반대로 이번 변환 대상daily-kpi-eval은message만 있음(jobs.json:664-667). - 재현:
bash python3 - <<'PY' import json jobs=json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs'] print(sum(1 for j in jobs if j.get('enabled', True) and j.get('sessionTarget')=='isolated' and (j.get('payload') or {}).get('kind')=='agentTurn' and not (j.get('payload') or {}).get('message'))) PY -
요구: Hermes cron schema와 web admin schema를 하나로 정리하거나, 양쪽 호환 변환/validator를 명시적으로 적용.
-
china-macro-collector성공 판정이 거짓 양성 - 파일/라인:
/Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/china-macro-collector.log:27-36. - 증거:
[PMI] FAIL - no data,[Housing] FAIL - no data,china_pmi: {},china_housing: {}. - 재현: 해당 로그 확인 또는
summary.json의stdout_tail확인. -
요구: rc=0만 성공으로 보지 말고 핵심 산출물 필드/데이터 개수를 성공 조건에 포함.
-
pipeline-self-healer수동 실행이 범위 밖 상태를 변경함 - 파일/라인: 로그
/Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/pipeline-self-healer.log:4-22. - 증거:
수정 완료: 14건, delivery chatId 보정, 여러 failure count 리셋. - 코드상 동작:
scripts/pipeline/pipeline_self_healer.py:44-69이 상태 리셋/배송 필드 보정을 수행,:104-111이 jobs.json을 저장. -
요구: stale 복구 검증 대상과 별도 부작용으로 분리 기록하고, before/after 오염을 제거한 재검증 필요.
-
daily-context-snapshot메트릭이 전체 stale 수를 숨김 - 파일/라인:
scripts/pipeline/daily_context_snapshot.py:85에서stale[:40],:157에서 그 길이를 “24h 이상 미실행 활성 크론”으로 출력. - 현재 직접 계산: 활성 stale 실제값 146개, 스냅샷 보고값 40개.
- 재현: 위 Python stale 계산 또는
latest.json의cron.enabled_stale_over_24h길이 확인. -
요구:
stale_total과stale_sample을 분리. -
정규 Hermes cron 경로 검증 부족
- 보고서 주장:
260414_stale_cron_audit.md:56“Cron ticker started 로그 확인”. - 내가 확인한 증거:
memory_guardian.log:11153-11186에서 pid 43519 생존은 확인. 그러나 targeted grep에서Cron ticker started문자열은 확인하지 못함. -
요구: 다음 due time을 강제로 앞당긴 1개 테스트 또는 cron service API/로그로 실제
agentTurndue-run이 완료되는 증거를 남길 것. -
중복 id가 남아 있음
- 파일/라인:
blueprint-updater가/Users/ron/.openclaw/cron/jobs.json:9389와:9422에 동시에 존재. - 재현:
bash python3 - <<'PY' import json, collections ids=[j.get('id') for j in json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs']] print({k:v for k,v in collections.Counter(ids).items() if v>1}) PY -
요구:
signal-synthesizer만 고치고 중복 id 검사를 끝내지 말 것.Noneid 3개도 별도 정리 대상. -
회귀 테스트와 플레이북 누락
- 체크리스트 D13/E16 실패.
- 재현:
knowledge-agent/500-signals에서*cron*,*stale*,*playbook*검색 결과 없음. - 요구: jobs.json validator 또는 최소 재현 스크립트를 500-signals/playbook으로 저장하고,
isolated + script/shell/command가 실패하는 테스트를 남길 것.
엣지 케이스 최소 5개
- 빈/잘못된 jobs 구조:
daily_context_snapshot.py:48-52는 빈 리스트로 떨어지지만, 실제 복구 스크립트가 jobs 구조 오류를 어떻게 롤백하는지 보고서에 없다. id=None잡: 현재jobs.json에 id가 없는 잡 3개가 중복 카운트된다. dedup/상태 추적/수동 실행에서 식별자가 사라진다.- 대량 stale:
daily_context_snapshot.py:85가 40개까지만 저장하고:157은 그 길이를 전체값처럼 출력한다. 현재 실제 stale 146개를 40개로 축소 표시한다. - 동시성/쓰기 경합: Codex C 패치, self-healer, Hermes cron이 모두 같은
jobs.json을 만질 수 있다. self-healer는 atomic replace를 쓰지만, 전체 복구 작업은 락/mtime 검증이 보고되지 않았다. 현재jobs.jsonmtime도 보고서 이후로 갱신되어 상태가 흔들릴 수 있다. - 네트워크 단절: Telegram/DNS 실패가 여러 로그에 있는데 rc=0이라 성공 처리된다.
china-macro-collector는 데이터 수집 자체도 모두 실패했다. - 의미적 성공 조건:
generated=0, 빈 dict, 업데이트 0개 같은 “정상일 수도 실패일 수도 있는” 상태가 rc=0만으로 성공 처리된다. - web admin 수동 실행 호환성: Hermes cron은
message, web admin은command를 요구한다. 이번 변환 대상은 web admin 수동 실행 버튼에서 “실행할 수 없는 타입”으로 떨어질 가능성이 높다. - agentTurn 프롬프트 실행: shell 명령이 deterministic runner가 아니라 agent 지시문 안에 들어가므로, agent가 설명만 하고 실행하지 않는 경우가 생길 수 있다.
같은 패턴 grep 결과 요약
수행한 필수 grep/파싱:
grep -rn "isolated job requires payload.kind=agentTurn\|isolated cron jobs require\|sessionTarget=\"isolated\" REQUIRES\|payload.kind=agentTurn" \
/Users/ron/.openclaw/workspace/src /Users/ron/.openclaw/workspace/scripts
주요 결과:
- src/agents/tools/cron-tool.ts:260
- src/cron/service/jobs.ts:82
- src/cron/service/timer.ts:774
- src/cron/service.runs-one-shot-main-job-disables-it.test.ts:742
추가 파싱 결과:
- 변경 전 활성 isolated non-agentTurn: 3개 → 변경 후 2개.
- 변경 전 활성 isolated agentTurn missing message: 95개 → 변경 후 87개.
- 변경 전 중복 id: {None: 3, signal-synthesizer: 2, blueprint-updater: 2} → 변경 후 {None: 3, blueprint-updater: 2}.
REVISE 기준 완료 조건
Codex C가 다시 제출하려면 최소 아래를 만족해야 한다.
- 활성
isolated잡 중payload.kind != agentTurn가 0개임을 파싱 결과로 제시. agentTurnpayload의message/commandschema 충돌을 정책으로 결정하고, Hermes cron과 web admin 양쪽에서 수동/정규 실행이 되는지 검증.china-macro-collector같은 데이터 실패를 rc=0 성공으로 포장하지 않도록 성공 조건을 강화.daily-context-snapshot의 stale 총수/샘플 분리 또는 보고서에서 cap을 명시.- 다음 due-run 또는 테스트 잡으로 실제 Hermes cron path 실행 로그를 제시.
- rollback 절차와 500-signals 플레이북을 저장.