Продвинутый

Agent-managed museum exhibit built with Rust and Gajae-Code

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

Автоматизированная выставка музея на Rust с Gajae-Code и LazyCodex. Полный отказ от человеческого вмешательства.

Agent-managed museum exhibit built with Rust and Gajae-Code

Автоматизированная выставка музея на Rust с Gajae-Code и LazyCodex. Полный отказ от человеческого вмешательства.

Архитектура системы

Агентное управление

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

rust
// src/agent.rs — основа агента для каждого экспоната
use gajae_code::{Agent, Event, State};
use lazycodex::{EventBus, StateStore};

#[derive(Debug)]
struct ExhibitAgent {
    id: String,
    agent: Agent,
    event_bus: EventBus,
    state_store: StateStore,
}

impl ExhibitAgent {
    fn new(id: String) -> Self {
        Self {
            id,
            agent: Agent::new(),
            event_bus: EventBus::new(),
            state_store: StateStore::new(),
        }
    }

    async fn run(&mut self) {
        loop {
            // 1. Получаем команды от диспетчера
            if let Some(cmd) = self.event_bus.recv().await {
                self.agent.apply_command(cmd).await;
            }

            // 2. Собираем данные с датчиков
            let sensor_data = self.agent.read_sensors().await;
            self.state_store.update(&self.id, sensor_data).await;

            // 3. Отправляем событие о состоянии
            let state = self.state_store.get(&self.id).await;
            self.event_bus
                .broadcast(Event::StateUpdate {
                    agent_id: self.id.clone(),
                    state,
                })
                .await;

            // 4. Проверка на завершение работы
            if self.agent.should_shutdown().await {
                break;
            }

            // Небольшая пауза для предотвращения блокировки
            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
        }
    }
}

Ключевые моменты архитектуры:

  • EventBus — это канал на основе Tokio, который обеспечивает асинхронную отправку команд диспетчера к агентам и рассылку событий состояния всем слушателям.
  • StateStore — это легковесное хранилище на основе RocksDB, которое сохраняет последнее известное состояние каждого агента для обеспечения предсказуемости после перезагрузки.
  • Agent — это абстрактный интерфейс, предоставляемый Gajae-Code, который инкапсулирует логику управления жизненным циклом, обработку команд и взаимодействие с оборудованием.

Rust-основанная реализация

Rust выбирается по нескольким причинам:

  1. Безопасность памяти — критически важна для устройств, подключенных к сети, где ошибки могут привести к физическому повреждению экспонатов.
  2. Параллелизм — встроенные функции агентов могут безопасно использовать мьютексы и каналы без сборщика мусора.
  3. Экосистема — Rust имеет отличную поддержку для асинхронного программирования (tokio), работы с базами данных (rocksdb) и HTTP-серверов (hyper).

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

toml
# config/exhibit.toml
[agent]
id = "vinnie-pugh-01"
sensor_poll_interval_ms = 200
max_memory_usage_mb = 128

[agent.hardware]
i2c_bus = "/dev/i2c-1"
temperature_sensor = "ds18b20"
humidity_sensor = "bme280"

[agent.endpoints]
command_bus = "tcp://192.168.1.10:8080"
state_bus = "tcp://192.168.1.10:8081"
rust
// src/main.rs — точка входа агента
use gajae_code::Agent;
use lazycodex::Config;
use std::fs;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Загрузка конфигурации
    let config_path = "config/exhibit.toml";
    let config_str = fs::read_to_string(config_path)?;
    let config: Config = toml::from_str(&config_str)?;

    // Инициализация агента с конфигурацией
    let mut agent = ExhibitAgent::new(config.agent.id.clone());

    // Подключение к шине команд и состояний
    agent
        .event_bus
        .connect(&config.agent.endpoints.command_bus)
        .await?;
    agent
        .event_bus
        .subscribe(&config.agent.endpoints.state_bus)
        .await?;

    // Запуск основного цикла агента
    agent.run().await;

    Ok(())
}

