Prior Work Deep-Dive · 소스 코드 근거

baseline1 (astroseger) — 파일을 편집하는 세계 모델 에이전트

이 페이지의 모든 시그니처와 인용은 arc-3-agents-baseline1/secure_baseline1 소스에서 그대로 가져온 것이다. 추측으로 채운 부분은 없으며, 불확실한 곳은 원문을 그대로 인용했다.

01

시스템 한 줄 정의

샌드박스 없는 Codex CLI를 파일 편집 에이전트로 쓰고, 세계 모델을 네 개의 영속 파일로 강제하는 시스템이다. 외부 컨트롤러(agent.py)가 상황에 맞는 프롬프트를 Codex에 밀어 넣으면, Codex는 게임을 직접 플레이하는 게 아니라 작업 디렉토리 안의 world_model_engine.py(동역학), world_model_state_io.py(상태 복원 + 렌더링), world_model_main_planner.py(플래너), world_model.md(텍스트 모델)를 계속 편집한다. 모델이 맞는지는 사람이 아니라 verify_world_model.py가 판정한다 — 기록된 모든 attempt를 처음부터 시뮬레이션해서 렌더링된 ASCII 프레임이 실제 관측과 픽셀 단위로 완전히 일치해야 통과다.

AGENT.md의 자기 설명(원문): “Our agent is based on unsandboxed Codex. … agent.py is the external controller. It prepares the run directory, starts Codex, sends prompts to Codex, runs the client, inspects progress, and decides which prompt to send next.” 격리는 Docker 컨테이너 2개(에이전트/서버)와 client–server 경계로 처리해서, 에이전트가 arc_agi 라이브러리(게임 구현)를 훔쳐볼 수 없게 한다.

02

파이프라인 플로우차트

바깥 루프는 컨트롤러의 프롬프트 상태머신이고, 안쪽 루프는 Codex가 수행하는 “예측 → 실행 → 대조 → 수리” 사이클이다. main_prompt.md의 Action discipline 절이 안쪽 루프를 문장으로 강제한다: “predict the settled result of the next action … if mismatch exists, stop and repair the model. Never continue blindly after a mismatch.”

controller — agent.py :: SimpleAgent.iteration_loop() 세션 디렉토리를 검사(inspect_sessions)해서 game-over / 새 레벨 / 정지(stuck) / 정상 진행을 판별하고, 그에 맞는 프롬프트 묶음을 선택
↓ send_prompt / send_pgroup
main_prompt.md + 상황별 프롬프트 → Codex CLI “세계 모델을 만들고, 모든 행동은 그 모델로 계획하라”는 계약 + 지금 상황에 대한 지시
↓ Codex가 파일 편집
world model = 4개의 영속 파일 world_model_engine.py · world_model_state_io.py · world_model_main_planner.py · world_model.md (+ 부분관측 레벨이면 initial_full_frames/level_N.txt)
↓ python3 verify_world_model.py
verify_world_model.py — 전 이력 재생 검증 레벨 1..N의 모든 attempt에 대해: 초기 상태 복원 → 엔진으로 전 스텝 시뮬레이션 → 매 스텝 status와 렌더링 프레임을 실측과 np.array_equal로 대조
↓ 불일치 시 VerificationMismatchError
mismatch artifacts (mismatch_frames/<n>/) simulated_frame.txt/.png · mismatch_region.png(불일치 주변만, 나머지는 갈색) · mismatch_as_magneta.png(불일치 픽셀을 마젠타로) — 에러 메시지에 절대경로가 박혀서 Codex가 눈으로 검사하게 됨
↓ 모델 통과 후 실제 게임에 적용
plan_executor.py action1 action2 … 각 액션마다 (1) 엔진으로 예측 → (2) client.py move 실행 → (3) 관측 프레임과 대조. 하나라도 다르면 즉시 중단 + artifacts 저장
↻ 컨트롤러 다음 iteration: simplification 3×3 pgroup / critique 서브에이전트 / trouble 에스컬레이션

