原文发表在知乎,格式更整齐一些,欢迎移步:链接
----------
Harness VLA 是 RPent 首篇论文提出的"agent+记忆+冻结 VLA"方法,RPent 为这一方法提供工具封装、服务调度和执行闭环等工程实现。让机械臂完成一句自然语言指令,需要先认出目标、判断位置,再选择动作,并根据执行结果不断调整。RPent 将这些环节组织成一个闭环:agent 理解任务和选择工具,感知工具提供几何信息,VLA 与脚本控制执行动作,环境返回新的观察与成功信号。
全程使用 VLA 的困难在于它对训练轨迹分布的依赖。论文摘要与第 1、2 节指出,预训练 VLA 擅长抓取等局部接触操作,但当指令改换目标、物体布局变化,或多个短技能需要组合成长任务时,它可能沿用训练中熟悉的动作模式,抓错目标、从不合适的位置开始操作,或因一次接触失败而使后续任务偏离。引入 agent 后,可以结合实时观察和历史经验重新确认目标、拆分步骤、把机械臂移动到适合 VLA 接管的位置,并在执行后检查结果,必要时调整位置再尝试;搬运、姿态调整和释放等环节则使用脚本工具。这样可以在保持 VLA 权重不变的情况下,复用其局部操作能力,适应训练轨迹之外的任务变化。
预定位在这里承担了一个关键作用:agent 根据当前场景,用脚本将机器人带到更适合冻结 VLA 发挥局部技能的状态,缩短 VLA 需要独立处理的动作过程。VLA 则负责接近物体后涉及接触、夹持和抬升的视觉动作预测。记忆积累的是这些工具的适用条件、参数选择和失败规律,帮助 agent 决定何时调用、怎样准备,以及执行后是否需要调整。框架由此复用一组固定工具,通过改变工具的组合方式应对任务变化。
在本文的"沙拉酱瓶入篮"案例中,这种分工体现为:定位当前瓶子 → 脚本预定位 → VLA 抓取 → 观察并复核夹持结果 → 脚本搬运、释放。下面沿实际运行过程展开。
本文沿着一次真实任务的运行顺序展开:
text
提交任务 → 启动服务 → 下发提示词和工具
→ 观察场景、读取记忆 → 定位目标
→ 抓取、搬运、释放 → 判断结果、保存产物
每走到一个阶段,再解释该阶段涉及的机制。这样可以把架构、记忆和工具放回它们实际发挥作用的位置。
1. 提交任务:让机械臂把沙拉酱瓶放进篮筐
本次运行使用以下核心参数。执行前,Python 环境、仿真资源、模型权重和 Codex 登录信息已经准备就绪。
bash
env CODEX_HOME=/home/vla/Personal/huangxuan \
rpent --robot libero --suite libero_object_swap --task 2 --seed 0 \
--cuda-device 0 --planner codex --model gpt-6.1-sol \
--reasoning-effort high
这条命令选定 LIBERO 仿真任务,以 Codex 作为高层 agent 运行时,使用 gpt-6.1-sol 进行规划。后续机械臂的局部动作由 Pi0.5 和脚本工具完成。
本次运行环境已设置 LIBERO_TYPE=pro,使用 LIBERO-Pro 的 libero_object_swap 套件。后缀 _swap 表示位置扰动:改变物体或场景设施的初始位置,任务要求保持不变。机器人需要在变化后的布局中重新找到目标和放置区域;旧经验中的绝对坐标不能直接沿用。
这个案例可以用来观察 agent 如何重新定位、为 VLA 准备抓取起点,再把局部抓取接到后续搬运流程中。本文没有运行同场景的端到端 VLA 对照,前文关于 VLA 分布外困难的讨论来自论文分析。
环境中的任务指令是:
text
Pick the salad dressing and place it in the basket
下图是本次任务的初始场景:

桌面上还有番茄酱、罐头、牛奶等干扰物。agent 需要在当前场景中找到沙拉酱瓶和篮筐,测量位置,再完成抓取和放置。下面先看程序如何把这些能力准备好。
2. 启动服务:建立 agent 与机器人环境之间的连接
RPent 读取任务参数,准备对应经验库,启动 env、VLA、SAM3 服务并等待就绪。随后创建工具集、初始化 episode、保存初始观察,再通过 MCP 将工具提供给 agent。
架构中只需要先认识以下几个组件:
| 组件 | 负责什么 |
|---|---|
| RPent 框架进程 | 管理任务与服务,准备提示词和记忆,执行工具逻辑,保存状态和结果 |
| Agent 进程(本地 Codex) | 管理模型对话,根据提示词和观察调用工具;高层模型推理由模型服务完成 |
| MCP 服务 | 运行在 RPent 框架进程内部,向 agent 发布工具并接收调用 |
| env 服务 | 持有 LIBERO 仿真环境,执行动作,返回图像、状态和原生成功信号 |
| VLA 服务 | 加载 Pi0.5,根据图像、机器人状态和短指令预测动作 |
| SAM3 服务 | 根据文本或点提示对图像进行分割 |
图中的箭头表示请求或调用方向,结果沿原链路返回。MCP 单独画出是为了显示工具入口,它并不是额外的独立进程。

