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 выбирается по нескольким причинам:
- Безопасность памяти — критически важна для устройств, подключенных к сети, где ошибки могут привести к физическому повреждению экспонатов.
- Параллелизм — встроенные функции агентов могут безопасно использовать мьютексы и каналы без сборщика мусора.
- Экосистема — 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 подходящим выбором для надежных, автономных агентов.