critique 루프: main_prompt.md는 critique_prompt.md를 그대로 넣은 “Generalization-critique subagent”를 요구한다 — 모델이 복잡해질 때마다, 스페셜 케이스를 추가하고 싶을 때마다, 레벨을 풀었다고 선언하기 전마다 적대적 리뷰를 돌리라는 지시다. 크리틱의 출력 형식도 고정되어 있다: Findings / What Seems Sound / Bottom Line.

03

핵심 함수 카드 — 시그니처는 소스 원문

world_model_engine(state, action) → (new_state, game_status)

src/agent/workspace_init/world_model_engine.py — 제공되는 스텁 전문 (8줄)

from game_status import RUNNING

def world_model_engine(state: dict, action: dict) -> tuple[dict, str]:
    """Placeholder world-model dynamics."""
    return dict(state), RUNNING

단일 attempt 안의 게임 동역학. 에이전트가 이 스텁을 실제 규칙으로 채운다. state"level" 필드가 의무인 dict, actionname(+ ACTION6일 때 x,y) dict. 반환 status는 RUNNING | LEVEL_COMPLETED | GAME_OVER 셋 중 하나.

입력 → 출력 예시

world_model_engine(
    {"level": 1, "avatar": (12, 40), "lives": 3, ...},   # 에이전트가 정의한 내부 상태
    {"name": "ACTION6", "x": 15, "y": 47},
)
# -> ({"level": 1, "avatar": (15, 47), "lives": 3, ...}, "RUNNING")
# 목표 도달 스텝이면 -> (terminal_state, "LEVEL_COMPLETED")

initial_state_reconstruction(level_index, initial_frame) → dict

src/agent/workspace_init/world_model_state_io.py

def initial_state_reconstruction(level_index: int, initial_frame: np.ndarray) -> dict:
    """Placeholder initial-state reconstruction."""
    return {"level": int(level_index)}

레벨의 초기 관측 프레임에서 내부 상태를 복원하는 유일한 진입점. 파일 로딩은 부분관측 레벨의 load_initial_full_frame(level_index) 단 하나만 허용 (main_prompt 원문: “This rule is strict and non-negotiable.”). 이후 임의 시점 상태는 별도 함수가 아니라 “초기 상태 + 엔진으로 액션 재생”으로만 만든다.

입력 → 출력 예시

initial_state_reconstruction(2, frame)   # frame: (64,64) int16, 값 0-15
# -> {"level": 2, "walls": {...}, "avatar": (7, 9), "baseline_lives": 3, ...}
#    (죽음-리셋이 있는 게임이면 fresh-attempt 재구축용 baseline 값도 여기서 심는다)

state_renderer(state) → np.ndarray  /  apply_render_overrides(frame, state, level_index, attempt_index, step_count)

src/agent/workspace_init/world_model_state_io.py

def state_renderer(state: dict) -> np.ndarray:
    """Placeholder renderer."""
    return np.zeros((64, 64), dtype=np.int16)

def apply_render_overrides(frame, state, level_index, attempt_index, step_count) -> np.ndarray:
    """Verification-only escape hatch for unresolved frame-specific mismatches."""
    return frame

내부 상태 → 기대 ASCII 프레임(64×64 int16). 검증이 이 함수를 통해서만 가능하므로 모델의 “수용 기준”이 곧 렌더러 일치다. apply_render_overrides는 설명 못 한 시각 디테일을 임시로 덮는 최후의 패치 훅인데, 검증기가 이 훅이 프레임을 바꾸면 경고를 출력한다: “treat this as temporary and as a clue to a missing puzzle mechanic”.

입력 → 출력 예시

state_renderer({"level": 1, "avatar": (15, 47), ...})
# -> array([[0,0,...],[...]], dtype=int16)  # shape (64,64), 실측 프레임과 완전 일치해야 통과

verify_world_model.py — _replay_attempt(…) 과 VerificationMismatchError

src/agent/workspace_init/verify_world_model.py

class VerificationMismatchError(AssertionError):
    """Expected verification mismatch; distinct from world-model implementation failures."""