A:框架启动并驱动 agent。 RPent 内的 Python SDK 启动本地 Codex app-server,通过标准输入输出管道发送 JSON-RPC 消息,创建会话、提交提示词并接收执行事件。
B:agent 请求使用工具。 Codex 通过 MCP 的 HTTP 接口发现工具、提交参数、接收结果。本次接口地址是 http://127.0.0.1:47431/mcp/,通信发生在本机。
C:MCP 把请求交给框架。 MCP 服务收到 move_to 或 back_project 等请求后,直接调用 RPent 的工具执行逻辑,完成参数校验和调度。这一步是同一进程内的函数调用。
D:框架调用机器人服务。 工具需要移动机械臂、运行 Pi0.5 或调用 SAM3 时,通过 RPent 自己的 HTTP RPC 接口访问对应服务。它使用 POST /call,传递方法名、参数和返回值;图像、深度等数组带有数据类型和形状信息。这里的组件 RPC 与 B 的 MCP 是两套不同的接口。
例如,稍后的一次抓取会沿着 B → C → D 到达 VLA 和 env 服务,再把执行后的图像和状态返回 agent。整条运行链中,模型负责选择操作,RPent 负责将操作落实到服务和环境。
3. 下发提示词和工具:告诉 agent 目标、规则与可用能力
3.1 提示词实际长什么样
RPent 根据任务参数生成初始提示词,说明角色、操作限制、工作流程和输出要求。本次完整文本有 35,216 个字符,下面保留其中几个核心片段,省略部分用 ... 表示;记忆目录缩写为 <memory-root>,章节分隔线省略。
text
ROLE AND EVALUATION
You are an LLM-in-the-loop hybrid agent for the LIBERO PRO benchmark, running
in PERCEPTION-ISOLATED mode: you are NOT given object world coordinates. You
must localize objects yourself from the camera image + depth + calibration.
...
> ONE-SHOT evaluation: you get **exactly ONE episode**. You MUST NOT call
> `reset`, and you must not restart the episode. Plan carefully, then execute
> your single best manipulation sequence toward top-level
> `terminated == true`.
...
WORKFLOW
1. READ THE GUIDES once each:
- `robots/libero/guides/strict_hybrid_guide.md`
- `robots/libero/guides/pro_hybrid_guide.md`
- `robots/libero/guides/env_calibration.md`
2. INSPECT INITIAL STATE: call `view_env_state({"step": 0})`; inspect
...
3. READ MEMORY for the observed `task_language` and scene. The library contains
operating wisdom, magic numbers, gotchas, and reusable manipulation patterns,
indexed by:
`<memory-root>/MEMORY.md`
...
OUTPUT DISCIPLINE
- Save the audit BEFORE calling `finish`.
...
CELL
- suite: libero_object_swap
- task: 2
- seed: 0
...
BEGIN
Read the guides, then call `view_env_state({"step": 0})` and inspect
`task_language` and `agentview_high.png`. Read matching memory and available
seed-0 references for the current task and scene, then localize, plan, and execute.
这段文本把工作顺序明确下来:先读指南、看现场、查经验,再定位和操作;本次只允许一个连续 episode,不能 reset;最后先保存执行报告,再结束任务。
框架中的规则部分和本次任务部分分别叫 system_prompt、user_message。在当前 Codex 适配器中,两部分拼接后作为一条初始输入提交给 agent。随后读到的经验、图像和工具结果会继续进入会话。提示词源码可查看项目仓库中的 规则与工作流程(evaluate.py) 和 任务参数与开始指令(user.py)。框架将这些模板与本次任务参数组合,生成实际发送的完整提示词;链接固定到本案例使用的代码版本。
3.2 同时把可用工具交给 agent
提示词说明"应该做什么",工具定义则告诉 agent"可以调用什么、需要传哪些参数"。本次默认 LIBERO 评测路径提供以下 16 个工具:
| 类别 | 工具 | 用途 |
|---|---|---|
| 文件与流程 | read_text_file、write_text_file、list_dir、finish |
读取经验、保存报告、列目录、结束规划 |
| 观察 | view_env_state、view_camera_meta |
读取状态、图像和相机标定 |
| 几何与分割 | back_project、segment |
像素反投影;SAM3 分割并定位 |
| VLA 技能 | pi0_pick、pi0_doubled |
抓取;非抓取类接触操作 |
| 脚本运动 | move_to、move_pose、rotate_wrist、rotate_pitch |
位置、姿态和旋转控制 |
| 夹爪 | set_gripper、release |
维持或改变夹持,释放物体 |
探索模式还会开放 reset;直接动作能力启用时另有底层动作工具。本次采用默认评测模式。上表列出的是 RPent 通过 MCP 发布的工具;Codex 还具有自身的执行工具,后文使用 shell 和 rg 检索经验就属于这一路径。
3.3 工具如何注册,又怎样被调用
工具定义使用 @tool 装饰器。以反投影工具为例,结构可简化为:
python
@tool(readonly=True, exclude=("state",))
def back_project(row=None, col=None, step=-1, *, state):
# 实际实现还有 camera、resolution、区域和高度过滤参数。
...
真实函数具有完整类型标注。装饰器从函数名、参数类型和 docstring 生成工具声明、参数 JSON Schema,以及运行时校验模型。state 是框架注入的内部资源,通过 exclude 从 agent 可填写的参数中移除。
注册与发布链为:
text
@tool 声明
→ iter_tools 收集指定模块或实例上的工具
→ Toolkit.add_tool 注册名称和绑定后的处理函数
→ toolkit.list_tools 返回声明
→ MCP tools/list 发布 name、description、inputSchema
→ Codex 暴露为 mcp__rpent__工具名
调用时方向相反:Codex 通过 MCP 发送工具名和参数,适配器交给 Toolkit.execute_tool;Toolkit 校验参数、获取执行调度权,然后调用对应处理函数。
这些工具准备好后,agent 开始实际操作。第一步是读取指南和初始观察。
4. 观察场景、读取记忆:先确定目标,再借鉴已有经验
4.1 从初始观察确认任务
agent 先读操作指南和相机标定指南,然后调用 view_env_state(step=0)。工具返回任务语言、末端位姿、夹爪状态,以及主相机和腕部相机图像。
在第 1 章展示的初始场景中,agent 根据绿色瓶盖与 Ranch Dressing 标签确认沙拉酱瓶,再确认右侧的篮筐。知道"要操作哪个物体、放到哪里"以后,才有依据查找相关经验。
4.2 这次读到了什么经验
框架在启动时已经准备好经验文件,并把根目录写入提示词。这些经验来自此前任务的探索与整理:agent 在尝试中记录操作、观察结果和失败原因,成功后总结可复用的策略与参数,框架则从执行轨迹导出动作序列。整理后的经验可以合并到本地经验库,公共语料由维护者审核并发布到 Hugging Face,供后续运行下载复用。本次选择 GPT_5.5_xhigh:原因是运行模型 gpt-6.1-sol 没有专门的 memory 映射,于是经验库回退到该版本。推理模型仍然是 gpt-6.1-sol。
agent 读取 MEMORY.md,列出 global/ 与 task-family/,并尝试读取当前任务的两个参考文件:
text
task-specific/object_swap_t2_s0.json
task-specific/object_swap_t2_s0_recipe.jsonl
两个文件当时都不存在。于是它用 shell 中的文本搜索工具 rg 检索 salad|dressing|tall bottle,读取相近经验。记忆检索也会随着观察继续:后续移动到篮筐上方时,它又找到 task-family_libero_object_task_t8.md,其中的任务语言与当前任务完全相同。
这种检索方式是"框架提供文件,agent 按目录、关键词和语义选择内容"。它没有在启动时把所有经验全文塞进 prompt,也没有在这次运行中使用向量数据库。
4.3 为什么 memory 分成不同层
RPent 的 memory 是持久化经验文件,主要使用 Markdown、JSON 和 JSONL。它通过上下文影响 agent 的决策;本次读取与更新机制不修改 Pi0.5 或高层模型的权重。
text
<memory-root>/
├── MEMORY.md # 经验索引
├── global/ # 跨任务技巧和失败规律
├── task-family/ # 同类任务的策略、参数、失败模式
├── task-specific/ # 具体任务的 audit / recipe
│ ├── <cell>.json
│ └── <cell>_recipe.jsonl
└── _internal/
├── inbox/<cell>/ # 当前任务草稿
│ └── wip/notes.md # 尝试过程中的工作笔记
├── merged/ # 合并后的草稿归档
└── conflicts/ # 与已有正文冲突的草稿
MEMORY.md 是索引,不是一个额外的知识层。三层主要经验的区别是复用范围:
| 层级 | 示例内容 | 应怎样复用 |
|---|---|---|
| global | 释放后物体被夹爪卡住时,可以张开后垂直撤退 | 检查适用条件后用于不同任务 |
| task-family | 沙拉酱瓶入篮时的身份辨认、预定位高度、抓取提示、失败模式 | 用于相同语义任务及其变体 |
| task-specific | 某个 suite/task/seed 的执行报告和动作序列 | 参考具体成功过程,重新测量当前场景坐标 |
部分发布快照还有 flash/ 或其他回放资产,用于对应的回放 planner。本次 Codex 运行没有读取这些回放计划。
4.4 一条经验怎样影响本次计划
本次读取的 global/pi0-prepositioned-simple-basket.md 建议:先在主相机中识别目标,将夹爪移到视觉确认的目标上方;如只需要抓取,可将 Pi0 调用限制在 8--12 chunks,再用脚本完成搬运。
后来找到的 task-family_libero_object_task_t8.md 包含:
yaml
scope: task-family
suite: libero_object
regime: task
task_id: 8
task_language: Pick the salad dressing and place it in the basket
confidence: single-shot
它描述了沙拉酱瓶的外观,建议预定位高度约为 0.17--0.18 m,并给出短提示 pick up the salad dressing。正文还明确要求不要缓存该经验中的绝对 xy。
本次对经验的使用可以逐项对照:
| 经验内容 | 本次采用方式 |
|---|---|
| 根据绿色瓶盖和标签辨认目标 | 先在主相机确定沙拉酱瓶,再用腕部图细化 |
| 抓取预备高度约 0.18 m | step 4 的目标 z=0.18 |
| 短提示指定沙拉酱 | prompt="pick up the salad dressing" |
| 严格控制抓取长度 | max_chunks=8 |
| 旧 xy 不应直接复用 | 对当前瓶子、篮筐重新反投影 |
| 抓取后可用脚本搬运 | 夹紧、中间点、对准、下降、释放 |
当前 task id 是 2,但读取了 task_t8 的经验,因为两者的实际任务语言相同。文件编号帮助组织语料,agent 仍需检查语义与场景是否匹配。
4.5 读到的经验是怎样积累和更新的
读到的经验来自此前的任务积累。这里顺着看一下它们如何更新:本次评测中,MemoryManager 对已发布语料采用只读访问,agent 可以在运行目录写报告;要将新经验纳入语料,则由探索模式提供另一条流程。
探索模式提供不同工作流:
text
尝试任务
→ 失败时归档 attempts/attempt_N_failed.json
→ 写入当前 cell 的 wip/notes.md
→ 按预算 reset,改变策略后再次尝试
→ 成功后整理 task-family/global 草稿和 audit
→ 框架导出成功 recipe
→ MemoryManager 合并并刷新索引
当前 LIBERO 默认探索预算是最多 3 个 planner 会话,每会话 5 次尝试;成功可以提前结束。新会话创建新的 planner 上下文和 Toolkit,通过 handoff 提示、失败归档和工作笔记继承经验。仿真状态保存在各自的 sessions/session_XXX/ 下,失败尝试编号在同一次运行内连续使用。
这里的 reset 是恢复一次 episode 的初始场景;新 planner 会话则重建高层对话上下文。两者作用不同,探索模式将它们组合成有预算的试验过程。
探索中,agent 的正式草稿写到 _internal/inbox/<cell>/。例如,若把这次观察整理成"抓住后重新测量物体相对末端偏移"的经验,可以形成以下草稿。这是写法示意,本次评测没有实际发布它。
yaml
---
id: measure-held-object-offset
scope: global
kind: strategy
title: 放置前测量物体相对末端的偏移
applies_when: 物体已经抓住,需要精确对准容器内部
confidence: single-shot
evidence:
cells: [object_swap_t2_s0]
related: []
---
正文应描述可迁移的方法:从腕部图像定位抓住的物体,测量其相对末端的偏移,再据此修正末端放置目标。+0.013 m 只属于这次观测,不能写成所有瓶子的固定偏移。
LIBERO 默认启用探索结束后的自动合并,可用 --no-auto-merge-memory 保留草稿供检查。合并器的主要行为是:
- 获取文件锁,校验 inbox 顶层 Markdown 草稿的 YAML 元数据。
- 按 scope 将新条目写入
global/或task-family/。 - 对已有条目合并证据信息;正文不同的来稿放入
_internal/conflicts/,保留已有正文,不自动进行语义改写。 - 将已处理的 inbox 归档到
_internal/merged/。wip/中的笔记不会直接当作正式条目发布。 - 当环境成功且 audit/recipe 都存在时,将配对产物发布到
task-specific/;已有目标文件不会简单被覆盖。 - 根据经验条目的元数据重建
MEMORY.md。
这是一套文件管理与证据合并逻辑,不是另外调用一个模型自动"理解并融合所有知识"。提示词要求成功后再蒸馏可靠经验;合并器的结构校验本身也不能证明一条经验在现实中必然有效。
本地合并不会自动上传到公共 Hugging Face 仓库。使用 HF profile 的后续评测仍读取其选定快照;要使用自己积累的经验,需要选择相应本地语料配置。
回到当前任务:这些经验帮助 agent 逐步确定"精确预定位、短程抓取、脚本搬运"的策略。但历史经验不能替代当前场景的坐标,下一步仍需实际测量瓶子和篮筐。
5. 定位目标:把当前图像转换成可操作的坐标
5.1 先粗定位,再用腕部相机修正
agent 在主相机图像中为瓶子和篮筐分别选取多个像素,调用 back_project。瓶子初步定位在 xy 约 (-0.181, -0.079)。
篮筐的第一次采样落在较高的内衬附近。agent 先让空夹爪移到篮筐上方,用腕部图像查看内部,再通过区域反投影将内部中心修正为约 (0.0051, 0.2632, 0.0129)。随后经中间点返回瓶子上方,使用腕部图像确认还是同一只沙拉酱瓶,将目标 xy 细化到约 (-0.186, -0.079)。
观察和移动在这里交替进行:为了得到更好的观察角度,agent 可以先移动相机,再继续定位。下面展开这一阶段反复使用的几何工具。
5.2 back_project 如何从像素得到 XYZ
这里常被口头称为"project 工具"的正式名称是 back_project,即反投影。它回答的是:当前图像的这个像素,对应世界中的哪个可见表面点?
普通 RGB 像素只有颜色,没有距离。恢复三维位置还需要深度,以及相机内外参。
| 数据 | 含义 | 本案例中的来源 |
|---|---|---|
| RGB | 供模型识别物体、选择像素 | MuJoCo 相机渲染,经 env 服务返回 |
| 深度 D | 每个像素沿相机光轴方向的深度 | 仿真深度缓冲,经 near/far 转换成米 |
| 内参 K | 焦距、主点,将像素联系到相机射线 | 仿真 worker 调用 robosuite 相机工具获取 |
| 外参 T_cam2world | 将相机坐标变换到世界坐标 | 当前仿真相机位姿;腕部相机随机械臂运动而变化 |
| world map | 每个像素对应的世界 XYZ | RPent 根据上述数据逐状态计算并保存 |
数据流是:
text
MuJoCo 渲染 RGB / 深度
+ 当前相机内参、外参
→ env 服务返回原始数据
→ RPent 对齐图像方向、转换深度单位
→ 计算每个像素的世界 XYZ
→ 保存 *_world.npz / *_world_high.npz
→ back_project 查询指定 step 的对应数组
反投影的原理是利用像素对应的深度和相机内参恢复相机坐标中的三维点,再通过相机外参将其转换为世界坐标。
back_project 调用时通常不重新运行这套投影计算,而是读取此前保存好的 world map。本次一次真实调用为:
json
{"row": 371, "col": 430, "step": 0}
结果为:
json
{
"camera": "agentview",
"resolution": "high",
"pixel": [371, 430],
"world_xyz": [-0.182, -0.0787, 0.1375],
"step": 0,
"image_size": [1024, 1024],
"source_artifact": "agentview_world_high.npz"
}
这个点是所选像素看到的表面点,不保证等于物体几何中心,更不保证就是最佳抓取位置。agent 因此会采样多个点,结合目标形状、夹爪姿态和经验选择操作位置。
工具也支持区域模式:传入 row_range、col_range,筛选该窗口的有效三维点,可进一步按世界高度过滤。当前实现返回的 center_xyz 使用有效点 x/y 范围的中点和 z 中位数,同时提供 median_xyz。这类区域估计适合检查篮筐内部等区域,但窗口混入边缘或背景时仍会有偏差。
5.3 为什么必须使用同一相机、分辨率和状态的数据
几个文件之间的关系如下:
| 文件 | 典型数据 | 作用 |
|---|---|---|
agentview_depth.npz / wrist_depth.npz |
256×256 深度,单位米 | 每个像素的相机深度 |
agentview_world.npz / wrist_world.npz |
256×256×3 XYZ | 标准分辨率世界坐标图 |
*_world_high.npz |
1024×1024×3 XYZ | 高分辨率世界坐标图 |
*_metadata.json |
相机内外参、深度范围 | 标定信息与解释 |
agentview_policy.png |
VLA 所用方向的 RGB | 对照动作模型输入 |
agentview.png / agentview_high.png |
与标定、world map 对齐的 RGB | 视觉选点和反投影 |
默认 back_project 使用高分辨率 world map。若像素来自 256×256 图像,应明确使用 resolution="low";policy 图像的方向也不能直接当作反投影图像方向。
世界坐标图在初始记录和各动作后状态采集时计算。查询 step=3 就是在查询该历史状态的图,不会自动变成当前状态。腕部相机外参必须对应同一状态,不能拿上一次姿态的外参投影新的图像。
此外,低分辨率 metadata 不能直接当作 1024×1024 图像的内参;高分辨率 world map 构建时会请求相应分辨率的相机参数。
在真实机器人上,对应的数据需要来自 RGB-D 或其他深度感知、相机标定、手眼标定,以及同步的机器人姿态。仿真提供了这些输入,实际部署时需要建立相同的数据契约。
5.4 如果改用 SAM3,定位链路有什么变化
segment 接收文本描述或一个正向点提示,在已有的观察图像上调用 SAM3 得到 mask。然后 RPent 用 mask 选出匹配 world map 中的三维点,以各坐标的中位数产生 world_xyz,并保存分割结果与叠加图。
text
图像 + 文本/点提示
→ SAM3 服务生成二维 mask
→ RPent 使用 mask 筛选 world map
→ 三维代表位置 + 分割图 + 置信信息
因此三维坐标来自 mask 与深度标定结果的结合,SAM3 本身负责图像分割。工具使用现有状态的图像,不会悄悄渲染一个更新的场景。
本次 agent 通过视觉选点和 19 次 back_project 完成定位,segment 调用次数为 0。这也说明"准备了分割模型"与"这次确实使用分割模型"需要分别记录。
至此,agent 已经同时掌握物体身份、当前几何位置和可参考的操作经验,可以开始抓取。
6. 抓取、搬运和释放:让计划在环境中执行
6.1 先用脚本动作接近目标
agent 从高处接近瓶子,再调用 move_to 将末端目标设为 [-0.186, -0.079, 0.18],保持夹爪张开。这对应经验中约 0.18 m 的抓取预备高度。
这一步把当前图像测得的目标位置转成 VLA 的操作起点:agent 先完成跨布局的接近,将张开的夹爪移到已确认的瓶子上方,再让 VLA 接管局部抓取。预定位高度和抓取预算参考经验,实际 xy 则来自当前观察。这样能减轻 VLA 在整个桌面范围内寻找目标和规划接近路径的负担;是否真的抓住,还要检查后续观察。
move_to 读取当前末端位置,计算到目标的误差,对每步位移限幅,再转换成底层 OSC 控制器接受的动作。达到容差、耗尽步数或环境终止时结束。
其简化逻辑是:
text
error = target_xyz - current_eef_xyz
step_delta = clip(error, -step_clip, +step_clip)
action_xyz = clip(step_delta / action_scale, -1, +1)
执行环境动作,取得新位置,重复
因此 move_to 的 xyz 是目标位置,工具内部输出的是多步控制命令。本次该调用使用默认位置容差 tol=0.012 m,请求 z=0.18,最终实际 z 约为 0.1914 m,与目标的距离约为 0.0114 m。后续抓取从这个实际状态开始。
这个控制器本身不负责理解目标物体,也不提供完整的全局避障规划。路径上的中间点、搬运高度和夹爪指令由 agent 选择。后续搬运时,agent 也会使用这个工具,并通过 gripper=1 保持夹持。
6.2 调用 Pi0.5 完成局部抓取
本次 VLA 服务加载的是已经过 LIBERO 监督微调的 RLinf-Pi05-LIBERO-130-fullshot-SFT checkpoint。"冻结"指运行过程中只做推理,不更新这些权重;抓取能力来自已有训练,agent 和记忆负责组织它的使用方式。
接近目标后,agent 调用 pi0_pick,实际参数如下:
json
{
"prompt": "pick up the salad dressing",
"max_chunks": 8,
"lift_thresh": 0.05,
"gripper_closed_thresh": 0.06
}
这条调用进入 RPent 后,由 pi0_pick 工具内部循环调用 Pi0.5 和环境。每次预测前,客户端将当前观察编码为以下输入;本案例只有一个环境,批次维度为 1。
| 输入字段 | 本案例中的内容与形状 |
|---|---|
main_images |
主相机 policy RGB,[1, 256, 256, 3],uint8 |
wrist_images |
腕部相机 policy RGB,[1, 256, 256, 3],uint8 |
states |
[1, 8]:末端位置 3 维、轴角姿态 3 维、两指关节位置 2 维 |
task_descriptions |
单条短指令:["pick up the salad dressing"] |
反投影得到的瓶子、篮筐世界坐标由 agent 用来选择预定位目标,不作为物体坐标直接传给 Pi0.5。Pi0.5 根据图像、机器人自身状态和短指令预测动作。
本次配置每次向环境提交 5 个动作,客户端去掉批次维度后,动作数组形状为 [5, 7]。每一行对应一条底层 OSC(操作空间控制)命令:前三维控制末端平移,中间三维控制旋转,最后一维控制夹爪。平移和旋转是控制器按配置缩放的增量输入,旋转使用轴角表示;这些数值不能直接当作 move_to 所接受的世界坐标目标。夹爪命令 -1 表示打开,+1 表示闭合,底层控制器再将命令落实到机器人关节。
工具内的一轮循环是:
- 读取最新主相机、腕部相机图像和机器人状态。
- 将短指令设为
pick up the salad dressing。 - 通过 VLA RPC 调用 Pi0.5,得到一个动作 chunk。
- 将动作交给 env 的
chunk_step执行。 - 更新观察、检查终止与抓取启发式,再决定是否继续。
本次服务配置 action_chunk=5,抓取用了 8 chunks,即 8 次 VLA 预测、40 个策略动作。同一个 chunk 内按顺序执行这 5 个动作,工具在 chunk 结束后用更新的观察进行下一次预测,并检查抓取启发式与环境终止信号。高层 Codex 在整次 pi0_pick 返回后才继续决策。
给 Codex 的长提示词与给 Pi0.5 的短技能指令分属两层控制。前者决定"选哪个物体、采用什么策略、何时切换工具";后者完成局部视觉动作预测。
pi0_doubled 则提供接触类技能循环,例如转动旋钮或短程推操作。它的成功字段跟随环境任务终止信号,执行一个中间接触技能后返回 false,也不自动说明物理交互没有发生。
6.3 检查抓取结果,并据此选择下一步
接着看这次调用怎样把控制权交回 agent。抓取工具执行结束后,Toolkit 自动采集新状态,将原动作的参数、结果和耗时保存到状态记录,再把观察 JSON 与三张图像通过 MCP 返回。JSON 保留任务语言、末端和夹爪状态;原动作结果位于 log.result。本次返回中与抓取判断有关的字段如下,其余字段省略:
json
{
"step": 5,
"terminated": false,
"truncated": false,
"log": {
"command": {
"action": "pi0_pick",
"prompt": "pick up the salad dressing",
"max_chunks": 8,
"lift_thresh": 0.05,
"gripper_closed_thresh": 0.06
},
"result": {
"success": false,
"chunks_used": 8,
"diagnostics": {
"start_eef_z": 0.1914,
"min_eef_z": 0.1121,
"post_min_peak_z": 0.2712,
"descent_m": 0.0793,
"post_min_ascent_m": 0.1591,
"descent_done": false
}
}
}
}
这里的 step=5 是第 5 条动作之后保存的状态编号;这次工具内部已经执行了 40 个策略动作。三张观察图像以 MCP 的图像内容块返回,JSON 中另列产物文件名。本次 Codex 将这些图像内容传给模型查看,其中主相机高分辨率图如下:

pi0_pick 用"先下降、再抬升,并且夹爪处于夹持范围"作为抓取成功的启发式。工具在各 chunk 结束时采样末端高度和夹爪开度,三个条件与本次诊断的对应关系是:
| 启发式条件 | 本次观察值 | 是否满足 |
|---|---|---|
从调用起点下降至少 descent_thresh=0.1 m |
0.1914 - 0.1121 ≈ 0.0793 m |
否 |
从记录到的最低点重新抬升至少 lift_thresh=0.05 m |
0.2712 - 0.1121 ≈ 0.1591 m |
是 |
两指间距代理值处于 [0, 0.06) m |
最终约 0.0369 m |
是 |
这里的两指间距代理值由两指关节位置的绝对值相加得到。工具要求三个条件同时满足;本次下降条件未满足,且环境任务尚未成功终止,因此耗尽 8 chunks 后返回 success:false。预定位已把夹爪带到较低的抓取起点,剩余下降行程可能不足固定阈值,这个启发式便可能漏判已经完成的抓取。
agent 继续结合图像检查:沙拉酱瓶已抬起、原位置为空,腕部图中瓶身位于两指之间,再结合夹爪开度判断已经抓住,于是调用 set_gripper(gripper=1, steps=8) 加固夹持。这里有三种不同的信息:工具的启发式没有通过;agent 根据图像与状态判断局部抓取已完成;顶层 terminated:false 表示"入篮"这个完整任务还没完成。最终任务成功仍需等待环境判定。
沿着这一次交互,agent 根据预定位后的观察选择 pi0_pick,工具在内部执行 VLA 动作循环,返回新状态和图像,agent 再选择夹紧与搬运。这就是工具之间的高层决策闭环;VLA 的 chunk 循环则发生在单个工具内部。
因此本次只显式调用一次 view_env_state,但每次动作完成后都能继续观察。实际传回模型的是 11 组、共 33 张图像:policy RGB、agentview 高分辨率 RGB、wrist 高分辨率 RGB。
调度器将会推进环境的动作互斥执行,允许符合调度条件的只读查询并发。这里 readonly=True 表示不会推进机器人环境;例如写文本文件也属于此类,它仍然会修改磁盘。RPent 文件工具的路径访问由 MemoryManager 校验;Codex 自身的 shell 调用不经过这套工具调度与路径校验。
6.4 测量夹持偏移,再搬运和释放
抓住物体后,agent 再次用腕部图像定位瓶身,测得瓶身相对末端约有 +0.013 m 的 x 偏移。篮筐内部中心 x 约为 0.005 m,所以末端的放置目标应当取约 0.005 - 0.013 = -0.008 m,这样对准篮筐的才是瓶身。
它保持夹爪闭合,经过抬高的中间点,移到篮筐上方。在确认瓶身与内部区域接近对齐后,垂直下降到请求高度 z=0.22,再调用 release(max_steps=30)。
从观察定位到最终释放,本次实际执行的完整物理动作如下。xyz 是末端目标世界坐标,单位为米。
| 保存的 step | 动作 | 目的 |
|---|---|---|
| 1 | move_to([-0.05, 0.24, 0.28], gripper=-1) |
观察篮筐 |
| 2 | move_to([-0.11, 0.075, 0.28], gripper=-1) |
返回中间点 |
| 3 | move_to([-0.181, -0.079, 0.28], gripper=-1) |
到瓶子上方 |
| 4 | move_to([-0.186, -0.079, 0.18], gripper=-1) |
下降到抓取预备位置 |
| 5 | pi0_pick,最多 8 chunks |
Pi0.5 抓取 |
| 6 | set_gripper(gripper=1, steps=8) |
加固夹持 |
| 7 | move_to([-0.06, 0.14, 0.30], gripper=1) |
搬运中间点 |
| 8 | move_to([-0.008, 0.263, 0.30], gripper=1) |
移到篮筐上方 |
| 9 | move_to([-0.008, 0.263, 0.22], gripper=1) |
缓慢下降 |
| 10 | release(max_steps=30) |
松开,环境判定成功 |
7. 判断结果并保存产物:任务怎样结束
7.1 环境返回成功,agent 再报告完成
本次释放实际执行 7 个环境控制步后,原生环境返回 terminated:true、truncated:false。成功状态出现在第 10 条动作后的记录中。

