진입점·에이전트 루프 — 횡단분석
진입점·에이전트 루프 — 횡단분석
한 줄 요약
에이전트 루프란 “사용자 한 마디 → 모델이 도구를 부르고, 그 결과를 다시 모델에 먹이고, 또 부르고… → 더 안 부르면 종료”의 반복이며, 7개 하네스가 이 한 골격을 누가 소유하고 언제 멈추는지로 갈린다. 왜 배우나 — 모든 하네스 동작의 심장이라, 이 루프 한 장을 이해하면 나머지 횡단 주제(컨텍스트·훅·검증)가 전부 이 루프의 “어디에 끼어드는가”로 설명되기 때문이다.
그림
flowchart TD
U(["사용자 한 마디 / 스크립트 -p"]) --> M[모델 추론]
M -->|도구 호출 있음| T[도구 실행]
T --> R[결과를 히스토리에 재주입]
R --> M
M -->|도구 호출 없음| G{"Stop 게이트<br/>끝내도 되나?"}
G -->|"block: 한 바퀴 더"| M
G -->|통과| E([턴 종료])
G -.무한루프 가드.-> E
flowchart LR
subgraph 소유형["루프 소유형 — while을 직접 돌림"]
CC[Claude Code]
CX[Codex]
GJ[gajae-code]
OB["ouroboros · AC그래프 병렬"]
end
subgraph 기생형["기생형 — CC 루프에 훅으로 얹힘"]
OMC[OMC]
FB[fable-ish]
MINE["내 패턴 · 플러그인 로딩"]
end
CC -- "lifecycle 이벤트" --> OMC
CC -- "lifecycle 이벤트" --> FB
CC -- "부팅 시 능력 등록" --> MINE
쉽게 풀기
에이전트 루프를 “요리사와 주방 보조” 로 비유해 보자.
- 주문이 들어온다 — 사용자가 “이거 고쳐줘”라고 말한다. 대화창에서 직접(
claude) 말할 수도 있고, 스크립트가 자동으로 주문서를 넣을 수도 있다(claude -p헤드리스). - 요리사(모델)가 생각한다 — 모델은 혼자서는 글만 쓸 수 있다. 실제로 칼질(파일 수정)이나 불 켜기(명령 실행)를 하려면 “보조야, 이 도구 좀 써줘”라고 도구 호출(tool_use) 을 내야 한다.
- 보조(하네스)가 도구를 실제로 돌린다 — 그리고 결과(잘 됐다/에러 났다)를 요리사 귀에 다시 속삭여 준다. 이 “결과 재주입”이 루프의 핵심이다. 결과가 다음 생각의 재료가 되어야 “관찰 → 행동”이 생긴다.
- 요리사가 더 시킬 게 없으면 “끝”이라고 말한다 — 도구를 더 안 부르면 한 턴이 끝난다.
- 그런데 문 앞에 감독관(Stop 게이트)이 있다 — “테스트도 안 돌리고 끝내려고? 한 번 더 해.”라고 되돌릴 수 있다. 단, 무한히 못 막게 가드(몇 번까지만 막기)가 항상 같이 붙는다.
여기서 가장 큰 갈림길은 “누가 주방을 소유하느냐” 다.
- 루프 소유형(CC·Codex·gajae·ouroboros): 자기 주방을 직접 운영한다. 멈춤·압축·병렬·취소를 다 제어하지만 만들 게 많다.
- 기생형(OMC·fable-ish·내 패턴): 남의 주방(CC)에 직원으로 들어가 “주문 들어올 때/나갈 때만 한마디 거든다.” 가볍지만, 요리사를 다시 부르는 실권은 주방 주인(CC)에게 있다. fable-ish의 표현이 정확하다 — “감독관이지 운전자가 아니다.”
핵심 정리
세 가지 질문으로 7개 하네스를 본다: (1) 루프를 누가 소유하는가, (2) 무엇이 종료/연장을 결정하는가, (3) AI에 무엇을 언제 주입하는가.
| 프레임워크 | 루프 소유 | 한 줄 특이점 |
|---|---|---|
| Claude Code | 소유(본체) | 3-cadence(세션/턴/도구), Stop 훅 decision:block으로 종료 거부·연장(8연속 강제종료 가드) |
| Codex | 소유(Rust) | 루프 한가운데 auto-compact, end_turn==None fallback, sampling은 순차+tool은 동시 |
| OMC | 기생 | 자체 루프 없음, stdin/stdout JSON 계약으로 컨텍스트 주입·도구 차단 |
| gajae-code | 소유(TS) | 단일 번역경계 convertToLlm, 두 겹 루프, 영수증(coverage) |
| ouroboros | 소유(Python) | 단일 턴이 아닌 AC 의존성 그래프 레벨 병렬 루프 |
| fable-ish | 기생 | 훅 3종 + ledger, 변경됐는데 검증 없으면 Stop 차단(최대 2회) |
| 내 패턴 | 기생(부팅) | 루프 아님 — settings→plugin.json 부팅으로 능력 일괄 등록 |
[!note] 세부 스키마·트리거·주입방식 (펼쳐 보기) Claude Code — 진입점 2종(
claude대화형 /claude -p헤드리스), 루프 3-cadence(세션1회 / 턴1회UserPromptSubmit→Stop/ 도구마다PreToolUse→PostToolUse),--max-turns·--max-budget-usd. 한 턴 = 모델 추론 →tool_use면 실행 후 결과 재주입 → 안 부르면Stop. 시스템프롬프트+CLAUDE.md+메모리+도구정의를 하네스가 조립해 주입. Codex —SessionTask트레잇 + 구현체(RegularTask/ReviewTask/CompactTask/UserShellCommandTask),ResponseEventenum 스트림(OutputItemDone/Completed{end_turn}).codex exec→UserTurn→spawn_task→tokio::spawn.build_prompt가 입력+model_visible_specs()+base_instructions 조립,FunctionCallOutput을 다음 히스토리에 포함. OMC —hooks/hooks.json이벤트→.mjs매핑표 +run.cjs공통런처. CC lifecycle 이벤트 시 스크립트 spawn.hookSpecificOutput.additionalContext로 주입([MAGIC KEYWORD: RALPH]등),permissionDecision:'deny'로 차단. gajae-code —agentLoop/agentLoopContinue(+ detailed wrapper 2종),AgentLoopConfig. 바깥(followUp 재진입)/안쪽(steering→stream→executeToolCalls→pause) 두 겹. 끝까지AgentMessage로 들고 LLM 직전에만Message[]로 변환,_i(intent) 필드 주입→실행 전 제거. ouroboros — 진입점 3통로(SKILL.md 매핑 /parse_ooo_command→registry / typer CLI).execute_seed→Seed→AC 의존성 LLM질의→StagedExecutionPlan레벨그래프. 스테이지 직렬·스테이지내 AC 병렬. 각 AC는 자기+형제경계+직전레벨 컨텍스트만. fable-ish —hooks.json3종(UserPromptSubmit=분류 / PostToolUse=증거기록 / Stop=검증게이트), ledger 스키마,should_block_stop.additionalContext로 연성신호 주입. 내 패턴 —settings.json→installed_plugins.json→plugin.json부팅. 켜진 플러그인의 skills/commands/agents/hooks/MCP를 도구카탈로그·시스템리마인더로 컨텍스트 맨앞 주입. CLAUDE.md=시스템프롬프트 흡수.
모두가 수렴하는 공통 패턴 4가지:
- “추론→실행→결과 재주입→재추론” 반복이 루프의 본질 — 모델 단독은 텍스트만 생성(CC
tools.md)하므로 결과 피드백이 다음 입력이 돼야 “관찰→행동”이 생긴다. - 종료 직전 “Stop 게이트” + 무한루프 가드는 항상 짝 — CC 8연속 block, fable-ish
MAX_STOP_BLOCKS=2, Codexstop_hook_active, ouroboros watchdog. - stdin JSON → stdout JSON이 호스트↔확장 표준 계약 —
additionalContext(주입)나permissionDecision/decision(통제)을 stdout으로. 다수 훅이 개별적으로 fail-open. - 컨텍스트는 “필요한 것만, 늦게” — gajae 단일 번역경계, ouroboros on-demand SKILL.md, Codex auto-compact, 내 패턴 deferred tool+ToolSearch. (단 “내 패턴”은 startup registration 성격이라 late injection과 결이 다름 — 아래 교차검증 참고.)
[!note] 분기점 — 누가 왜 다르게 했나
- 루프 소유 vs 기생(가장 큰 분기): 소유형은 종료·압축·병렬·취소를 완전 제어하나 구현 부담 큼. 기생형은 가볍지만 실권은 CC에 있고 확장은 주입/차단의 연성신호에 그침.
- 단일 턴 vs 그래프 병렬(ouroboros의 급진성): ouroboros만 루프 자체를 AC 의존성 그래프로 재정의(의존성 질의→위상정렬→레벨 병렬→코디네이터 충돌중재). 충돌·오염을 구조적으로 줄이는 대신 막대한 복잡도. 단 Codex도 한 sampling turn 안에서 tool future를 동시 관리하므로, 비교축은 “작업 단위 병렬화가 loop의 1급 구조인가”가 정확.
- 종료 신호 신뢰도: CC는
stop_reason신뢰. Codex는end_turn==Nonefallback까지(멀티프로바이더 중립성의 대가). gajae는 Harmony 누출(GPT-5 찌꺼기)도 종료/재시도 경로에 흡수.- 상태 공유 매개체: fable-ish ledger(
<sha256>.json), ouroboros EventStore, gajae in-memory 영수증, Codex 히스토리 자체. 영속성 요구가 매개체를 가름.- 진입점 다중성: ouroboros만 3통로(백엔드 중립 야심), 나머지는 1~2통로.
실제 예시
1) Stop 게이트로 “끝내려는 순간”을 가로채 한 바퀴 더 돌리는 패턴 (기생형, OMC/fable-ish 계약)
// CC/OMC Stop 훅 출력 — stdout 한 줄. 'message' 필드는 무효(코드 주석에 명시)
{
"decision": "block", // 종료 거부 → 루프 한 바퀴 더
"reason": "변경 파일이 있는데 성공 검증이 없습니다. 테스트를 먼저 돌리세요."
}
// 무한루프 가드: ledger stop_blocks >= 2 이거나 CC 8연속 block이면 통과시켜야 함
# /home/seunghyeong/harness-work/fable-ish/hooks/stop_gate.py
# 핵심 규칙: 변경됐는데 성공검증 없음 → block (단 MAX_STOP_BLOCKS=2)
# "이제 하겠다"는 의지만 말하고 실제 작업 안 한 경우도 차단
# 모든 훅 fail-open: 예외 시 SystemExit(0) 으로 작업을 막지 않음
2) 루프를 자기 소유하는 형(Codex) — 한가운데서 압축 후 continue
// /home/seunghyeong/harness-work/codex/codex-rs/core/src/session/turn.rs
// run_turn: stream() 소비 → function_call이면 future를 FuturesOrdered로 큐잉
// → 끝에서 drain_in_flight (sampling 순차 + tool 동시)
// Pre/Mid-turn 토큰한계 시 auto-compact 후 같은 루프를 continue
// end_turn == None 이면 fallback 처리 (프로바이더가 안 채우는 경우)
3) 내 스택(Claude+Codex+OMC)에 차용할 구체안 — 루프는 안 짜고 확장점만 무장
1. Stop 게이트 + 무한루프 가드를 OMC 훅으로 내장
- 기존 context-guard-stop.mjs / persistent-mode.mjs 와 순서·우선순위 정의
- 예외(context-limit / user abort / auth / scheduled wakeup)는 반드시 통과
- fable-ish식 검증게이트(변경됨 && 검증없음 → 1회 block, ledger>=2면 통과) 이식
2. fable-ish ledger 패턴을 OMC 공유상태로
- session_id+cwd sha256 키, 원자적 replace, redact, "이번 턴 증거"만 담는 경량 장부
- Node 환경이므로 SystemExit(0) 대신 try/catch 후 {continue:true, suppressOutput:true} 또는 {}
3. Codex auto-compact 사고를 CC PreCompact 훅에 반영
- "압축 직전 핵심상태를 wiki/notepad에 flush" 까지만 가능 (mid-turn continue 제어권은 plugin에 없음)
4. gajae coverage(toolsAvailable/Invoked/Unused) 집계
- SubagentStop verify-deliverables.mjs는 의도적으로 additionalContext를 안 냄(항상 suppress)
- 따라서 parent-visible ledger / SessionEnd report / 별도 state artifact로 모을 것
5. gajae 단일 번역경계 원칙을 Codex 워커 호출에 적용 (프롬프트 조립 한 곳에 모아 prefix 캐시 적중)
6. 차용 안 함: ouroboros AC-그래프 병렬 루프는 일상엔 과함
- 대형 멀티파일 리팩터링에서만 "AC 분할→레벨 병렬→충돌중재"를 ultrawork/team 위에 선택적으로
요약 & 셀프체크
3줄 요약:
- 에이전트 루프의 본질은 “추론 → 도구 실행 → 결과 재주입 → 재추론”의 반복이고, 도구를 더 안 부르면 종료다.
- 7개 하네스는 루프를 직접 소유하는 4개(CC·Codex·gajae·ouroboros) 와 CC 루프에 기생하는 3개(OMC·fable-ish·내 패턴) 로 갈리며, 후자는 “감독관이지 운전자가 아니다.”
- 종료 직전 Stop 게이트와 무한루프 가드는 늘 짝으로 오고, 내 스택에선 루프를 새로 짜는 대신 이 확장점을 OMC 훅으로 무장하는 게 관건이다.
셀프체크:
- 모델이 도구를 부른 뒤 “결과 재주입”을 생략하면 루프가 왜 망가지는가?
- 기생형(OMC)이 Stop에서
decision:block을 내도 “무한루프 가드”가 없으면 무슨 일이 벌어지나? - ouroboros의 그래프 병렬 루프를 내 일상 작업에 통째로 들이면 안 되는 이유와, 그럼에도 차용할 한 가지는?
연결
근거 — 참조한 기능노트:
- CC_10_agent-loop — CC 진입점 2종, 3-cadence, Stop
decision:block, 8연속 block 가드 - CX_10_agent-loop-and-turn —
run_turn/SessionTask/ResponseEvent, auto-compact,end_turnfallback - OMC_10_entrypoint-hooks-loop — hooks.json 이벤트표, run.cjs, stdin/stdout JSON 계약, MAGIC KEYWORD 주입
- GJ_10_agent-loop —
AgentLoopConfig, 두 겹 루프, convertToLlm 단일경계, 영수증, Harmony 처리 - OB_10_entrypoint-cli-and-ooo-command-routing · OB_40_orchestrator-execution-loop — 3통로 진입점, AC-tree 레벨 병렬, 코디네이터 게이트, watchdog
- FB_20_hook-event-loop — 훅 3종, ledger, should_block_stop, fail-open
- MINE_10_entrypoint-plugin-loading — settings.json→마켓플레이스→plugin.json 부팅, 능력 일괄 등록
근거 — 소스/문서 경로:
/mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/how-claude-code-works.md,headless.md,cli-reference.md,hooks.md(L22 cadence, L2293 Stop 제어)/home/seunghyeong/harness-work/codex/codex-rs/core/src/session/turn.rs(run_turn, auto-compact),tasks/mod.rs(SessionTask),codex-api/src/common.rs(ResponseEvent)/home/seunghyeong/harness-work/oh-my-claudecode/hooks/hooks.json,scripts/run.cjs,scripts/keyword-detector.mjs/home/seunghyeong/harness-work/gajae-code/packages/agent/src/agent-loop.ts,types.ts,run-collector.ts/home/seunghyeong/harness-work/ouroboros/src/ouroboros/orchestrator/{runner.py,parallel_executor.py,coordinator.py,dependency_analyzer.py},router/command_parser.py/home/seunghyeong/harness-work/fable-ish/hooks/{user_prompt_submit.py,post_tool_use.py,stop_gate.py},scripts/{ledger.py,verify_state.py}/home/seunghyeong/.claude/settings.json,plugins/installed_plugins.json,plugins/marketplaces/omc/.claude-plugin/plugin.json
[!tip] Codex 교차검증 (gpt-5.5가 harness-work 소스와 직접 대조) 총평: 큰 축(“루프 소유형 vs CC 기생형”, “종료 직전 게이트”, “컨텍스트 주입 시점”)은 맞다. 다만 아래 사실 보정이 필요하다. 사실/형식 오류 — ① OMC는 “13개”가 아니라 11개 이벤트(UserPromptSubmit/SessionStart/PreToolUse/PermissionRequest/PostToolUse/PostToolUseFailure/SubagentStart/SubagentStop/PreCompact/Stop/SessionEnd): hooks.json. 최신 Claude
HookEvent는 19종이라 “13”은 어느 기준으로도 애매. ② OMC “fail-open 철칙”은 과장 —run.cjs는 스크립트 누락·timeout만 fail-open하고 정상 child의 exit code는 전파: run.cjs. “각 훅이 개별적으로 fail-open”이 정확. ③ CodexUserShellCommandTask.kind()는UserShell이 아니라TaskKind::Regular반환: tasks/mod.rs. ④ Codex “spawn depth 제한”은 V1 한정 — V2는MultiAgentVersion::V2 => true로 열림: spec_plan.rs. ⑤ gajae 진입점은 “3개”가 아니라 “2 핵심 + 2 detailed wrapper”. ⑥ “내 패턴”은 같은 층위의 agent loop가 아니라 plugin loading/능력 등록 패턴. 빠진 차이점 — CC row에PostToolBatch/Setup/ConfigChange/WorktreeCreate·Remove/MessageDisplay등 최신 HookEvent가 빠짐(“Claude hook surface”와 “OMC가 실제 매핑한 surface”는 분리해야 함). Codex는 “직렬 모델↔도구”가 아니라 “단일 turn loop + 내부 tool concurrency”(FuturesOrdered). ouroboros watchdog은 두 종류 혼재 — AC executorSTALL_TIMEOUT=900s/heartbeat 30s vsooo auto용 runtime watchdog(기본 4시간). 분기점 해석 수정 — “Stop 게이트 보편”은 과함: gajaegetFollowUpMessages는 Stop hook이 아니라 yield 직전 follow-up queue, ouroboros stall/watchdog도 orchestration safety. “late injection” 묶음에 “내 패턴”을 넣으면 안 됨(startup registration 성격). 차용안 현실성 — Stop gate+ledger 이식은 현실적이나 기존 OMC 훅과 순서 정의·예외 통과 필수. ledger fail-open은 Node식({continue:true,suppressOutput:true})으로 변환. auto-compact는 “flush”까지만 가능. gajae coverage를 SubagentStopverify-deliverables.mjs에 붙이는 안은 비현실적 — 이 훅은 의도적으로 항상 suppress: verify-deliverables.mjs. coverage는 parent-visible ledger/SessionEnd report/별도 state artifact로 모을 것.