3줄 요약

  1. rmalde가 2026년 9월 20일 공개한 저장소다. GPT-6 Astra가 목표를 세우고 TypeSafe의 Jev가 그때그때 할 행동을 고르는 방식으로 마인크래프트 자바 1.16.5를 플레이한다.
  2. 가장 최근 실행인 nether-final-08은 빈 인벤토리로 시작해 8분 43.300초 만에 엔더 드래곤을 잡고 종료 포탈에 들어갔다. Jev 결정 131회, Astra 호출 35회, 사망은 없었다.
  3. 저장소가 함께 공개한 비교표에서 49분이 12분대로 줄어든 구간을 보면, 개선으로 열거한 항목이 모두 하네스를 손본 일이다. 완료된 보급 작업을 없애고, 반복 대기를 피하며, 물 위 이동 경로를 미리 측량하고, 취소한 경로의 옛 목표를 정리했다. 저장소는 이 숫자들이 시스템 전체를 한 번씩 돌려 비교한 값이며 모델 하나의 성능을 잰 값은 아니라고 여러 차례 명시했다.

무엇을 만든 저장소인가

작성자는 Ronak Malde다. 커밋은 “Add Astra and JEV Minecraft agent, native renderer, route tests, and verification tools” 하나뿐이고 파일은 238개다. 주 언어는 자바스크립트이며 라이선스 파일은 없다. 공개 네 시간 반이 지난 시점의 별은 77개다.

에이전트는 공식 바닐라 서버와 Mineflayer 위에서 동작한다. 게임 규칙이나 엔티티 상태를 바꾸지 않고, 선택한 행동은 모두 일반 플레이어 프로토콜을 거쳐 서버로 간다. 예외처럼 보이는 장치도 하나 있다. 서버의 드래곤 틱에 읽기 전용 관측 호출을 더해 드래곤 머리의 정확한 위치를 알려주는 자바 센서다. 이 센서는 아이템을 주거나 플레이어를 옮기거나 체력과 드래곤 AI를 건드리지 않고, HTTP 엔드포인트도 로컬에서만 열린다.1

녹화는 숨겨 둔 마인크래프트 정식 클라이언트가 맡는다. 로컬 프로토콜 미러가 봇의 상태를 이 클라이언트로 보내면, 클라이언트는 데스크톱 입력 없이 게임 화면을 그린다. 캡처는 데스크톱 화면을 찍지 않는다. 게임 프레임 버퍼를 직접 읽어 960 × 540 해상도, 초당 20프레임, 무음 영상으로 남긴다. 영상 위쪽에는 반투명 띠가 있어 모델 이름, 경과 시간, XYZ 좌표, 계획 모델이 세운 목표, 선택된 행동을 보여준다. 이 값들은 모두 실제 응답과 게임 이벤트에서 가져온 것이며 추론한 속내를 적지 않는다.1

시선 회전과 걷기에도 제한이 있다. 시선 회전은 축마다 초당 240도, 가속도는 초당 960도까지만 허용하는 연속 동작으로 이루어진다. 플레이어가 경로 방향을 20도 안으로 바라볼 때까지는 걷지 않는다.12

API 키는 구글 시크릿 매니저에서 읽어 프로세스 메모리에만 둔다. 일반적인 OPENROUTER_API_KEY 환경변수는 의도적으로 무시하는데, 관계없는 키가 섞여 들어가는 일을 막기 위해서다. 실행 기록에도 키를 남기지 않는다. 녹화 영상과 실행 증거는 git에서 제외했고, 저장소에 들어 있는 것은 코드, 테스트, 빌드 스크립트, 경로 데이터뿐이다.1

두 모델이 나눠 맡는 일

치비 서소영이 작은 접힌 쪽지를 아래쪽의 네모난 블록 인형에게 건네고, 인형은 바닥에 놓인 여러 장의 빈 카드 중 한 장으로 손을 뻗는 그림

계획을 맡는 모델은 openai/gpt-6-astra가 기본이고 openai/gpt-5.6-sol로 바꿀 수 있다. OpenRouter의 /api/v1/chat/completions로 호출하며 응답 형식은 JSON 객체로 고정한다. 추론 강도는 낮음으로 두고 최대 토큰은 1,800으로 정한다. 이 모델은 목표 문자열과 아이템 목표치를 내놓고, 이동 웨이포인트와 메모를 함께 붙인다.3

