Skip to main content
Glama
Alonbbar6

robot-runtime

by Alonbbar6

Un runtime de control para políticas robóticas remotas

Un Franka Panda simulado realiza tareas de pick-and-place, impulsado por una política que vive detrás de un límite HTTP — y sigue funcionando cuando ese límite se comporta mal.

el runtime resistiendo una caída del servidor y reanudando

La parte interesante no es el brazo. Es todo lo que hay entre el brazo y el modelo: programación de fragmentos de acción, rechazo de datos obsoletos, reintentos con backoff, un interruptor de circuito, una retención protectora que se despeja sola, y una superficie de herramientas MCP para que un agente pueda manejar la celda sin poder dañarla.

Se ejecuta por completo en un portátil. Sin GPU, sin instalación de ROS, sin hardware.


Por qué la red es la parte difícil

Las políticas de manipulación quieren una GPU. Los robots quieren un bucle de control en tiempo real. Rara vez son la misma máquina, así que en la práctica el modelo está detrás de un salto de red — por eso las políticas emiten fragmentos de acción en lugar de pasos individuales. No puedes hacer un viaje de ida y vuelta a un servidor de inferencia a 50 Hz, pero puedes pedir 400 ms de acciones a la vez y seguir ejecutando mientras el siguiente fragmento está en vuelo.

Cada problema difícil en este repositorio se deriva de ese único salto:

  • Un fragmento describe un mundo que existía cuando se tomó la observación. Para cuando llega, ya está desactualizado. ¿Cuánto desactualizado es demasiado?

  • El bucle de control debe comandar el brazo cada 20 ms, responda o no el servidor. ¿Qué hace cuando no hay nada válido que ejecutar?

  • Las solicitudes fallan, se reintentan y llegan fuera de orden. ¿Qué impide que una respuesta más antigua sobrescriba una más nueva?

  • Un modelo puede emitir NaNs; un servidor con una versión incorrecta puede enviar objetivos para un robot diferente. ¿Qué se niega a pasar eso a los actuadores?

Related MCP server: omni-kit-mcp

Resultados

25 semillas por condición, HTTP real, fallos inyectados desde un RNG con semilla. python experiments/latency_sweep.py --seeds 25 --ablations

condición

éxito de tarea

terminó de forma segura

tiempo mediano

retenido

recuperaciones

latencia p50

rechazos obsoletos

reintentos

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% pérdida

100%

100%

12.6 s

0.36 s

68

280 ms

222

64

lossy — 20% pérdida

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 — servidor caído 3 s

100%

100%

13.9 s

6.2 s

25

60 ms

0

24

Éxito de tarea es el cubo en el objetivo. Terminó de forma segura es una columna separada a propósito: una ejecución puede fallar la tarea y aun así ser correcta, porque detenerse a veces es la respuesta correcta. Colapsar las dos ocultaría la diferencia entre la red estaba mal y el robot hizo algo que no debería.

El patrón en la tabla es el objetivo de diseño: a medida que el enlace se degrada, el robot se vuelve más lento, no incorrecto. Un enlace congestionado de 250 ms duplica el tiempo de ciclo y rechaza 222 fragmentos obsoletos; no deja caer el cubo ni alcanza un lugar donde no debería.

Ablaciones — cada mitigación, eliminada

Una comprobación de seguridad que nunca has visto fallar es una comprobación de seguridad que no puedes afirmar que funciona.

eliminado

éxito de tarea

tiempo mediano

nota

(nada — línea base congested)

100%

12.6 s

recuperación de la retención (outage)

0%

0.5 s

parada enclavada, nunca se reanuda

comprobación de obsolescencia (congested)

88%

23.7 s

ejecuta planes para un mundo que se movió

reintentos (congested)

96%

17.5 s

avance adaptativo (congested)

100%

12.4 s

pero 1.10 s retenido vs 0.36 s

reintentos (lossy)

100%

7.9 s

más rápido sin ellos — ver abajo

Lo que el barrido de fallos realmente encontró

Ambos fueron defectos reales. Ninguno era visible contra un servidor localhost sano; ambos aparecieron la primera vez que se ejecutó el barrido.

1. La parada protectora no tenía forma de volver. En el perfil outage, el runtime detectó correctamente el servidor muerto, mantuvo la posición y enclavó un e-stop — luego se quedó ahí mientras el servidor volvía tres segundos después. Correcto, e inútil. Un robot que necesita que un humano camine hasta él y lo rearme después de cada parpadeo de red se desenchufa en la segunda semana.

