Продвинутый

Claw-Code: Rust‑based агент‑управляемый экспонат музея

Алексей Кузнецов
Алексей Кузнецов
Системный администратор13 ноября 2026 г.12 мин чтения

Агент‑управляемый экспонат музея, написанный на Rust с использованием Gajae‑Code / LazyCodex, полностью автономно разрабатывается и поддерживается без вмешат...

Claw-Code: Rust‑based агент‑управляемый экспонат музея

Агент‑управляемый экспонат музея, написанный на Rust с использованием Gajae‑Code / LazyCodex, полностью автономно разрабатывается и поддерживается без вмешательства человека.

Агент‑управляемый экспонат музея

Техническая архитектура

Во-первых, агент‑управляемый экспонат состоит из нескольких независимых компонентов, которые взаимодействуют через локальный API‑сервер.
Основный процесс — это rust‑binary, собранный с помощью cargo в режиме --release. Он реализует простой протокол по‑принципу «запрос‑ответ» через UNIX‑сокет и HTTP/2.

Для загрузки динамических ресурсов (например, 3D‑модели, аудио‑треки) используется LazyCodex — лёгкая библиотека, позволяющая откладывать загрузку файлов до момента их реального потребления. Пример инициализации LazyCodex:

rust
// src/lazy_codex.rs
use lazycodex::{LazyLoader, CachePolicy};

fn init_loader() -> LazyLoader {
    LazyLoader::new()
        .with_cache_policy(CachePolicy::RemoteFirst)
        .with_timeout(std::time::Duration::from_secs(30))
}

Агент получает конфигурацию из TOML‑файла, который хранится в /etc/claw_codex/agent.toml. Пример:

toml
# /etc/claw_codex/agent.toml
[server]
url = "https://museum-backend.example.com/api"
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6..."

[exhibit]
id = "dinosaur-01"
refresh_interval = "5m"

Система управления процессом реализована через systemd. Единичный unit‑файл выглядит так:

ini
# /etc/systemd/system/claw-agent.service
[Unit]
Description=Claw‑Code museum exhibit agent
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/claw-agent --config /etc/claw_codex/agent.toml
Restart=on-failure
LimitNOFILE=65536
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_SYS_ADMIN

[Install]
WantedBy=multi-user.target

После размещения unit‑файла его включают и запускают:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now claw-agent.service

Справочная информация о протоколе общения с бекендом загружена в официальной документации API v1.

С практической точки зрения, предсказуемость достигается тем, что каждый компонент фиксирует свою версию, а обновления проходят через подписанные манифесты. При старте агент проверяет хеш‑сумму текущего бинарника и, если манифест отличается, скачивает новую версию в /var/lib/claw_codex/releases/ и переключается на неё без простоя.

Автономное развертывание

Автономность достигается за счёт zero‑touch provisioning: при первом включении устройство (например, Raspberry Pi 4 или отдельный сервер) автоматически регистрирует себя в бекенде и получает токен доступа. Для этого используется небольшая утилита на Python, которая запускается из cloud‑init‑скрипта:

yaml
# cloud-init/user-data
#cloud-config
write_files:
  - path: /opt/claw_codex/register.py
    content: |
      #!/usr/bin/env python3
      import requests, os, json, subprocess

      API_URL = os.getenv('CLAW_REGISTRATION_URL')
      TOKEN   = os.getenv('CLAW_REGISTRATION_TOKEN')

      def register():
          payload = {
              "host_name": os.uname().nodename,
              "os": "linux",
              "arch": "amd64"
          }
          headers = {"Authorization": f"Bearer {TOKEN}"}
          r = requests.post(API_URL, json=payload, headers=headers)
          r.raise_for_status()
          print("Registration successful:", r.json())

      if __name__ == "__main__":
          register()

runcmd:
  - chmod +x /opt/claw_codex/register.py
  - /usr/bin/python3 /opt/claw_codex/register.py

После регистрации агент получает путь к конфигу и сразу создаёт unit‑файл, как описано выше, после чего systemd запускает его.