Пайплайн обработки событий:

[Диспетчер] → [EventBus] → [Агент] → [StateStore] → [Веб-интерфейс]
      ↓              ↑              ↓              ↑
   Команда      Событие состояния   Данные датчика   Веб-сокет
  • Диспетчер отслеживает работоспособность всех агентов, распределяет ресурсы и может перезапустить упавший процесс.
  • EventBus работает как шину сообщений, используя tokio::mpsc для безопасной передачи данных между потоками.
  • StateStore сериализует состояние в бинарный формат, используя bincode, для эффективного хранения и последующего быстрого извлечения.

Пример конфигурации диспетчера

yaml
# config/dispatcher.yaml
agents:
  - id: vinnie-pugh-01
    model: "rust-agent-v1"
    resources:
      cpu: 200m
      memory: "128Mi"
    restart_policy: "always"
    health_check:
      endpoint: "http://vinnie-pugh-01:8080/health"
      interval: "30s"

  - id: piggy-bank-02
    model: "rust-agent-v2"
    resources:
      cpu: 100m
      memory: "64Mi"
    restart_policy: "on_failure"
    health_check:
      endpoint: "http://piggy-bank-02:8080/health"
      interval: "60s"

monitoring:
  metrics_port: 9090
  log_level: "info"
  alert_rules:
    - name: "agent_down"
      condition: "agent_status == 'down' for 2m"
      action: "restart"

Типичные проблемы и решения

ПроблемаРешение
Потеря состояния после перезагрузкиИспользование StateStore с автоматическим резервным копированием в облако через lazycodex::Backup
Конфликт ресурсов между агентамиКонфигурация cgroups через systemd с помощью Agent::setup_cgroup
Задержка в шине событийИспользование tokio::mpsc с буфером и разделением на приоритеты для критически важных событий
Обновление прошивкиИспользование gajae_code::OTA для беспроводного обновления агентов без остановки

Вывод

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

Автономность и поддержка

Отказ от человеческого вмешательства

В музее используется Rust-based агент, который полностью автономен. Система работает по принципу «запустил и забыл» — никакого ручного вмешательства, кроме периодической перезагрузки всего стека.

rust
// agent.rs — основной цикл
#[tokio::main]
async fn main() {
    let exhibit = GajaeCodeExhibit::new().await;
    loop {
        exhibit.tick().await;
        tokio::time::sleep(Duration::from_secs(30)).await;
    }
}

Конфигурация агента хранится в YAML и описывает только целевые состояния:

yaml
# config.yaml
exhibit:
  id: "museum-001"
  mode: "agent-managed"
  health_check_interval: 30
  auto_recover: true
  log_level: "info"

Когда датчик обнаруживает аномалию — например, температура выходит за допустимые границы, — агент самостоятельно корректирует параметры или перезапускает проблемный компонент. Это как автоматическая система полива растений: установил граничные значения, и она сама поливает, если почва пересыхает.

bash
# health-check.sh — периодическая проверка состояния
#!/bin/bash
threshold_temp=35
current_temp=$(sensors | grep 'Core 0' | awk '{print $3}' | sed 's/+//;s/°C//')
if (( $(echo "$current_temp > $threshold_temp" | bc -l) )); then
    echo "Critical: temperature $current_temp°C exceeds $threshold_temp°C"
    systemctl restart exhibit-temp-control
fi

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

yaml
# docker-compose.yml
version: '3.8'
services:
  exhibit-agent:
    image: museum/exhibit-agent:stable
    restart: unless-stopped
    environment:
      - RUST_LOG=info
    volumes:
      - ./configs:/app/configs
      - ./logs:/app/logs

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

Для полного отсутствия человеческого вмешания нужен прозрачный мониторинг. В системе используется сочетание Prometheus + Grafana для метрик и Loki для логирования.

toml
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'exhibit-agent'
    static_configs:
      - targets: ['localhost:9090']
    metrics_path: '/metrics'

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

