Files
factorio-ai-agent/docs/plan.md
kswdev0 153f02f5e9 feat: 메모리 섹션 추가 및 상태 요약 개선
- `_append_memory_sections` 함수를 추가하여 압축 모드에서 기억된 광맥과 마지막 행동 정보를 상태 요약에 포함하도록 개선
- `main.py`에서 상태 요약 생성 시 메모리 섹션을 자동으로 추가하여 AI의 의사결정에 필요한 정보를 제공
- `README.md`에 새로운 메모리 기능 및 사용 방법에 대한 설명 추가
- `state_reader.py`에서 인벤토리 판독 로직을 개선하여 캐시 사용 조건을 명확히 하여 안정성 향상
2026-03-26 10:45:30 +09:00

13 KiB

채굴 시 mining_state 반복 설정 제거 (우클릭 유지)

문제

  • mine_resource 내부에서 약 0.1초마다 update_selected_entity + mining_state = { mining = true, ... }를 RCON으로 재전송하고 있었음.
  • 플레이어는 보통 우클릭을 누른 채 유지하는데, 스크립트는 매번 상태를 다시 써서 뗐다 누르는(re-trigger) 것에 가까운 동작이 됨.

변경

  • 이동·근처 광물 확인 후, 채굴 루프 진입 직전에 한 번만 mining_state 설정.
  • 루프에서는 sleep과 인벤토리/채굴 진행률 읽기만 수행. 종료·목표 달성 시에만 mining = false.

범위

  • action_executor.pymine_resource
  • README.md — 동작 설명 한 줄 보강

채굴 실패 시 제외 좌표 반복 버그 수정 계획

문제 재현/관찰

  • mine_resource에서 실패한 타일을 failed_positions에 추가한 뒤 Lua에 exclude 테이블로 전달하지만, 다음 시도에서도 동일 좌표(예: 388,2)로 다시 이동하는 로그가 발생합니다.

원인 후보

  • Lua에서 제외 판정에 쓰는 좌표 키가 string.format("%.0f,%.0f", ...) 기반인 반면, Python에서 exclude["{fx:.0f},{fy:.0f}"]를 만들 때 반올림/절삭 방식이 Lua와 1:1로 일치하지 않는 케이스가 있을 수 있습니다.
  • 이 경우 exclude[key]가 항상 false가 되어, Lua가 “가장 가까운 광석”을 계속 같은 엔티티로 반환할 수 있습니다.

변경 목표

  1. failed_positions를 “Lua 키 생성과 동일한 정수 타일 좌표(tx, ty)”로만 저장합니다.
  2. Lua에서 후보 광석 엔티티를 검사할 때도 정수 타일 좌표를 계산해 키/반환/마이닝 좌표에 일관되게 사용합니다.
  3. 그 결과, 제외한 좌표는 다음 루프에서 절대로 다시 선택되지 않도록 보장합니다.

구현 범위

  • action_executor.py
    • mine_resource 내부에서 좌표 처리 로직을 정수 타일 기반으로 통일
    • Lua 반환값을 tx,ty 정수 문자열로 변경하고 Python 파싱을 이에 맞춤

README 업데이트 계획

  • 채굴 제외(exclude) 로직이 “정수 타일 키 기반으로 통일”되도록 README의 기술/동작 설명(또는 체크리스트)을 업데이트합니다.

자원 패치 중심 오차로 인한 채굴 실패 완화 계획

문제 관찰

  • scan_resources()가 패치의 평균 중심(center_x/y)을 추천 좌표로 사용하면서, 플레이어가 해당 좌표로 이동한 직후 mine_resource의 엔티티 탐색 반경 안에서 실제 자원 엔티티를 찾지 못해 실패하는 케이스가 발생할 수 있음.

근거(팩토리오 Lua API)

  • LuaInventory.get_contents()dictionary[string -> uint]를 반환하므로, 인벤토리 판독은 name -> count 형태로 처리하는 것이 맞다.
  • 자원 엔티티의 e.position은 부동소수(MapPosition) 좌표이므로, 타일 중심/평균 중심과 실제 엔티티 좌표 간 오차가 생길 수 있다.

