TL;DR
- 턴 구조: subgoal 경계마다 VLM 호출. VLM 입력 = [씬 이미지 1장 + 객체 인벤토리 + 직전 결과 + (첫 턴) instruction], 출력 = JSON 1개 (skill/object_id/receptacle_id/reasoning)
- VLA 입력: 카메라 2뷰(624×352→letterbox 224) + 관절 8-D + 정책 프롬프트 (
pick up the mug and place it on the table. — 학습분포 그대로 렌더 확인)
- VLA 출력: 절대 관절 타깃 8-D × horizon 15, 8스텝 소비 후 재추론 (15Hz)
- 본문 예시는 전부 T3 실측 (house 0, "put the mug on the dining table", 5 API call, $0.131)
1 전체 루프 — 한 턴의 데이터 흐름
Sim (MuJoCo)
exo 렌더 + 인벤토리
→
VLM Planner
gemini-3.6-flash
→
Subgoal JSON
검증+task 생성
→
π0.5-FT VLA
ws://8080, 15Hz
→
Sim 실행
success/timeout
↪ outcome을 다음 VLM 턴으로
VLM은 subgoal 하나만 발행하고, 완료 판정(성공 술어 or 스텝 예산 소진)이 나면 결과가 다음 VLM 턴의 입력이 된다. 대화(conversation)가 상태로 유지되므로 메모리 context 주입·추가 지시(multi-turn)는 user 턴 append만으로 확장된다.
2 Model 1 — High-level VLM Planner gemini-3.6-flash @ Letsur
INPUT ① — System 프롬프트 (에피소드당 1회, 고정)
You are the task planner of a household robot in a simulator.
Each turn you receive: the current scene image, an object inventory, and the outcome context of the previous subgoal. Decide exactly ONE next subgoal.
Skills:
- pick(object_id): grasp and lift the object
- pick_and_place(object_id, receptacle_id): one atomic unit — you cannot split pick and place into separate subgoals
- open/close: DISABLED (이 로봇의 학습 데이터에 없음 — 선택 금지)
- done: the user instruction is fully satisfied
- abort: impossible to proceed (explain in reasoning)
Rules:
- Refer to objects ONLY by the exact id in [brackets] from the inventory.
- Output a single JSON object, nothing else: {"reasoning": "...", "skill": "...", "object_id": "...", "receptacle_id": "..."}
INPUT ② — 턴마다 들어가는 user 메시지 (T3 k=1 실측 재구성)
| 파트 | 내용 | 비고 |
| 1. 씬 이미지 | exo_camera_1 렌더 1장, 624×352 JPEG(q85) base64 | env.render_rgb_frame() — 시뮬 스텝 소모 없음 |
| 2. 인벤토리 | [id] category — room (x, y, z) 줄 목록 | VLM은 이 id로만 지칭 가능 (자유 텍스트 금지) |
| 3. 직전 결과 | Previous subgoal outcome: timed_out after 900 steps | oracle 모드만. vlm 모드는 이미지 자가판정 지시로 대체 |
| 4. Instruction | User instruction: put the mug on the dining table | 첫 턴만 |
VLM이 실제로 받는 씬 이미지 (T3 subgoal 0 시작 프레임, exo_camera_1 624×352)
Current object inventory: (house 0 실측 — 총 133줄 중 발췌)
[diningtable_f113cf7f8367e89f709b53cbee1a1c05_1_0_2] DiningTable — room_2 (5.9, 6.8, 0.4)
[diningtable_f113cf7f8367e89f709b53cbee1a1c05_2_0_3] DiningTable — room_3 (3.0, 5.2, 0.4)
[diningtable_15d4d7c88896632c7f36ae642a41eb46_1_0_4] DiningTable — room_4 (3.9, 2.7, 0.5)
[mug_3ebc45568ed53a18c8797978b3744a99_1_0_6] Mug — room_6 (11.2, 8.3, 0.3)
[mug_8caf1bb3f88e9a00e02dfe9e6518aeb0_1_0_7] Mug — room_7 (15.0, 8.4, 0.8)
[cup_4697b73732349085e30935e0f35566dc_1_0_2] Cup — room_2 (5.3, 6.9, 0.8)
[microwaveoven_5eaaa6bcab9a252c76d99abaec4c196a_1_0_2] Microwave — room_2 (5.7, 6.5, 1.0)
[chair_0ac9b0b021ef299afd8f5636f317ad41_1_0_2] Chair — room_2 (5.0, 6.8, 0.4)
…
house 0 실측 전문의 발췌 (133개 객체). [id]=시뮬 body name(=task 생성·성공판정의 유일키), category=에셋 주석, room=infer_room_name 추정, 좌표=월드(m). 구조물(벽/바닥)은 제외
카메라 한계 (실측 확인): 위 이미지는 로봇 베이스 장착 DROID식 카메라(fov 71°)의 작업공간 클로즈업이다 — 씬 오버뷰가 아니며, 팔은 시야에 들어올 때만 보인다. house 0에서 로봇은 침실(협탁 앞)에 있고 mug(room_6)·테이블(room_2~4)은 전부 시야 밖 → VLM의 전역 공간 인지는 사실상 인벤토리 텍스트가 담당한다. 후속: planner 전용 오버뷰 카메라 옵션 + 도달권 내 타깃으로 데모 instruction 교체 (navigation/N0 텔레포트가 붙기 전까지의 제약).
OUTPUT — JSON 1개 (T3 실측 원문 4건)
k=0 {"reasoning": "The user wants to place a mug on the dining table. I will pick up
mug_3ebc...4a99_1_0_6 and place it on diningtable_f113...1c05_1_0_2.",
"skill": "pick_and_place",
"object_id": "mug_3ebc45568ed53a18c8797978b3744a99_1_0_6",
"receptacle_id": "diningtable_f113cf7f8367e89f709b53cbee1a1c05_1_0_2"}
→ 실행: 900스텝 timeout
k=1 {"reasoning": "The previous attempt timed out while trying to reach the dining table
in room_2. I will try placing the mug on the dining table in room_3 instead.", ...}
→ 같은 머그, 다른 테이블로 전환. 900스텝 timeout
k=2 {"reasoning": "Both dining tables in room_2 and room_3 timed out. I will try placing
the mug on the dining table in room_4 instead.", ...}
→ 세 번째 테이블. 900스텝 timeout
k=3 {"reasoning": "All attempts to place the mug onto the available dining tables timed
out due to navigation or execution failure. Aborting the task.",
"skill": "abort", ...}
→ 에피소드 종료 (final="abort")
확인된 것: VLM이 직전 outcome을 정확히 인용하며 전략을 바꾼다(재시도 → 테이블 전환 → abort). 모든 id가 인벤토리 실존 id — 환각 지칭 0건 (T2/T3 합계 8 결정).
검증 & 재질의 규칙
| 체크 | 실패 시 |
| JSON 파싱 (raw → 코드펜스 → brace-balance 순 시도) | 실패 사유를 붙여 재질의 1회:
"Your output was invalid: <err>. Reply with ONLY the corrected JSON object." 2회 연속 실패 → 자동 abort |
| 스키마 (skill enum, pick 계열은 object_id 필수, pnp는 receptacle_id 필수) |
| id가 인벤토리 valid set에 실존하는가 |
비용 실측 (T3)
$0.1309 / 5 call
3 Model 2 — Low-level VLA π0.5-FT franka_2cam_15k/14999
DROID-base π0.5를 MolmoSpaces Franka 데이터로 15k step 파인튜닝한 체크포인트 (SigLIP frozen). openpi websocket 서버(--port 8080)로 서빙, 시뮬 쪽 PI_Policy가 무수정 클라이언트.
INPUT — 추론 1회당 (msgpack over websocket)
| 키 | 형태 | 내용 |
observation/exterior_image_1_left | uint8 224×224×3 | exo_camera_1 624×352를 letterbox(비율 유지 + 상하 검은띠 49px) 리사이즈 |
observation/wrist_image_left | uint8 224×224×3 | wrist_camera 동일 letterbox |
observation/joint_position | float (7,) | 절대 관절각 (rad) |
observation/gripper_position | float (1,) | 0(열림)~1, qpos/0.824033 정규화 |
prompt | str | 정책 프롬프트 — 아래 5 템플릿만 (T3 실측: pick up the mug and place it on the table.) |
VLA의 exterior 입력 원본 예시 (T3 subgoal 2 마지막 프레임, letterbox 전 624×352). wrist 뷰는 손목 장착 카메라로 동일 처리
정책 프롬프트 — 학습 분포와 바이트 일치 (소문자+마침표)
| skill | 템플릿 | 비고 |
| pick | pick up the {name}. | 학습 커버리지 있음 (bench 평균 SR 36%) |
| pick_and_place | pick up the {name} and place it on the {name2}. |
| open / close | open the {name}. / close the {name}. | Franka 학습 데이터 0% → 기본 비활성 |
| packing | pack container. | 미사용 |
주의: {name}은 VLM의 객체 id가 아니라 ObjectMeta.short_descriptions 경로로 렌더된 자연어 명칭("mug", "table")이다. 이 5 템플릿 밖의 문구는 OOD로 최대 14% 성능 손실(논문 Fig.13) — 그래서 VLM 출력은 구조화 JSON으로 받고 정책 프롬프트는 규칙으로 렌더한다.
OUTPUT — action chunk
actions: float (15, 8) # horizon 15
[:, 0:7] → 절대 관절 타깃 (rad) # 변환 없이 그대로 arm 명령
[:, 7] → 그리퍼 (이진화: >0.5 → 닫힘 255, else 0)
소비 규칙: 앞 8스텝만 실행 후 재추론 (chunk_size=8), 15Hz (policy_dt 66ms)
Embodiment
Franka FR3 고정베이스
Zero-shot → FT
SR 10% → 36%
4 Subgoal 완료 판정 (VLA → VLM 복귀 조건)
| 모드 | 판정 주체 | VLM에 주는 정보 | 용도 |
oracle (기본) | 시뮬 물리 술어: pick=들림+로봇단독접촉 / pnp=receptacle 지지 / open,close=관절 비율. + 스텝 예산(pnp 900) 소진 시 timeout | succeeded | timed_out after N steps | 벤치 측정 — done 노이즈가 메모리 신호에 안 섞임 (공리 A3) |
vlm | VLM이 새 이미지 보고 자가판정 (oracle 결과 비공개, timeout은 안전망) | 자가판정 지시문만 | 실전 근사 / oracle ablation |
polling (예약) | MemER식 N스텝 주기 폴링 | — | 필요 시 추가 (호출 5~7배) |
중요: 성공 술어는 dataset 주석이 아니라 task 클래스 내장 물리 체크라 VLM이 씬의 임의 객체 조합을 골라도 자동 바인딩된다. 새 술어가 필요한 스킬은 TOGGLE 하나(예정)뿐.
5 T3 실측 에피소드 타임라인 (풀스택 증거)
| k | VLM 결정 | 정책 프롬프트 | 결과 |
| 0 | pnp: mug → DiningTable(room_2) | pick up the mug and place it on the table. | timeout 900 |
| 1 | pnp: 같은 mug → DiningTable(room_3) 전략 변경 | timeout 900 |
| 2 | pnp: 같은 mug → DiningTable(room_4) 전략 변경 | timeout 900 |
| 3 | abort: "All attempts ... timed out. Aborting." | — | final=abort |
subgoal 0 시작
subgoal 0 종료 (900스텝 후) — 팔이 움직였고(프레임간 diff가 flatline 없음) 물리 상태가 다음 subgoal로 이어짐
π0.5 물리 실패의 진짜 원인 (실측 좌표로 확정): 로봇은 침실 협탁 앞에 스폰됐고 mug는 room_6 (11.2, 8.3), 테이블들은 room_2~4 — 고정베이스 Franka에게 기하학적으로 불가능한 태스크였다. 3×900스텝 timeout → abort는 이 상황에서 시스템이 낼 수 있는 최선의 궤적. 게이트 기준은 메커니즘(전환/피드백/기록)이라 전부 통과. 물리 성공 데모는 도달권 내 타깃(협탁 위 물체)으로 T4에서 교체.
벤치 설계 인사이트 (T2에서 관측): 이미 receptacle 위에 있는 물체를 고르면 1스텝 만에 성공 처리 — 설계 공리의 "instant-success hazard"가 실측으로 확인됨. 에피소드 생성 시 이미-충족 타깃 배제 필수.
6 Takeaway & Next
의미
"online으로 API가 명령을 줘가며 제어 가능한가"에 대한 답은 실측 YES. VLM↔VLA 사이의 모든 계약(입출력 형식·검증·재질의·완료판정)이 위 표대로 고정됐고, 다른 세션은 VlmPlanner의 conversation에 [메모리 context 턴 / 추가 instruction 턴]만 꽂으면 된다.
남은 게이트
T4: 같은 데모를 sbm worker job으로 (worker egress는 사전 실측 완료, EGL만 검증 필요). retract-home 구현 진행 중 (팔이 실패 자세를 물려받는 문제 — T3 실측으로 필요성 확정).
알려진 한계
open/close는 Franka 학습 데이터 0%로 비활성. navigation은 고정베이스라 부재 — 벤치마크의 N0 텔레포트 설계로 해결 예정. done 판정 oracle은 시뮬 전용(실로봇 이전 시 vlm 모드/학습 detector로 교체).