Skip to main content
Glama
Alonbbar6

robot-runtime

by Alonbbar6

Управляющая среда выполнения для удалённых политик робота

Симулированный Franka Panda выполняет операцию «взять и поставить», управляемый политикой, которая живёт за HTTP-границей — и продолжает работать, когда эта граница начинает вести себя некорректно.

среда выполнения, удерживающая позицию во время сбоя сервера и возобновляющая работу

Интересна не рука. Интересно всё, что между рукой и моделью: планирование пакетов действий, отбраковка устаревших данных, повторные попытки с экспоненциальной задержкой, автоматический выключатель, защитная остановка, которая сбрасывается сама, и поверхность MCP-инструментов, позволяющая агенту управлять ячейкой, не имея возможности ей навредить.

Работает целиком на ноутбуке. Без GPU, без установки ROS, без железа.


Почему сеть — это самое сложное

Политики манипуляций хотят GPU. Роботы хотят контур управления в реальном времени. Это редко одна и та же машина, поэтому на практике модель находится за сетевым переходом — именно поэтому политики выдают пакеты действий, а не отдельные шаги. Нельзя сделать round-trip к серверу инференса на 50 Гц, но можно запросить 400 мс действий за раз и продолжать выполнять, пока следующий пакет в полёте.

Каждая сложная проблема в этом репозитории вытекает из этого одного перехода:

  • Пакет описывает мир, каким он был в момент снятия наблюдения. К моменту получения он уже устарел. Насколько устаревшее — это слишком?

  • Контур управления должен отдавать команды руке каждые 20 мс, независимо от того, ответил ли сервер. Что он делает, когда нет ничего валидного для выполнения?

  • Запросы падают, повторяются и приходят не по порядку. Что мешает более старому ответу перезаписать более новый?

  • Модель может выдать NaN; сервер с неправильной версией может прислать целевые точки для другого робота. Что отказывается передавать это на приводы?

Related MCP server: omni-kit-mcp

Результаты

25 сидов на условие, реальный HTTP, сбои, внедряемые из сидированного ГПСЧ. python experiments/latency_sweep.py --seeds 25 --ablations

условие

успех задачи

безопасное завершение

медианное время

удержание

восстановления

p50 задержка

отклонено устаревших

повторные попытки

clean

100%

100%

6.4 с

0.0 с

0

20 мс

0

0

lan — 20 мс ± 5

100%

100%

6.7 с

0.0 с

0

40 мс

0

0

wan — 150 мс ± 40

100%

100%

11.1 с

0.0 с

0

160 мс

0

0

congested — 250 мс ± 150, 5% потерь

100%

100%

12.6 с

0.36 с

68

280 мс

222

64

lossy — 20% потерь

100%

100%

9.8 с

0.26 с

50

60 мс

197

222

flaky_server — 20% 5xx

100%

100%

8.1 с

0.0 с

0

60 мс

0

248

outage — сервер недоступен 3 с

100%

100%

13.9 с

6.2 с

25

60 мс

0

24

Успех задачи — это куб на цели. Безопасное завершение — отдельная колонка не случайно: запуск может провалить задачу и при этом быть корректным, потому что остановка иногда — правильный ответ. Сведение этих двух метрик в одну скрыло бы разницу между сеть была плохой и робот сделал то, чего не должен был.

Закономерность по всей таблице — это и есть цель дизайна: при деградации канала робот становится медленнее, но не ошибочнее. Перегруженный канал с задержкой 250 мс удваивает время цикла и отклоняет 222 устаревших пакета; он не роняет куб и не тянется туда, куда не следует.

Абляции — каждая мера защиты, удалённая

Проверка безопасности, которую вы ни разу не видели в сбое, — это проверка, о которой вы не можете утверждать, что она работает.

удалено

успех задачи

медианное время

примечание

(ничего — базовое congested)

100%

12.6 с

восстановление из удержания (outage)

0%

0.5 с

защёлкивающаяся остановка, не возобновляется

проверка устаревания (congested)

88%

23.7 с

выполняет планы для мира, который сдвинулся

повторные попытки (congested)

96%

17.5 с

адаптивное упреждение (congested)

100%

12.4 с

но 1.10 с удержания против 0.36 с

повторные попытки (lossy)

100%

7.9 с

быстрее без них — см. ниже

Что на самом деле обнаружил прогон сбоев

Оба пункта были реальными дефектами. Ни один не был виден при работе со здоровым localhost-сервером; оба проявились при первом же прогоне.

