0. 한눈에 보는 전체 그림
키워드 하나가 "검증된 문제 한 세트"가 되어 나오기까지, 부품이 컨베이어 벨트를 지나듯 흐릅니다.
"최단 경로" → ① 출제자 → ② 설계자 → ③ 프로그래머 → ④ 검사기 🔬 → 라우터
outputs/<run>/ → 측정 게이트
91% 무회귀
이 흐름은 추상적인 다이어그램이 아니라 실제 코드의 그래프 정의 그대로입니다. 거짓말 안 보태고, 소스에 이렇게 적혀 있어요:
builder.set_entry_point("architect") # 시작: 출제자
builder.add_edge("architect", "designer") # 출제자 → 설계자
builder.add_edge("designer", "coder") # 설계자 → 프로그래머
builder.add_edge("coder", "executor") # 프로그래머 → 검사기
builder.add_edge("executor", "record") # 검사기 → 기록
# 기록 후, 라우터가 "다음에 누구에게 갈지" 결정 (조건부 분기)
builder.add_conditional_edges("record", route_after_executor, ...)
아래에서 이 노드들을 하나씩, 실제 코드와 함께 따라갑니다.
1. 왜 이게 어려운 일일까?
"AI한테 코딩 문제 만들어 줘" 하면 그럴듯한 게 나옵니다. 문제는 그 "정답"이 진짜 정답인지 아무도 모른다는 거예요. AI가 "도시 5개 … A→B 최단 경로?" 문제를 만들고 정답을 "17" 이라 적었다 칩시다. 17이 맞나요? AI는 글은 잘 쓰지만 계산은 틀리기도 합니다. 정답 틀린 시험을 내면 채점이 통째로 어긋나죠.
쿠키 공장이 모양 예쁜 쿠키를 잘 굽는다고 안에 든 게 다 안전한 건 아니에요. "먹어도 되는지" 는 따로 검사해야 합니다. AI가 쓴 문제도 똑같아요 — 지문(모양)이 좋아도 정답(속)은 별도로 검사해야 합니다.
2. 핵심 아이디어 — "AI에게 다시 묻지 않고, 계산기로 검산한다"
여기가 IPE의 심장입니다. 정답이 맞는지 확인할 때 다시 AI에게 묻지 않습니다. 대신 그 알고리즘의 수학 정의를 코드로 짜 두고(=검증기 verifier), 그 코드가 답을 처음부터 다시 계산합니다.
"최단 경로" 검증기에는 4가지 수학 법칙(invariant)이 박혀 있어요. 주석에 그대로 적혀 있습니다:
# Dijkstra symbolic verifier — 4 invariants 결정론적 검증
# 1. non_negative_distance : 거리는 음수일 수 없다
# 2. source_zero : 출발=도착이면 거리는 0
# 3. reachability_consistent: 도달 불가인데 거리값이 있으면 모순
# 4. shortest_distance_optimal: 독립 알고리즘(Bellman-Ford)으로
# 다시 계산한 답과 일치해야 한다 ← 진짜 검산
네 번째가 핵심. 문제를 만든 쪽(Dijkstra로 풀었다 치고)과 완전히 다른 알고리즘 Bellman-Ford로 답을 독립 재계산합니다:
def _shortest_distance_bellman_ford(graph):
dist = [inf] * graph.V
dist[graph.s] = 0.0 # 출발점만 0, 나머지 무한대
for _ in range(graph.V - 1):
for u, v, w in graph.edges:
if dist[u] != inf and dist[u] + w < dist[v]:
dist[v] = dist[u] + w # 더 짧은 길 발견 → 갱신
return int(dist[graph.t]) # s→t 최단 거리
golden = _shortest_distance_bellman_ford(graph)
if actual != golden:
return InvariantViolation(
invariant_kind="shortest_distance_optimal",
description=f"actual={actual} != Bellman-Ford golden={golden}",
)
→ AI가 17, 검증기가 15라면 actual=17 != golden=15 로 즉시 불합격. 사람·AI 판단 0.
검증기는 지문(글)을 읽지 않습니다. 오직 그래프 데이터와 수학 정의에서 답을 끌어냅니다. 그래서 출제 AI가 지문을 오해해 헛소리를 적어도 검증기는 휘둘리지 않아요 — "글 오해 위험"과 "검산"이 코드 레벨에서 분리돼 있습니다. 이런 검증기가 19개 알고리즘에 준비돼 있고, 만든 문제의 약 91%가 통과합니다(못 하면 폐기).
3. 일꾼들은 "글"이 아니라 "타입 고정 서류"로 대화한다
일꾼들이 자유 문장으로 소통하면 받는 쪽이 또 오해합니다. 그래서 IPE는 노드 사이 통신을 형식이 고정된 서류(typed contract)로 강제합니다. 예컨대 "검사 결과"는 자유 글이 아니라 실패 종류가 미리 정해진 목록(enum)으로만 표현돼요:
class FailureMode(StrEnum):
NONE = "none" # 통과
SAMPLE_MISMATCH = "sample_mismatch" # 예시 답 불일치
INVARIANT_VIOLATION = "invariant_violation" # 수학 법칙 위반
BRUTE_DISAGREEMENT = "brute_disagreement" # 정해 vs 브루트 불일치
SAMPLE_CRASH = "sample_crash" # 코드가 터짐
class StructuredFeedback(BaseModel):
target_node: TargetNode # 누구에게 돌려보낼지 (enum)
actionable_hint: str # 무엇을 고칠지
blocking_signature: str # 같은 실패 반복 감지용 '지문'
자유 메모("음 뭔가 이상한데요?") 대신 체크박스 검사 양식을 쓰는 거예요. "사유: ☑ 예시 답 불일치 / 담당: ☑ 출제자." 받는 쪽이 해석할 여지가 없으니 다음 행동이 기계적으로 정해집니다. 이게 평균 1.07회 만에 문제를 완성하는 비결이에요. 모든 노드의 입·출력이 이런 Pydantic 서류(ProblemSpec / AlgorithmDesign / SolutionAttempt / VerificationResult)로 묶여 있습니다.
1 일꾼 ① 출제자 (Architect)
키워드를 받아 이야기 있는 지문 + 예시 입력·출력을 씁니다. 출력은 자유 글이 아니라 ProblemSpec 서류. 지원하는 알고리즘은 enum으로 못박혀 있어, 오타나 엉뚱한 값이 끼어들 수 없어요:
class TargetAlgorithm(StrEnum):
DIJKSTRA = "dijkstra"; LIS = "lis"; SEGTREE = "segtree"
TWO_SUM = "two_sum"; BFS = "bfs"; KNAPSACK = "knapsack"
KRUSKAL_MST = "kruskal_mst"; FENWICK = "fenwick"; ...
# … 총 19개. free str 대신 enum 으로 좁혀
# symbolic verifier dispatch 의 silent fallback 회피
이 enum 하나가 7장 검사기에서 "어떤 검증기를 쓸지" 정확히 연결되는 열쇠가 됩니다.
2 일꾼 ② 설계자 (Algorithm Designer)
출제자가 만든 문제를 받아 "어떻게 풀지" 계획서(AlgorithmDesign)를 만듭니다 — 알고리즘 이름, 목표 시간/공간 복잡도, 지켜야 할 invariant, 주의할 함정(엣지 케이스). 원래 v0에서는 프로그래머가 이걸 다 떠안았는데, "설계"와 "구현"을 분리해서 각자 한 가지에 집중하게 했어요.
건축에서 설계사가 도면을 그리고 시공사가 짓듯이. 한 사람이 도면도 그리고 벽돌도 쌓으면 둘 다 어설퍼져요. IPE도 설계자가 "이건 다익스트라로, 시간복잡도 O(E log V), 음수 간선 주의" 같은 도면을 넘기면, 프로그래머는 그 도면대로 코딩에만 집중합니다.
3 일꾼 ③ 프로그래머 (Coder)
도면대로 정답 코드 두 벌을 짭니다 — 빠른 "정해(golden)"와 느리지만 확실한 "브루트포스(brute)". 일부러 두 벌을 만들어 나중에 서로 답을 대조합니다(13장에서 활용). 또 하나 중요한 점 — 프로그래머는 이전 시도의 실패를 구조화된 JSON으로 전달받아 같은 실수를 피합니다:
payload = {
"failure_mode": v.failure_mode.value, # 무엇이 틀렸나 (enum)
"invariant_violations": [...], # 어떤 수학 법칙 위반
"feedback": {
"target_node": ..., # 내가 고칠 차례인가
"actionable_hint": ..., # 구체적 수정 방향
"blocking_signature": ..., # 같은 실패 반복 감지
},
}
return "prev verification (structured JSON):\n" + json.dumps(payload, ...)
"막연히 다시 해봐"가 아니라 "3번 invariant가 이 입력에서 깨졌으니 거기를 고쳐"라고 콕 집어 알려줍니다. 그리고 누적된 교훈(lessons)도 함께 전달해 같은 함정에 두 번 안 빠지게 합니다.
4 일꾼 ④ 검사기 (Executor + Verifier) 핵심
2장의 검산이 여기서 돌아갑니다. 검사기는 ① 예시들을 실제로 실행해 답을 받아보고 ② 4장의 enum으로 골라낸 알고리즘별 검증기를 돌립니다. 어떤 검증기를 쓸지는 출제자가 박은 enum으로 정확히 연결돼요:
verifier = verifier_getter(state.target_algorithm) # enum → 해당 검증기
if verifier is not None:
violations = list(verifier.verify(spec=spec, design=...,
attempt=attempt, sample_outputs=outputs))
# 검증기가 '실제로' 검사한 예시 수도 센다 (장식 아님을 측정)
samples_engaged = verifier.count_engaged_samples(spec)
여기서 samples_engaged가 사이트의 그 99.1% 숫자입니다 — "검증기가 있다"가 아니라 "거의 모든 예시에서 실제로 검산이 작동했다"는 증거예요. 이렇게 모은 결과로 합격/불합격을 판정합니다(우선순위: 실행 실패 > 수학 위반 > 통과).
8. 그런데 — AI가 짠 코드를 그냥 실행해도 될까?
안 됩니다. AI가 만든 코드를 그대로 돌리면 파일을 지우거나 네트워크로 뭔가 보낼 수도 있어요. 그래서 검사기는 코드를 격리된 공간(sandbox)에서만 실행합니다. 환경에 따라 가장 강한 격리를 자동으로 고릅니다 — 4단계 fallback:
# macOS: T1(docker) → T2.5(sandbox-exec) → T3(rlimit)+경고
# Linux: T1(docker) → T2(nsjail) → T3(rlimit)
# 1) T1 Docker — 모든 OS에서 가장 강력 (네트워크 차단 + 읽기전용)
try:
from ipe.sandbox.docker_runner import DockerRunner
return DockerRunner()
except RuntimeError:
... # Docker 없으면 다음 단계로
# 4) T3 fallback — 최소한의 자원 제한(RLIMIT)이라도
return RlimitRunner()
정체불명의 화학 샘플을 맨손으로 만지지 않고 방호 장갑 낀 채 밀폐 후드 안에서 다루듯이. 가장 좋은 후드(Docker)가 없으면 차선책(sandbox-exec), 그것도 없으면 최소한의 장갑(RLIMIT)이라도 끼고 다룹니다. 맨손은 절대 없어요.
9. 불합격이면? — "책임자에게 정확히 돌려보낸다"
검사기가 똑똑한 진짜 이유는 "불합격"으로 끝이 아니라, 누구 잘못인지 콕 집어 돌려보낸다는 점. 측정으로 발견한 패턴이 코드에 그대로 박혀 있어요:
# 예시 답은 틀렸는데(SAMPLE_MISMATCH) 수학 invariant 는 전부 통과(violations 없음)?
# → 코드는 사실상 맞고 '출제자가 정답을 잘못 적은 것'이다.
if mode == FailureMode.SAMPLE_MISMATCH and not violations:
target = TargetNode.ARCHITECT # → 출제자에게 (코드 탓 아님!)
routed_hint = f"{hint} (verifier invariants all pass — "
f"likely architect expected_output error, regenerate spec)"
else:
target = TargetNode.CODER # 그 외엔 프로그래머가 코드 수정
그리고 라우터는 이 결정을 enum으로만 받아 기계적으로 분기합니다. 자유 문장 해석이 끼어들 틈이 없어요:
if v.overall_pass:
return "end_success" # 통과 → 끝
...
target = v.feedback.target_node
if target == TargetNode.ARCHITECT:
return "architect" # 출제자 다시
if target == TargetNode.DESIGNER:
return "designer" # 설계자 다시
return "coder" # 기본: 프로그래머 다시
"수학 검산은 다 맞는데 예시 답만 틀렸다" → 코드가 아니라 출제자가 예시 정답을 잘못 적었다는 강한 신호. 이 한 줄짜리 규칙이 까다롭던 Two Sum 문제군을 실제로 회복시켰습니다.
10. 무한루프와 비용은 어떻게 막나
되돌려보내기를 계속하다 같은 실패가 반복되면? 또는 너무 오래 끌면? IPE에는 두 안전장치가 있습니다. 라우터가 분기 전에 먼저 검사해요:
# 같은 '실패 지문(signature)'이 2번 이상 반복되면 = 제자리 맴돌이
same_sig_count = sum(1 for r in state.context.iterations
if r.blocking_signature == sig)
if same_sig_count >= OSCILLATION_THRESHOLD: # 임계값 = 2
return "end_oscillation" # 깔끔히 포기 (헛돌지 않음)
if state.iteration >= state.max_iterations:
return "end_budget" # 시도 횟수 초과 → 중단
같은 길에서 같은 곳에 두 번 막히면 "이 길은 안 되는구나" 하고 멈추는 거예요. 무한정 같은 벽에 머리를 박지 않습니다. 끝나는 방식도 success(성공) / oscillation(맴돌이) / budget(예산 초과) / schema_violation(서류 형식 오류)로 명확히 구분해서, 나중에 "왜 실패했는지"를 정확히 분석할 수 있습니다.
11. 통과한 문제는 어떻게 보관되나
검증을 통과하면 그 문제 한 세트를 실제 파일로 저장합니다. run_id별 폴더에 지문·정답 코드·검증 결과는 물론, 온라인 저지(코딩 시험 사이트) 형식과 호환되는 입력/출력 파일까지 떨어뜨려요:
outputs/<run_id>/
├── problem.md # 사람이 읽는 문제 (제목 + 지문 + 예시)
├── spec.json # 출제자 서류 (ProblemSpec)
├── design.json # 설계자 서류 (AlgorithmDesign)
├── attempt.py # 정답 코드 (golden)
├── verification.json # 검증 결과 (어떤 검증기가 통과했는지)
├── outcome.json # 최종 상태 + 몇 번 만에 됐는지
└── samples/
├── 1.in / 1.out # 채점용 입력·정답 (online judge 호환)
└── 2.in / 2.out
즉 결과물이 "화면에 한 번 보이고 끝"이 아니라, 그대로 시험에 낼 수 있는 자산으로 남습니다. (대량 측정 땐 디스크 부담 때문에 이 저장을 옵션으로 끌 수 있어요.)
12. "좋아졌다"를 느낌이 아니라 숫자로 — 측정 게이트
IPE 개발에는 철칙이 하나 있어요. 모든 변경은 "91% 통과율을 떨어뜨리지 않는다"는 측정 시험을 통과해야만 반영된다. 좋아지려다 오히려 나빠지면(over-correction) 즉시 되돌립니다 — 실제로 그렇게 되돌린 적도 있어요.
- 각 변경은 N≥3회 반복 측정 + 여러 알고리즘 교차 측정으로 검증
- 기준선(anchor)인 91.2%를 깎으면 머지 거부
- 추가 측정이 더 이상 의미 없으면(diminishing returns) 멈추고 동결(freeze)
이 숫자들(91.2% / 19종 / 99.1% / 1.07회)은 마케팅 문구가 아니라 측정 결과입니다. v0 초기엔 27%였던 게 여기까지 온 것도 매 단계 측정으로 확인하며 올라온 거예요. 자세한 여정은 Dashboard에 차트로 있습니다.
13. 한 발 더 — "검증기를 못 만드는 문제"는 어떻게 믿나 (Phase 3)
지금까지(0~12장)가 v1 — "정석이 분명한" 19개 알고리즘 파이프라인입니다. 그런데 수학 검증기는 그 19개에만 있어요. 알고리즘을 숨기거나 두세 기법을 섞은 어려운 문제(= 진짜 기업 코테)는 정석 검증기가 안 맞습니다. 그럼 그런 문제의 답은 어떻게 믿죠? Phase 3 (= v2, 아래 13~16장)가 이걸 풉니다 — 그리고 이건 더 이상 계획이 아니라 전부 구현돼 ipe/v2 에 배선된 코드예요(실 LLM 으로 한 바퀴 검증 완료).
축 1 — 차분(differential): 정해와 브루트포스를 같은 입력에 돌려 답을 대조합니다 (ICPC·IOI 출제자들의 실무):
g = run_code(runner, golden_code, inp, ...)
b = run_code(runner, brute_code, inp, ...)
# 둘 다 정상 종료 + 출력이 같을 때만 '일치'로 인정
agreed = g.status == "OK" and b.status == "OK" and g_out == b_out
축 2 — 변형 검사(metamorphic): 문제 종류와 무관한 보편 법칙을 봅니다. 예: "같은 입력을 다시 넣으면 같은 답이 나와야 한다"(결정성):
# determinism : 같은 입력 재실행 → 동일 출력
# (불안정한 정답지 = 채용 시험에서 치명적)
# well_formed : 정상 종료 + 빈 출력 아님 + 에러 누출(Traceback) 없음
종합 — 신뢰 등급(Tier): 세 축(수학·차분·변형)을 종합해 문제마다 신뢰 등급을 매기고, 기업용은 일정 등급 이상만 출하합니다:
if symbolic is TierAxis.PASS:
tier = Tier.A # 수학 검증 통과 → 완전 신뢰
elif symbolic is TierAxis.FAIL:
tier = Tier.C # 수학 검증 실패 → 출하 불가
elif diff_axis is TierAxis.PASS and meta_axis is TierAxis.PASS:
tier = Tier.B # 수학 검증기 없지만 차분+변형 통과 → 신뢰
else:
tier = Tier.C # 신호 부족 → 기업용 reject (폐기/강등)
정해·브루트가 지문을 똑같이 오해하면 둘 다 같은 오답을 내고 "일치"로 통과해 버립니다. 그래서 Phase 3는 두 코드를 서로 다른 출처에서 유도하고(정해=형식 명세, 브루트=서사 시나리오), 변형 검사를 더해 이 위험을 줄입니다. 그리고 핵심 — 19개 알고리즘으로 "새 검증(B등급)이 기존 수학 검증(A등급)과 같은 결론을 내는지" 실측해서, 신뢰를 주장이 아니라 측정으로 획득합니다. 이게 M1 단계에서 실측 완료됐고, 이후 M2~M6(병렬 합성·알고리즘 은닉·풀 채점셋·QA 게이트·기법 합성)까지 전부 구현됐어요.
이 검증 토대(차분·변형·등급) 위에서, v2는 한 발 더 나아갑니다 — 알고리즘 정체를 숨긴 진짜 코테 문제. 어떻게 만들어지는지 바로 아래에서.
v2 알고리즘을 숨긴 "진짜 코테 문제" 구현 완료
진짜 기업 코딩테스트는 "무슨 알고리즘을 써야 하는지조차 응시자가 알아내야" 합니다. 그래서 v2는 알고리즘을 현실 이야기 속에 숨겨요.
"다익스트라로 최단 경로 구하세요"는 답을 반쯤 알려준 셈이에요. 대신 "택배 기사가 여러 허브를 거쳐 가장 빨리 배송하려면?" 이라 물으면, 응시자는 먼저 "아, 이건 최단 경로 문제구나"를 스스로 간파해야 합니다. 알맹이(최단 경로)는 같지만 껍데기(택배 이야기)가 정체를 가립니다.
이 은닉을 세 일꾼이 분업합니다. 먼저 전략가(Strategist)가 "무엇을 숨길지"와 "어떤 현실 도메인으로 위장할지"를 정해요:
# typed StrategySeed 로 반환 (자유 글 파싱 없음):
# - reduction_core: 숨길 핵심 알고리즘 — solver 가 지문만 보고
# 바로 알아채지 못하게 할 대상
# - composition : 추가로 섞을 기법 1~2개 (난이도·위장 강화)
# - domain : 위장할 현실 도메인 (logistics, social-network …)
# - rationale : 이 위장이 왜 reduction_core 를 숨기는지 한 줄 근거
다음으로 형식담당(Formalizer)이 입출력 형식과 수학 불변식을 동결하고(여기는 정확성이 생명이라 더 신중한 모델 사용), 마지막으로 이야기꾼(Narrative)이 그 동결된 설계를 현실 지문으로 풉니다. 은닉 모드에서는 규칙이 단호해요:
# hidden=True (은닉, 기업용):
# reduction_core(숨은 알고리즘) 이름을 **절대 언급하지 말 것**.
# domain 의 자연스러운 상황으로 위장하되, 형식적으로는 정확히
# 그 알고리즘으로 환원되게.
# hidden=False (직접, 학습용): 알고리즘을 명시해도 됨 (토픽 드릴)
그런데 이야기로 포장하다 원래 문제가 변질되면? (예: 택배 이야기를 쓰다 조건이 바뀌어 더 이상 최단 경로가 아니게 되는 것.) 그래서 충실성 검사기(Faithfulness)가 "지문이 여전히 의도한 알고리즘으로 정확히 환원되는가"를 되짚고, 어긋나면 이야기만 다시 쓰게 합니다 — 사람 판단 없이 라우터가 결정론적으로:
def route_after_faithfulness(state):
f = state.faithfulness
if f is not None and f.faithful:
return "end_success" # 충실 → 출하
if state.iteration >= state.max_iterations:
return "end_faithfulness" # 예산 소진 → 실패 종료
return "regen" # 왜곡 → 이야기만 다시 (싼 반복)
핵심: 숨기되 망가뜨리지 않는다. 그리고 답의 신뢰는 위 13장의 차분·변형·등급(A/B/C) 검증을 그대로 물려받습니다.
15. 마지막 관문 — 네 명의 심사위원 + 자동 재수정
정답이 검증됐어도 문제 자체가 좋은 문제인지는 또 다른 얘기예요. 그래서 출하 전에 네 심사위원이 동시에 검토합니다(병렬 — 최종 품질 게이트라 정성판단이 좋은 Sonnet 모델로 승급). 실제 코드의 심사 항목 설명을 그대로 옮기면:
"ambiguity" # 모호성 — 지문·입출력 형식이 유일하게 해석되는가
"fairness" # 공정성 — 지문과 형식만으로 풀 수 있는가 (숨은 전제 X)
"leakage" # 유출 — 유명 문제(저지·교재)와 사실상 동일하지 않은가
"difficulty" # 난이도 일관성 — 사실상 trivial 하게 풀리는 모순 없는가
네 의견을 한 집계기가 모아 합격/불합격을 정합니다. 진짜 똑똑한 부분 — 불합격이면 버리지 않고, 지적사항을 들고 이야기를 다시 써서 재심사합니다(예산 안에서). 이미 끝낸 검증·채점셋은 보존한 채로요:
def route_after_qa(state):
r = state.qa_report
if r is None: return "end_qa" # 집계 불능
if r.overall_pass: return "end_success" # 합격 → 출하
if state.qa_routebacks < state.max_qa_routebacks:
return "routeback" # 지적 들고 이야기 재작성 → 재심사
return "end_qa" # 예산 소진 → 불합격 종료
v1의 "불합격 → 책임자에게 정확히 라우팅"(9장)과 같은 원리예요. 자유 판단이 아니라 typed 보고서 + 횟수 상한으로만 분기해, 무한 재시도 없이 "고치거나, 깔끔히 포기"합니다. 이 4심사 + 자동수정 루프가 실제 LLM 으로 한 바퀴 도는 것까지 검증됐습니다.
16. 그래서 어떻게 받아 쓰나 — 문제 은행 + API
문제 한 개를 잘 만드는 것과 실제로 갖다 쓰는 것은 다른 문제예요. v2엔 두 전달 경로가 생겼습니다.
① 문제 은행(batch): 시드(알고리즘 종류)를 전수로 × 여러 번 돌려, 검증 통과한 문제만 쌓아 "문제 은행"을 채웁니다 — 필요할 때 꺼내 쓰도록.
② HTTP API: 다른 서비스가 네트워크로 문제 생성을 요청하는 작은 웹 서버(FastAPI)가 있어요. 생성은 오래 걸리니 "접수증(job_id)만 먼저 주고, 나중에 결과를 조회"하는 방식입니다:
@app.get("/healthz") # 서버 살아있나
@app.post("/v1/problems/generate", status_code=202)
def generate(req):
...
return {"job_id": job.job_id} # 즉시 접수증 (202 Accepted)
@app.get("/v1/jobs/{job_id}") # 접수증으로 결과 조회
def get_job(job_id):
... # running / 완료(문제 패키지) / 404
같은 요청을 두 번 보내도 한 번만 처리하고(idempotency key), 동시 생성이 너무 많으면 "잠시 후 다시"(429)로 보호합니다. 컨테이너(Dockerfile.api)로도 띄울 수 있어요. — 다만 인증·다중 사용자 같은 본격 운영 기능은 아직 정리 국면입니다(과장 없이).
17. 딱 네 줄 요약
- IPE는 코딩 시험 문제를 자동으로 만드는 공장이다. 출제자→설계자→프로그래머→검사기 네 일꾼이 타입 고정 서류로 협업한다.
- 정답이 맞는지 AI에게 다시 묻지 않고, 알고리즘 수학 정의를 담은 코드로 직접 검산한다. 코드는 격리된 샌드박스에서 돌리고, 실패는 책임자에게 정확히 라우팅되며, 무한루프·예산은 자동 차단된다.
- 통과한 문제만 파일로 남기고, 모든 변경은 91% 측정 게이트를 통과해야 한다. 검증기 없는 어려운 문제는 정해↔브루트 대조 + 변형 검사 + 신뢰 등급(A/B/C)으로 믿는다.
- v2(전부 구현 완료)는 여기서 더 간다 — 알고리즘을 현실 이야기에 숨기고(전략가·형식담당·이야기꾼 + 충실성 검사), 네 심사위원이 모호성·공정성·유출·난이도를 보고 불합격이면 자동으로 고쳐 재심사한다. 완성된 문제는 문제 은행에 쌓고 HTTP API로 받아 쓴다. (인증 등 본격 운영은 정리 국면 — 과장 없이.)
더 깊이 → 측정 Dashboard · 소스 전체 (ipe/v1) · 설계 RFC