本次只有一个连续 episode,没有 reset。环境信号是成功判定的依据;agent 随后发出的 finish(status="success") 是对任务结束的报告。
7.2 先写 audit,再导出动作序列
agent 调用 write_text_file,将本次执行报告保存为 object_swap_t2_s0.json。这个 audit 包含读取过的 memory、策略说明、定位点、抓取诊断和最终状态。
随后 agent 调用 finish(status="success", summary=...)。本次评测路径中的 finish 返回 _finish:true 以及 agent 填写的状态和说明,planner 据此结束规划并返回。
环境成功检查和 recipe 导出由外层运行器完成:运行器读取 Toolkit 累积的环境成功状态,确认任务已完成,再从记录中导出 object_swap_t2_s0_recipe.jsonl。recipe 导出逻辑还会检查环境轨迹中是否存在成功终止记录;仅填写 status="success" 不会让失败轨迹成为成功 recipe。最后关闭 Toolkit、保存视频,并清理本次启动的后台服务。
JSONL 每行是一条动作,保存"依次执行了什么";audit 解释"为什么这样做、结果如何"。本次只有运行目录中的这些产物,没有调用探索模式的 memory 合并流程,因此后续任务不会自动从原 HF 快照中读到这份新经验。
recipe 保存的是工具命令与参数,例如一次 move_to 的目标位置或一次 pi0_pick 的短指令和预算。它不包含当时全部观察与决策过程,也不是逐控制步的底层动作数组。换一个物体布局时,agent 仍需重新观察和定位,不能把这次的绝对坐标序列当作保证成功的通用计划。
7.3 回看一项决策时,应查哪些文件
| 文件或目录 | 内容 | 适合回答的问题 |
|---|---|---|
run.log、服务日志 |
启动、调用摘要、加载和退出信息 | 服务是否就绪、哪里报错 |
codex_*.txt |
易读的 agent 消息与工具摘要 | 大致采用了什么策略 |
*.stream.jsonl |
SDK 原始事件和工具数据快照 | prompt、参数、事件顺序是什么 |
states.json |
动作前后状态、结果、终止标志 | 环境真实发生了什么 |
<cell>.json |
agent 编写的 audit | agent 如何解释本次操作 |
<cell>_recipe.jsonl |
框架导出的动作序列 | 成功轨迹包含哪些工具命令 |
*.png/、*.npz/ |
按状态编号保存的图像、深度和坐标图 | 当时看到了什么、坐标来自哪里 |
episode.mp4 |
执行视频 | 整体动作过程是什么样 |
分析时应同时对照 agent 的报告、工具返回和环境状态。比如当前 LIBERO 路径下 transcript 的 environment_success 为 null,但本次 states.json 的最终原生状态为 terminated:true,不能把 null 直接解释为失败。
这条 pipeline 中,高层 agent 在工具之间观察和选择策略,VLA 与脚本控制在单个工具内部执行多步动作。经验帮助选择方法,当前图像与标定提供位置,环境状态负责反馈结果。三个环节共同决定下一步操作。
一次成功运行说明这条执行链在该任务上跑通;真实部署仍取决于视觉识别、深度与标定、动作模型和控制器的能力。高层决策也不是每个控制周期都调用的实时控制环,本次 Codex planner 耗时约 437 秒。
7.4 对照源码继续阅读
| Pipeline 阶段 | 源码入口 |
|---|---|
| 启动任务与服务 | main.py、robot_spec.py |
| 提交提示词、发布工具 | codex.py、http_mcp_server.py |
| 工具定义与调度 | tools/base.py、tools/toolkit.py |
| 读取和更新记忆 | libero/memory.py、memory/manager.py |
| 定位与操作 | libero/tools.py |
| Pi0.5 输入编码与预测 | pi05_vla_client.py、pi05_vla_server.py |
| 状态、recipe 和收尾 | libero/toolkit.py、main.py |