AcTrail: слежка за ИИ-агентом

Введение

ИИ-агенты уже давно перестали быть лабораторной игрушкой. Сегодня они пишут код, управляют файлами, ходят в интернет и принимают решения без подтверждения пользователя. Развернуть такого «помощника» можно где угодно: на выделенном сервере в дата-центре, в облачном контейнере — или прямо на собственном ноутбуке.

И вот тут начинается проблема, о которой не принято говорить в восторженных обзорах.

Агент с доступом к локальной файловой системе — это, по сути, процесс с привилегиями пользователя. Он читает, перемещает, перезаписывает и удаляет данные так же свободно, как это сделали бы вы сами. Ошибка в цепочке рассуждений, неверно интерпретированная команда, конфликт версий инструментов — и вот уже агент «оптимизирует» папку, в которой лежал единственный бэкап клиентского проекта или лезет «ломать» какой-то сетевой сервис. За действиями такого рода просто необходимо какое-то наблюдение. И вот про инструмент такого наблюдения и пойдёт речь в сегодняшней статье.

Сразу придётся сделать оговорку, что речь пойдёт именно про наблюдение над действиями агента, а не ограничение этих действий. То есть мы рассмотрим инструмент, который поможет разобраться «что же такого наделал ИИ-агент» уже после того как всё случилось 🙂

 

Что мы будем делать сегодня?

Мы разобьём эту статью на два раздела. В первом мы просто запустим локальную модель qwen и подключим к ней (уже типовой на данный момент) ИИ-агент openclaw. Всё будет работать локально, чтобы мы не зависели от каких-либо внешних сервисов и данный эксперимент можно было бы при желании повторить. А во втором разделе, мы установим инструментарий AcTrail для наблюдения за действиями ИИ-агента и продемонстрируем на простом примере как это работает.

Локальная установка qwen и openclaw

Для начала давайте опишем стенд, на котором мы будем проводить все наши действия.

Запускать всё это хозяйство мы будем на сервере Huawei 2288H V5, на его борту 2 процессора Intel(R) Xeon(R) Gold 5118 CPU @ 2.30GHz и 256 Гб ОП. Также в сервер установлены две карты Nvidia GV100GL (Tesla V100 PCIe 16GB).

Работает этот сервер под управлением ОС от нашего сообщества — openScaler 24.03 SP4 (версия ядра 6.6.0).

Так как мы заботимся о безопасности, то и модель и ИИ-агента мы будем запускать внутри контейнеров, а не напрямую на хосте. Для этого мы установили на наш сервер дополнительно:

  • драйвер NVIDIA driver 570+ (CUDA 12.8) (NVIDIA-Linux-x86_64-570.211.01.run)

  • Docker версии 29.7.2 (с плагином Compose)

  • NVIDIA Container Toolkit.

В docker был добавлен runtime nvidia, командой:

nvidia-ctk runtime configure —runtime=docker

Проверим драйвер nvidia на хосте:

Вывод nvidia-smi на хосте.

Если что, вот вывод карт nvidia в lspci:

Вывод команды lspci на хосте.

Для, проверки, что внутри контейнеров GPU доступен для использования, мы дадим команду:

Проверка доступности GPU внутри контейнера.

Запускать модель qwen мы будем при помощи ollama, а для запуска openclaw мы возьмём его официальный docker образ.

Создадим все необходимые каталоги для запуска:

mkdir -p ~/local-qwen/{ollama,openclaw-config,openclaw-workspace,openclaw-secrets}

перейдём в каталог ~/local-qwen и дальнейшие действия будут выполняться внутри этого каталога.

Чтобы запускать всё одной командой, мы создадим compose файл:

docker-compose.yml:
name: local-qwen