La solución divide un concepto en dos: una retención protectora que se despeja sola en el instante en que llega un fragmento válido, y un e-stop enclavado ocho segundos después si nunca llega. outage pasó de 0% → 100%, y el mismo cambio arregló congested. La figura al principio es esa solución funcionando.

2. El tiempo de avance de la solicitud era más corto que la latencia. El runtime pedía el siguiente fragmento cuando quedaban 120 ms de acciones. En el enlace congestionado, el viaje de ida y vuelta era de 280 ms. Cada solicitud se emitía 140 ms demasiado tarde para ser útil, así que el brazo se quedaba sin acciones en casi cada límite de fragmento. Ninguna cantidad de reintentos arregla una solicitud que se envió demasiado tarde — tienes que pedir antes.

El runtime ahora mide su propia latencia p95 y escala el avance a ella. El tiempo retenido en congested bajó de 1.10 s a 0.36 s.

3. Una mitigación que no se paga a sí misma. En el perfil lossy, desactivar los reintentos hizo las cosas más rápidas (7.9 s vs 9.8 s) sin pérdida de tasa de éxito, y eliminó 197 rechazos obsoletos. En un enlace de baja latencia, el fragmentado ya proporciona la redundancia: para cuando llega un reintento, una solicitud nueva habría sido más útil. Los reintentos ganan su lugar en congested (96% → 100%) y no en lossy. Está en la tabla porque informar solo de las mitigaciones que funcionaron es como terminas enviando las que no.

Cómo funciona

        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                │

módulo

un trabajo

contracts.py

cada tipo que cruza el cable, definido una vez

clock.py

tiempo, inyectable — real o virtual

sim.py

MuJoCo detrás de seis métodos; cambia por hardware aquí

policies/scripted.py

hace las veces de un VLA: sin estado, fragmentado, reactivo

server.py

la política, detrás de HTTP

client.py

enviar/consultar, plazos, reintentos, backoff, interruptor de circuito

scheduler.py

qué fragmentos confiar, qué acciones ejecutar

safety.py

asume que la política está equivocada

runtime.py

el bucle de 50 Hz

recording.py

registro MCAP

mcp_server.py

la celda como herramientas MCP

Tres decisiones que vale la pena destacar:

El bucle nunca se bloquea en la red. El paso 2 envía, el paso 1 consulta, nada espera. Un bucle de control que un servidor lento puede detener no es un bucle de control.

La obsolescencia se mide desde observed_at, no desde la llegada. Un fragmento que tardó 300 ms en volver está 300 ms desactualizado en el momento en que llega.

Las comprobaciones de seguridad se ejecutan a dos velocidades diferentes. La validación de fragmentos es costosa (cinemática directa en cada acción) y se ejecuta una vez por fragmento en el límite de confianza. La limitación de velocidad es barata y se ejecuta en cada tick. Rechazar un plan malo por completo es mejor que ajustarlo a algo sutilmente incorrecto.

El truco del reloj

El tiempo virtual corre ~100× más rápido que el tiempo real, así que 400 ms de tiempo de robot transcurren en 4 ms de reloj de pared — más rápido que un viaje de ida y vuelta HTTP a localhost. Sin cuidado, cada respuesta parece tardía y el experimento mide el arnés en lugar del runtime.

Así que SimClock.settle() se bloquea en segundos reales sin avanzar los virtuales. El único retraso que el runtime observa es el retraso que el perfil de fallo pidió. Mismo código de cliente, mismas rutas de reintento, misma lógica de obsolescencia — bajo WallClock en hardware, settle() es un no-op. Eso es lo que hace que cada número de arriba sea reproducible al bit.

Manejándolo desde un agente (MCP)

python -m robot_runtime.mcp_server

Doce herramientas. Seis de solo lectura (estado, cámaras, perfiles de fallo, grabaciones, registro de auditoría), seis que mueven el robot. El control de acceso es estado del lado del servidor, no una solicitud en el prompt:

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"}
  • El movimiento está controlado; la lectura no; el botón de parada nunca lo está. Un control de seguridad al que tienes que autenticarte para llegar no es un control de seguridad.

  • El armado requiere una razón y expira, y la razón se registra.

  • Los errores son resultados estructurados con un hint, nunca excepciones. Un agente que lee hint: start the policy server puede arreglar el problema. Un stack trace lo hace adivinar.

  • Cada llamada se añade a un registro de auditoría legible a través de la misma interfaz, así que "¿qué llamó exactamente?" siempre tiene una respuesta.