# 검증 본체: 초기 프레임 렌더 대조 -> 매 스텝 (status, frame) 대조
state, game_status, rendered = _predict_step(state, step, attempt_index, index + 1)
if game_status != step["observed_status"]: _raise_status_mismatch(...)
if not np.array_equal(rendered, step["final_frame"]): _raise_frame_mismatch(...)

레벨 1..N 전 attempt를 재생 검증한다. 프레임이 하나라도 다르면 아티팩트 5종을 저장하고 그 경로를 예외 메시지에 담아 던진다. 전체 검증에 TIMEOUT_SECONDS = 180 제한이 있고 초과하면 “Your world model is too slow, please consider refactoring it.”이 뜬다.

실패 시 출력 예시 (예외 메시지 형식, 소스의 문자열 그대로)

VerificationMismatchError:
Model level 3, replay level 2, attempt 1, replay, step 14: rendered frame mismatch.
real ascii: .../client/session/level_2_attempt_1/step_0014_final.txt
real png: .../step_0014_final.png
simulated ascii: .../mismatch_frames/7/simulated_frame.txt (predicted frame as text)
simulated png: .../mismatch_frames/7/simulated_frame.png (predicted frame rendered)
mismatch region png: .../mismatch_frames/7/mismatch_region.png (localized mismatch neighborhoods)
mismatch as magneta png: .../mismatch_frames/7/mismatch_as_magneta.png (full frame with magenta mismatch pixels)

“magneta”는 소스의 오타를 그대로 옮긴 것이다 — 파일명, 함수명, 메시지 모두 magneta로 일관되게 쓰여 있다.

save_mismatch_region_png_v1(real_frame, predicted_frame, output_png_path, scale) → Path

src/agent/workspace_init/frame_plot_lib.py — MISMATCH_RADIUS = 7, BROWN_RGB = (139, 69, 19), MAGENTA_RGB = (255, 0, 255)

def save_mismatch_region_png_v1(real_frame, predicted_frame, output_png_path, scale=DEFAULT_PNG_SCALE) -> Path:
    # Shows only neighborhoods around mismatch pixels; everything else is plotted in brown,
    # a color outside the game palette. (소스 주석 원문)

def save_mismatch_as_magneta_png_v2(real_frame, predicted_frame, output_png_path, scale=DEFAULT_PNG_SCALE) -> Path:
    # Paints exact mismatch pixels as magenta on the full real frame.

불일치 시각화 두 방식. v1은 불일치 픽셀 반경 7 원 안쪽만 실제 색으로 남기고 나머지를 전부 게임 팔레트 밖의 갈색으로 칠해 “어디가 다른가”에 시선을 고정시킨다. v2는 실제 프레임 위에 불일치 픽셀만 마젠타로 찍는다 — 불일치가 흩어져 있을 때 유용.

입력 → 출력 예시

save_mismatch_region_png_v1(real, predicted, Path("mismatch_frames/7/mismatch_region.png"))
# -> PosixPath('mismatch_frames/7/mismatch_region.png')
#    (불일치 셀 주변 반경 7만 원색, 배경은 갈색(139,69,19)인 PNG)

plan_executor.py — 예측-실행-대조 루프

src/agent/workspace_init/plan_executor.py — main() 핵심 루프 (원문 발췌)

for action in actions:
    predicted_state, predicted_status = world_model_engine(current_state, action)
    predicted_frame = state_renderer(predicted_state) if predicted_status == RUNNING else None
    subprocess.run(_client_command(action), cwd=CLIENT_DIR, check=True)   # 실제 게임에 실행
    ...
    if actual_status != predicted_status:
        raise AssertionError(f"plan_executor.py: status mismatch after {format_action(action)}: ...")
    ...
    if predicted_frame is None or not np.array_equal(predicted_frame, observed_frame):
        _raise_frame_mismatch(...)   # artifacts 저장 후 AssertionError