행동을 고르는 모델은 typesafe/jev-1.13이고 /api/alpha/decisions로 호출한다. 이쪽 요청에는 게임 상태를 JSON 문자열로 넣고 질문 하나를 choice 형식으로 보낸다. 선택지는 하네스가 만들어서 a0, a1, a2 같은 키에 설명을 달아 넘긴다. Jev가 목록에 없는 키를 돌려주면 코드가 예외를 던지고 그 결정을 버린다.3

Jev가 고를 수 있는 행동은 이동, 블록 한 개 채굴, 떨어진 아이템 수집, 제작, 상자 열기, 식사, 취침, 전투 상호작용이다. 실제 경로 탐색과 프로토콜 처리는 Mineflayer가 맡는다. 저장소는 이 방식을 구조화된 상태를 통한 제어라고 부르면서, 스크린샷을 읽거나 키를 하나씩 누르는 제어와 구분해 둔다.1

하네스는 게임 상태를 통째로 보내지도 않는다. compactObservation 함수는 48블록 안의 몹을 가까운 순서로 16마리까지, 24블록 안의 떨어진 아이템을 16개까지만 남긴다. 최근 행동은 마지막 다섯 개만 실어 보내고 핫바, 장비, 경험치, 녹화 상태는 아예 제거한다.3

선택지는 코드가 만들어 준다

구름이 밀려오는 쪽을 놀란 얼굴로 바라보며, 화살표가 그려진 카드 한 장만 두 손에 쥔 치비 서소영

policy.mjs의 selectUsefulOptions를 보면 선택의 폭이 상황에 따라 달라진다. 플레이어가 위험한 자리에 있으면 탈출 행동만 목록에 남는다. 이미 웨이포인트에 도착한 뒤라면 이동과 노 젓기를 후보에서 빼 버린다. 달리 할 일이 하나도 없을 때라야 대기 행동이 후보에 오른다.4

실패한 행동에는 쿨다운이 붙는다. 엔드에서는 1.5초, 이동과 설치 계열은 3초, 나머지는 12초 동안 같은 행동을 다시 제안하지 않는다.4

계획 모델을 다시 부르는 시점도 코드가 정해 둔다. 첫 호출 때와 쓸 만한 행동이 하나도 없을 때, 차원이나 준비 단계가 바뀔 때, 실패가 두 번 쌓이거나 웨이포인트에 도착했을 때, 그리고 마지막 호출로부터 120초가 지났을 때다. 행동 하나마다 계획을 새로 세우지 않는다.4

전투 행동 하나의 설명을 읽어 보면 모델과 코드가 각각 무엇을 맡는지 드러난다. 엔드 전투의 one_timed_bed는 이런 설명을 달고 목록에 오른다.

침대 공격 한 번을 준비한다. 자연 기둥 위에 침대를 놓고, 머리맡은 동쪽으로 향하게 한다. 드래곤이 기둥 높이보다 두 블록 이상 위에 있으면, 관측한 드래곤 머리가 머리맡에서 4.3블록 안으로 들어올 때까지 최대 16초 기다린다. 그런 뒤 지상 엄폐물에서 한 번 폭발시킨다. 근처에 브레스가 오면 중단한다.

Jev는 이 문장을 읽고 그 행동을 고를지 말지 정할 뿐이다. 코드는 침대를 놓을 위치를 미리 정해 두었고, 기다릴 시간과 중단할 조건까지 함께 적어 놓았다. 실행하는 동안에도 코드가 엄폐 위치를 벗어났는지, 침대가 부서졌는지, 브레스가 엄폐물 안으로 들어왔는지를 매 틱 확인하고, 조건이 깨지면 그 행동을 중단한다.5

기록은 이렇게 줄었다

키가 큰 모래시계 옆에 서서 훨씬 작은 모래시계 위에 한 손을 올린 채 미소 짓는 치비 서소영

저장소에는 서로 다른 세 번의 비교가 기록돼 있다. 먼저 첫 완주 recorded-06과 최적화를 거친 optimized-final-04의 비교다. 최적화 실행에서 계획을 맡은 모델은 Sol이었다.6

항목첫 완주최적화 실행
전체 영상49분 08초12분 36초
Jev 결정785회161회
계획 호출78회20회
의미 없는 대기215회0회
실패한 행동54회10회
사망1회0회

다음은 계획 모델만 Sol에서 Astra로 바꾼 비교다. 시드와 난이도는 같다.7

항목Sol 계획Astra 계획
전체 영상12분 36초12분 58초
종료 포탈 도달12분 03초12분 57초
Jev 결정161회167회
계획 호출20회8회
보트 관련 결정62회34회
실패한 행동10회4회

