ouroboros · 고유기능: 진화 루프 · 드리프트 게이트 · 감사 원장(provenance)
ouroboros · 고유기능: 진화 루프 · 드리프트 게이트 · 감사 원장(provenance)
한 줄 요약
ouroboros는 실행→검증→반성→재생성을 반복하며 스스로 사양을 개선하되, 목표 이탈을 막고(드리프트 게이트) 모든 결정의 출처를 영수증으로 남긴다(감사 원장). 왜 배우나: 단발 생성이 실패하는 건 사양에 숨은 가정 때문이다. 이 셋이 맞물려 가정을 드러내고·이탈을 막고·근거를 추적 가능하게 만든다.
그림
flowchart TD
S[1세대 사양 Seed] --> EX[실행 Execute]
EX --> EV["3단계 평가<br/>기계검증→시맨틱→다중모델합의"]
EV --> DR{"드리프트 ≤ 0.3 인가?"}
EV --> CV{"수렴했나?<br/>유사도≥0.95 / 정체 / 진동"}
CV -- 아니오 --> W["Wonder: 온톨로지 공백을 질문으로"]
W --> R["Reflect: 온톨로지 변이안 생성"]
R --> SG["Seed 재생성 + OntologyDelta 계산"]
SG --> EX
CV -- 예/소진 --> END["수렴 / 소진 / 정체로 종료"]
EX -. Write·Edit 훅 .-> DM[drift-monitor.py 자동 호출]
EV -. 이벤트 기록 .-> LOG["(EventStore<br/>lineage.* / drift.measured)"]
뱀이 자기 꼬리를 무는(ouroboros) 것처럼, 한 세대의 출력이 다음 세대의 입력이 된다.
쉽게 풀기
세 부품이 맞물려 돈다.
1. 진화 루프 — “초안을 계속 고쳐 쓰는 작가” 글을 쓰고(실행)→편집자가 보고(검증)→점수를 매긴다(평가). 여기까지가 한 세대(generation). 이어서 “뭘 놓쳤지?”를 묻고(Wonder), 반성해서(Reflect), 원고를 다시 쓴다(Seed 재생성). 개념 구조(ontology)가 안정되거나(수렴) 최대 세대수에 닿을 때까지 반복한다.
2. 드리프트 게이트 — “나침반” 고치다 보면 처음 의도에서 슬금슬금 벗어난다. 매 세대 “목표에서 얼마나 멀어졌나”를 목표 0.5 + 제약 0.3 + 온톨로지 0.2 가중치로 합산하고, 0.3을 넘으면 이탈 신호다. 사람이 아니라 코드를 고칠 때마다(Write/Edit) 훅이 자동으로 점검한다.
3. 감사 원장(provenance) — “출처가 적힌 영수증” 사양의 각 항목이 (가) 사용자가 말한 사실인지, (나) 저장소에서 읽은 사실인지, (다) 모델 추측인지를 출처로 분류해 기록한다. 핵심 규율: 추측과 근거를 절대 섞지 않는다.
바닥 — 이벤트 소싱(Event Sourcing) 모든 상태는 EventStore 이벤트의 **재생(replay)**으로 복원된다(거래내역으로 잔고 재계산). 덕분에 lineage_id 하나만 있으면 세션이 끊겨도 이어서 진화한다.
flowchart LR
subgraph 진화루프
A[실행] --> B[평가] --> C[Wonder] --> D[Reflect] --> E[Seed 재생성] --> A
end
B -.측정.-> G["드리프트 게이트<br/>≤0.3"]
E -.기록.-> L["감사 원장<br/>근거/추측 분리"]
A & B & C & D & E -.append.-> ES["(이벤트 소싱<br/>lineage_id로 재개)"]
핵심 정리
루프를 제어하는 손잡이 (EvolutionaryLoopConfig · evolution/loop.py)
| 손잡이 | 기본값 | 무엇을 정하나 |
|---|---|---|
max_generations | 30 | 강제 종료 상한(세대수) |
convergence_threshold | 0.95 | 이 이상 유사도면 수렴 후보 |
stagnation_window | 3 | 변화 없음 연속 N세대면 정체 |
eval_min_score | 0.7 | 수렴 허용 최소 평가점수 |
[!note]- 펼쳐보기: 나머지 손잡이
min_generations(기본 3): 이 세대 전에는 수렴 신호를 검사하지 않는다.enable_oscillation_detection(기본 True): A→B→A→B 진동 감지 on/off.eval_gate_enabled(기본 True): 평가가 불만족이면 수렴을 막는다.runtime_controls: 무진행 타임아웃 등 watchdog 제어.
드리프트 점수 공식 (DriftMetrics · observability/drift.py)
| 항목 | 범위 | 어떻게 재나 |
|---|---|---|
goal_drift | 0–1 | Seed 목표 텍스트와 Jaccard 거리 |
constraint_drift | 0–1 | 제약 위반 × 0.1, 최대 1.0 |
ontology_drift | 0–1 | 온톨로지 필드명 집합 Jaccard 거리 |
combined = goal*0.5 + constraint*0.3 + ontology*0.2, is_acceptable = combined ≤ 0.3(NFR5 게이트).
[!note]- 펼쳐보기: 드리프트 상수 전체
GOAL_DRIFT_WEIGHT=0.5,CONSTRAINT_DRIFT_WEIGHT=0.3,ONTOLOGY_DRIFT_WEIGHT=0.2,DRIFT_THRESHOLD=0.3,CONSTRAINT_VIOLATION_PENALTY=0.1
영수증 한 줄의 구조 (LedgerEntry · auto/ledger.py)
| 필드 | 필수 | 의미 |
|---|---|---|
key/value | Y | 항목 식별자 / 결정값 (예: goal.primary) |
source | Y | 출처 분류(아래 콜아웃) |
confidence | Y | 신뢰도 0–1(clamp) |
status | Y | missing/weak/defaulted/inferred/confirmed/conflicting/blocked |
출처는 “근거”와 “추측”으로 갈리며, 충돌 시 모델이 판단하지 않고 결정론적으로 해소한다.
flowchart TD
K[같은 key 충돌] --> P{SOURCE_PRIORITY 비교}
P -- 우열 있음 --> WIN[높은 우선순위 채택]
P -- 동급 --> CF{confidence 비교}
CF -- 우열 있음 --> WIN
CF -- 동점 --> CONF["CONFLICTING<br/>사람 결정 대기"]
[!note]- 펼쳐보기: 출처(LedgerSource) 전체 분류
- 근거 기반(evidence-backed) 5종:
user_goal,repo_fact,existing_convention,user_preference,non_goal- 추측 클래스(
assumption_only_sections로 분리):assumption,inference,conservative_default,auto_fill_inference(그 외blocker등)- 충돌 해소:
SOURCE_PRIORITY→ confidence → 동점이면CONFLICTING(결정론적).- 선택 필드:
reversible(기본 True),rationale,evidence(인용 목록).- 수렴·진동 측정 단위 (OntologyDelta.similarity ·
core/lineage.py): 가중 유사도 =0.5*name_score + 0.3*type_score + 0.2*exact_score(이름 존재 0.5 / 같은 타입 0.3 / 설명까지 동일 0.2). 이 값이 수렴·정체·진동 판정의 입력이다.
AI 파이프라인에 어떻게 끼어드나
한 세대는 고정 순서로 흐르고, 각 단계 전후로 이벤트가 쌓인다. evolve_step은 상태를 들지 않고 매번 이벤트 재생으로 복원하므로 세션이 바뀌어도 이어 돈다.
flowchart LR
T["ooo evolve/ralph/auto"] --> RP["replay_lineage<br/>이벤트 재생"]
RP --> PJ["LineageProjector.project<br/>OntologyLineage 복원"]
PJ --> RR["find_resume_point<br/>재개"]
RR --> GEN["wondering→reflecting→seeding→executing→evaluating"]
GEN -. lineage.generation.* append .-> ES["(EventStore)"]
GEN -. _check_shutdown .-> SD[협력적 중단 체크포인트]
- 트리거:
ooo evolve/ralph/auto→evolve_step()(한 세대). Ralph는RalphLoopRunner.run()이 이를 백그라운드 잡으로 반복. - 드리프트 감시 두 경로: (a) 평가 직후
DriftMeasurement.measure()가observability.drift.measured이벤트 기록, (b)hooks/hooks.jsonPostToolUse(Write|Edit)가 편집마다scripts/drift-monitor.py호출(timeout 3s). - 모델 입력: 이전 세대의 평가요약(EvaluationSummary)·실행출력. Wonder가 온톨로지 공백을 질문으로, Reflect가 질문을 변이안으로 바꾼다.
- 3단계 평가(
evaluation/pipeline.py): Stage1 기계검증(lint/build/test/static/coverage, $0)→실패 시 즉시 종료 / Stage2 시맨틱(LLM, AC 준수 + score≥0.8) / Stage3 다중모델 합의(drift·ontology_changed 트리거 시).
[!note]- 펼쳐보기: Ralph 정지 조건 (
ralph_loop.py)
- 성공:
qa passed·converged- 실패:
failed·interrupted·exhausted·stagnated- 이상 패턴:
oscillation_detected(최근 window findings_hash 동일) ·grade_regressing(최근 window 등급 단조 하락)- 시간 한계:
iteration_timeout(기본 1800s) ·wall_clock_exhausted
실제 예시
[!note]- 펼쳐보기: 진화 루프 한 세대 골격 (실제 발췌
evolution/loop.py)# _run_generation_phases, Gen2+ 골격 if generation_number > 1 and lineage.generations: prev_gen = next( (g for g in reversed(lineage.generations) if g.phase == GenerationPhase.COMPLETED), lineage.generations[-1], ) # Wonder → "무엇을 더 배워야 하나" wonder_result = await self.wonder_engine.wonder( current_ontology=current_seed.ontology_schema, evaluation_summary=prev_gen.evaluation_summary, execution_output=prev_gen.execution_output, lineage=lineage, seed=current_seed, ) # Reflect → ontology 변이안 생성 reflect_result = await self.reflect_engine.reflect( current_seed=current_seed, execution_output=prev_gen.execution_output or "", evaluation_summary=prev_gen.evaluation_summary, wonder_output=wonder_output, lineage=lineage, ) # Seed 재생성 + ontology delta 계산 new_seed = self.seed_generator.generate_from_reflect(current_seed, reflect_output).value ontology_delta = OntologyDelta.compute(current_seed.ontology_schema, new_seed.ontology_schema) current_seed = new_seed
[!note]- 펼쳐보기: 가중 드리프트 + 수렴 게이트 (실제 발췌)
# observability/drift.py — combined_drift 가중 공식 @property def combined_drift(self) -> float: return ( self.goal_drift * GOAL_DRIFT_WEIGHT # 0.5 + self.constraint_drift * CONSTRAINT_DRIFT_WEIGHT # 0.3 + self.ontology_drift * ONTOLOGY_DRIFT_WEIGHT # 0.2 ) @property def is_acceptable(self) -> bool: return self.combined_drift <= DRIFT_THRESHOLD # 0.3# evolution/convergence.py — eval 게이트(수렴 차단 영수증) if self.eval_gate_enabled and latest_evaluation is not None: eval_blocks = not latest_evaluation.final_approved or ( latest_evaluation.score is not None and latest_evaluation.score < self.eval_min_score # 0.7 ) if eval_blocks: return ConvergenceSignal( converged=False, reason=f"Ontology stable (similarity {latest_sim:.3f}) but evaluation unsatisfactory", ontology_similarity=latest_sim, generation=current_gen, )
직접 만들 때 — 복붙 가능한 최소 골격
[!note]- 펼쳐보기: 최소 진화 루프 예제 (개념 단순화판)
WEIGHTS = {"goal": 0.5, "constraint": 0.3, "ontology": 0.2} DRIFT_THRESHOLD = 0.3 CONV_THRESHOLD = 0.95 MAX_GEN = 30 def combined_drift(goal, constraint, ontology): return goal*WEIGHTS["goal"] + constraint*WEIGHTS["constraint"] + ontology*WEIGHTS["ontology"] def similarity(prev_fields, curr_fields): # OntologyDelta 단순화판(name_score만) pn, cn = set(prev_fields), set(curr_fields) alln = pn | cn return 1.0 if not alln else len(pn & cn) / len(alln) def evolve(seed, executor, evaluator, wonder, reflect, regenerate): events, prev_sim, gen, ontology = [], 0.0, 0, seed["ontology"] while gen < MAX_GEN: gen += 1 out = executor(seed) # Execute ev = evaluator(out) # Evaluate (mech→semantic→consensus) events.append({"type": "generation.completed", "gen": gen, "eval": ev}) d = combined_drift(ev["goal_drift"], ev["constraint_drift"], ev["ontology_drift"]) events.append({"type": "drift.measured", "gen": gen, "combined": d, "acceptable": d <= DRIFT_THRESHOLD}) # 영수증 sim = similarity(ontology, seed["ontology"]) # 수렴 게이트: 유사도 + 평가 만족 둘 다여야 converged if gen >= 3 and sim >= CONV_THRESHOLD: if ev["approved"] and ev["score"] >= 0.7: return {"status": "converged", "events": events, "seed": seed} # 안정했지만 평가 불만족 → 차단하고 계속 q = wonder(ontology, ev) # Wonder mut = reflect(seed, ev, q) # Reflect ontology = seed["ontology"] seed = regenerate(seed, mut) # Seed 재생성 return {"status": "exhausted", "events": events, "seed": seed} # 감사 원장: 추측과 근거를 분리해 기록 EVIDENCE_BACKED = {"user_goal", "repo_fact", "existing_convention", "user_preference", "non_goal"} def ledger_entry(key, value, source, confidence): return {"key": key, "value": value, "source": source, "confidence": confidence, "evidence_backed": source in EVIDENCE_BACKED}
직접 만들 때 체크리스트
- 모든 상태 전이를 EventStore에 append, 재개는 이벤트 재생으로만(직접 영속화 금지 — OntologyLineage는 read model).
- 단계 순서 고정: wondering→reflecting→seeding→executing→evaluating. 단계 전후 중단 체크포인트.
- 드리프트 goal0.5/constraint0.3/ontology0.2, 게이트 ≤0.3. 매 세대 측정 이벤트 기록.
- 수렴은 단일 신호 금지: 유사도≥0.95 AND eval(approved+score≥0.7) AND AC/regression AND ontology 실제 진화(evolved_count>0) AND validation 미스킵.
- 정체(window=3)·진동(period-2 양쪽 반주기)·반복질문(70% overlap×2회) 별도 종료 신호.
- 무한루프 방지: max_generations + per-iteration 타임아웃 + grade-regression/oscillation 정지(Ralph).
- 원장: source로 evidence/assumption 분리, key 충돌은 priority→confidence→CONFLICTING(모델 판단 금지). gateway provenance는 env var로만, redact 후 기록.
요약 & 셀프체크
3줄 요약
- 진화 루프는 실행→평가→Wonder→Reflect→Seed 재생성을 수렴 또는 최대 세대수까지 반복해 사양 자체를 진화시킨다.
- 드리프트 게이트(목표0.5/제약0.3/온톨로지0.2, ≤0.3)가 목표 이탈을, 다중 게이트(eval·AC·regression·evolution·validation)가 품질 후퇴를 막는다.
- 감사 원장은 모든 결정의 출처를 근거/추측으로 분리해 기록하고, 이벤트 소싱 덕에 lineage_id 하나로 언제든 재현·재개된다.
스스로 답해보기
- 드리프트 점수가 0.3을 넘으면 어떤 신호이고, 어떤 세 항목을 어떤 가중치로 합산하나?
- “수렴(converged)“으로 종료되려면 온톨로지 유사도 외에 어떤 조건들이 함께 충족돼야 하나?
- 감사 원장에서
assumption과repo_fact를 절대 섞지 않는 이유는 무엇이고, key 충돌은 어떻게 해소하나?
연결
OB_개요 · _분석축_루브릭 · OB_30_event-sourcing-and-projection-readmodel · OB_40_orchestrator-execution-loop · OB_20_spec-engine-seed-and-double-diamond · OB_70_extension-points-hooks-skills-commands
[!tip]- Codex 교차검증 보존 위 내용은 ouroboros 소스에서 직접 확인한 스키마·상수·단계순서를 근거로 한다. 드리프트 가중치 0.5/0.3/0.2와 ≤0.3 게이트, 수렴의 다중 게이트(eval≥0.7 + AC/regression + evolution + validation), 출처 5종 evidence-backed vs 추측 클래스 분리, 충돌의 결정론적 해소(source priority→confidence→CONFLICTING)는 코드 상수와 일치함.
근거 파일:
/home/seunghyeong/harness-work/ouroboros/src/ouroboros/evolution/loop.py(EvolutionaryLoop, evolve_step, _run_generation_phases, EvolutionaryLoopConfig, 단계순서·중단·드리프트 측정)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/evolution/convergence.py(ConvergenceCriteria/ConvergenceSignal, eval·AC·regression·evolution·validation 게이트, stagnation/oscillation/repetitive)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/core/lineage.py(OntologyLineage read model, OntologyDelta 가중 유사도, GenerationRecord/Phase, EvaluationSummary, ControlDirectiveEmission)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/observability/drift.py(DriftMetrics 가중공식 0.5/0.3/0.2, ≤0.3 게이트, DriftMeasured/ThresholdExceeded 이벤트)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/ralph_loop.py(RalphLoopRunner, 정지조건: qa/converged/failed/oscillation/grade_regressing/timeout)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/evaluation/pipeline.py(3단계 평가 Stage1 mechanical→Stage2 semantic→Stage3 consensus, 트리거)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/auto/ledger.py(LedgerSource 분류, evidence vs assumption, resolve_conflict 결정론 정책, SeedDraftLedger.summary provenance)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/auto/provenance.py(OUROBOROS_AUTO_PROVENANCE_JSON 게이트웨이 출처, redact)/home/seunghyeong/harness-work/ouroboros/hooks/hooks.json(PostToolUse Write|Edit → scripts/drift-monitor.py)/home/seunghyeong/harness-work/ouroboros/src/ouroboros/events/lineage.py(lineage_created/generation_*/ontology_evolved/converged/exhausted/stagnated 이벤트 팩토리)