변경 목표

  1. scan_resources() 결과에 패치 대표 좌표를 평균 중심뿐 아니라 플레이어 기준 가장 가까운 실제 엔티티 좌표(앵커 anchor_x/yanchor_tile_x/y)로 함께 제공
  2. summarize_for_ai()에서는 평균 중심 대신 앵커 좌표 기반으로 거리/추천을 계산
  3. mine_resource 후보 엔티티 탐색 반경을 더 크게 잡아(행동 레벨) 이동 직후 실패를 흡수

구현 범위

  • state_reader.py
    • scan_resources()anchor_x/yanchor_tile_x/y 추가
    • summarize_for_ai() 거리 계산을 앵커 우선으로 변경
  • action_executor.py
    • mine_resource에서 후보 엔티티 탐색 반경을 80250으로 확장

GLM 응답 지연 원인 계측/완화 계획

문제 관찰

  • ai_planner.py에서 GLM 호출이 [GLM] 생각 중... 이후 30초~1분 이상 지연되는 현상 발생
  • JSON 파싱 실패로 인한 재시도는 가끔 발생(하지만 느림의 주 원인이 아닐 가능성도 있음)

1단계(증거 수집)

  • ai_planner.py_call_glm()에 타이밍 로그 추가
    • total: 요청 시작~콘텐츠 반환까지 전체 소요
    • http_read: HTTP 응답 본문 수신까지 소요
    • json_parse: 응답 JSON 파싱 시간
    • prompt_chars/system_chars/max_tokens: 입력 크기 동시 기록

2단계(완화: 로그 확인 후)

  • json_parse가 아니라 http_read/total이 큰 경우:
    • max_tokens를 우선 크게 줄여 생성 길이 상한 조정
    • SYSTEM_PROMPT/state_summary 길이를 더 강하게 제한
  • json_parse가 큰 경우:
    • JSON 파싱/복구 로직 비용 또는 응답 형식 편차 원인 재점검

README 업데이트

  • README.md주의사항/실행 섹션에 GLM 지연 계측 로그 출력 방식 및 파라미터 조정 힌트를 반영

Cursor Hooks - Windows sessionStart 앱 선택창 원인/해결 계획

문제 재현/관찰

  • Cursor @cursor.hooks 로그에서 sessionStart 훅 실행 중 ./hooks/session-start가 stdout JSON을 내지 못하거나, Windows에서 앱 선택창이 뜨는 현상이 관찰됨

원인 후보

  • Windows에서 확장자 없는 ./hooks/session-start를 실행할 때 bash로 해석되지 않아, OS가 실행 대신 “어떤 앱으로 열지”를 요청할 수 있음

변경 목표

  1. 프로젝트 훅은 powershell로 stdout에 JSON을 출력하도록 고정
  2. superpowers 플러그인의 Cursor 훅은 bash로 감싸 실행되도록 구성 변경

구현/검증 범위

  • E:/develop/factorio-ai-agent/.cursor/hooks.json
  • E:/develop/factorio-ai-agent/.cursor/session-start-hook.ps1
  • .../superpowers/.../hooks/hooks-cursor.json
  • 검증: Cursor 재시작 후 View -> Output -> Hooks에서 sessionStart 훅이 정상 파싱되어 OUTPUT에 JSON이 나타나는지 확인

인벤토리 캐시(메모리) 추가 계획

목표

  • 파이썬 에이전트만 종료/재실행할 때 RCON으로 인벤토리를 읽지 못하거나({} 반환) 빈 값이 나오면, 직전에 성공적으로 읽은 인벤토리를 기억해서 프롬프트에 반영한다.

