Skip to main content
Glama
Alonbbar6

robot-runtime

by Alonbbar6

远程机器人策略的控制运行时

一个模拟的 Franka Panda 执行抓取-放置任务,由位于 HTTP 边界之后的策略驱动——并且在该边界出现故障时仍能继续工作。

运行时在服务器中断期间保持运行并恢复

有趣的部分不是机械臂。而是机械臂与模型之间的一切:动作块调度、陈旧拒绝、带退避的重试、熔断器、可自行解除的保护性保持,以及一个 MCP 工具面,让智能体能够驱动工作单元而不至于造成伤害。

完全在笔记本电脑上运行。无需 GPU,无需安装 ROS,无需硬件。


为什么网络才是难点

操作策略需要 GPU。机器人需要实时控制回路。它们很少在同一台机器上,因此实际上模型位于一次网络跳转之后——这正是策略发出动作块而非单步动作的原因。你无法以 50 Hz 的频率往返访问推理服务器,但你可以一次请求 400 ms 的动作,并在下一个块在途时继续执行。

本仓库中的每一个难题都源于这一次跳转:

  • 一个块描述的是采集观测时存在的世界。等它到达时已经过时了。过时多少才算太多?

  • 控制回路必须每 20 ms 命令一次机械臂,无论服务器是否已应答。当没有有效动作可执行时,它该怎么办?

  • 请求会失败、重试,并且乱序到达。是什么阻止了较旧的答案覆盖较新的答案?

  • 模型可能输出 NaN;版本不匹配的服务器可能发送针对另一台机器人的目标。是什么拒绝将这些传给执行器?

Related MCP server: VELMA

结果

每种条件 25 个种子,真实 HTTP,从带种子的 RNG 注入故障。 python experiments/latency_sweep.py --seeds 25 --ablations

条件

任务成功率

安全结束

中位时间

保持时间

恢复次数

p50 延迟

陈旧拒绝

重试次数

clean

100%

100%

6.4 s

0.0 s

0

20 ms

0

0

lan — 20 ms ± 5

100%

100%

6.7 s

0.0 s

0

40 ms

0

0

wan — 150 ms ± 40

100%

100%

11.1 s

0.0 s

0

160 ms

0

0

congested — 250 ms ± 150,5% 丢包

100%

100%

12.6 s

0.36 s

68

280 ms

222

64

lossy — 20% 丢包

100%

100%

9.8 s

0.26 s

50

60 ms

197

222

flaky_server — 20% 5xx

100%

100%

8.1 s

0.0 s

0

60 ms

0

248

outage — 服务器消失 3 秒

100%

100%

13.9 s

6.2 s

25

60 ms

0

24

任务成功率是立方体到达目标位置。安全结束是单独一列,这是有意的:一次运行可以任务失败但仍然正确,因为停止有时才是正确的答案。将两者合并会掩盖网络不好与机器人做了不该做的事之间的区别。

表格中的规律就是设计目标:随着链路恶化,机器人变得更慢,而不是出错。250 ms 的拥塞链路使周期时间翻倍并拒绝 222 个陈旧块;它不会掉落立方体,也不会伸到不该伸的地方。

消融实验——移除每一项缓解措施

一个你从未见过它失效的安全检查,就不能声称它有效。

移除项

任务成功率

中位时间

备注

(无——基线 congested)

100%

12.6 s

从保持中恢复(outage)

0%

0.5 s

锁存停止,永不恢复

陈旧检查(congested)

88%

23.7 s

执行针对已移动世界的计划

重试(congested)

96%

17.5 s

自适应提前量(congested)

100%

12.4 s

但保持 1.10 s 对比 0.36 s

重试(lossy)

100%

7.9 s

去掉反而更快——见下文

故障扫描实际发现了什么

这两个都是真实缺陷。在健康的 localhost 服务器上都不可见;两者都是在扫描第一次运行时才显现的。

1. 保护性停止没有返回路径。 在 outage 配置下,运行时正确检测到服务器已死,保持位置,并锁存了急停——然后服务器三秒后恢复时它却坐在那里。正确,但毫无用处。一个每次网络抖动都需要人走过去重新布防的机器人,在第二周就会被拔掉插头。

修复将一个概念拆分为两个:一个保护性保持,在有效块到达的瞬间自动解除;以及一个八秒后仍未解除时的锁存急停。outage 从 0% 变为 100%,同样的修改也修复了 congested。顶部的图就是该修复在起作用。

2. 请求提前量短于延迟。 运行时在剩余 120 ms 动作时请求下一个块。在拥塞链路上往返需要 280 ms。每个请求都晚了 140 ms 才发出,毫无用处,因此机械臂几乎在每个块边界都会饥饿。无论重试多少次都无法修复一个发送太晚的请求——你必须更早请求。