services:
  ollama:
    image: docker.io/ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    environment:
      OLLAMA_HOST: "0.0.0.0:11434"
      NVIDIA_VISIBLE_DEVICES: "all"
      NVIDIA_DRIVER_CAPABILITIES: "compute,utility"
      OLLAMA_KEEP_ALIVE: "30m"
    volumes:
      - ./ollama:/root/.ollama:Z
    ports:
      - "127.0.0.1:11434:11434"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    healthcheck:
      test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
      interval: 15s
      timeout: 10s
      retries: 20
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  openclaw-gateway:
    image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
    container_name: openclaw-gateway
    restart: unless-stopped
    depends_on:
      ollama:
        condition: service_healthy
    env_file:
      - .env
    environment:
      HOME: /home/node
      OPENCLAW_HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_STATE_DIR: /home/node/.openclaw
      OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
      OPENCLAW_CONFIG_DIR: /home/node/.openclaw
      OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
    volumes:
      - ${OPENCLAW_CONFIG_DIR:-./openclaw-config}:/home/node/.openclaw:Z
      - ${OPENCLAW_WORKSPACE_DIR:-./openclaw-workspace}:/home/node/.openclaw/workspace:Z
      - ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-./openclaw-secrets}:/home/node/.config/openclaw:Z
    ports:
      - "127.0.0.1:${OPENCLAW_GATEWAY_PORT:-18789}:18789"
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    security_opt:
      - no-new-privileges:true
    init: true
    command:
      [
        "node",
        "dist/index.js",
        "gateway",
        "--bind",
        "${OPENCLAW_GATEWAY_BIND:-lan}",
        "--port",
        "18789",
      ]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  openclaw-cli:
    image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
    profiles:
      - cli
    env_file:
      - .env
    environment:
      HOME: /home/node
      OPENCLAW_HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_STATE_DIR: /home/node/.openclaw
      OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
      OPENCLAW_CONFIG_DIR: /home/node/.openclaw
      OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
      BROWSER: echo
    volumes:
      - ${OPENCLAW_CONFIG_DIR:-./openclaw-config}:/home/node/.openclaw:Z
      - ${OPENCLAW_WORKSPACE_DIR:-./openclaw-workspace}:/home/node/.openclaw/workspace:Z
      - ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-./openclaw-secrets}:/home/node/.config/openclaw:Z
    network_mode: "service:openclaw-gateway"
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    security_opt:
      - no-new-privileges:true
    stdin_open: true
    tty: true
    init: true
    entrypoint: ["node", "dist/index.js"]
    depends_on:
      - openclaw-gateway 

 

Необходимо сразу дать некоторые комментарии по этому файлу. Как вы видите, мы разделили агента на собственно постоянно запущенный агент (openclaw-gateway) и консоль управления им (openclaw-cli). Контейнер с консолью управления не запускается автоматически при помощи команды docker compose up, но доступен для запуска через docker compose run — это всё достигается применением profiles.

Теперь давайте создадим файл, в который поместим все необходимые для запуска приложений переменные среды:

cat > ~/local-qwen/.env <<'EOF'
# Ollama
OLLAMA_HOST=0.0.0.0:11434

# OpenClaw
OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:latest
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_CONFIG_DIR=./openclaw-config
OPENCLAW_WORKSPACE_DIR=./openclaw-workspace
OPENCLAW_AUTH_PROFILE_SECRET_DIR=./openclaw-secrets
OPENCLAW_TZ=Europe/Moscow
EOF

 

Для запуска openclaw нам понадобится создать токен:

echo "OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)" >> ~/local-qwen/.env
cat ~/local-qwen/.env

 

Создадим минимальный конфиг для запуска openclaw:

cat > ~/local-qwen/openclaw-config/openclaw.json <<'EOF'
{
  "gateway": {
    "mode": "local",
    "bind": "lan"
  }
}
EOF

И наконец, зададим нужные права для каталогов, в расчёте на то, что они должны быть доступны пользователю с UID 1000, под которым работает контейнер openclaw.
chown -R 1000:1000 ~/local-qwen/openclaw-config \ ~/local-qwen/openclaw-workspace \ ~/local-qwen/openclaw-secrets ls -ld ~/local-qwen/openclaw-config \ ~/local-qwen/openclaw-workspace \ ~/local-qwen/openclaw-secrets

 

Отлично! Всё готово для первого запуска! Всё наше хозяйство мы запускаем привычной всем контейнероводам командой:

Запуск контейнеров.

Проверяем, что контейнеры успешно запустились:

Перечень запущенных контейнеров.

Отлично!

Теперь нам надо загрузить модель Qwen. Делать это мы будем командой:

Загрузка модели Qwen.

После успешного выполнения, мы можем увидеть скаченную модель командой:

Перечень скаченных моделей.

Сразу поясним, почему мы выбрали именно эту модель: а всё просто — она влезает в одну V100 16GB (Q4_K_M квант, ~9GB) и имеет хороший баланс скорости и качества.

