지식위키

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_generations30강제 종료 상한(세대수)
convergence_threshold0.95이 이상 유사도면 수렴 후보
stagnation_window3변화 없음 연속 N세대면 정체
eval_min_score0.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_drift0–1Seed 목표 텍스트와 Jaccard 거리
constraint_drift0–1제약 위반 × 0.1, 최대 1.0
ontology_drift0–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/valueY항목 식별자 / 결정값 (예: goal.primary)
sourceY출처 분류(아래 콜아웃)
confidenceY신뢰도 0–1(clamp)
statusYmissing/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/autoevolve_step()(한 세대). Ralph는 RalphLoopRunner.run()이 이를 백그라운드 잡으로 반복.
  • 드리프트 감시 두 경로: (a) 평가 직후 DriftMeasurement.measure()observability.drift.measured 이벤트 기록, (b) hooks/hooks.json PostToolUse(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줄 요약

  1. 진화 루프는 실행→평가→Wonder→Reflect→Seed 재생성을 수렴 또는 최대 세대수까지 반복해 사양 자체를 진화시킨다.
  2. 드리프트 게이트(목표0.5/제약0.3/온톨로지0.2, ≤0.3)가 목표 이탈을, 다중 게이트(eval·AC·regression·evolution·validation)가 품질 후퇴를 막는다.
  3. 감사 원장은 모든 결정의 출처를 근거/추측으로 분리해 기록하고, 이벤트 소싱 덕에 lineage_id 하나로 언제든 재현·재개된다.

스스로 답해보기

  • 드리프트 점수가 0.3을 넘으면 어떤 신호이고, 어떤 세 항목을 어떤 가중치로 합산하나?
  • “수렴(converged)“으로 종료되려면 온톨로지 유사도 외에 어떤 조건들이 함께 충족돼야 하나?
  • 감사 원장에서 assumptionrepo_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 이벤트 팩토리)