1. У защитной остановки не было пути назад. На профиле outage среда выполнения корректно обнаружила мёртвый сервер, удержала позицию и защёлкнула аварийную остановку — а затем сидела, пока сервер не вернулся через три секунды. Корректно и бесполезно. Робот, которому нужен человек, чтобы подойти и перевзвести его после каждого сетевого сбоя, будет отключён на второй неделе.

Исправление разделяет одно понятие на два: защитное удержание, которое сбрасывается само в момент получения валидного пакета, и защёлкивающаяся аварийная остановка через восемь секунд, если пакет так и не пришёл. outage вырос с 0% до 100%, и то же изменение исправило congested. Рисунок вверху — это работающее исправление.

2. Время упреждения запроса было меньше задержки. Среда выполнения запрашивала следующий пакет, когда оставалось 120 мс действий. На перегруженном канале round-trip составлял 280 мс. Каждый запрос отправлялся на 140 мс позже, чем нужно, чтобы быть полезным, поэтому рука голодала почти на каждой границе пакета. Никакое количество повторных попыток не исправит запрос, отправленный слишком поздно — нужно спрашивать раньше.

Теперь среда выполнения измеряет собственную p95-задержку и масштабирует упреждение под неё. Время удержания на congested упало с 1.10 с до 0.36 с.

3. Мера защиты, которая себя не окупает. На профиле lossy отключение повторных попыток сделало всё быстрее (7.9 с против 9.8 с) без потери доли успеха и устранило 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

submit/poll, дедлайны, повторные попытки, backoff, выключатель

scheduler.py

каким пакетам доверять, какие действия выполнять

safety.py

исходит из того, что политика ошибается

runtime.py

цикл на 50 Гц

recording.py

запись в MCAP

mcp_server.py

ячейка как MCP-инструменты

Три решения, которые стоит выделить:

Цикл никогда не блокируется на сети. Шаг 2 отправляет, шаг 1 опрашивает, ничего не ждёт. Контур управления, который медленный сервер может остановить, — это не контур управления.

Устаревание измеряется от observed_at, а не от момента прибытия. Пакет, который шёл 300 мс, устарел на 300 мс в момент приземления.

Проверки безопасности выполняются с двумя разными частотами. Валидация пакета дорогая (прямая кинематика на каждое действие) и выполняется один раз на пакет на границе доверия. Ограничение частоты дёшево и выполняется каждый тик. Отклонить плохой план целиком лучше, чем зажимать его до чего-то едва заметно неправильного.

Трюк с часами

Виртуальное время идёт в ~100 раз быстрее реального, поэтому 400 мс роботного времени проходят за 4 мс настенных часов — быстрее, чем HTTP round-trip к localhost. Без осторожности каждый ответ выглядит запоздавшим, и эксперимент измеряет стенд, а не среду выполнения.

Поэтому SimClock.settle() блокируется в реальных секундах, не продвигая виртуальные. Единственная задержка, которую когда-либо наблюдает среда выполнения, — это задержка, запрошенная профилем сбоя. Тот же клиентский код, те же пути повторных попыток, та же логика устаревания — под WallClock на железе settle() — это no-op. Именно это делает каждое число выше воспроизводимым до бита.

Управление из агента (MCP)

python -m robot_runtime.mcp_server

Двенадцать инструментов. Шесть read-only (состояние, камеры, профили сбоев, записи, журнал аудита), шесть двигают робота. Ограничение — это серверное состояние, а не просьба в промпте:

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, может исправить проблему. Stack trace заставляет его гадать.

  • Каждый вызов добавляется в журнал аудита, читаемый через тот же интерфейс, так что на вопрос «что именно он вызывал?» всегда есть ответ.

Наблюдаемость и воспроизведение

Каждый эпизод записывается в 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 тиков в секунду, пока он открыт, и запись всех пятидесяти не говорит ничего, чего не сказала бы первая.

Воспроизведение записи заново прогоняет те же команды в свежесидированный симулятор:

$ 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 не может поддерживать pack index.

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 тестов, около пяти секунд, без моков тестируемого объекта. Клиентские тесты используют фейковый транспорт, но настоящий инжектор сбоев; тесты среды выполнения идут через реальный 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() становится no-op, и каждое значение задержки в этом README измеряется заново по-настоящему.

Благодарности

Модель Panda из MuJoCo Menagerie (Apache 2.0). Физика: MuJoCo. Логирование: MCAP.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Hosted MCP endpoint with realistic fake data for prototyping agents. 12 tools, no setup.

  • A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal E…

  • Control plane for autonomous software labor. Agents claim objectives over MCP with audit trail.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Alonbbar6/robot-runtime'

If you have feedback or need assistance with the MCP directory API, please join our Discord server