Обновления приходят через signed release packages. Пример команды, которая проверяет подпись и устанавливает новую версию:

bash
#!/usr/bin/env bash
set -euo pipefail

RELEASE_URL="https://museum-backend.example.com/releases/latest.tar.gz.sig"
BINARY_PATH="/usr/local/bin/claw-agent"

# проверка подписи
gpg --verify <(curl -s "$RELEASE_URL") /tmp/release.tar.gz.sig

# распаковка
tar -xzf /tmp/release.tar.gz -C /tmp
cp /tmp/claw-agent "$BINARY_PATH"
chmod +x "$BINARY_PATH"

# перезапуск сервиса без простоя
systemctl restart claw-agent.service

Таким образом, автономное развертывание исключает необходимость ручного вмешательства: после физического размещения устройства и включения питания оно само проходит регистрацию, загрузку конфигурации и запуск агента.

Если требуется добавить новый экспонат, достаточно добавить запись в TOML‑файл и перезапустить сервис — всё происходит без переустановки ОС и без вмешательства оператора.

Запомните: в отличие от «решения на коленке», такой подход гарантирует предсказуемость и надёжность работы экспоната в долгосрочной перспективе.

Разработка на Rust с Gajae‑Code / LazyCodex

### Выбор языка и фреймворка

Rust выбран не случайно. Он обеспечивает безопасность памяти, высокую производительность и стратегическую предсказуемость – всё, что нужно для автономного экспоната, который должен работать без перезапусков и без падений.

В качестве фреймворка используется Gajae‑Code – набор абстракций, специально подготовленных для создания управляемых агентов в реальном времени. Он построен на top‑level async‑runtime, предоставляет удобный API для работы с периферией (GPIO, I2C, UART) и уже включает готовые типы для протоколов общения с музеенными системами (MQTT, Modbus, OPC‑UA).

Пример инициализации проекта

bash
cargo new museum-agent --bin
cd museum-agent

Cargo.toml (минимальная конфигурация)

toml
[package]
name = "museum-agent"
version = "0.1.0"
edition = "2021"

[dependencies]
gajae-code = "0.3"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Почему не использовать готовые решения вроде actix или tokio?
actix хорош для высоконагруженных веб‑служб, но не предоставляет готовых abstraкций для контрольных циклов экспонатов (например, «переключить светодиодную подсветку через 5 минут»). Gajae‑Code уже содержит шаблоны ExhibitLoop, SensorWatcher и ActuatorController, что сокращает количество boilerplate‑кода и уменьшает риск ошибок.

Базовый скелет кода

rust
use gajae_code::{ExhibitLoop, SensorWatcher, ActuatorController};
use tokio::main;

#[main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Инициализируем наблюдатель датчиков
    let sensor = SensorWatcher::new("/dev/i2c-1")?;
    // Контроллеры активаторов (например, реле, MOSFET)
    let actuator = ActuatorController::new("/dev/gpiochip0")?;

    // Запускаем главный цикл агент‑управляемого экспоната
    ExhibitLoop::run(sensor, actuator).await?;
    Ok(())
}

### Интеграция с LazyCodex

LazyCodex – декларативный DSL, позволяющий описать поведение экспоната в виде конфигурационного файла. Он преобразуется в структуры данных, которые Gajae‑Code читает во время работы, что делает логику экспоната полностью перезапускаемой без изменения кода.

Пример конфигурации экспоната (exhibit.yaml)

yaml
# exhibit.yaml – описание поведения экспоната «Динозавр‑песочница»
version: "1.0"

metadata:
  name: "Динозавр‑песочница"
  location: "зал 3, стенд A"
  description: "Интерактивный экспонат, реагирующий на движение посетителей"

sensors:
  motion:
    type: "ir"
    device: "/dev/i2c-1"
    interval_sec: 2

actuators:
  light:
    type: "gpio"
    pin: 17
    on_duration_sec: 5
    off_delay_sec: 30

