ultraworkers/claw-code: Экспонат музея на Rust
Автоматически управляемый музейный экспонат на Rust с использованием Gajae-Code/LazyCodex, созданный без человеческого вмешательства.
Архитектура агента-менеджера
Архитектура агента-менеджера
Реализация museum-экспоната в ultraworkers/claw-code демонстрирует чистую архитектуру агента-менеджера, где агент отвечает за выполнение конкретных задач, а менеджер координирует работу и управляет состоянием. Такой подход позволяет нам добиться предсказуемости конфигурации и стабильности работы без человеческого вмешательства.
Основная идея — разделение обязанностей: менеджер следит за жизненным циклом агентов, обменивается состояниями и обеспечивает целостность системы, в то время как агент своей узкой области ответственности. Это позволяет нам масштабировать систему, добавляя новых агентов для новых типов экспонатов, не меняя менеджерский код.
rust// src/agent_manager.rs — основа архитектуры
struct AgentManager {
registry: HashMap<String, Box<dyn Agent>>,
state_store: Arc<dyn StateStorage>,
coordinator: Arc<dyn CoordinationProtocol>,
}
impl AgentManager {
fn new(config: ManagerConfig) -> Self {
let state_store = Arc::new(FileStateStore::new(&config.state_path));
let coordinator = Arc::new(ZooKeeperCoordinator::new(&config.cluster_nodes));
let mut registry = HashMap::new();
registry.insert("museum_exhibit".to_string(), Box::new(MuseumExhibitAgent::new()));
registry.insert("exhibit_monitor".to_string(), Box::new(MonitorAgent::new()));
Self {
registry,
state_store,
coordinator,
}
}
async fn start(&self) -> Result<(), ManagerError> {
self.coordinator.cluster_health_check().await?;
self.spawn_agents().await
}
}
Реализация на Rust
Реализация агентов на Rust выбрана из-за предсказуемости производительности и контроля над ресурсами — это важно для музейного экспоната, который должен работать 24/7 без неожиданных сбоях.
rust// src/agents/museum_exhibit.rs — агент экспоната
use tokio::sync::mpsc;
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
struct ExhibitState {
current_mode: ExhibitMode,
last_interaction: u64,
sensor_readings: Vec<SensorData>,
}
enum ExhibitMode {
Passive,
Interactive,
Maintenance,
}
struct MuseumExhibitAgent {
state: RwLock<ExhibitState>,
comm_channel: mpsc::Sender<AgentCommand>,
}
impl Agent for MuseumExhibitAgent {
type State = ExhibitState;
type Config = ExhibitConfig;
async fn start(&mut self) -> AgentResult<()> {
let mut interval = interval(TickInterval::new(Duration::from_secs(5)));
loop {
tokio::select! {
_ = interval.tick() => {
self.update_sensors().await?;
}
cmd = self.comm_channel.recv() => {
if let Some(command) = cmd {
self.handle_command(command).await?;
} else {
break;
}
}
}
}
Ok(())
}
async fn handle_command(&mut self, cmd: AgentCommand) -> AgentResult<()> {
match cmd {
AgentCommand::SetMode(mode) => {
*self.state.write().await = ExhibitMode::Interactive;
self.log_interaction().await?;
}
AgentCommand::Calibrate => {
self.calibrate_sensors().await?;
}
_ => {}
}
Ok(())
}
}
Ключевые аспекты реализации:
- Безопасность на уровне типов: каждый агент работает с собственным типом состояния, что предотвращает ошибки на компиляции
- Асинхронный дизайн: использование
tokioобеспечивает предсказуемую задержку для взаимодействия с датчиками - Сериализация состояния: состояние экспоната сохраняется в файловое хранилище для восстановления после перезагрузки
- Каналы коммуникации: изолированные каналы между агентами обеспечивают предсказуемость сообщений
rust// Конфигурация экспоната — минималистичный TOML
# config/exhibit.toml
[exhibit]
name = "historical_clock"
tick_interval_ms = 5000
[sensors]
temperature.enabled = true
humidity.enabled = true
motion.enabled = true
[interaction]
enabled_modes = ["passive", "interactive"]
default_mode = "passive"
[logging]
level = "info"
file = "/var/log/exhibit.log"
Интеграция с Gajae-Code и LazyCodex
Интеграция с Gajae-Code и LazyCodex позволяет нам расширить базовый функционал музейного экспоната интеллектуальными возможностями генерации кода и ленивой обработки данных. Gajae-Code обеспечивает инфраструктуру для управления агентами, а LazyCodex — механизмы ленивой оценки и кэширования.
rust// src/integrations/gajae_integration.rs
use gajae_code::{AgentFramework, AgentConfig};
use lazycodex::{LazyProcessor, ProcessingStrategy};
struct GajaeLazyIntegration {
framework: AgentFramework,
lazy_processor: LazyProcessor,
exhibit_agent: Arc<dyn Agent>,
}
impl GajaeLazyIntegration {
fn new() -> Self {
let config = AgentConfig {
name: "museum_exhibit".to_string(),
resources: ResourceLimits {
cpu: 0.5,
memory: 512, // MB
network: true,
},
health_check: HealthCheckConfig {
interval: Duration::from_secs(30),
timeout: Duration::from_secs(5),
},
};
let framework = AgentFramework::new(config);
let lazy_processor = LazyProcessor::new(ProcessingStrategy::Adaptive);
Self {
framework,
lazy_processor,
exhibit_agent: Arc::new(MuseumExhibitAgent::new()),
}
}
async fn initialize(&self) -> IntegrationResult {
// Регистрация агента в Gajae-Code
self.framework.register_agent(self.exhibit_agent.clone()).await?;
// Настройка ленивой обработки для экспоната
self.lazy_processor.register_task(
"exhibit_sensors",
LazySensorTask::new(),
ProcessingStrategy::OnDemand,
);
// Инициализация связи между системами
self.setup_bridge().await?;
Ok(())
}
async fn setup_bridge(&self) -> IntegrationResult {
// Мост для обмена данными между Gajae-Code и LazyCodex
let bridge = DataBridge::new()
.with_source(self.framework.event_stream())
.with_target(self.lazy_processor.data_stream());
bridge.start().await?;
Ok(())
}
}
Основные моменты интеграции:
- Единая модель агентов: Gajae-Code ожидает агентов с определенным интерфейсом, который наш Rust-агент реализует через трейт
Agent - Ленивая обработка данных: LazyCodex обрабатывает данные датчиков по запросу, что снижает нагрузку на систему
- Потоковая архитектура: оба фреймворка используют потоковую обработку, что обеспечивает предсказуемость задержек
rust// Пример конфигурации интеграции
# config/integration.toml
[gajae]
host = "localhost"
port = 8080
api_key = "${GAJAE_API_KEY}"
[lazycodex]
cache_dir = "/var/cache/lazycodex"
max_memory_mb = 1024
processing_strategy = "adaptive"
[bridge]
buffer_size = 10000
flush_interval_ms = 100
Интеграция позволяет нам масштабировать функциональность экспоната: новые датчики добавляются как ленивые задачи, а новые агенты — как компоненты в Gajae-Code, без изменения core logic экспоната. Такая архитектура обеспечивает нам предсказуемость и расширяемость — ключевые качества для музейного экспоната, который должен работать десятилетиями.
Безлюдное развитие и поддержка
Безлюдное развитие и поддержка
### Автоматизация CI/CD
Для проекта ultraworkers/claw-code, который представляет собой полностью автоматизированный музейный экспонат на Rust, CI/CD-пайплайн должен быть максимально предсказуемым и надежным. Я настраиваю его так, чтобы не требовалось вмешательства человека — от коммита до работающего контейнера.
Структура репозитория
ultraworkers/claw-code/
├── .github/
│ └── workflows/
│ └── rust-ci.yml
├── src/
│ └── museum_exhibit.rs
├── Cargo.toml
├── Dockerfile
└── monitor/
├── prometheus.yml
└── alerts.yml
GitHub Actions для автоматизации сборки
yaml# .github/workflows/rust-ci.yml
name: Rust CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
# Ежедневная проверка работоспособности без изменений в коде
- cron: '0 2 * * *'
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C opt-level=3 -C debuginfo=0"
jobs:
test-and-build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: x86_64-unknown-linux-gnu
- target: armv7-unknown-linux-gnueabihf
steps:
- uses: actions/checkout@v4
- name: Установка Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
override: true
- name: Кэширование Cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Кэширование Cargo index
uses: actions/cache@v3
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-git-${{ hashFiles('**/Cargo.lock') }}
- name: Кэширование целевых бинарников
uses: actions/cache@v3
with:
path: target/
key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}
- name: Проверка кода
run: cargo check --all --target ${{ matrix.target }}
- name: Запуск тестов
run: cargo test --all --target ${{ matrix.target }} -- --nocapture
- name: Сборка проекта
run: cargo build --release --target ${{ matrix.target }}
- name: Проверка работоспособности бинарника
run: |
./target/${{ matrix.target }}/museum_exhibit --health-check
- name: Создание Docker-образа
run: |
docker build -t ultraworkers/claw-code:${{ github.sha }} \
--target ${{ matrix.target }} .
- name: Тестирование контейнера
run: |
docker run --name test-exhibit ultraworkers/claw-code:${{ github.sha }} \
--test-mode --verbose
- name: Отправка артефактов в хранилище
uses: actions/upload-artifact@v3
with:
name: museum-exhibit-${{ matrix.target }}
path: target/${{ matrix.target }}/museum_exhibit
Автоматическая развертывание
bash#!/bin/bash
# deploy.sh - Скрипт автоматического развертывания
set -euo pipefail
EXHIBIT_NAME="ultraworkers/claw-code"
LATEST_TAG=$(git describe --tags --abbrev=0)
DEPLOY_TARGET=${1:-"production"}
echo "Начинаем развертывание ${EXHIBIT_NAME} версии ${LATEST_TAG} в ${DEPLOY_TARGET}"
# Обновление кода
git pull origin main
# Обновление Rust-вложений
cargo update
# Сборка с оптимизациями
cargo build --release --features "production"
# Проверка предсказуемости конфигурации
if ! cargo run --release -- --validate-config; then
echo "Ошибка: Конфигурация не предсказуема"
exit 1
fi
# Развертывание через Docker
docker-compose -f docker-compose.prod.yml pull
docker-compose -f docker-compose.prod.yml up -d
# Ожидание готовности
sleep 30
# Проверка работоспособности
if ! curl -f http://localhost:8080/health; then
echo "Ошибка: Экспонат не достиг здоровья после развертывания"
docker-compose -f docker-compose.prod.yml logs
exit 1
fi
echo "Развертывание ${EXHIBIT_NAME} версии ${LATEST_TAG} успешно завершено"
### Мониторинг и алертинг
Для полностью автоматизированного музейного экспоната мониторинг должен быть бесшумным, но эффективным. Я использую сочетание Prometheus + Grafana для метрик и Alertmanager для алертинга.
Конфигурация Prometheus
yaml# monitor/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'museum-exhibit'
static_configs:
- targets: ['localhost:8080']
metrics_path: '/metrics'
scrape_interval: 5s
- job_name: 'node-exporter'
static_configs:
- targets: ['localhost:9100']
scrape_interval: 30s
- job_name: 'docker-exporter'
static_configs:
- targets: ['localhost:9255']
scrape_interval: 30s
rule_files:
- "alerts.yml"
scrape_interval: 5s
Правила алертинга
yaml# monitor/alerts.yml
groups:
- name: museum_exhibit
rules:
# Критические алерты
- alert: ExhibitDown
expr: up{job="museum-exhibit"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Музейный экспонат недоступен"
description: "Экспонат ultraworkers/claw-code упал на {{ $time }}"
# Предупреждения
- alert: HighMemoryUsage
expr: (process_memory_bytes{job="museum-exhibit"} / container_memory_limit_bytes) > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "Высокое использование памяти"
description: "Использование памяти превышает 85%"
# Информационные алерты
- alert: RustPanic
expr: rust_panics_total{job="museum-exhibit"} > 0
for: 0m
labels:
severity: info
annotations:
summary: "Обнаружен Rust-паник"
description: "Экспонат столкнулся с паникой в Rust на {{ $time }}"
# Производительность
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job="museum-exhibit"}[5m])) > 1.0
for: 3m
labels:
severity: warning
annotations:
summary: "Высокое время отклика"
description: "95-й процентиль времени отклика превышает 1 секунду"
Конфигурация Grafana
json{
"dashboard": {
"title": "Мониторинг музейного экспоната",
"panels": [
{
"title": "Работоспособность экспоната",
"type": "stat",
"targets": [
{
"expr": "up{job='museum-exhibit'}",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"0": {
"index": 0,
"text": "Работает",
"color": "green"
},
"1": {
"index": 1,
"text": "Не работает",
"color": "red"
}
],
"type": "value"
}
]
}
}
},
{
"title": "Использование памяти",
"type": "graph",
"targets": [
{
"expr": "process_memory_bytes{job='museum-exhibit'} / container_memory_limit_bytes * 100",
"legendFormat": "Память %"
}
]
},
{
"title": "Количество Rust-паников",
"type": "graph",
"targets": [
{
"expr": "rust_panics_total{job='museum-exhibit'}",
"legendFormat": "Паники"
}
]
}
]
}
}
Скрипт проверки работоспособности
bash#!/bin/bash
# health_check.sh - Проверка работоспособности экспоната
set -euo pipefail
EXHIBIT_URL="http://localhost:8080"
METRICS_URL="http://localhost:8080/metrics"
TIMEOUT=30
echo "Запуск проверки работоспособности экспоната..."
# Проверка HTTP-здоровья
if ! curl -f -s -o /dev/null --max-time 10 "${EXHIBIT_URL}/health"; then
echo "Критическая ошибка: Экспонат не отвечает на /health"
exit 1
fi
# Проверка метрик
if ! curl -f -s -o /dev/null --max-time 10 "${METRICS_URL}"; then
echo "Критическая ошибка: Метрики недоступны"
exit 1
fi
# Проверка наличия ожидаемых метрик
EXPECTED_METRICS=(
"rust_build_info"
"http_requests_total"
"process_memory_bytes"
)
for metric in "${EXPECTED_METRICS[@]}"; do
if ! curl -s "${METRICS_URL}" | grep -q "${metric}"; then
echo "Предупреждение: Ожидаемая метрика ${metric} не найдена"
fi
done
# Проверка целостности конфигурации
if ! curl -f -s -o /dev/null --max-time 10 "${EXHIBIT_URL}/config/validate"; then
echo "Критическая ошибка: Проверка конфигурации не пройдена"
exit 1
fi
echo "Проверка работоспособности экспоната успешно завершена"
echo "Статус: Все системы работают предсказуемо"
Автоматический алертинг через Alertmanager
yaml# monitor/alertmanager.yml
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
route:
receiver: 'default'
group_by: ['alertname', 'instance']
routes:
- match:
severity: critical
receiver: 'pagerduty'
continue: true
- match:
severity: warning
receiver: 'slack-warning'
continue: true
receivers:
- name: 'default'
- name: 'slack-warning'
slack_configs:
- channel: '#alerts'
title: '{{ template "slack.title" . }}'
text: '{{ template "slack.text" . }}'
- name: 'pagerduty'
pagerduty_configs:
- service_key: '{{ .PagerDuty.ServiceKey }}'
severity: critical
templates:
- '/etc/alertmanager/template/*.tmpl'
Эти компоненты обеспечивают полную автоматизацию CI/CD-пайплайна и предсказуемый мониторинг/алертинг для экспоната музея на Rust. Система работает без вмешательства человека, автоматически обнаруживает проблемы и предоставляет предсказуемую диагностическую информацию для быстрого устранения неполадок.
Архитектура экспоната
Хранение данных и ZFS
Для экспоната ultraworkers/claw-code используется Rust для управления музейными артефактами, а ZFS обеспечивает надежное хранение данных. В системе используется ZFS на флагманском сервере домашней лаборатории — Dell PowerEdge R730 с 48 ТБ полезного пространства на TrueNAS.
bash# Проверка пула данных в ZFS
zpool status museum_pool
# Создание снапшота перед обновлением экспоната
zfs snapshot museum_pool/current_exhibit@backup_$(date +%Y%m%d)
# Восстановление из снапшота при необходимости
zfs rollback museum_pool/current_exhibit@backup_20231201
Архитектура использует ZFS-архивацию для создания точек восстановления, что позволяет откатываться к предыдущим состояниям экспоната без потери данных. Пул данных настроен с зеркальным резервированием и включенным сжатием, что обеспечивает предсказуемость при работе с большими объемами музейных данных.
bash# Создание пула данных с зеркальным резервированием
zpool create -f museum_pool mirror /dev/sda2 mirror /dev/sdb2
# Настройка резерва
zfs set quota@exhibits 40T museum_pool/current_exhibit
zfs set compression=lz4 museum_pool/current_exhibit
Взаимодействие с посетителями
Система взаимодействия с посетителями построена на Rust и использует gRPC для быстрого обмена данными с периферийными устройствами. Экспонат реагирует на движения, прикосновения и голосовые команды через Raspberry Pi, подключенный к домашней лаборатории.
rust// Пример Rust-кода для обработки взаимодействия посетителя
use tokio::net::TcpStream;
use protobuf::Message;
#[derive(Debug, Clone)]
struct VisitorAction {
action_type: ActionType,
timestamp: SystemTime,
device_id: String,
}
#[tokio::main]
async fn handle_visitor_interaction(stream: &mut TcpStream) -> Result<(), Box<dyn Error>> {
let mut buffer = vec![0; 1024];
let n = stream.read(&mut buffer).await?;
if n > 0 {
let action: VisitorAction = VisitorAction::parse(buffer)?;
match action.action_type {
ActionType::Touch => trigger_display_sequence(action.device_id).await,
ActionType::Motion => start_audio_guide(action.device_id).await,
ActionType::VoiceCommand(cmd) => execute_voice_command(cmd, action.device_id).await,
}
}
Ok(())
}
Для управления интерфейсом используется systemd-сервис, что обеспечивает предсказуемость работы:
ini# /etc/systemd/system/claw-code-exhibit.service
[Unit]
Description=Музейный экспонат ultraworkers/claw-code
After=network.target
[Service]
Type=simple
User=exhibit
Group=exhibit
WorkingDirectory=/opt/claw-code
ExecStart=/usr/local/bin/claw-code --config /etc/claw-code/config.toml
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Система отслеживает активность посетителей и автоматически переключает режимы работы: демонстрационный режим для одиночных посетителей, образовательный режим для групп и режим глубокого погружения для исследователей. Все логи взаимодействий записываются в Loki для последующего анализа поведения посетителей.
bash# Проверка логов взаимодействий
journalctl -u claw-code-exhibit --since "2024-01-01" | grep "visitor_action"
Выводы и перспективы
Масштабирование
Для автоматизированного музейного экспоната на Rust критически важно понимать, что масштабирование — это не просто увеличение вычислительных ресурсов, а системный подход к поддержанию предсказуемости поведения. Когда я впервые запускал подобный проект на Raspberry Pi, то быстро понял, что проблема не в Rust-библиотеках, а в инфраструктуре.
Стратегия масштабирования начинается с systemd-сервиса, который сам следит за собой:
ini# /etc/systemd/system/claw-code-exhibit.service
[Unit]
Description=Автоматизированный музейный экспонат Claw-Code
After=network.target
[Service]
Type=simple
User=exhibit
Group=exhibit
WorkingDirectory=/opt/claw-code
ExecStart=/opt/claw-code/target/debug/claw-code --config /etc/claw-code/config.toml
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Ограничение ресурсов для предсказуемости
CPUQuota=50%
MemoryLimit=512M
TasksMax=50
[Install]
WantedBy=multi-user.target
Мониторинг предсказуемости работы экспоната:
rust// Пример из реального кода: отслеживание латентности
#[derive(Debug)]
struct ExhibitMetrics {
response_time_nanos: u64,
frame_drops: u32,
restart_count: u32,
}
impl ExhibitMetrics {
fn check_health(&self) -> bool {
// Предсказуемость — это когда отклонения контролируемы
let max_latency = 100_000_000; // 100ms
let max_drops = 10;
self.response_time_nanos < max_latency && self.frame_drops < max_drops
}
}
Практический совет: когда экспонат начинает показывать аномальную латентность, система автоматически перезапускает только проблемный компонент, а не весь контейнер. Это достигается через docker compose restart exhibit-renderer с последующим логированием в Loki.
Перспективы автономных музейных решений
Автономные музейные решения — это не просто «интересный проект для хобби», а реальный тренд, который проникает в домашние лаборатории благодаря предсказуемости и надёжности. С практической точки зрения, будущее за объединением нескольких концепций:
1. Интеграция с домашней лабораторией
yaml# docker-compose.yml для музейного экспоната
version: '3.8'
services:
claw-code:
image: ultraworkers/claw-code:latest
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
volumes:
- ./artifacts:/app/artifacts:ro
- exhibit_data:/app/data
depends_on:
- exhibit-storage
restart: unless-stopped
exhibit-storage:
image: bitnami/minio:latest
volumes:
- exhibit_data:/data
environment:
MINIO_ROOT_USER: exhibit
MINIO_ROOT_PASSWORD: ${EXHIBIT_MINIO_PASS}
2. Автоматизация документации
Когда я перешёл от ручного README к автоматической генерации документации, то понял, что предсказуемость — это не только работа системы, но и её описания. Сценарий:
bash#!/bin/bash
# scripts/generate-docs.sh
set -euo pipefail
# Генерация структуры конфигурации
cargo doc --no-deps
cp -r target/doc/* docs/api/
# Обновление Mermaid-диаграммы зависимостей
cargo depgraph | dot -Tpng -o docs/dependencies.png
echo "Документация обновлена в $(date)"
3. Перспективы мониторинга и алертинга
Будущее — это объединение экспоната с системой мониторинга, которая не просто фиксирует падения, а предсказывает их. С использованием Prometheus + Alertmanager:
yaml# alertmanager.yml
route:
receiver: 'telegram'
group_by: ['alertname']
routes:
- match:
severity: 'critical'
receiver: 'pagerduty'
receivers:
- name: 'telegram'
telegram_configs:
- chat_id: -123456789
message: |
{{ template "telegram.message" . }}
templates:
- '/etc/alertmanager/template.yml'
4. Практическая перспектива: автономное обучение
Когда экспонат работает в автономном режиме (без постоянного интернет-соединения), он должен уметь адаптироваться к изменившимся условиям. Реализация:
rust// Пример адаптивного поведения
impl ExhibitController {
async fn adapt_to_offline(&mut self) {
// Если нет интернета — использовать кэшированные данные
if !self.network_available().await {
self.load_cached_state().await;
self.enable_low_power_mode();
}
}
}
Заключение: автономные музейные решения — это не просто технический эксперимент, а системный подход к созданию предсказуемых, самообновляющихся экспонатов. Ключ к успеху — в разделении обязанностей: Rust-код занимается бизнес-логикой, systemd — управлением процессами, а домашняя лаборатория — мониторингом и масштабированием.