계획된 액션 열을 모델과 실제 게임 양쪽에서 동시에 실행하는 유일한 통로. 매 스텝 예측을 먼저 확정한 뒤에야 클라이언트를 호출하므로, “예측 없는 실 액션 금지” 규칙이 코드 수준에서 강제된다. RESET은 지원하지 않는다(명시적 거부).

입력 → 출력 예시

$ python3 plan_executor.py ACTION1 ACTION1 '{"name": "ACTION6", "x": 15, "y": 47}'
plan_executor.py: running ACTION1
plan_executor.py: running ACTION1
plan_executor.py: running ACTION6 x=15 y=47
plan_executor.py: sequence executed without mismatch      # 또는 mismatch에서 즉시 중단 + artifact 경로 출력

read_all_attempts_for_level(level_index, session_dir=None) → list[Attempt]

src/agent/workspace_init/session_tools.py — Attempt / AttemptStep TypedDict (원문)

class Attempt(TypedDict):
    level_index: int;  attempt_index: int;  path: str
    initial_frame: np.ndarray;  initial_metadata: dict[str, Any]
    initial_frame_filename: str;  initial_frame_png_filename: str
    steps: list[AttemptStep];  status: str

class AttemptStep(TypedDict):
    action: dict[str, Any];  final_frame: np.ndarray;  metadata: dict[str, Any]
    final_frame_filename: str;  final_frame_png_filename: str
    intermediate_frame_filenames: list[str];  intermediate_frame_png_filenames: list[str]
    observed_status: str

클라이언트가 client/session/level_<N>_attempt_<A>/에 남긴 기록(스텝별 metadata JSON + ASCII/PNG 프레임)을 읽어 구조화한다. ASCII 프레임은 한 글자 = 16진수 한 자리로 파싱된다(int(char, 16) → int16 배열). 이것이 검증기와 상태 복원의 유일한 데이터 소스다.

입력 → 출력 예시

read_all_attempts_for_level(2)
# -> [ {"level_index": 2, "attempt_index": 1, "status": "GAME_OVER",
#      "initial_frame": array(64x64), "steps": [ {"action": {"name": "ACTION1"},
#      "final_frame": array(64x64), "observed_status": "RUNNING", ...}, ...]}, ... ]

planner(state) → list[dict] | None

src/agent/workspace_init/world_model_main_planner.py — 스텁 전문

def planner(state: dict) -> list[dict] | None:
    """Placeholder planner."""
    return None

내부 세계 모델 상태에서 레벨 완료까지의 액션 열을 찾는다. 못 찾으면 None. run_main_planner.py는 반환된 계획을 반드시 엔진으로 재시뮬레이션해서 LEVEL_COMPLETED에 도달하는지 자체 검증한다(도달 못 하면 AssertionError) — “모델 밖 플래닝 금지”가 여기서도 코드로 강제된다. 보조 플래너는 planner(state, goal=None) 형태로 goal dict를 받을 수 있다.

입력 → 출력 예시

planner({"level": 1, "avatar": (12, 40), ...})
# -> [{"name": "ACTION1"}, {"name": "ACTION1"}, {"name": "ACTION6", "x": 15, "y": 47}]
# 도달 불가면 -> None
04