运行时现在测量自身的 p95 延迟并将提前量缩放到该值。congested 上的保持时间从 1.10 s 降至 0.36 s。

3. 一项不划算的缓解措施。 在 lossy 配置下,关闭重试反而更快(7.9 s 对比 9.8 s),成功率没有损失,并且消除了 197 次陈旧拒绝。在低延迟链路上,分块本身已经提供了冗余:等重试到达时,一个新的请求本来会更有用。重试在 congested 上值得存在(96% → 100%),在 lossy 上则不然。它被列在表中,因为只报告有效的缓解措施,正是导致你上线那些无效措施的原因。

工作原理

        robot side                          │            policy side
                                            │
  ┌──────────────────────────────┐          │       ┌────────────────────┐
  │ runtime.py  50 Hz loop       │          │       │ server.py          │
  │   1 collect ── poll ─────────┼── HTTP ──┼──────▶│  POST /predict     │
  │   2 request ── submit        │          │       │  obs → 20 actions  │
  │   3 act                      │◀─────────┼───────│                    │
  │   4 check                    │          │       └────────────────────┘
  └──┬────────┬────────┬─────────┘          │        stateless; knows
     │        │        │                    │        nothing about episodes
     ▼        ▼        ▼                    │        or scheduling
  client   scheduler  safety                │
  retries  staleness  NaN/limits/workspace  │
  backoff  ordering   rate limit            │
  breaker  discards   e-stop                │

模块

单一职责

contracts.py

所有跨线传输的类型,只定义一次

clock.py

时间,可注入——真实或虚拟

sim.py

六个方法背后的 MuJoCo;在此处替换为硬件

policies/scripted.py

代替 VLA:无状态、分块、响应式

server.py

策略,位于 HTTP 之后

client.py

提交/轮询、截止时间、重试、退避、熔断器

scheduler.py

哪些块可信,哪些动作可执行

safety.py

假设策略是错的

runtime.py

50 Hz 循环

recording.py

MCAP 日志记录

mcp_server.py

作为 MCP 工具的工作单元

三个值得指出的决策:

循环从不阻塞在网络上。 第 2 步提交,第 1 步轮询,没有任何等待。一个会被慢服务器卡住的控制回路不是控制回路。

陈旧度从 observed_at 开始测量,而不是到达时间。 一个花了 300 ms 才返回的块,在它落地的那一刻就已经过时了 300 ms。

安全检查以两种不同的速率运行。 块验证很昂贵(对每个动作做正向运动学),在每个块的信任边界运行一次。速率限制很便宜,每个 tick 都运行。整体拒绝一个坏计划,好过把它钳制到某个微妙错误的东西上。

时钟技巧

虚拟时间以约 100 倍于真实时间的速度运行,因此 400 ms 的机器人时间在 4 ms 的墙钟时间内流逝——比到 localhost 的 HTTP 往返还快。如果不加小心,每个响应看起来都迟到了,实验测量的就是测试装置而不是运行时。

所以 SimClock.settle() 以真实秒阻塞,而不推进虚拟秒。运行时观察到的唯一延迟就是故障配置所要求的延迟。相同的客户端代码、相同的重试路径、相同的陈旧逻辑——在硬件上使用 WallClock 时,settle() 是空操作。这就是上面每个数字都能逐位复现的原因。

从智能体驱动它(MCP)

python -m robot_runtime.mcp_server

十二个工具。六个只读(状态、相机、故障配置、录制、审计日志),六个移动机器人。门控是服务器端状态,而不是提示中的请求:

run_pick_and_place  → {"ok": false, "error": "cell is not armed",
                       "hint": "call arm_cell with a reason before commanding motion"}
arm_cell("  ")      → {"ok": false, "error": "a reason is required"}
arm_cell("demo")    → {"ok": true, "armed": true, "expires_in_s": 120.0}
emergency_stop()    → {"ok": true, "estopped": true}
run_pick_and_place  → {"ok": false, "error": "cell is e-stopped"}
clear_estop()       → {"ok": false, "error": "confirmation required"}
  • 运动被门控;读取不受门控;停止按钮永远不受门控。 一个需要认证才能触达的安全控制不是安全控制。

  • 布防需要理由并且会过期,理由会被记录。

  • 错误是带有 hint 的结构化结果,绝不是异常。 一个读到 hint: start the policy server 的智能体可以修复问题。堆栈跟踪只会让它猜。

  • 每次调用都会追加到审计日志,可通过同一接口读取,因此"它到底调用了什么?"永远有答案。

可观测性与回放

每次试验都记录到 MCAP——ROS 2 所记录的容器格式——四个主题:/observation、/action_chunk、/command、/event。事件主题才是关键,因为它记录的是决策,而不仅仅是数据:

3.28s  request_failed:     unreachable
3.52s  breaker_rejected:   circuit open, request not sent
3.80s  protective_hold:    no valid action for 0.50s
6.66s  hold_released:      resumed on chunk 136

九行,而不是第一版写的 128 行——重复事件被折叠。熔断器在打开的每一秒的 50 个 tick 中都会拒绝请求,写全部五十次并不会比第一次多传达任何信息。

回放录制会将完全相同的命令重新运行到新播种的模拟器中:

$ python experiments/replay.py recordings/outage-seed0.mcap
  commands_replayed: 533
  placement_error_m: 0.007850735794278705   # live run: 0.007850735794278705
  time drift: 0.000 ms

逐位精确。起初并非如此:/command 被四舍五入到六位小数记录,这给一次运行与其自身回放之间引入了微米级的漂移。很小——但它使"精确复现"为假,而这正是录制的全部意义。

这也是现场故障的修复方式:将 MCAP 从现场寄回,回放它,在你的笔记本电脑上再次看到机械臂做错事。

运行

python3 -m venv ~/.venvs/robotarm && ~/.venvs/robotarm/bin/pip install -r requirements.txt

虚拟环境特意放在内部磁盘上——本仓库位于 exFAT 卷上,macOS 会在其中散布 AppleDouble ._ 文件,MuJoCo 的插件加载器会尝试对其 dlopen 然后崩溃,而且 git 无法在其上维护包索引。

python experiments/latency_sweep.py --seeds 25 --ablations   # the results table
mjpython demos/run_with_viewer.py --profile outage           # watch it hold and recover
python experiments/replay.py recordings/outage-seed0.mcap    # replay a recording
python -m robot_runtime.mcp_server                           # agent-facing tools
mjpython demos/pick_and_place.py                             # the original scripted demo
pytest -q                                                    # 38 tests, ~5 s

任何带查看器的内容都用 mjpython 而不是 python——在 macOS 上窗口必须拥有主线程。

测试

38 个测试,约五秒,没有对被测对象的 mock。客户端测试使用假传输但使用真实的故障注入器;运行时测试通过真实 HTTP 访问线程中的真实服务器。

其中三个是上述缺陷,作为回归测试保留: test_server_outage_holds_then_recovers、 test_adaptive_lead_reduces_time_spent_holding,以及 test_the_stage_machine_does_not_oscillate——一个早期策略因为先检查高度再检查位置而翻转了 lift→carry→lift→carry。

这不是什么

直白地说,因为对机器人工程师过度吹嘘会在面试中被识破,而不是在筛选阶段。

  • 仅仿真。 没有硬件,没有 sim-to-real 迁移,没有针对真实 Panda 的接触模型标定。

  • 策略是手写的,而非学习得到的。 它刻意被塑造成 VLA 的样子——无状态、分块、语言条件化、反应式——以便运行时像被真实模型驱动一样被充分锻炼。但这里没有任何训练,也不对模型质量做任何声明。

  • 物体位姿来自仿真器,而非感知。 观测携带相机图像,契约也支持它;但脚本化策略忽略了像素。真实部署需要感知栈,而这是这里最大的差距。

  • 单机械臂、单个刚体、单一任务。

  • 不是 ROS。 使用 MCAP 是因为它是合适的容器且生态能读取它,但没有节点、没有 TF 树、没有 launch 文件。

  • 大约一天内构建完成,作为运行时层的聚焦演示。

后续方向

按价值粗略排序:

  1. 感知闭环——从相机图像而非仿真器获取位姿。弥补了这份清单上最大的差距。

  2. 训练策略。 用脚本化控制器生成演示数据,拟合一个动作分块的行为克隆模型,通过同一个 /predict 契约提供服务。运行时应该不需要改动一行代码——这就是 Policy 接口所做的声明,而目前它尚未经过测试。

  3. 双臂,这将分块调度变成一个真正的协调问题,而非簿记问题。

  4. 真实的 Panda,此时 settle() 变成空操作,本 README 中的每个延迟数字都将被重新真实测量。

致谢

Panda 模型来自 MuJoCo Menagerie(Apache 2.0)。物理引擎:MuJoCo。日志记录:MCAP。

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables driving Omniverse Kit apps (Isaac Sim, Isaac Lab) over MCP, allowing agents to control simulations, run Python, and call namespace-scoped tools via a single bridge.
    1
    2
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to control a simulated Pollen Robotics Microduck through MCP tools for movement, tricks, pushing, camera views, and state inspection.
    8
    3
    Apache 2.0
  • A
    license
    A
    quality
    B
    maintenance
    Enables LLM agents to safely inspect, plan, preview, and execute motions on ROS 2 manipulators, with server-enforced safety envelopes covering joint limits, workspace constraints, force/torque thresholds, rate limits, e-stop, human approval, and audit logging.
    22
    237 PyPI
    Apache 2.0