BLUF — 방향은 성립하지만 아직 운영 투입은 이르다
135~196ms
job ID를 돌려준 뒤 작업이 계속돼 Claude turn을 붙잡지 않았다.
3/3 + 3/3
모의 worker와 실제 Codex app-server 강제중단 시험이 수정 후 모두 통과했다.
조건부 Go
v1 실작업 파일럿 가치는 있다. 기존 rN↔rxN은 영구 유지한다.
범위와 격리
검증 사실 모든 상태는 research-logs/report-job-orchestrator-pilot-20260816/ 아래에만 생성했다. 기존 codex-relay/, cc_reports1~8.bat, cx_reports1~8.bat의 diff는 0건이었다. 기존 app-server 상태와 rN/rxN thread는 재개·종료하지 않았다.
제어면은 job/attempt 분리, checkpoint, heartbeat, cooperative cancel, append-only event, idempotency key를 구현했다. 실제 Codex는 임시 app-server에서 새 thread를 만들고 정상 turn→서버 재기동→동일 thread resume→turn 도중 서버 종료→thread/read→복구 turn을 수행했다.
정의된 5개 제어면 시나리오와 1개 Codex 장애 시나리오를 각 3회 반복한 표본 시험이다. 재부팅·8개 이상 부하·Hub 동시등록·승인대기는 제외했다.
Exhibit 1. 수정 후 반복 결과
| 시험 | 방법 | 결과 | 의미 |
|---|---|---|---|
| 비동기 submit | detached 6-step job 제출 | 3/3, 135·196·135ms | foreground 대기 제거 가능 |
| 병렬 출력 | 두 job 동시·고유 파일 | 3/3 | 고유 보고서 파일은 독립 실행 가능 |
| 취소 | 실행 중 cancel | 3/3 | heartbeat에서 안전 중단 가능 |
| worker 중단 복구 | checkpoint 3 후 PID tree 종료→attempt 2 | 3/3, effect 각 1회 | 멱등성 키로 중복 부작용 방지 |
| event 감사 | 시작·checkpoint·lost·완료 확인 | 3/3 | 장애 원인 복원 가능 |
| 실제 Codex 장애 | turn 도중 app-server 종료·재기동 | 3/3, 모두 interrupted | 같은 thread 재개 가능; 중단 turn은 자동 완주되지 않음 |
환경: Windows, Node 24.16.0, Codex CLI 0.147.0. submit 시간은 로컬 벽시계이며 처리량 지표가 아니다.
Exhibit 2. 시험 중 발견해 고친 문제
database is locked가 발생했다. 최초 생성 때만 WAL을 설정하도록 수정했다.recovery_pending→queued와 resume_requested를 transaction으로 먼저 확정한 뒤 worker를 띄우도록 순서를 바꿨다.shell:true를 제거했다.interrupted로 조회됐다. supervisor는 PID뿐 아니라 SQLite와 Codex thread 상태를 함께 맞춰야 한다.Exhibit 3. 추가 확인된 장단점
| 항목 | 판정 | 기존 구조와의 관계 |
|---|---|---|
| Claude turn 점유 | submit 즉시 반환 실증 | 새 구조의 명확한 순증 장점 |
| 자동 장애 인식 | worker_lost·interrupted 판별 | 현재 수동 복구를 보완 |
| 중복 방지 | 모의 effect만 검증 | HTML·Hub·외부 API별 키가 더 필요 |
| 직접 관찰 | TUI attach 미구현 | 현재 rx 창이 우세 |
| 운영 복잡도 | DB·worker·supervisor·로그 추가 | 실제 순증 비용 |
| rate limit·공유 파일 | 미검증 | 현재도 존재하며 자동 해결되지 않음 |
| 문맥·resume | 동일 thread 3/3 | 현재 rx도 제공하므로 독점 장점 아님 |
다음 권고 — topology·lease를 먼저 고친 뒤 재시험
- app-server startup mutex: cold-start 단일 owner와 stale lease 회수 규칙을 구현해 공유 PID를 정확히 1개로 만든다.
- job CAS·heartbeat: run/resume 중복 호출에서 attempt 하나만 생성하고, 죽은 worker를
running에 방치하지 않는다. - writer lease: followup과 TUI attach가 같은 thread writer를 겹쳐 잡지 못하게 queue/reject한다.
- 입력 검증: job ID·brief 경로를 격리 루트로 제한하고 Hub title을 HTML escape하며 turn timeout을 둔다.
- Claude 최소안 별도 시험: 현재 relay를 background Bash로 보내 동일 rN을 즉시 재사용하고, 완료 회수·동일 rxN queue를 검증한다.
재시험 합격선: cold-start 3-job에서 app-server 1개, 고아 0, active-writer 충돌 0, worker kill 자동 감지, terminal event 유실 0. 그 전에는 기존 rN↔rxN을 기본 경로로 유지한다.
근거와 미확인
- 1차 시험:
control-plane-test-result-fixed-run1.json~run3.json,appserver-smoke-result-run1.json~run3.json. - 실제 v1:
v1/v1-test-summary.json,v1/three-job-test-summary.json,registrar-fault-result.json, SQLite와 versioned HTML. - 선행 구조 비교 보고서.
- OpenAI Codex App Server 공식 문서.
- Claude Code background command 공식 문서와 Hooks·asyncRewake 공식 문서.
미확인 재부팅 복구, 8개 이상 동시 job, 실제 웹 조사형 보고서, startup mutex·worker heartbeat·TUI attach, asyncRewake callback, 실제 Hub와 배포 멱등성.
검증 사실은 JSON·SQLite event·process command line·Codex thread 상태와 생성 HTML에 근거했다. 외부 웹 조사는 Claude 기능 확인을 위한 공식 문서에만 사용했고, 실제 Hub 등록·배포는 하지 않았다.
Exhibit 4. 실제 보고서 v1 수직 실험 — 통과
| 검증 항목 | 관측값 | 판정 |
|---|---|---|
| 첫 실제 HTML | 55.2초, 검증 9/9, version 1 보존 | 통과 |
| followup 강제장애 | app-server 종료 후 0.899초 내 recovery_pending | 통과 |
| 같은 thread 복구 | 직전 turn=interrupted, attempt 3에서 v2 완료 66.346초 | 통과 |
| 내용 요구 | 운영 중단 조건·3-job·rN↔rxN 영구 유지 포함 | 통과 |
| 격리 registrar | 2회 호출 후 카드 1개, v2만 연결, v1 파일 보존 | 통과 |
| 모바일 | 390px viewport에서 문서 폭 390px, 표만 내부 스크롤 | 통과 |
근거: v1/v1-test-summary.json, SQLite event 15건, version 1·2 HTML, 격리 Hub. 외부 검색·실제 Hub 등록·배포 없음.
Exhibit 5. 실제 3-job 동시 파일럿 — 기능 통과, topology 실패
| 항목 | 결과 | 판정 |
|---|---|---|
| 보고서 3종 | 짧은 근거형·큰 표형·상충 근거형, 고유 thread 3·고유 출력 3 | 통과 |
| 품질·모바일 | 엄격 문구 누락 1건을 같은 thread followup으로 교정; 390px 3/3 | 통과 |
| DB·격리 Hub | integrity_check=ok, 카드 4·href 4·유실 0 | 통과 |
| registrar fault | publish 전 강제종료 중 기존 Hub hash 유지, 재실행 성공 | 통과 |
| 공유 server topology | cold-start에서 PID 2840·47312·40032 세 개 생성 | 실패 |
| 후속 writer | 고아 server가 lease를 잡아 already has an active writer | 실패 |
근거: v1/three-job-test-summary.json, registrar-fault-result.json, 세 report HTML과 SQLite attempts/events.
Exhibit 6. 현재 방식에서도 Claude를 먼저 풀 수 있다
Claude가
relay.mjs Bash 호출을 background로 실행하게 한다. Claude Code는 즉시 task ID를 반환하고 같은 rN이 새 prompt를 받을 수 있다.같은 rxN thread에는 기존 Codex turn 완료 전 새 일반 relay를 겹치면 안 된다. 새 요청은 queue하거나 다른 rx/job으로 보내야 한다.
기본 background 출력은 task ID·파일로 회수한다. idle Claude를 자동으로 깨우려면
asyncRewake hook을 별도 격리 시험해야 한다.사용자 지시 예: “Codex relay는 Bash background로 넘기고 task ID만 즉시 알려줘. 끝날 때까지 기다리지 마. 같은 rxN이 바쁘면 새 일반 요청은 queue해.” 실행 중인 Bash는 Ctrl+B로 background 전환할 수도 있다. 공식 문서상 background command는 비동기로 실행되고 task ID를 즉시 반환하며 Claude는 새 prompt에 응답할 수 있다. Claude Code Interactive mode
미검증 asyncRewake는 exit code 2로 idle Claude를 깨우고 출력을 system reminder로 전달할 수 있으나, 중복 hook·사용자 turn과의 순서·Claude 종료·보안은 아직 시험하지 않았다. Claude Code Hooks reference
await turn/completed를 없애지 않는다. 안정 운영은 validated job ID·status/result 파일 또는 SQLite 원장을 함께 써야 한다.