Проверяем, что ollama доступна с хоста и загруженная модель есть в выводе:

Проверка, что ollama видит модель.

Мы должны увидеть вывод JSON со списком моделей, как это показано на нашем скриншоте.

Теперь нам осталась сущая малость, а именно настроить наш ИИ-агент для работы с нашей локальной моделью. Для этого дадим следующую команду:

Настройка агента openclaw.

Прошу заметить, что мы вручную отвечаем на вопросы, выбирая или вбивая ответ:

  • Ollama modeLocal only
  • Ollama base URLhttp://ollama:11434

И теперь нам осталось только установить установленную модель в качестве модели по умолчанию. Для этого мы даём следующие команды:

Установка модели Qwen в качестве модели по умолчанию.

Выполняем проверку, что наша модель qwen2.5:14b выбрана в качестве default:

Проверка настроенной модели по умолчанию.

Ну и теперь мы можем проверить работу ИИ-агента, задав ему какой-нибудь вопрос. Спросим у агента, знаком ли ему Марк Твен:

Тестовый запрос агенту.

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

Нагрузка на GPU.

Так как нас больше всего заботит деятельность ИИ-агента, то давайте убедимся, что openclaw видит файлы в каталоге, который мы ему выделили (разумеется он должен видеть только то, что ему выделили при настройке контейнера).

Для этого мы просто попросим его их показать:

Перечень файлов, которые видит ИИ-агент.

Убеждаемся, что агент не врёт и мы видим эти же самые файлы в нашем локальном каталоге:

Файлы в каталоге на хосте.

 

Давайте теперь проверим, что агент может и сам создавать файлы по нашему запросу. Давайте попросим его об этом:

Запрос ИИ-агенту на создание файла.

Проверяем, что такой файл действительно появился:

Проверка созданного файла.

Отлично, промежуточной цели мы достигли — у нас теперь есть ИИ-агент, который использует локально запущенную модель Qwen для выполнения задач, которые мы ему поручим.

Переходим к разделу, посвящённому настройке наблюдений над его действиями.

Установка и настройка AcTrail

Для начала поясним, а что же такое AcTrail? AcTrail — система отслеживания активности для AI-агентов, работающая на уровне ядра (требования к ядру — версия не ниже 5.x, у нас в openScaler 24.03 SP4 ядро 6.6.0) и использование механизма eBPF. В отличие от обычных логов, которые пишет сам агент, AcTrail захватывает реальные действия процесса: какие команды он выполняет, к каким файлам обращается, какие сетевые соединения устанавливает, какой трафик передаёт (включая LLM-запросы/ответы).

На что нацелен проект AcTrail? Суть решаемой им проблемы и краткий ответ мы можем увидеть на следующей схеме:

Источник: https://raw.atomgit.com/openeuler/AcTrail/files/master/images/figure1-agent-log-gap-zh-cn.drawio.svg

Если быть кратким, то логи ИИ-агента рассказывают лишь то, что агент сам готов о себе рассказать; правда о его действиях лежит этажом ниже — в операционной системе. И вот мониторингом на этом «этаже» и занимается AcTrail.

Источник: https://raw.atomgit.com/openeuler/AcTrail/files/master/images/actrail-readme__evidence-to-action__candidate.drawio.svg

Новаторство AcTrail именно в том, что вместо «дампа логов» он строит семантическую цепочку «факт → действие → ответ», превращая мониторинг в инструмент расследования инцидентов. После того как ИИ-агент «навел порядок» в файлах, вы можете постфактум восстановить: кто его запустил, что именно изменилось, куда ушли данные и как он расплодил дочерние процессы.

Все заинтересовавшиеся могут пройти по ссылке в репозиторий с исходными кодами этого проекта: https://atomgit.com/openeuler/AcTrail и самостоятельно ознакомится.

В нашем случае AcTrail покажет всё что OpenClaw будет выполнять на хосте, всё его общение с Ollama по HTTP. Можно будет увидеть дерево процессов: какие дочерние процессы запускает агент . Для удобства просмотра этой информации у AcTrail есть веб-интерфейс для визуализации трейсов.

Для установки AcTrail мы просто скачаем уже готовый rpm файл (собранный для openEuler) и установим его при помощи привычного dnf:

wget https://gitcode.com/openeuler/AcTrail/releases/download/v0.7.0/AcTrail-0.7.0-1.oe2403sp3.x86_64.rpm

dnf install ./AcTrail-0.7.0-1.oe2403sp3.x86_64.rpm

 

После установки нам надо инициализировать конфигурационный файл, дав команду:

actraild init

Конфигурационный файл создаётся по адресу /etc/actrail/actraild.conf

После этого мы можем запустить сервис командой

actraild start

 

По умолчанию сокеты, которые actraid создаём в каталоге /run/actrail/*.sock имеют права 660 (только root и группа). Но, мы запускаем нашего ИИ-агента в контейнере, к тому же внутри контейнера процесс OpenClaw работает под пользователем node (UID 1000). А для того, чтобы мы смогли наблюдать за деятельностью openclaw нам придётся запустить его при помощи actrailctl (который в свою очередь мы просто прокинем внутрь контейнера). Поэтому, чтобы пользователь с uid 1000 мог работать с этими сокетами, нам придётся поменять в конфигурационном файле actraild:

sed -i 's/socket_mode_octal = "660"/socket_mode_octal = "666"/; s/sync_socket_mode_octal = "660"/sync_socket_mode_octal = "666"/' \
/etc/actrail/actraild.conf

 

Чтобы наши изменения оказались активными, давайте перезапустим actraild:

actraild stop 
actraild start

 

Останавливаем текущие контейнеры:

cd ~/local-qwen 
docker compose down

 

И правим наш compose файл:

name: local-qwen

services:
  ollama:
    image: docker.io/ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    environment:
      OLLAMA_HOST: "0.0.0.0:11434"
      NVIDIA_VISIBLE_DEVICES: "all"
      NVIDIA_DRIVER_CAPABILITIES: "compute,utility"
      OLLAMA_KEEP_ALIVE: "30m"
    volumes:
      - ./ollama:/root/.ollama:Z
    ports:
      - "127.0.0.1:11434:11434"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    healthcheck:
      test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
      interval: 15s
      timeout: 10s
      retries: 20
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  openclaw-gateway:
    image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
    container_name: openclaw-gateway
    restart: unless-stopped
    depends_on:
      ollama:
        condition: service_healthy
    env_file:
      - .env
    environment:
      HOME: /home/node
      OPENCLAW_HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_STATE_DIR: /home/node/.openclaw
      OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
      OPENCLAW_CONFIG_DIR: /home/node/.openclaw
      OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
    volumes:
      - ${OPENCLAW_CONFIG_DIR:-./openclaw-config}:/home/node/.openclaw:Z
      - ${OPENCLAW_WORKSPACE_DIR:-./openclaw-workspace}:/home/node/.openclaw/workspace:Z
      - ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-./openclaw-secrets}:/home/node/.config/openclaw:Z
      # AcTrail integration
      - /run/actrail:/run/actrail:Z
      - /etc/actrail:/etc/actrail:ro,Z
      - /usr/bin/actrailctl:/usr/bin/actrailctl:ro,Z
      - /usr/bin/libactrail_tls_payload_probe_sync.so:/usr/bin/libactrail_tls_payload_probe_sync.so:ro,Z
    ports:
      - "127.0.0.1:${OPENCLAW_GATEWAY_PORT:-18789}:18789"
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    security_opt:
      - no-new-privileges:true
    init: true
    command:
      [
        "actrailctl",
        "--config", "/etc/actrail/actraild.conf",
        "--socket-path", "/run/actrail/control.sock",
        "launch",
        "--host-ebpf", "auto",
        "--seccomp-notify", "auto",
        "--name", "openclaw-gw",
        "--",
        "node",
        "dist/index.js",
        "gateway",
        "--bind", "lan",
        "--port", "18789",
      ]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  openclaw-cli:
    image: ${OPENCLAW_IMAGE:-ghcr.io/openclaw/openclaw:latest}
    profiles:
      - cli
    env_file:
      - .env
    environment:
      HOME: /home/node
      OPENCLAW_HOME: /home/node
      TERM: xterm-256color
      OPENCLAW_STATE_DIR: /home/node/.openclaw
      OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
      OPENCLAW_CONFIG_DIR: /home/node/.openclaw
      OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace
      BROWSER: echo
    volumes:
      - ${OPENCLAW_CONFIG_DIR:-./openclaw-config}:/home/node/.openclaw:Z
      - ${OPENCLAW_WORKSPACE_DIR:-./openclaw-workspace}:/home/node/.openclaw/workspace:Z
      - ${OPENCLAW_AUTH_PROFILE_SECRET_DIR:-./openclaw-secrets}:/home/node/.config/openclaw:Z
    network_mode: "service:openclaw-gateway"
    cap_drop:
      - NET_RAW
      - NET_ADMIN
    security_opt:
      - no-new-privileges:true
    stdin_open: true
    tty: true
    init: true
    entrypoint: ["node", "dist/index.js"]
    depends_on:
      - openclaw-gateway

 

Давайте сделаем краткие пояснения к тому, что мы изменили по сравнению с предыдущей версией нашего compose файла:

Были добавлены volumes для AcTrail:

  • /run/actrail:/run/actrail:Z — сокеты daemon’а (read-write, нужны для подключения)

  • /etc/actrail:/etc/actrail:ro,Z — конфиг daemon’а (read-only)

  • /usr/bin/actrailctl:/usr/bin/actrailctl:ro,Z — бинарь CLI (read-only)

  • /usr/bin/libactrail_tls_payload_probe_sync.so:/usr/bin/libactrail_tls_payload_probe_sync.so:ro,Z — preload-библиотека для TLS-захвата (read-only)

Изменена команда запуска:

  • Было: node dist/index.js gateway --bind lan --port 18789

  • Стало: actrailctl --socket-path /run/actrail/control.sock launch --host-ebpf auto --seccomp-notify auto --name openclaw-gw -- node dist/index.js gateway --bind lan --port 18789

Снова убеждаемся что actraild запущен:

actraild status

 

Запускаем наши отредактированные контейнеры:

cd ~/local-qwen 
docker compose up -d

 

Ждём 30-60 секунд. Проверяем статус:

docker compose ps

 

Проверяем, что actrail начал захват событий:

Запуск контейнеров и actrail.

 

запускаем в отдельном терминале:

Запуск веб-интерфейса actrail.

Заходим при помощи любого браузера по ссылке из вывода предыдущей команды и переходим на вкладку traces:

Веб-интерфейс actrail: вкладка traces.

и видим, что State в статусе Active. Всё нормально, можно начинать давать запросы агенту и наблюдать за его активностью.

Так как проще всего наблюдать за созданием и удалением файлов (мы же сами просим агента создать файл с заданным именем), то для базовой демонстрации это представляется самым удобным примером. Давайте дадим агенту в консоли следующий запрос.

Запрос ИИ-агенту на создание файла с заданным содержимым.

Проверяем созданный файл и его содержимое:

Проверка созданного файла.

 

А теперь идём в веб консоль actrail и на вкладке Files можем видеть запись (операция write) в указанный нами файл:

Операция write в веб-интерфейсе actrail

К слову, искать можно и среди всех событий на вкладке Events:

перечень Events в веб-интерфейсе actrail.

Если у нас много событий, то можно попробовать отфильтровать их при помощи поискового окна (вверху справа):

Использование фильтра в веб-интерфейсе — Timeline.

Использование фильтра в веб-интерфейсе — Events.

Использование фильтра в веб-интерфейсе — Files.

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

Команды ИИ-агента в веб-интерфейса actrail.

Дерево процессов ИИ-агента в веб-интерфейса actrail.

Подведём итоги

Появление AcTrail от openEuler — это своевременный и очень нужный ответ на стихийное размножение ИИ-агентов. Утилита предлагает современный подход (использование технологии eBPF) к системному трейсингу, позволяя в реальном времени детально отслеживать цепочки рассуждений помощника, его обращения к API и операции с файловой системой. Конечно, решение еще не лишено «детских болезней»: веб-интерфейс местами откровенно сыроват, к примеру, хотелось бы получить возможность фильтровать события по нескольким критериям. Однако в реалиях, где нейросети получают широкие права доступа, прозрачность и глубина логирования всегда важнее красивой обертки. AcTrail — это уверенный шаг к тому, чтобы перестать гадать, что творит ваш агент «под капотом», и наконец превратить ИИ из непредсказуемого «черного ящика» в безопасный и подконтрольный инструмент.

А по поводу системы накладывания ограничений на действия ИИ-агента мы поговорим в одной из наших следующей статей. Следите за анонсами!