schedule:
  - when: "motion_detected"
    then: "light.on"
  - when: "time_of_day"
    between: "09:00-18:00"
    then: "light.off"

# Переопределения по времени суток
time_rules:
  - time: "09:00"
    action: "light.off"
  - time: "18:00"
    action: "light.on"

Как загрузить и применить конфиг в Rust

rust
use gajae_code::config::ConfigLoader;
use std::path::Path;

fn load_lazycodex(path: &Path) -> Result<Config, Box<dyn std::error::Error>> {
    // LazyCodex поддерживает YAML и JSON out‑of‑the‑box
    ConfigLoader::from_yaml(path)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1️⃣ Загрузим декларативное описание экспоната
    let cfg = load_lazycodex(Path::new("exhibit.yaml"))?;

    // 2️⃣ Создаём наблюдатель датчиков на основе конфигурации
    let sensor = SensorWatcher::from_config(&cfg.sensors.motion)?;

    // 3️⃣ Инициализируем актуаторы
    let actuator = ActuatorController::from_config(&cfg.actuators.light, &cfg.metadata.location)?;

    // 4️⃣ Запускаем главный цикл, передавая ему правила из schedule
    ExhibitLoop::run_with_config(sensor, actuator, cfg).await?;
    Ok(())
}

Ключевые преимущества такой интеграции

  • Отсутствие компиляции при изменении логики – меняем лишь exhibit.yaml, перезапускаем сервис, и экспонат сразу реагирует на новые правила.
  • Ясность ответственности – каждый блок (sensors, actuators, schedule) отвечает за отдельную часть поведения, что упрощает аудит и отладку.
  • Тестируемость – конфиг можно загрузить в unit‑тестах, а бизнес‑логика проверяется без обращения к реальному железу.

Практический совет: храните конфиги в директории /etc/museum-agent/conf.d/ и используйте символические ссылки для переключения между «режимами» (например, exhibit_prod.yaml и exhibit_test.yaml). При старте сервиса указывайте нужный файл через переменную окружения EXHIBIT_CONF, что позволяет гибко управлять версиями без пересборки кода.

Развертывание в домашней лаборатории

Инфраструктура Proxmox и TrueNAS

Для развертывания Rust‑агента удобно использовать Proxmox VE как гипервизор‑контейнерный хост, а TrueNAS – как надёжное хранилище данных.

Создание контейнера с Ubuntu

bash
# на Proxmox‑хосте
lxc launch images:ubuntu/22.04 agent-container \
    -c security.privileged=true \
    -c raw.lxc.arch=arm64 \
    -c faster.io=1

Контейнер получает отдельный IP‑адрес в сети Proxmox, что упрощает доступ к API‑сервису агента.

Настройка хранилища на TrueNAS

  1. Создание ZFS‑пула (пример для диска sda и sdb):
bash
zpool create -f tank /dev/sda /dev/sdb
  1. Создание датасета для файлов агента:
bash
zfs create -o mountpoint=/mnt/agent-data tank/agent-data
  1. Экспорт NFS‑шареного каталога (TrueNAS UI → Services → NFS → Add Share).
    В конфигурационном файле /etc/exports на TrueNAS добавьте:
/mnt/agent-data  *(rw,sync,no_subtree_check)

После изменения перезапустите службу NFS:

bash
service nfs restart
  1. Монтирование NFS в контейнере (в контейнере Proxmox):
bash
# внутри agent-container
apt update && apt install -y nfs-common
mkdir -p /mnt/agent-data
mount -t nfs <trueNAS_IP>:/mnt/agent-data /mnt/agent-data

Системный сервис для автоматического монтирования

ini
# /etc/systemd/system/nfs-agent.mount
[Unit]
Description=Mount NFS share for Rust agent
After=network-online.target
Wants=network-online.target

[Mount]
What=<trueNAS_IP>:/mnt/agent-data
Where=/mnt/agent-data
Type=nfs
Options=rw,vers=4,proto=tcp,hard,intr

