start-harness.sh라는 시작 명령 한 줄로 켜지는 '하네스(harness, 증거를 검사해 통과를 막는 감시 장치)' 시스템을 처음부터 끝까지 뜯어봅니다. 출발점은 이 문제였습니다. AI가 스스로 코드를 고치고 "다 됐다"고 말해도, 증거가 없으면 정말 됐는지 알 수 없습니다. 하네스란, 그 증거를 검사해 통과 못 하면 막아 주는 자동 감시·실행 장치입니다. 이 시스템은 스스로 실수를 찾아 규칙과 코드를 고쳐 나가며, Codex(오픈AI의 코딩 AI) 위에서 돌아갑니다. 여기에, 원래 Claude Code(앤트로픽의 코딩 AI 도구)에서 쓰던 핵심 명령 네 가지 /team · /init-project · /qa-cycle · /qa-scenario-gen이 Codex 환경으로 옮겨지면서 무엇이 어떻게 달라졌는지 나란히 비교합니다. 본문에 계속 나오는 부품은 다섯입니다. 이름이 겹치는 것이 있어 먼저 풀어 두면, loop-era-supervisor란 반복 회차를 지휘하고 감시하는 프로그램에 붙은 이름이고, self-improve-worker란 그 아래에서 실제 수정을 맡는 일꾼에 붙은 이름입니다. 다섯은 start-harness.sh(시스템을 켜고 끄는 시작 명령), loop-era-supervisor.sh(뒤에서 늘 돌며 살아 있는지 살피는 감시 프로그램, 이른바 데몬), ralph-loop.sh(프로젝트를 등록하고 기본 파일을 심는 명령), loop-era-supervisor.py(15단계를 순서대로 지휘하는 프로그램), self-improve-worker.py(격리한 작업 사본에서 AI에게 수정을 맡기는 일꾼)입니다.
loop-era-supervisor.py라는 지휘 프로그램 안의 PHASE_ORDER라는 목록이 15단계의 실행 순서를 정해 둡니다. 이 시스템이 잘 돌아가는지 재는 잣대는 l6_blocker_count인데, 이건 "아직 남은 치명적 문제(blocker)의 개수"이고 0이 목표입니다. 각 단계의 결과는 phase_history라는 기록에 차곡차곡 쌓이고, 마지막에는 results.tsv에 "살림(keep)"인지 "버림(discard)"인지로 남습니다.
이 시스템의 두 번째 버전(v2)에서 qa_scenario_gen이라는 단계가 두 번째 자리에 새로 끼면서, 원래 14단계였던 흐름이 → 15단계로 늘었습니다. 아래 카드의 영문 제목은 지휘 프로그램이 쓰는 단계 이름 그대로이고, 그 이름이 무슨 일을 하는지는 카드 본문에 한 줄씩 풀어 두었습니다.
app_candidates / service_candidates / database_hints 즉 화면·서비스·데이터베이스의 흔적을 찾아내고, 그에 맞춰 어떤 검사를 꼭 해야 할지를 자동으로 정합니다.init-project-runtime.py → .codex/loopy-era/init-project.json + project-profile.jsonqa_scenario_gen NEWqa-scenario-gen.py → .codex/loopy-era/qa-scenarios.jsonself-improve-trigger.sh → ~/.codex/pending/self-improve-{key}.jsonself-improve-worker.py run (timeout: 2400s).codex/autoresearch/verify-l6.sh → {metric, value, status}.codex/autoresearch/adversarial-review-l6.py → {blocker_count, findings[]}blocker_total=0이면(남은 문제가 없으면) status: "skip" 을 돌려주고 이 단계를 건너뜁니다. 문제가 있으면 가장 급한 경로부터, 그리고 검증·반박 리뷰·확인용 일꾼을 만들어 붙입니다.team-runtime.py --execute --json → .codex/ralph-loop/team-execution-latest.jsonqa-scenarios.json에 적힌 검사별 명령을 실행하고, 통과 증거(evidence contract)가 규격대로 나왔는지 검증합니다. 실패는 종류별 이름표(failure_labels)로 분류합니다.team-runtime.py --execute → qa-cycle-runtime.py (loop)inject_adversarial_findingsmerge_adversarial_findings()self-improve-worker.py runverify-l6.shadversarial-review-l6.pyresults.tsv + loop-era-metrics.jsonl + action-log.sh
qa-scenario-gen.py는 위 흐름의 2단계에서 따로 실행되는 파이썬 프로그램입니다. 하는 일은 "무엇을 검사할지"를 자동으로 정하는 것입니다.
project-profile.json이라는 프로필 파일에서 app_candidates, service_candidates, database_hints, 즉 화면·서비스·데이터베이스의 흔적을 읽어, 7가지 검사(시나리오) 각각이 required 즉 "꼭 필요한지"를 자동으로 정하고, qa-scenarios.json이라는 검사 목록 파일을 만들어 냅니다. 7가지 검사에는 각각 영문 이름이 있습니다. 화면 쪽 다섯은 ui-button-event(버튼을 눌러 반응이 오는지), modal-popup(모달 창이 열리고 닫히는지), confirm-dialog(확인 창에서 수락과 취소가 되는지), alert-dialog(알림 창의 글이 보이는지), browser-console-clean(브라우저 콘솔에 오류가 없는지)이고, 서버 쪽은 api-flow(요청·응답·오류 처리가 되는지), 데이터 쪽은 database-state(쓰고 읽은 뒤 상태가 맞는지)입니다.
그다음 qa-cycle-runtime.py안의 scenario_checks()라는 함수가 이 목록 파일을 읽어 각 검사 명령을 실제로 돌리고, 통과 증거(evidence contract)를 JSON(프로그램끼리 데이터를 주고받는 글자 형식)으로 출력하는데, 그것이 규격대로 나왔는지 확인합니다.
project-profile.jsonapp_candidates → 화면(UI) 검사service_candidates → 서버 통신(API) 검사database_hints → 데이터베이스(DB) 검사*_scenario_command × 7*_repair_command × 7
.codex/loopy-era/qa-scenarios.json 파일로 출력
id, requiredcommand, repair_commandrequired_coverage[]evidence_contract 규격
scenario_checks() 반복 검사app_candidatesbutton, event_click, state_changeapp_candidatesmodal_popup, open_closeapp_candidatesconfirm_dialog, accept_cancelapp_candidatesalert_dialog, message_visibilityapp_candidatesconsole_errors, console_warningsconsole_errors: 0 오류 0 필수service_candidatesrequest, response, error_handlingdatabase_hintswrite, read, state_assertionstatus: "pass"/"ok"/"success", executed: true, coverage: [...] (확인해야 할 항목 목록과 일치),
browser-console-clean 즉 브라우저 콘솔에 오류가 없는지 보는 검사는 추가로 console_errors: 0 이 반드시 있어야 합니다.
이 중 하나라도 빠지거나 안 맞으면 그 검사는 실패(FAIL) 처리됩니다.
// qa-scenarios.json 파일이 어떻게 생겼는지 예시 { "scenarios": [ { "id": "ui-button-event", "required": true, "command": "python3 .codex/scripts/test-button-event.py", "repair_command": "python3 .codex/scripts/repair-button-event.py", "required_coverage": ["button", "event_click", "state_change"], "evidence_contract": { "status": "pass | ok | success", "executed": true, "coverage": ["...확인해야 할 항목들..."] } }, { "id": "browser-console-clean", "required": true, "evidence_contract": { "console_errors": 0, // 필수 — 오류가 0이어야 통과(PASS) "coverage": ["console_errors", "console_warnings"] } } ] }
init-project-runtime.py가 만들어 내는 project-profile.json에는, 검사별로 "실행 명령"과 "고치는 명령"을 담는 항목이 14개 새로 들어갔습니다. 항목 이름은 대개 검사 종류 뒤에 scenario_command(실행 명령) 또는 scenario_repair_command(고치는 명령)를 붙인 꼴이고, 브라우저 콘솔 검사만 browser_console_command(실행 명령)와 browser_console_repair_command(고치는 명령)로 적습니다. 이 항목들이 qa-scenario-gen.py → qa-cycle-runtime.py 로 이어지는 QA 전체 과정을 조종합니다.
app_candidates→ 화면(UI) 검사 5종service_candidates→ api_flow required=truedatabase_hints→ database-state required=trueui_scenario_commandNEWmodal_scenario_commandNEWconfirm_scenario_commandNEWalert_scenario_commandNEWbrowser_console_commandNEWapi_scenario_commandNEWdatabase_scenario_commandNEWui_scenario_repair_commandNEWmodal_scenario_repair_commandNEWconfirm_scenario_repair_commandNEWalert_scenario_repair_commandNEWbrowser_console_repair_commandNEWapi_scenario_repair_commandNEWdatabase_scenario_repair_commandNEWapi_flow = true
self-improve-trigger.sh가 5군데에서 "고칠 게 있다"는 신호를 모읍니다. 다섯 군데란 fix_commit(git log --grep='^fix:' 명령으로 찾은, 작업 시작 뒤에 버그를 고친 커밋), bug_fixer_retry(로그 파일의 agent_spawn 기록에 남은 자동 수리 재시도 흔적), recurrence(check-recurrence.py 가 과거 개선 기록과 버그 수정 이력을 대조해 찾은 같은 문제의 반복), telemetry(self-improve-telemetry.py 가 사용 기록에서 찾은 한국어 불만 표현), adversarial_finding(adversarial-review-l6.py 가 낸 P1/P2 등급의 남은 문제)입니다. 이미 처리한 신호는 걸러내(중복 방지)
~/.codex/pending/self-improve-{key}.json라는 대기 파일에 저장합니다. 이렇게 모인 신호가 있을 때만, 일꾼(worker)이 AI(LLM)에게 수정을 요청합니다. 수정을 마친 일꾼은 신호를 처리했다는 표시(self-improve-ack.sh), 통합 점검(self-improve-smoke.sh), 기록 남기기(self-improve-log.sh) 순서로 마무리합니다.
self-improve-
telemetry.pyadversarial-
review-l6.pygit worktree add --detach HEAD (실패하면 통째로 복제해서 대신, 그리고 원본 백업)codex -o output.json < prompt — 답은 정해진 형식(JSON)으로만 받도록 강제 (결과·수정 범위·검증 항목)file_contains · repo_script · codex_smoke · git_diff_check
Claude Code에서 쓰던 /team · /init-project · /qa-cycle · /qa-scenario-gen 이 네 가지 핵심 명령이 Codex 환경에서 어떻게 다시 만들어졌는지 비교합니다. 공통된 방향은 하나입니다 — AI에게 말로 시키던 것을, 파이썬 프로그램이 직접 실행하도록바꾼 것입니다.
/qa-cycle 를 자동으로 이어서 실행team-runtime.py --execute --json 이라는 명령 하나로 실행ThreadPoolExecutor (파이썬 병렬 실행 도구)로 여러 일꾼을 실제 동시 실행blocker_total=0이면(문제가 없으면) 계획 단계에서 status: "skip" 을 돌려주고 건너뜀| 핵심 차이 | ||
|---|---|---|
| 실행 방식 | AI가 전문 에이전트를 불러 쓰고, 작업 추적 기능으로 진행을 따라감 | 파이썬 프로그램이 일꾼 프로세스를 직접 관리 (병렬 실행 도구 사용) |
| 동시 실행 보장 | Promise.all() 방식으로 묶어 실행하되, 실제 동시 실행 여부는 에이전트 판단에 맡김 |
결과(JSON)에 parallel_execution: true 라고 분명히 적혀 있는지 확인 |
| 수정 안전성 | 코드를 바로 고치고, 실패하면 자동 수리 도구로 재시도 | HARD 손대도 되는지 검문하는 게이트(gate, 조건을 못 채우면 다음 단계로 못 넘어가게 막는 관문) + 별도 작업 사본에서만 수정 |
| QA 연결 | 4단계에서 /qa-cycle 명령을 직접 불러 실행 |
9단계 qa_cycle에서 지휘 프로그램이 자동 실행 |
.claude/CLAUDE.md, .claude/skills/~/.claude/skills/{project}-scaffold/ 자동 생성settings.json 에 외부 도구 연결(MCP) 설정 포함auto-issue.md, qa-scenarios.md 자동 생성.codex/rules/project-conventions.rules.codex/skills/ (backend-patterns, frontend-patterns, qa-strategy)~/.codex/skills/{project}-scaffold/ 자동 생성project-profile.json에 검사용 명령 14개 항목 포함| 핵심 차이 | ||
|---|---|---|
| 설정 위치 | .claude/ → Claude Code 전용 폴더 |
.codex/ → Codex 전용 폴더, .claude/ 에 기대지 않음 |
| 실행 빈도 | 사람이 직접 /init-project 불렀을 때 한 번 |
AUTO 매 반복 회차 시작 때 자동 실행 |
| 검사 항목 설정 | qa-scenarios.md 라는 텍스트 문서로 생성 |
project-profile.json에 실행·수정 명령 14개 항목으로 담음 |
| 외부 도구 연결(MCP) 설정 | settings.json안에 외부 도구 서버 포함 |
config.toml이라는 별도 파일로 분리, 이 단계 담당 아님 |
agent-browser로 실제 화면을 눌러 보며 검증.qa-cycle-passed 라는 통과 표시 파일로 관리qa-cycle-runtime.py 라는 파이썬 프로그램, 결과는 JSON으로 출력--max-rounds N 값으로 반복 횟수 조절scenario_checks(): 검사 목록을 읽어 7가지 검사 실행.codex/loopy-era/qa-cycle-report.json 에 결과 저장| 핵심 차이 | ||
|---|---|---|
| 화면(UI) 테스트 | agent-browser + web-qa-tester 로 실제 브라우저에서 눌러 보며 확인 |
scenario_checks()가 검사 목록의 명령을 실행하고 증거를 검증 |
| 실패 분류 | 심각도(치명·높음·보통·낮음) 기준 | failure_labels (build/type/lint/test) + repair_strategy.source |
| 자동 수정 | 자동 수리 에이전트를 최대 4번 호출 | qa_fix_retry 단계에서 team-runtime.py를 다시 돌리고 고치는 명령 실행 |
| push gate | HARD 통과 표시 파일 + 1시간 유효 해시 검증으로, 통과 못 하면 올리기(push)를 막음 | 지표로만 추적하고, 올리기를 막는 검문소는 따로 없음 |
docs/qa-test-plan.md 생성 — 테스트 항목(TC)마다 번호를 매김qa-scenario-gen.py 라는 독립 파이썬 프로그램으로 구현project-profile.json 을 읽어 7가지 검사 중 무엇이 필요한지 자동 결정qa-scenarios.json 파일 출력 → qa-cycle-runtime.py 가 그 파일을 받아 사용
.codex/ralph-loop/state.json 라는 상태 파일 기준입니다. 이 파일의 metric_name(무엇을 성적으로 재는지 적는 항목)은 l6_blocker_count(남은 치명 문제의 개수)입니다. 11번째 반복에서 l6_blocker_count = 0 즉 "남은 치명 문제 0개"를 달성했습니다. "멈춰도 된다"는 완료 조건을 채워 상태는 status: completed(완료)가 됐고, 작업 배분(team_plan)은 남은 문제가 없어 건너뛰었습니다.
ralph-loop/state.json — iteration 11skip (blocker_total=0)self-improve-smoke.sh는 문법만 보는 게 아니라, 진짜로 임시 git 저장소를 하나 만들어 전체 흐름이 처음부터 끝까지 도는지 확인합니다. 이 통합 점검(smoke)을 통과해야만, 앞서 본 "남은 문제 개수" 측정(verify-l6.sh, 남은 치명 문제 개수를 재는 검사 프로그램)에서 문제로 잡히지 않습니다.
/qa-scenario-gen도 따로 부르던 명령에서 2단계 프로그램으로 완전히 옮겨져, 통과 증거(evidence contract)에 따른 자동 검증이 가능해졌습니다.
다만 브라우저로 화면을 직접 눌러 보는 검증과, 코드 올리기를 막는 검문소(push gate)는 아직 옮기지 못했습니다.