완주에는 22초가 더 걸렸지만, 계획 호출은 20회에서 8회로 줄었다. 저장소는 이 표도 두 시스템 전체를 각각 한 번씩 실행해 비교한 결과이며 모델 품질만 따로 잰 값은 아니라고 적었다. 실패 집계 기준이 도중에 바뀌었다는 점도 함께 밝혔다.7

마지막은 경로를 통째로 바꾼 결과다. 새 시드에서 네더를 지름길로 쓰는 경로를 미리 측량해 넣자, 이전 영상 14분 31.800초가 8분 43.300초로 짧아졌다. 저장소는 40% 단축이라고 적었다. 엔드 전투 구간만 떼어 보면 332초가 152초로 줄었다. 드래곤은 첫 착륙에서 침대 여섯 번으로 죽었고 플레이어는 체력을 가득 채운 채 종료 포탈에 들어갔다.1

같은 시드를 쓴 사람의 검증된 스피드런 기록은 2분 54.545초다. 저장소는 이 기록을 경로가 쓸 만하다는 증거로만 인용했고 속도 비교로 쓰지 않았다.2

실패 기록이 곧 설계 이력이다

optimization/NOTES.md에는 개발 도중 드러난 결함이 목록으로 남아 있다.8

  • 이미 도달한 웨이포인트인데도 의미 없는 이동 행동이 계속 제시됐다.
  • 평화 난이도인데 음식과 나무와 도구를 더 모았다.
  • 제작이 끝난 뒤에도 작업대를 놓았다가 다시 회수했다.
  • 물속에서 블록을 캐는 바람에 떨어진 아이템을 놓쳤다.
  • 보트 회수 행동을 골라도 플레이어가 내리기만 하고 보트를 두고 오는 경우가 있었다.
  • 다리 이동을 취소해도 경로 탐색기 안에 옛 복귀 목표가 남았고, 이동 루프는 새 목표를 확인하기 전에 그 낡은 목표 쪽으로 플레이어를 이동시켰다.

마지막 항목이 특히 길게 적혀 있다. optimized-final-01에서 플레이어가 해안을 반복해 오가다 멈췄는데, 원인은 경로 탐색기 안에 남아 있던 옛 목표였다. 모델이 새 계획을 내놓아도 그 목표를 지울 방법이 없었다. 저장소는 이 대목에 한 문장을 남겼다.

더 자세한 모델 지시만으로는 멈춰 있는 이동 목표나 사라진 제작 선택지를 고칠 수 없었다.

브레스 구름 사고의 기록도 구체적이다. optimized-final-02의 171번째 결정에서 플레이어는 구름 하나를 피해 동쪽으로 달아났다. 그런데 그곳도 다른 구름 안이었다. 172번째 결정에서는 다시 첫 구름을 통과해 돌아왔다. 수정된 탈출 행동은 16방향으로 지면과 머리 높이의 여유를 잰다. 그리고 출발점이 들어 있지 않은 구름을 지나는 경로를 버린 다음, 남은 경로를 안전한 출구와 노출 정도로 정렬해 Jev에게 세 개만 제시한다.8

전투 쪽에도 같은 기록이 있다. 침대 한 개가 주는 피해는 로컬 시험에서 11에서 46으로 올랐는데, 폭발 전에 먼저 조준하고 유효 구간을 좁힌 결과였다.

연습 실행에서는 집 벽 너머로 침대를 주우려다 5초 수거 제한에 걸렸다. 이후 하네스는 침대를 부수기 전에 가까이 접근하고, 수거를 15초까지 기다리도록 바뀌었다. 마그마 블록을 밟고 죽은 일도 있어서, 경로에서 마그마를 제외하고 마그마를 밟는 동안에는 웅크린 상태를 유지하도록 고쳤다.12

증거를 다루는 규칙

한 손으로 돋보기를 들어 봉인된 기록 두루마리를 살피면서, 다른 손으로는 미리 준비된 도구가 담긴 쟁반을 단호히 밀어내는 치비 서소영

이 저장소는 성과를 과장하지 않으려는 설명을 유난히 여럿 적어 두었다.

승리 판정부터 까다롭다. victory.json은 드래곤 사망 증거와 종료 포탈 이벤트를 함께 요구한다. 사망 증거로 인정하는 것은 처치 도전과제 하나, 또는 서버가 보고한 체력 0과 드래곤 사망 단계의 조합이다. 침대로 잡으면 도전과제가 빠질 수 있어서 최종 검토는 세계 파일의 DragonFight 상태와 영상까지 확인한다. 자바 1.16.5의 종료 이벤트는 숫자 4로 들어온다.1

