# Исполнение графов Qwen3 в Webots: согласованная архитектура ## Цель и границы Новая система получает текущую сцену Webots, выдаёт `scene_json` для внешнего вызова Qwen3, принимает готовый JSON-граф модели и выполняет только его. Номер одного из 20 опытов задаёт **исходный набор объектов**, но не диктует цель или эталонную последовательность. Например, в сцене опыта 20 допустим отдельный запрос «возьми присоску, переместись в рабочую позицию, верни присоску»: исполнитель не должен самостоятельно наносить герметик или переносить люк. Вызов Qwen3, оценивание качества модели и инфраструктура сравнения с эталоном входят в последующий проект, а не в этот. Создать два ROS 2 пакета: `iiwa_scene_context` (снимок сцены и `scene_json`) и `iiwa_llm_plan_executor` (проверка и исполнение графа). Минимальные изменения существующих `iiwa_experement` и `iiwa_web` допустимы для запуска мира и для API/MCP. Реальный робот пока не подключать. Будущая замена источника сцены и физических навыков должна быть возможна через адаптеры, без переписывания валидатора графа. ## Что обнаружено в проекте `iiwa_experement` уже создаёт сцены, предоставляет `/iiwa_experement/scene_ready`, состояние объектов `/iiwa_experement/object_state`, команды захвата `/iiwa_experement/object_command`, измеренную геометрию, мост сцены MoveIt и эталонные операции. В `mode=view` supervisor работает только на чтение; `mode=execute` сразу запускает эталон. Для внешнего графа требуется третий `mode=planner`: supervisor с разрешёнными действиями, без эталонного runner, без TCP gizmo. Старые два режима сохраняют прежнюю семантику. `iiwa_web` уже публикует FastAPI и получает MCP через `FastMCP.from_fastapi`. Для новых функций следует добавить тонкие маршруты в этот сервер, а не запускать второй HTTP/MCP сервер. Приложенные `generated.train.jsonl` (4499 строк) и `generated.validation.jsonl` (500 строк) содержат общий процесс переноса в хранилище, без Grant-объектов и инструментов. Они используют в ответах `pick`, `move_to_storage`, `inspect`. Текущий `planner_dataset/assets/response-format.json` описывает другой список навыков, а показанный пользователем граф добавляет `align`. Поэтому исполнитель не должен слепо импортировать какую-либо одну из этих схем; нужна собственная версия контракта, совместимая с опубликованными `available_skills` и с оговорёнными алиасами. ## Подход к исполнению Выбран **граф + реестр навыков**. Движения, неизбежные внутри одного навыка, задаются его проверяемым контрактом: например, `grab` может сам подойти сверху, опуститься, захватить и подняться до сохранённой высоты, если явных узлов `approach`/`lower`/`lift` не было. Если они уже выполнялись, повторного движения нет. Это геометрические фазы **того же** действия, а не добавление нового технологического задания. Исполнитель не придумывает следующую деталь, нужный инструмент или цель из номера эксперимента. Если `grab(cap)` требует вакуумный инструмент, а в `patron` его нет, граф не отклоняется на входе: при исполнении узла проверяется живое состояние supervisor, захват не начинается, контакт не выполняется, и узел возвращает `grasp_failed` с причиной `required tool vacuum is not mounted`; далее исполняется ровно тот переход `grasp_failed`, который указала модель. Если в графе нет явного узла `grab(vacuum)`, инструмент автоматически не берётся. `release` инструмента **не возвращает его автоматически**. Для возврата граф должен явно содержать перемещение к гнезду `vacuum_socket` и затем `release(vacuum, vacuum_socket)` либо `dock_tool(vacuum, vacuum_socket)`; оба последних навыка только выполняют стыковку и отделение в уже достигнутом гнезде. `release(vacuum)` из `work` без подходящего места для инструмента отклоняется, присоска остаётся в `patron`. Если граф заканчивается в `work` с присоской в `patron`, она остаётся там до следующего явного задания. Новый процесс может подключить другой адаптер и свои контракты; неизвестный навык или объект не исполняется. Рассмотренный, но отвергнутый подход — переписывать граф модели и автоматически вставлять недостающие технологические действия. Это могло бы выполнить лишние операции при произвольных запросах. Второй отвергнутый подход — напрямую переводить ID навыка в фиксированную траекторию без проверки текущей позы и состояния; он не учитывает смещения объектов и ветви событий. ## Снимок сцены и интерфейсы `iiwa_scene_context` собирает **текущий** состав и логическое состояние из `ScenarioSpec` и живого supervisor, а не из эталонного маршрута. Внешний `scene_json` сохраняет поля, ожидаемые Qwen3: `domain`, `components`, `destinations`, `tools`, `initial_state`, `cameras`, `available_skills`, `skill_contracts`, `service_objects`, `recovery_attempts_per_component`. ID соответствуют Webots: `cap`, `vacuum`, `vacuum_socket`, `screw_1`…`screw_10`, `hatch_seat`, `hole_1`…`hole_4`, `work` и т. д. Гнездо каждого присутствующего инструмента публикуется как отдельная именованная цель. Список зависит от реально присутствующих объектов. Абсолютные координаты в модельный `scene_json` не передаются: навык `detect` получает актуальную позу при исполнении. Транспортный конверт содержит `experiment_id`, `scene_revision`, время снимка, версию состояния supervisor и сам объект `scene_json`. `scene_revision` меняется при дискретном изменении сцены или владения объектом, но не на каждом кадре движения удерживаемой детали. Версия графа передаётся отдельно; исполнитель отвергает граф, построенный по устаревшей сцене. Снимок доступен как ROS 2 сервис `/iiwa_scene_context/get_scene` (ответ с JSON), ROS 2 топик `/iiwa_scene_context/scene_json` с transient-local QoS, HTTP `GET /planner/scene` и одноимённый инструмент MCP через существующий `iiwa_web`. Контракты навыков берутся из серверного реестра, а не из текста, который прислала модель. ## Приём и переходы графа `iiwa_llm_plan_executor` получает готовый ответ Qwen3, `scene_revision` и `run_id` через ROS 2 сервис `/iiwa_llm_plan_executor/submit`; HTTP `POST /planner/runs` и MCP передают то же через адаптер `iiwa_web`. Модель в этой версии вызывается **вне** проекта. После успешной проверки исполнитель публикует принятый граф без изменений в ROS 2 топик `/iiwa_llm_plan_executor/plan` (`std_msgs/String`, JSON-конверт с `run_id`, `scene_revision` и исходным `response`, QoS transient-local, глубина 1), прежде чем выполнять первый узел. Отклонённый граф в этом топике не появляется; причина отказа доступна в ответе приёма и статусе. Выполнение асинхронно, не более одного активного графа на робота. Отдельный топик `/iiwa_llm_plan_executor/status` сообщает текущий узел, полученное событие, выбранное ребро и итоговый терминал; HTTP и MCP дают те же сведения. Отмена останавливает движение и сохраняет состояние захвата. Для запроса статуса **по `run_id`** добавить сервис `GetRunStatus.srv` в существующий `iiwa_msgs` (`string run_id` в запросе, `bool found` и `string status_json` в ответе); стандартный `Trigger` не имеет поля запроса. Предзапусковая проверка: строгий JSON без лишних исполняемых полей; `status=blocked` не запускает робот; уникальные ID, известный `entry`, только разрешённые навыки/объекты/цели/события/терминалы, все ссылки существуют, достижимость узлов, предел размера/числа шагов, безопасные ветви для отказа и столкновения, допустимые условия владения инструментом и деталью. Каждый результат навыка выбирает **ровно одно** ребро графа. Терминалы: `completed`, `replan_required`, `operator_required`. На `replan_required` система останавливается и отдаёт свежий `scene_json`; повторный вызов Qwen3 делает внешний клиент. `completed` означает выполнение переданного графа и его зарегистрированных постусловий, а не доказательство, что модель правильно поняла произвольный русский запрос. ## Семантика движения и наблюдения При `detect` робот сначала отходит от контакта по оси к сохранённой высоте, затем переходит в `work`. Ждёт свежий кадр `/d455_top/image_raw`, берёт точную позу из supervisor и возвращает `found`/`not_found`/`failed`. Для объекта, требующего близкого контроля, конфигурация навыка задаёт безопасную точку обзора и повторный кадр с `/camera/color` или `/camera/depth`; поза всё ещё берётся из supervisor. Задача компьютерного зрения по RGB/D в этот этап не входит. `approach` ставит рабочий кончик над объектом и сохраняет позу/высоту подхода. `lower` движется вдоль оси к контакту. `grab` при необходимости выполняет недостающие фазы подхода и опускания, подтверждает захват через supervisor и поднимается **к сохранённой позе подхода**. Явный `lift` после этого подтверждает достигнутую высоту и не дублирует подъём. `transfer` идёт к указанной цели на безопасной высоте, `align` меняет ориентацию только на высоте. `release` опускается в указанном безопасном месте установки/передачи, отпускает и поднимается к сохранённой высоте; для инструмента разрешено только его именованное гнездо. `dock_tool` выполняет ту же локальную стыковку инструмента в гнезде без скрытого переноса к стойке. `retreat` не выполняет горизонтальное перемещение до осевого отвода. Для винта отпускание у отверстия означает передачу виртуальному человеку, без вкручивания. Рабочий кончик направлен вниз. Предпочтительная ось 7 — значение в `work` (сейчас 0 рад); начальный жёсткий коридор ±90° и максимальный наклон рабочего инструмента 10° задаются в конфигурации и уточняются проверкой достижимости. Перед каждым планом используются текущие суставы как IK seed, ограничения MoveIt на `joint7` и ориентацию, а перед исполнением — проверка каждой точки траектории, столкновений и скачков суставов. При недостижимости в этом коридоре исполнитель сообщает отказ; не выбирает ветвь IK с поворотом примерно на −180°. Поворот кисти или возврат в `work` допустим только после осевого подъёма. На каждом переходе между узлами проверяется свежесть состояния сцены и робота. Для этого `iiwa_llm_plan_executor` нужен собственный адаптер планирования на установленном MoveItPy: текущий `MoveToPose.action` не передаёт ограничение `joint7`. Он повторно использует геометрию и обновление сцены из `iiwa_experement`, но не запускает эталонную последовательность. Физический слой остаётся сменным для будущей адаптации к реальному роботу. ## Поддерживаемые навыки первого этапа Реестр поддерживает имена из показанного графа и необходимые действия с текущими объектами Grant: `detect`, `approach`, `lower`, `grab`, `lift`, `transfer`, `align`, `release`, `retreat`, `capture`, `verify`, `stop`, `wait_scene_clear`, `inspect_state`, `notify_operator`, `dispense`, `dock_tool`, `handoff_screw`. Для графов из обучающего набора описать и проверить совместимые алиасы `pick`, `move_to_storage`, `inspect`; их запуск разрешать лишь при поддерживаемом типе объекта и места назначения в текущей сцене. Любой другой навык (включая `tighten`, если у отверстия нет установленного винта) получает отказ до движения. Каталог можно расширять новым обработчиком и контрактом, без изменения движка графа. ## Проверки и результат этапа Пакетные тесты проверяют формат снимка, контракт навыков, валидацию графа и переходы, физические фазы на поддельном backend, ограничения суставов и API/MCP адаптеры. Минимальные пробы Webots должны подтвердить произвольные короткие запросы в одной и той же сцене: взять и вернуть присоску; обнаружить люк; взять люк и установить; выбрать один из десяти винтов и передать у указанного отверстия; остановка и перепланирование после отказа. Это проверка работоспособности двух пакетов, **не** система оценивания Qwen3 и не сравнение 20 экспериментов с эталоном.