转载自公众号:敢敢AUTOHUB
0. 简介
ASPIRE(Agentic Skill Programming through Iterative Robot Exploration)是 NVIDIA GEAR 联合 UMich、UIUC、UC Berkeley、CMU 提出的机器人持续学习系统,面向的是一个长期没被解决好的场景:用 code-as-policy 范式写机器人控制程序时,一次 rollout 失败往往只给出"任务没成"这一条粗粒度信号,智能体既不知道是感知错了、抓取不稳、还是运动规划失败,更谈不上把这次修复经验留给下一个任务。
ASPIRE 没有沿用固定的"感知—规划—执行"流水线,而是通过三件东西把调试闭环补齐:一个暴露逐原语多模态执行轨迹的机器人执行引擎、一个把验证过的修复沉淀成可复用条目的技能库、以及一套在程序空间里做进化式搜索的探索机制。
从实验看,最值得关注的是它的自我增强曲线——在 LIBERO-Pro 扰动任务上相对最强基线最高提升 77 个百分点,在 Robosuite 双臂交接任务上从 20% 拉到 92%,并且技能库越大,迁移到没见过的长程任务时零样本成功率越高。下面结合论文与其依赖的 CaP-X 框架,重点拆解它的执行引擎、技能库和进化搜索这三条主线是怎么咬合成一个开放式学习循环的。
项目主页:https://research.nvidia.com/labs/gear/aspire/
1. 为什么又要给机器人编程造一个新系统
1.1 机器人程序的失败是"多因耦合"的失败
机器人编程之所以出名地难,核心问题在于失败很少是单点的。一段控制程序要同时协调多模态感知、运动规划、抓取生成、接触动力学,还要在长程任务里维持子目标之间的顺序。一次失败的 rollout 只能告诉你"任务没完成",却无法告诉你根因在哪一环。感知框错了物体?抓取姿态不稳?规划器算不出可行路径?还是下游的恢复逻辑崩了?没有细粒度的诊断轨迹,智能体就像蒙着眼睛修车——它甚至不知道该去看哪个证据、该在哪里定位失败、该尝试哪种修复策略。这正是过去 code-as-policy 系统一直卡住的地方。
1.2 现有路线的两个缺口
进一步看,已有工作可以分成两类,各有短板。第一类是**端到端 VLA(Vision-Language-Action)**策略,例如 OpenVLA、、,它们把感知直接映射到动作,但行为被压进神经网络权重里,出错时无法"打开来看、逐行改"。第二类是早期的 code-as-policy 系统,例如 Code-as-Policies、ProgPrompt、VoxPoser、RoboCodex,以及本文的直接基线 CaP-Agent0 ,它们把机器人行为写成可执行程序,理论上可读可改,但依赖的执行环境只给"任务级"反馈,且做完一个任务就把修复经验丢掉了。这里的关键是:一个解到第一百个任务的智能体,本质上和解第一个任务时一样"没经验"。ASPIRE 的核心判断是——要让机器人编程智能体像人类工程师一样,把调试经验复利式地攒起来。
2. 整体框架:coordinator–actor 加一个开放式学习循环
2.1 输入输出接口与三大组件
ASPIRE 采用 coordinator–actor 架构。中央 coordinator 管理共享技能库,并为每个任务派发一个 actor 编程智能体,从而实现跨任务的并行学习;每个 actor 负责在机器人执行引擎里写程序、执行、诊断、修复。这里要厘清的是 actor 之间不交换完整对话历史或原始 rollout 轨迹——可迁移的经验被蒸馏成技能库条目,这样每个 actor 的上下文窗口都能聚焦在当前任务规格、当前程序、以及当前失败对应的结构化执行轨迹上。整个系统由三个组件构成:暴露逐原语轨迹的机器人执行引擎(§3)、沉淀验证修复的技能库(§4)、在程序空间里探索的进化搜索(§5)。它们共同构成一个开放式学习循环——ASPIRE 见的任务越多,技能库越大,向新任务、更长程行为、乃至真机场景的迁移就越强。
2.2 一个关键的不对称:actor 写、coordinator 审
这套架构在工程上有一个非常关键的不对称设计。actor 是并行的、允许犯错的、上下文短的一次性工人;coordinator 是串行的、负责审计的、维护长期记忆的管家。actor 报告结构化 findings(失败模式、验证过的修复、可能可迁移的模式),coordinator 审计这些 findings、核对是否符合允许的 API 策略,只把通过调试验证、且大概率能迁移出本任务的修复晋升进共享库。这意味着技能库的写入是串行化的,避免了并行 actor 互相冲突地改库。换句话说,探索靠并行放量,质量靠串行把关,两条线各司其职。
3. 机器人执行引擎:把粗反馈换成逐原语轨迹
3.1 从固定反馈通道到开放式调试环境
具身编程智能体要调试程序,必须有执行证据。核心问题在于过去的方法用的是固定的、人工设计的反馈接口——要么是人工整理的场景级摘要,要么是一组预定义的观测。这带来一个两难:证据太少会藏住真正出错的原语,证据太多(比如塞一堆原始视觉帧)又会把智能体从"导致失败的因果链"上带偏。ASPIRE 把这个固定通道改造成了一个开放式调试环境。机器人执行引擎为感知、规划、控制的每一次原语调用记录多模态轨迹,把轨迹暴露给编程智能体,并执行智能体写的修复做闭环验证。这里的关键是引擎不给智能体喂完整视频,而是只保留每次原语调用前后各一帧加对应的 overlay 和返回值,让智能体能聚焦在被失败牵连的那几次调用附近。
下面这段代码用 CaP-X 风格的原语 API 展示了一段典型的机器人控制程序长什么样——它组合了感知、规划、抓取三类原语,每一次调用都会被执行引擎记录进轨迹。这里要厘清的是,code-as-policy 范式的价值恰恰在于这种显式性:行为不是藏在权重里的黑箱,而是一段可读、可改、可打断点的程序,执行引擎因此能在每个原语调用点埋探针。换句话说,程序结构本身就是调试接口。
# robot_program.py — CaP-X 风格的 code-as-policy 程序(导航并抓取物体)
def navigate_and_pick(target_name: str):
# 感知原语:返回物体位姿 + RGB overlay,全部进 trace
obj = perceive_object(target_name) # -> Detection(pose, mask, overlay)
if obj is None:
return Status.PERCEPTION_ERROR
# 规划原语:为导航生成可行目标位姿,返回 PLANNING_ERROR 时记录候选点
goal = compute_navigation_goal(obj.pose, standoff=0.5)
status = navigate_to_pose(goal) # -> Status + 规划日志
if status != Status.OK:
return status # 失败信号进 trace 供归因
# 抓取原语:生成候选抓取并执行,overlay 标出抓取点
grasp = generate_grasp(obj) # -> GraspCandidate(pose, score)
return execute_grasp(grasp)
这段代码做了什么,值得逐层拆开。它把一个"导航去抓收音机"的行为写成三次原语调用的串联,每一次调用都返回带状态码和视觉证据的结构化对象。它为什么这样设计?因为程序被显式表达成可执行原语序列,执行引擎才能在每个 perceive/navigate/generate_grasp 调用点埋下探针,记录 invoked API、输入输出、返回状态、以及 RGB keyframe、overlay、抓取候选、物体位姿、运动规划结果这些多模态证据。工程细节上,navigate_to_pose 返回 PLANNING_ERROR 时不是简单抛异常,而是把生成的候选导航目标一并写进日志——这恰恰是下一节调试能定位到"目标点落在桌子碰撞缓冲区里"的前提。
难点提示(逐原语轨迹为什么重要):把它想象成汽车的行车记录仪加 OBD 诊断口。没有它,修车师傅只知道"车没到目的地";有了它,师傅能回放每一段视频、读出每个传感器的报错码,直接定位到"是转向助力泵在某个转角报故障"。ASPIRE 的执行引擎就是给机器人程序装上了这套"逐帧回放 + 报错码",把不可见的失败变成可归因的证据链。
3.2 一个真实调试回合:收音机为什么抓不到
Fig 2 放大了 BEHAVIOR-1K 里"导航并抓取收音机"的一个调试回合。第一视角关键帧显示,机器人找到了收音机,却反复靠近失败,直到换了个方向才成功。原语轨迹把失败精确定位了:感知成功并返回了收音机位姿,但连续的navigate_to_pose调用都返回PLANNING_ERROR。智能体通过检查导航返回值和相关日志发现,生成的导航目标离桌子边界太近——大约在桌沿 20 厘米以内,触发了碰撞避免,导致规划器失败。这意味着失败根因不是检测或抓取收音机,而是目标位姿在桌子碰撞约束下不可行。这正是逐原语轨迹的价值:它让智能体能选择性地检查显著的原语日志,逐步定位失败,再通过重执行验证修复。
3.3 修复直接从诊断长出来
修复直接顺着诊断长出来,而不是瞎试。这里的关键是智能体没有去改感知 prompt、也没有去换抓取原语,而是写了一个多角度接近例程(Multi-Angle Approach):在收音机周围采样若干候选导航目标,选一个能清开碰撞缓冲区的接近方向,重新感知再抓取。下面这段修复代码就是这个例程的核心。
# repair_multi_angle_approach.py — 由失败轨迹诊断长出的修复
def multi_angle_approach(obj_pose, n_angles=8, radius=0.6, clearance=0.25):
for theta in sample_angles(n_angles): # 绕物体环形采样接近方向
cand = pose_around(obj_pose, radius, theta)
# 关键:先检查候选点是否落在障碍碰撞缓冲区内,落在里面直接跳过
if min_dist_to_obstacles(cand) < clearance:
continue
if navigate_to_pose(cand) == Status.OK:
return re_perceive_and_grasp(obj_pose) # 换到可达侧重新感知+抓取
return Status.PLANNING_ERROR
这段代码为什么关键?因为它把"目标点落进碰撞缓冲区"这个诊断结论直接编码成了防御逻辑——min_dist_to_obstacles(cand) < clearance 这一行显式过滤掉了会触发碰撞避免的候选点,正好对应上一节发现的"20 厘米桌沿"问题。工程细节上,clearance 取 0.25 米比诊断出的 0.20 米更保守,留了安全裕度。执行引擎随后暴露证据、验证这段打过补丁的程序,让智能体分析日志、形成假设、做出有针对性的修复决策。验证通过后,这段修复被收编成一条可复用的 Multi-Angle Approach 技能——注意,进库的是"导航恢复模式"这个模式,不是完整的收音机抓取程序。
4. 技能库:把验证过的修复变成可迁移知识
4.1 存的是修复模式,不是整段任务程序
程序失败会跨任务反复出现,但可复用的知识很少是一整段任务程序。ASPIRE 的技能库存的是异构的修复知识:定位启发式、感知 prompt、抓取约束、导航恢复策略、运动原语、场景理解例程、调试工作流。这里要厘清的是——这套分类不是预先规定的。技能是从验证过的修复里归纳出来的:编程智能体从执行轨迹诊断失败、给程序打补丁、在调试配置上验证修复,coordinator 只把可复用的模式收编进共享库。对前面的收音机任务,进库的技能是一条导航恢复模式,而不是完整的收音机抓取程序——当规划器在障碍边界附近反复报错、因为采样目标位姿落进碰撞缓冲区时,就在物体周围采样其它接近方向,再重试感知和抓取。
4.2 每条技能长什么样
每条技能被存成紧凑的 in-context 指引,包含失败签名(failure signature)、何时适用(when-to-apply)条件、修复策略,以及在有用时附上一段代表性代码草图。这种表示让未来的 actor 直接复用验证过的修复,而不必在测试时重新推理一遍;它支撑向更难的模拟任务做零样本迁移;也提供了让选定的"模拟中发现的技能"跨本体泛化、迁移到真机的机制。下面这段展示了一条技能条目的结构化 schema。
# skill_entry.py — 技能库条目的结构化表示
skill = {
"name": "multi_angle_approach",
"category": "navigation_recovery",
"failure_signature": "repeated PLANNING_ERROR near obstacle boundary",
"when_to_apply": "sampled nav goal falls inside collision buffer of a nearby surface",
"repair_strategy": "sample alternative approach directions around object, "
"filter by clearance, re-perceive from reachable side",
"code_sketch": "multi_angle_approach(obj_pose, n_angles=8, clearance=0.25)",
"origin_task": "behavior1k/navigate_and_pick_up_radio",
"validation_success_rate": 0.88,
}
这个 schema 为什么这样设计?核心在于 failure_signature 和 when_to_apply 这两个字段——它们决定了未来 actor 遇到新失败时能不能检索到这条技能。failure_signature 是失败的可匹配指纹("障碍边界附近反复 PLANNING_ERROR"),when_to_apply 是情境化的检索守卫。工程细节上,validation_success_rate 记录了这条修复在调试配置上的验证成功率,coordinator 据此判断是否值得晋升进库。
直觉理解(技能库为什么像索引卡):把老工程师脑子里"遇到 X 症状就试 Y 招"的经验,写成一张张可以被检索的索引卡。卡片正面是症状(failure_signature),卡片的适用条件是"什么时候该翻出这张卡"(when_to_apply),背面才是具体招式。新手(新 actor)遇到问题时不用从头推理,先按症状翻卡片,命中了就照着招式改——这就是 in-context 检索比"每次都重新想一遍"高效的地方。
4.3 coordinator 的准入审计
actor 报告结构化 findings,总结失败模式、验证过的修复、以及潜在可迁移的修复模式。coordinator 审计这些 findings、核实是否符合允许的 API 策略,只把通过调试验证、且大概率能迁移出本任务的可复用修复晋升进共享库。这里要点出的是并行 actor 被指示去写"任务级"修复和 findings,而coordinator 串行化技能准入,以避免冲突的库写入。这套准入机制是技能库质量的守门人——它保证进库的都是真正验证过、且有迁移潜力的模式,而不是某个任务特有的、换个场景就失效的 hack。
5. 进化搜索:跳出单轨迹修复的局部循环
5.1 为什么光靠轨迹调试会陷进死胡同
轨迹引导调试单独用会有个毛病——它容易坍缩进局部修复循环,也就是智能体反复给同一个失败策略打补丁,而不是去探索根本不同的解题方式。核心问题在于单轨迹自我改进的搜索半径太小。ASPIRE 用进化搜索来拓宽可执行机器人程序的探索空间,鼓励多样的修复假设和任务策略。每一轮里,基于技能库,编程智能体提出一个包含 个候选程序的种群,这些候选以表现最好的历史程序加上先前评估的失败轨迹为条件生成。每个候选在执行引擎里跑一遍,产出任务结果加新的诊断轨迹。下一轮再以最佳程序加它们残余的失败模式为条件——这样搜索就能探索不同策略,而不是反复精修同一个解。
用一个式子刻画这个"以历史精英为条件提出下一代"的过程。进化搜索借用了遗传算法的直觉,但基因不是数值向量,而是一整段可执行程序;变异和交叉不是位翻转,而是 LLM 在读过精英程序和失败轨迹后重写代码。这里要点出的是,条件里同时塞进"最好的历史"和"完整历史"两路信息,前者提供可继承的好解,后者提供"哪些路已经走死"的记忆。
其中 是冻结的前沿 LLM(本文用 Claude Opus 4.6,1M token 上下文), 是任务, 是历史 里表现最好的三个程序, 是当前技能库, 是每轮种群规模。这里的关键是提议同时吃"精英程序"和"完整历史"两路信息,既继承好解、又记住哪些方向已经试过。
5.2 执行打分与终止条件
每个候选通过闭环执行来筛选,而不是靠 LLM 自己"想象"哪个程序更好。这一点很关键:进化搜索的选择压力来自真实的模拟器 rollout,而非模型的自我评估,这避免了 LLM 高估自己写的代码的常见毛病。记 在配置集 上运行程序 ,返回分数 和轨迹束 :
每一轮都在调试集 上评估整个种群,取分数最高的候选 更新全局最优 。这里要厘清的是调试集和验证集是刻意分开的两组种子——搜索只在调试集上迭代,最终最优程序要在没见过的验证集上再验一次,这套"训练/验证"切分直接搬用了机器学习防过拟合的老办法。搜索的终止条件是双重的:
或
也就是说,要么某个候选在调试配置上解到了阈值 ,要么搜索预算 轮耗尽。这意味着进化搜索不是无限跑,而是"够好就停、超预算也停",把算力花在刀刃上。搜索结束后,验证过的修复才在满足"能跨环境变化和任务泛化"的前提下被收编进技能库——这一步是"探索"和"沉淀"的分界线,只有扛住了泛化检验的修复才配进库。
5.3 Algorithm 1 的完整实现
把前两节的式子拼起来,就是论文 Algorithm 1 的进化搜索主循环。下面这段 Python 忠实复现了论文伪代码的逻辑结构——从初始程序打分、逐轮提议候选、闭环执行、更新最优,到最后在验证集上抽取可泛化模式,每一步都对应上面推导的一个环节。读代码时可以对照着看:H 是那个既存精英又存历史的台账,ProposeRepairs 是变异算子,Execute 是选择压力的来源。
# evolutionary_search.py — 论文 Algorithm 1 的程序空间进化搜索
def evolutionary_search(task, P0, S_dbg, S_val, library, M, K, T, theta):
r_star, Z0 = Execute(P0, S_dbg) # 初始程序打分
P_star, H = P0, [(P0, r_star, Z0)] # H 是历史台账
for i in range(1, T + 1):
# 以 Top3 历史 + 技能库 + 完整历史为条件,提出 K 个候选修复
candidates = ProposeRepairs(M, task, top3(H), library, H)
for P in candidates:
r, Z = Execute(P, S_dbg) # 每个候选闭环执行
H.append((P, r, Z))
if r > r_star: # 更新全局最优
P_star, r_star = P, r
if r_star >= theta: # 解到阈值提前停
break
# 在独立验证集上评估最优程序,抽取可泛化的修复模式
r_val, Z_val = Execute(P_star, S_val)
G = ExtractValidatedPatterns(H, P_star, r_val, Z_val)
return P_star, r_val, G
这段代码在做什么?它维护一个历史台账 H,每轮以 Top3 历史程序、技能库、完整历史为条件生成 K 个候选,逐个闭环执行打分,更新全局最优 P_star,解到阈值 theta 就提前退出。为什么这样设计?关键在于最后两行:P_star 要在独立的验证集 S_val 上再评估一次,ExtractValidatedPatterns 只抽取那些能跨环境变化泛化的修复模式 G——这是防止"过拟合到调试配置"的关键闸门。工程细节上,ProposeRepairs 同时吃精英和历史,既能利用好解又能避免重蹈覆辙。
6. 从模拟到真机:技能能不能跨本体迁移
6.1 迁移不是策略部署,是 in-context 指引
真机技能迁移这部分的实验设计要厘清一个常见误解——这不是把策略直接部署到真机。真机用自己的感知、标定、控制栈,编程智能体仍然必须通过真实世界的执行反馈来适配程序。核心问题在于:检索"模拟中发现的技能",能不能提供有用的 in-context 指引,从而减少达到一个成功程序所需的真机调试量?论文在一个双臂 YAM 操作台上,选了三条在 Franka 模拟中编译出的技能——汽水罐抓取、碗放盘子、抽屉推拉——作为 in-context 指引喂给真机编程智能体,且真机用的是不同的本体和 API。这里要点出的是真机设定同样暴露 ASPIRE 的多模态执行轨迹,让智能体能自主运行、检查、调试程序,调试循环里不需要任务特定的人工指导。
6.2 迁移一致地降低调试成本
迁移技能一致地降低了调试成本,而对最终成功率的影响则因任务而异。这里给一组具体数字:碗放盘子在有无技能两种设定下都成功,但检索技能后用的 token 更少(总 token 从 8.65M 降到 5.11M);汽水罐提举从 13/20 提升到 19/20,同时总 token 减少了近一个数量级(61.94M 降到 6.58M);抽屉操作在技能指引下达到 11/20 成功,而无技能基线在耗尽更大的 token 预算后仍没产出一个成功的评估程序(0/20,总 token 高达 334.9M,有技能则只用 81.67M 就到 11/20)。这意味着失败衍生的技能能跨本体和 API 变化指导真机程序合成,而不只是记住了模拟器特定的代码。
工程价值(近一个数量级的 token 节省意味着什么):真机调试的每一次 rollout 都要真实机械臂动一次,既慢又有磨损成本。把汽水罐任务的总 token 从 61.94M 压到 6.58M,等于把"需要人盯着反复试错的一整天"压缩成"喝杯咖啡的功夫"。这就是复用经验相对从零 debug 的复利效应——技能库越大,边际调试成本越低。
7. 总结
ASPIRE 的核心贡献不是再造一个名词,而是把机器人编程智能体从"一次性解题器"改造成"越debug越强的持续学习者"——用逐原语多模态轨迹让失败可归因、用技能库让修复经验跨任务复利、用进化搜索跳出局部修复循环。LIBERO-Pro 上最高 77 个百分点的提升、双臂交接从 20% 到 92%、零样本长程迁移从 4% 到 31% 这三个数据点合起来,足以让这套架构进入下一代机器人编程范式的候选清单。
但两个缺口还悬着——冻结前沿 LLM 的依赖决定了这套范式能否平民化,技能库的长期记忆管理决定了它能否规模化到工业级任务套件。真机部分虽然给了跨本体迁移的初步证据,但论文自己也承认,它还不是一个全自主的真实世界终身学习者:真机部署仍需要鲁棒的成功检测、安全复位、安全监控和标定维护。等 CaP-X 生态和完整技能库代码开源、等真机的"评估—复位"闭环补齐之后,再回过头看这套范式能不能真正落到工业和真实家庭场景。
205