상황별 프롬프트 상태머신 (prompts/*.txt 21개)

컨트롤러는 매 iteration마다 세션 디렉토리만 보고 상태를 판별한다. LLM의 자기 보고를 믿지 않고 파일시스템이 곧 진실이다. 분기는 agent.py :: iteration_loop() 네 갈래: ① game-over → reset_protocol, ② 레벨이 바뀜(레벨 1 제외) → new_level_protocol, ③ 지난 iteration 이후 스텝 수가 그대로 → stuck_protocol, ④ 그 외 → normal_continuation_protocol. 거기에 reset 시 스텝 누적량으로 trouble 1/2 에스컬레이션이 얹힌다 (레벨당 +100스텝마다 trouble1, +200스텝마다 trouble2 — trouble2는 runner.new_session()으로 Codex 세션까지 갈아엎고 main_prompt부터 다시 준다).

프롬프트 파일컨트롤러가 보내는 시점
main_prompt.md시작 시(게임 초기 화면 출력과 함께), recovery 시, trouble2 시(새 세션의 서두로)
continuation_string.txt거의 모든 프롬프트의 꼬리표: “LEVEL_COMPLETED나 GAME_OVER까지 멈추지 말고 계속하라”
continuation_level1.txt / continuation_l2.txt정상 진행 시 계속 지시. 레벨 1이면 “몇 수만 탐색하면 답이 보이는 단순 퍼즐” 버전, 레벨 2+면 “세계 모델 관점에서 생각하고 플래너를 갱신하라” 버전
light_simplification_level1.txt레벨 1에서 매 continuation/reset/stuck 직전에 — 가벼운 단순화 리뷰 1발
world_engine_simplification_step1–3.txt
world_model_state_io_simplification_step1–3.txt
world_model_planner.txt
레벨 2+에서 매 continuation/reset/stuck 직전에 7연발 pgroup(“hard refactoring”): 엔진 3단계 + state_io 3단계 + 플래너 1단계 압축·일반화 지시
on_new_level_v1.txt레벨 인덱스가 바뀐 첫 iteration: 새 레벨 첫 프레임의 전 요소 인벤토리(기지/신규/불확실 분류) 지시
death_prompt.txtGAME_OVER로 reset_protocol에 들어갈 때 최우선 전송: “왜 죽었는지 모델에 편입하라, 애니메이션 PNG 검사는 의무”
trouble1_prompt.txt / trouble1_prompt_level1.txt같은 레벨에서 지난 trouble1 이후 100스텝 넘게 소모 후 reset될 때
trouble2_prompt.txt / trouble2_prompt_level1.txt200스텝 넘게 소모 후 reset될 때 — Codex 세션을 새로 만들고 main_prompt + trouble2로 재시동 (“터널 비전에 빠졌을 수 있다”)
stuck_reminder_prompt.txt한 iteration 동안 게임 스텝이 0일 때: “움직이지 않으면 진다”. 그 후에도 무진행이면 stop_condition이 런 종료
recovery_prompt.txt--recovery 모드 재기동 시: 모델·로그·게임 상태를 리뷰하고 상황 보고 지시
reset_prompt.txt, light_simplification.txt파일은 존재하지만 agent.py의 load_prompt 호출 목록에는 없음 — 현 컨트롤러 경로에서는 미사용으로 보인다 (정직 고지)

종료 조건(stop_condition): 현재 레벨 스텝 ≥ 1500, stuck 프로토콜 이후에도 무진행, 게임 클리어, 또는 현재 레벨을 찾을 수 없음.

05

우리가 채택한 것 / 채택하지 않은 것

✓ 채택

  • localize → mismatch() — 예측 프레임과 실측 프레임의 차이를 국소화해 들이미는 mismatch artifact 발상. baseline1의 save_mismatch_region_png_v1/_raise_frame_mismatch가 우리 엔진의 mismatch 국소화 프리미티브의 원형이다.
  • critique → sleep criticcritique_prompt.md의 적대적 일반화 리뷰(“레벨 특이 암기를 숨기고 있지 않은가”)를 우리는 sleep 단계의 크리틱으로 옮겼다.
  • curriculum → 프롬프트 블록 A — main_prompt.md의 “레벨은 커리큘럼이다: 이후 레벨은 이전 메카닉의 확장이므로 레벨 N 모델은 1..N 전부에 유효해야 한다” 절을 우리 프롬프트 블록 A로 흡수했다.

✗ 비채택

  • Codex 파일 편집 오케스트레이션 — 세계 모델을 디스크 위 .py 파일 4개로 두고 CLI 에이전트가 편집하게 하는 구조 전체. 우리는 엔진 fork가 실행 가능한 세계 모델 역할을 하므로 별도 파일 계층이 필요 없다.
  • Docker 격리 체계 — unsandboxed Codex를 안전하게 돌리기 위한 에이전트/프록시/서버 3컨테이너 구조(Dockerfile.agent/proxy/server). 우리 실행 모델에는 해당 없음.