[Install]
WantedBy=multi-user.target
bash
systemctl daemon-reload
systemctl enable --now nfs-agent.mount

Мониторинг и логирование

Автономный агент требует системного мониторинга состояния контейнеров и сбора логов. Наиболее лёгкая комбинация – Prometheus + node_exporter + Loki + Promtail.

Prometheus

bash
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v /etc/prometheus:/etc/prometheus \
  -v prometheus_data:/prometheus \
  prom/prometheus

Базовый prometheus.yml (пример):

yaml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['host.docker.internal:9100']   # node_exporter в контейнере
  - job_name: 'agent'
    static_configs:
      - targets: ['agent-container:9100']      # exporter, если агент публикует метрики

Grafana – визуализация:

bash
docker run -d \
  --name grafana \
  -p 3000:3000 \
  -v grafana_data:/var/lib/grafana \
  grafana/grafana

Loki + Promtail – централизованные логи

Loki (docker‑образ):

bash
docker run -d \
  --name loki \
  -p 3100:3100 \
  -v loki_data:/loki \
  grafana/loki

Promtail – агент, который читает системные журналы и отправляет их в Loki:

yaml
# /etc/promtail.yaml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /var/log/promtail.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: systemd
    static_configs:
      - targets:
          - localhost
    pipeline_stages:
      - regex:
          expression: '^(?P<log_level>[^ ]+)'
      - labels:
          job: systemd
      - output:
          source: message

Запуск Promtail:

bash
docker run -d \
  --name promtail \
  -v /var/log:/var/log \
  -v /etc/promtail.yaml:/etc/promtail.yaml \
  -v loki_data:/loki \
  grafana/promtail

Systemd‑юнит для сбора журналов (пример для собственного сервиса агента)

ini
# /etc/systemd/system/agent.service
[Unit]
Description=Rust‑based museum exhibit agent
After=network.target

[Service]
ExecStart=/usr/local/bin/agent --config /etc/agent/config.yaml
Restart=on-failure
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
bash
systemctl daemon-reload
systemctl enable --now agent.service

Алерты
В конфигурации Prometheus добавьте правило, которое оповещает при недоступности контейнера:

yaml
groups:
- name: agent.rules
  rules:
  - alert: AgentDown
    expr: up{job="agent"} == 0
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Rust‑agent is not reachable"
      description: "The agent container has been down for more than 2 minutes."

Таким образом, Proxmox + TrueNAS обеспечивают изолированную и надёжную инфраструктуру, а Prometheus + Loki + Grafana позволяют поддерживать высокий уровень предсказуемости и быстро реагировать на любые отклонения.

Поддержка без человеческого вмешательства

Поддержка без человеческого вмешательства

Когда мы говорим об автономном агенте, важно понимать: автономность — это не отсутствие ошибок, а способность системы самостоятельно возвращаться в работоспособное состояние. В случае с Claw-Code, написанным на Rust, мы не можем полагаться на ручной перезапуск сервисов или правку конфигов при возникновении паники (panic) в рантайме. Система должна быть предсказуемой.

С практической точки зрения, я разделяю поддержку на два уровня: управление жизненным циклом процесса и автоматизацию изменений в конфигурации.

Автоматизация через systemd и Ansible

Для управления самим агентом я использую systemd. Это база. Если агент упал из-за нехватки памяти или ошибки в логике, systemd должен поднять его до того, как об этом узнает мониторинг. В случае с автономными системами я всегда настраиваю строгую политику перезапуска.

Создадим unit-файл для Claw-Code. Обратите внимание на параметры Restart и StartLimitIntervalSec — они защищают нас от бесконечного цикла перезапусков, если проблема критическая (например, повреждение бинарного файла).

ini
[Unit]
Description=Claw-Code Autonomous Agent
After=network.target

[Service]
Type=simple
User=claw-agent
Group=claw-group
WorkingDirectory=/opt/claw-code
ExecStart=/opt/claw-code/bin/claw-agent --config /etc/claw-code/config.toml
# Перезапуск при падении
Restart=on-failure
# Ждать 5 секунд перед перезапуском, чтобы не нагружать CPU
RestartSec=5s
# Если агент падает чаще 5 раз за 60 секунд, прекратить попытки
StartLimitIntervalSec=60
StartLimitBurst=5