정책(캐시 fallback 조건 변경)

  • get_inventory()에서 JSON 파싱/출력 추출이 성공(parsed_ok=True) 했으면, 결과가 {}라도 캐시로 덮지 않는다.
    • 캐시를 덮어쓰면 실제 진행이 반영되지 않아 같은 행동을 반복하는 루프가 생길 수 있다.
  • get_inventory()에서 파싱이 실패(parsed_ok=False) 하거나 예외가 발생한 경우에만 inventory_memory.json 캐시를 로드한다.

구현 범위

  • state_reader.py
    • inventory_memory.json 로드/저장 유틸 추가
    • get_inventory() 반환값을 캐시 fallback으로 교체
  • README.md
    • 인벤토리 캐시 동작과 파일명(inventory_memory.json)을 설명

발견된 광맥 좌표 기억(메모리) 추가 계획

문제 관찰

  • explore로 광맥을 발견해도, 재실행/재계획/초기화 상황에서 동일 좌표를 다시 추천받지 못해 불필요한 탐색이 반복될 수 있음.

변경 목표

  1. 자원 엔티티(광맥)의 좌표를 ore_patch_memory.json에 ore 종류별로 여러 패치 좌표로 저장
  2. 다음 상태 요약에서 “기억된 광맥” 섹션으로 AI에게 제공
  3. LLM이 가능하면 기억된 좌표로 먼저 이동해 move -> mine_resource를 수행하도록 유도

구현 범위

  • action_executor.py
    • explore 성공 시 발견한 광맥 좌표를 파일에 갱신
  • state_reader.py
    • scan_resources() 결과로도 광맥 좌표를 ore_patch_memory.json에 갱신
    • summarize_for_ai()에 “기억된 광맥” 출력 추가
  • ai_planner.py
    • SYSTEM_PROMPT에 기억된 광맥 우선 이동 가이드 추가

README 업데이트

  • ore_patch_memory.json 파일과 동작 방식 설명 추가

에이전트 재시작 시 마지막 행동 기억(메모리) 추가 계획

문제 관찰

  • 코드를 수정하면 보통 에이전트를 재시작해야 하고, 이때 ai_planner.pyfeedback_log/직전 실패 정보가 초기화되어 같은 시행착오가 반복될 수 있음.

변경 목표

  1. 재시작 시에도 “직전에 실행했던 action”과 결과(success/message)를 파일에 저장
  2. 다음 실행의 상태 요약(state_reader.summarize_for_ai())에 “기억된 마지막 행동” 섹션을 포함
  3. LLM이 마지막 행동이 실패였다면 같은 행동을 즉시 반복하지 않도록 유도

저장 기준(확정)

  • 성공/실패 상관없이 가장 마지막으로 실행을 시도한 action 1개만 저장한다.

구현 범위

  • main.py
    • 행동 실행 직후 action/result를 agent_last_action_memory.json에 저장
  • state_reader.py
    • 재시작 시 파일을 로드해 상태 요약에 포함
  • ai_planner.py
    • SYSTEM_PROMPT에 “마지막 행동 실패 재시도 금지/원인 해결 우선” 가이드 추가

README 업데이트

  • agent_last_action_memory.json 존재/동작 설명 추가

place_entity BLOCKED 완화 계획 (인접 타일 탐색)

문제 관찰

  • 로그에서 stone-furnace 배치가 FAIL 배치 불가(내부적으로 BLOCKED)로 반복됨
  • 또한 stone-furnace가 이미 맵에 설치돼 있어도, executor가 먼저 NO_ITEM으로 종료해 재사용 탐색이 실행되지 않는 케이스가 관찰됨
  • mine_resource가 자원 엔티티 근처(종종 자원 패치 타일)로 이동한 뒤, 같은 좌표에 건물을 배치하려고 하면 Factorio의 can_place_entity 조건에 걸려 막힐 수 있음