시험과 실제 완주를 구분해 두는 문장도 여럿이다.1

  • combat-lab은 별도의 시험 서버이며 여기서 나온 결과는 완주로 인정하지 않는다.
  • combat-probe.mjs는 어느 모델도 호출하지 않는 로컬 시험 드라이버이며, 모델이 제어한 실행으로 제시해서는 안 된다고 적혀 있다.
  • 코드 수정 때문에 캡처 파일이 여러 개로 나뉜 녹화는 사망과 중단을 반드시 보고해야 하고, 사망 없는 실행이나 중단 없는 실행으로 불러서는 안 된다.
  • 준비된 아이템과 위치를 쓴 전투 시험은 전투 기능만 증명한다고 명시한다.

시드 선택에도 같은 태도가 있다. 이 시드에는 마을이 있고, 상자 세 개에서 흑요석 21개와 철 곡괭이를 얻을 수 있다. 자연 상태로 활성화된 엔드 포탈의 좌표는 (-1130, 34, 856)이다. 저장소는 이를 쓸 만한 스피드런 시드라고만 말하고 가장 쉬운 시드라는 증명은 아니라고 덧붙였다. 측량은 같은 시드의 별도 세계에서 했고 최종 녹화 세계에서는 준비 명령을 쓰지 않는다.12

가장 흥미로운 지점

기각된 행동 하나가 이 저장소의 성격을 잘 보여준다. 별도 실험실에서 엔드 포탈로 길게 떨어지면 낙하 피해가 엔드까지 따라온다는 점을 확인했고, 이 행동은 채택하지 않았다. 녹화된 실행에서는 짧게 확인한 뒤 아래로 파 내려가는 방법을 썼다.1

이 결정을 내린 쪽은 사람이다. 사람이 시험을 설계하고 결과를 보고 행동 목록에서 그 선택지를 지웠다. Jev는 지워진 선택지를 고를 수 없으니 그 실수를 저지를 기회도 없다.

그러므로 “Jev가 모든 플레이어 행동을 고른다"는 저장소의 문장은 그대로만 읽기 어렵다. 행동을 고르는 일은 분명 모델이 한다. 그런데 그 목록에 무엇을 올릴지는 코드가 남김없이 정해 둔다. 8분 43초라는 기록에서 모델의 몫과 하네스의 몫을 따로 계산하려는 시도는 그래서 어려워진다. 저장소가 세 번의 비교마다 시스템 전체의 비교라고 일러둔 이유도 여기에 있을 것이다.

같은 회사가 만든 Jev를 다룬 앞선 두 편, System One 모델 소개와 도구 선택 비교 실험을 함께 읽으면 이 저장소가 Jev에게 무엇을 맡기고 무엇을 맡기지 않았는지가 더 분명해진다.

출처

Ronak Malde, rmalde/minecraft-agent (2026년 9월 20일 공개) 원문: https://github.com/rmalde/minecraft-agent

원문 저장소에는 인용할 만한 이미지가 없어서(녹화와 검증 산출물이 git에서 제외되어 있다) 본문 삽화는 치비 서소영 라인아트로 대신했다.9


  1. README.md. 최신 실행 결과, 모델 역할, 자격 증명 처리, 증거 규칙, 실행 방법이 모두 이 문서에 있다. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. optimization/nether/REVIEW.md. 네더 경로 측량, 카메라 제어, 연습 실행의 실패와 수정 기록. ↩︎ ↩︎ ↩︎ ↩︎

  3. models.mjs. plan, decide, compactObservation 세 함수가 두 모델 호출과 상태 압축을 담당한다. ↩︎ ↩︎ ↩︎

  4. optimization/policy.mjs. selectUsefulOptions, failureCooldown, planTrigger, stageFor. ↩︎ ↩︎ ↩︎

  5. end-combat.mjs. one_timed_bed와 탈출 행동의 설명 문자열, 그리고 중단 조건. ↩︎

  6. optimization/COMPARISON.md. ↩︎

  7. optimization/pass-2/RESULT.md. ↩︎ ↩︎

  8. optimization/NOTES.md. ↩︎ ↩︎

  9. 삽화는 블로그 글 「느낌적인 느낌을 숫자로 옮기는 일」의 치비 서소영 라인아트를 참조하여 gpt-image-2.5-flare image-to-image로 생성했다. ↩︎