Observabilidad y reproducción

Cada episodio se graba en MCAP — el contenedor en el que ROS 2 registra — en cuatro temas: /observation, /action_chunk, /command, /event. El tema de eventos es el que importa, porque registra decisiones, no solo datos:

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

Nueve líneas, no las 128 que escribía la primera versión — los eventos repetidos se colapsan. El interruptor de circuito rechaza una solicitud en cada uno de los 50 ticks por segundo que está abierto, y escribir los cincuenta no dice nada que el primero no dijera.

Reproducir una grabación re-ejecuta los comandos exactos en un simulador recién sembrado:

$ 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

Bit-exacto. No lo era, al principio: /command se registraba redondeado a seis decimales, lo que ponía una micra de deriva entre una ejecución y su propia reproducción. Pequeño — y hacía que "reproduce exactamente" fuera falso, que es el punto completo de grabar.

Así es también como se arregla un fallo de campo: envía el MCAP de vuelta desde el sitio, reprodúcelo, mira al brazo hacer lo incorrecto de nuevo en tu portátil.

Ejecutándolo

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

El venv va en el disco interno deliberadamente — este repositorio está en un volumen exFAT, donde macOS dispersa archivos AppleDouble ._ que el cargador de plugins de MuJoCo intenta dlopen y muere, y que git no puede mantener un índice de paquetes a través de.

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 en lugar de python para cualquier cosa con un visor — en macOS la ventana debe ser dueña del hilo principal.

Pruebas

38 pruebas, unos cinco segundos, sin mocks de la cosa bajo prueba. Las pruebas del cliente usan un transporte falso pero el inyector de fallos real; las pruebas del runtime van por HTTP real a un servidor real en un hilo.

Tres de ellas son los defectos de arriba, mantenidos como regresiones: test_server_outage_holds_then_recovers, test_adaptive_lead_reduces_time_spent_holding, y test_the_stage_machine_does_not_oscillate — una política anterior alternaba lift→carry→lift→carry porque comprobaba la altura antes que la posición.

Lo que esto no es

Dicho sin rodeos: exagerar ante ingenieros de robótica hace que fracases en la entrevista, no en la pantalla.

  • Solo simulación. Sin hardware, sin transferencia sim-to-real, sin calibración del modelo de contacto contra un Panda real.

  • La política está escrita a mano, no aprendida. Está deliberadamente diseñada como una VLA — sin estado, por fragmentos, condicionada por lenguaje, reactiva — para que el runtime se ejercite como lo haría con un modelo real. Pero nada aquí está entrenado, y no se hace ninguna afirmación sobre la calidad del modelo.

  • Las poses de los objetos provienen del simulador, no de la percepción. La observación incluye una imagen de cámara y el contrato la soporta; la política programada ignora los píxeles. Un despliegue real necesita una pila de percepción, y esa brecha es la más grande de esta lista.

  • Un solo brazo, un solo objeto rígido, una sola tarea.

  • No es ROS. MCAP se usa porque es el contenedor adecuado y el ecosistema lo lee, pero no hay nodos, ni árbol TF, ni archivos de lanzamiento.

  • Construido en aproximadamente un día, como una demostración centrada de la capa de runtime.

Hacia dónde va después

En orden aproximado de valor:

  1. Percepción en el bucle — pose desde la imagen de la cámara en lugar de desde el simulador. Cierra la brecha más grande de esta lista.

  2. Entrenar la política. Generar demostraciones con el controlador programado, ajustar un modelo de clonación de comportamiento por fragmentos de acción, servirlo a través del mismo contrato /predict. El runtime no debería necesitar ni una línea cambiada — esa es la afirmación que hace la interfaz Policy, y actualmente no está probada.

  3. Dos brazos, lo que convierte la programación de fragmentos en un problema de coordinación real en lugar de uno de contabilidad.

  4. Un Panda real, donde settle() se convierte en un no-op y cada número de latencia de este README se vuelve a medir de verdad.

Créditos

Modelo Panda de MuJoCo Menagerie (Apache 2.0). Física: MuJoCo. Registro: 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