변경 목표

  1. action_executor.pyplace_entity에서 배치 아이템 유무(NO_ITEM) 확인 전에, 먼저 surface.find_entities_filtered로 기존 엔티티(같은 name)가 있는지 탐색
  2. (기존) 요청 좌표 주변 ±1 타일(9칸) 후보에서 기존 엔티티가 있으면 REUSED로 성공 처리
  3. (추가) 요청 좌표 중심으로 반경 3 타일 내에 같은 엔티티가 있으면 가장 가까운 것을 REUSED로 성공 처리
  4. 기존 엔티티가 없을 때만 인벤토리에 name 아이템이 있는지 확인하고, surface.can_place_entity + ±1 타일 후보로 실제 배치를 시도
  5. 실제로 배치된/재사용된 좌표를 결과 메시지에 포함해 이후 stone-furnace 자동 부트스트랩이 좌표를 정확히 파싱하도록 보장

구현 범위

  • action_executor.py
    • 기존 엔티티 탐색을 NO_ITEM 체크보다 먼저 수행
    • 재사용(REUSED) 성공 케이스의 메시지/좌표 포맷 확정
    • 반경 3 타일의 넓은 재사용 탐색 추가

README 업데이트

  • place_entityBLOCKED/NO_ITEM 상황에서도 기존 엔티티를 찾아 REUSED로 재사용하고, stone-furnace는 재사용이어도 자동 부트스트랩이 동작하도록 설명 반영

GLM HTTP read timeout 대응 계획 (예외 포함 재시도)

문제 관찰

  • 로그에 TimeoutError: The read operation timed out 가 발생
  • 현재 ai_planner.py는 JSON 파싱 실패만 재시도하고, 네트워크 타임아웃/연결 오류는 별도 재시도 경로가 약함

변경 목표

  1. AIPlanner.decide()의 재시도 예외 범위를 TimeoutError, ConnectionError, urllib.error.URLError까지 확장
  2. 해당 오류가 발생하면 동일한 “plan 응답 받기” 재시도로 복구 시도
  3. 3회 연속 실패 시에는 상태 요약 기반 휴리스틱(광맥 있으면 채굴/이동, 없으면 explore 방향 순환)으로 폴백

구현 범위

  • ai_planner.py
    • decide()except 절 범위 확장 및 경고 로그 보강

README 업데이트

  • GLM read timeout/연결 오류 발생 시 재시도 동작을 주의사항에 추가

GLM 전부 실패 시 explore 무한 루프 완화 (상태 기반 폴백)

문제 관찰

  • GLM API가 TimeoutError/ConnectionError로 연속 실패하면 폴백이 항상 explore(고정 east)만 선택됨
  • 이미 주변 자원 패치·기억된 광맥에 철/석탄 좌표가 있는데도 탐색만 반복되어 진행이 멈춤

변경 목표

  1. state_reader.summarize_for_ai() 텍스트에서 플레이어 위치·앵커/기억 광맥 좌표를 정규식으로 추출
  2. 광맥이 있으면 mine_resource 우선(거리가 크면 move 선행), 없을 때만 explore 방향 순환
  3. 기본 HTTP 타임아웃 90초→120초, 재시도 간격 소폭 완화

구현 범위

  • ai_planner.py: _fallback_plan_from_summary, _parse_player_position, _parse_ore_anchors, 환경변수 GLM_HTTP_TIMEOUT_SECONDS, GLM_FALLBACK_MOVE_THRESHOLD

README 업데이트

  • 폴백 동작 및 환경변수 설명 반영

GLM 예외 상세 로그 + 연결 검사 스크립트

목표

  • TimeoutError/ConnectionError 한 줄만으로는 DNS·SSL·프록시·HTTP 본문 오류를 구분하기 어려움
  • URLError.reason·체인 예외·errno를 항상 출력하고, GLM_DEBUG=1에서 스택까지 확보
  • scripts/glm_connection_check.py로 최소 POST만 수행해 네트워크/API 키를 분리 진단

구현

  • ai_planner.describe_glm_exception(), _glm_debug_enabled()
  • scripts/glm_connection_check.py

README

  • GLM API 연결 문제 디버깅 절 추가