rust
// logger.rs
impl ExhibitAgent {
    fn log_event(&self, event: Event) {
        let entry = Entry::new()
            .with_timestamp(Utc::now())
            .with_level(Level::Info)
            .with_message(event.to_string());
        
        // Пишем в локи через gajae-logger
        gajae_logger::write(entry);
    }
}

Алерты настроены так, чтобы не требовать ручного подтверждения:

yaml
# alertmanager.yml
receivers:
  - name: 'slack-ops'
    slack_configs:
      - channel: '#incidents'
        title: 'Аномалия в музейном экспонате'
        message: '{{ template "slack.default" . }}'

route:
  receiver: 'slack-ops'
  group_by: ['alertname']
  continue: true

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

bash
# watchdog.sh — контроль предсказуемости
#!/bin/bash
expected_state="stable"
current_state=$(curl -s http://localhost:8080/state | jq -r '.status')
if [[ "$current_state" != "$expected_state" ]]; then
    echo "Аномалия состояния: ожидается $expected_state, получено $current_state"
    systemctl restart exhibit-agent
fi

Такой подход позволяет музейному экспонату работать неделями без участия человека — только системные уведомления по Slack и периодические отчёты в Grafana.

Интеграция и масштабирование

Gajae-Code и LazyCodex

Для автоматизации музейной экспозиции мы используем Gajae-Code в связке с Rust и LazyCodex. Gajae-Code — это высокопроизводительный агентный фреймворк, который идеально подходит для задач мониторинга и управления устройствами в реальном времени. Он позволяет нам запускать Rust-процессы напрямую с контроллером, избегая лишнего слоя абстракции.

Первым делом настраиваем агент на Raspberry Pi, который будет управлять экспонатами:

bash
# Установка Gajae-Code на Raspberry Pi
curl -fsSL https://gajae-code.example.com/install.sh | bash
gajae-code setup --role=exhibit-controller

Основной конфигурационный файл агента выглядит так:

toml
# /etc/gajae/exhibit-agent.toml
[agent]
id = "exhibit-controller-01"
host = "0.0.0.0"
port = 8080

[rust]
runtime = "1.78.0"
features = ["tokio", "serde", "axum"]

[lazycodex]
endpoint = "http://lazycodex.local:9090"
sync_interval = "5s"

[exhibits]
enabled = true
auto_recover = true

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

Музейная экспозиция

Экспозиция состоит из нескольких интерактивных стендов, каждый из которых управляется отдельным Rust-сервисом через Gajae-Code. Каждый сервис отвечает за определённый тип взаимодействия:

rust
// src/exhibit.rs
use gajae_code::{AgentClient, Command};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct ExhibitState {
    id: String,
    name: String,
    active: bool,
    last_update: u64,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = AgentClient::new("http://localhost:8080").await?;
    
    // Регистрация экспоната
    let state = ExhibitState {
        id: "exhibit-001".to_string(),
        name: "Historical Artifacts".to_string(),
        active: true,
        last_update: chrono::Utc::now().timestamp() as u64,
    };
    
    client.send_command(Command::Register(state)).await?;
    
    // Обработка команд управления
    while let Some(cmd) = client.listen_commands().await? {
        match cmd {
            Command::Start(id) => {
                // Логика запуска экспоната
                println!("Starting exhibit {}", id);
            }
            Command::Stop(id) => {
                // Логика остановки экспоната
                println!("Stopping exhibit {}", id);
            }
            _ => {}
        }
    }
    
    Ok(())
}

Для каждого стенда мы создаём отдельный Docker-контейнер с Rust-сервисом, который подключается к агенту:

dockerfile
# Dockerfile для экспоната
FROM rust:1.78-slim as builder

WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates
COPY --from=builder /app/target/release/exhibit-service /usr/local/bin/
CMD ["exhibit-service", "--agent-url", "http://gajae-code-agent:8080"]

docker-compose для всей экспозиции:

yaml
# docker-compose.yml экспозиции
version: '3.8'

services:
  exhibit-001:
    build: ./exhibit-001
    container_name: exhibit-001
    depends_on:
      - gajae-agent
    environment:
      - AGENT_URL=http://gajae-agent:8080
      - EXHIBIT_ID=exhibit-001
    restart: unless-stopped

  exhibit-002:
    build: ./exhibit-002
    container_name: exhibit-002
    depends_on:
      - gajae-agent
    environment:
      - AGENT_URL=http://gajae-agent:8080
      - EXHIBIT_ID=exhibit-002
    restart: unless-stopped

  gajae-agent:
    image: gajae-code/agent:latest
    container_name: gajae-agent
    ports:
      - "8080:8080"
    volumes:
      - ./configs:/etc/gajae
    restart: unless-stopped

  lazycodex:
    image: lazycodex/syncer:latest
    container_name: lazycodex
    ports:
      - "9090:9090"
    environment:
      - AGENT_ENDPOINT=http://gajae-agent:8080
    restart: unless-stopped

Система автоматически восстанавливается после перебоев в питании. Gajae-Code следит за состоянием каждого Rust-сервиса и перезапускает упавшие экземпляры без вмешательства оператора.

Интеграция с LazyCodex обеспечивает предсказуемость работы: каждый запрос логируется, состояние синхронизируется, и мы можем отследить любую ошибку в экспозиции. Это позволяет нам поддерживать полную автоматизацию — выставка работает 24/7, а мы получаем уведомления только о реальных проблемах.

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

Как агенты обмениваются командами с центральным диспетчером и что обеспечивает их автономность?

Агенты используют EventBus для получения команд от диспетчера и отправки событий состояния обратно, при этом каждый экспонат работает как независимый процесс с собственным циклом жизни. StateStore поддерживает локальное состояние для каждого агента, что позволяет ему продолжать работу даже при временной недоступности диспетчера. Agent::apply_command асинхронно обрабатывает команды, а read_sensors и should_shutdown обеспечивают автономный сбор данных и контроль за завершением работы. Такая архитектура гарантирует, что сбой одного агента не влияет на остальные.

Какие основные компоненты Gajae-Code и LazyCodex используются в архитектуре выставки и как они взаимодействуют?

Gajae-Code предоставляет абстракции Agent, Event и State, которые инкапсулируют логику обработки команд, событий и состояния для каждого экспоната. LazyCodex добавляет EventBus для межпроцессного обмена сообщениями и StateStore для распределённого хранения данных. Агент считывает данные с датчиков, обновляет состояние в StateStore, а затем транслирует событие через EventBus, чтобы диспетчер или другие агенты могли отреагировать. Взаимодействие происходит через стандартные точки входа, что делает систему масштабируемой и расширяемой.

Как обеспечивается обработка сбоев и восстановление состояния экспонатов в системе?

Каждый агент сохраняет своё состояние в StateStore, что позволяет восстановить последнюю известную конфигурацию после перезапуска или сбоя. EventBus поддерживает очередь сообщений, что предотвращает потерю команд во время кратковременных отключений. В коде агента предусмотрена проверка should_shutdown, которая гарантирует корректное завершение работы, а цикл run содержит обработку ошибок для восстановления после необработанных исключений. Такая архитектура обеспечивает предсказуемость и устойчивость всей выставки.

Какие преимущества использования Rust для агентной архитектуры музейной выставки по сравнению с другими языками?

Rust обеспечивает безопасную многопоточность без сборщика мусора, что снижает накладные расходы и устраняет риск утечек памяти в критически важных агентах. Гарантии отсутствия нарушений безопасности при доступе к памяти повышают предсказуемость поведения системы, особенно при длительной работе выставки. Компилируемый код позволяет достичь высокой производительности и эффективного использования ресурсов на аппаратном уровне, что важно для систем, работающих без технического обслуживания. Эти преимущества делают Rust подходящим выбором для надежных, автономных агентов.

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