[Install]
WantedBy=multi-user.target

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

Пример задачи в Ansible для подготовки окружения:

yaml
- name: Prepare Claw-Code environment
  hosts: museum_nodes
  become: true
  tasks:
    - name: Create dedicated user for the agent
      user:
        name: claw-agent
        system: yes
        shell: /sbin/nologin

    - name: Ensure configuration directory exists
      file:
        path: /etc/claw-code
        state: directory
        owner: claw-agent
        group: claw-group
        mode: '0755'

    - name: Deploy systemd unit file
      template:
        src: claw-code.service.j2
        dest: /etc/systemd/system/claw-code.service
      notify: reload systemd

Обновления и отладка

Самый сложный этап автономности — это обновление. Поскольку Claw-Code использует механизмы LazyCodex для саморазвития, процесс обновления бинарного файла должен быть атомарным. Если в процессе записи файла произойдёт сбой, мы получим «кирпич» вместо агента.

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

Процесс отладки в полностью автономной системе переносится на уровень логов. Поскольку мы не можем подключиться по SSH и посмотреть stdout, вся информация должна агрегироваться. Я настраиваю journald так, чтобы логи Claw-Code не забивали диск, но при этом сохраняли достаточно контекста для анализа причин сбоя.

Чтобы проверить, как агент справляется с ошибками, можно принудительно ограничить его ресурсы через systemd. Это полезно для тестирования предсказуемости поведения при дефиците памяти:

bash
# Ограничиваем память до 512 МБ для теста
sudo systemctl set-property claw-code.service MemoryMax=512M
sudo systemctl restart claw-code.service

Если агент столкнулся с критической ошибкой, он должен не просто упасть, а записать состояние (state dump) в выделенную директорию перед завершением работы. Это позволяет в дальнейшем проанализировать, какой именно паттерн кода привел к деградации системы.

Часто задаваемые вопросы

Как обеспечить отказоустойчивость агента при сбоях?

Для обеспечения предсказуемости работы системы используется systemd. В конфигурационном файле сервиса необходимо установить параметр Restart=on-failure, чтобы процесс автоматически перезапускался при критических ошибках. Также важно увеличить лимит открытых файлов через LimitNOFILE, так как агент активно работает с сетевыми соединениями и динамическими ресурсами.

Зачем использовать LazyCodex вместо обычной загрузки ресурсов?

Использование LazyCodex позволяет откладывать загрузку тяжёлых объектов, таких как 3D-модели или аудио, до момента их фактического обращения. С практической точки зрения это снижает нагрузку на сеть при старте приложения и ускоряет запуск самого бинарного файла. Вы можете гибко настроить политику кэширования, например, через CachePolicy::RemoteFirst, чтобы оптимизировать потребление ресурсов.

Где хранятся настройки и как их изменить?

Все параметры конфигурации, включая URL бэкенда и токены авторизации, находятся в TOML-файле по пути /etc/claw_codex/agent.toml. Если вам нужно изменить интервал обновления данных или ID экспоната, отредактируйте этот файл и перезапустите сервис командой sudo systemctl restart claw-agent.service. После внесения изменений всегда проверяйте корректность синтаксиса TOML, чтобы избежать ошибок при парсинге.

Какие права доступа требуются для работы сервиса?

Для корректной работы агента в секции [Service] unit-файла должны быть указаны соответствующие возможности через CapabilityBoundingSet. В частности, CAP_NET_BIND_SERVICE позволяет приложению занимать привилегированные порты, а CAP_SYS_ADMIN может потребоваться для специфических операций с системными ресурсами. Если вы запускаете агент не от имени суперпользователя, убедитесь, что у процесса есть права на чтение конфигурационного файла и запись в логи.

Поделиться:TelegramX / TwitterVK