Ultraworkers/claw-code: агент‑управляемый музей стенд на Rust
Агент‑управляемый музейный стенд, написанный на Rust с Gajae‑Code/LazyCodex, полностью автономен и не требует вмешательства человека.
Концепция и архитектура
Роль автономного агента
Автономный агент — это процесс, который полностью самостоятельно контролирует работу музейного стенда: собирает метрики, принимает решения на основе заранее заданных правил и инициирует корректирующие действия без участия человека.
Во-первых, агент постоянно опрашивает сенсоры (температуру, уровень нагрузки на CPU, состояние диска) через systemd‑unit с watchdog‑таймаутом. При превышении порога он генерирует событие в journald, после чего система алертинга Alertmanager отправляет уведомление в чат.
Пример systemd‑юнита для агента:
ini[Unit]
Description=Музейный стенд‑агент
After=network.target
[Service]
ExecStart=/usr/local/bin/claw-agent --config /etc/claw-agent.yaml
Restart=on-failure
WatchdogSec=30
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Команда systemctl enable --now claw-agent запускает сервис и гарантирует его автозапуск при загрузке системы.
Во-вторых, агент использует LazyCodex — декларативный DSL для описания правил поведения. Правила написаны в YAML‑файле и компилируются в Rust‑код во время старта. Пример правила, который автоматически перезапускает контейнер, если его процесс не отвечает более 5 минут:
yamlrules:
- name: "restart_unresponsive"
when:
- metric: "container_cpu_usage"
operator: ">"
value: 0.0
- timer: "last_restart"
operator: ">"
value: 300s
then:
- action: "restart_container"
target: "museum-service"
LazyCodex преобразует этот DSL в Rust‑функцию, которая вызывается из основного цикла агента.
В-третьих, автономность достигается за счёт self‑healing: при любой ошибке (например, падение контейнера) агент сразу инициирует systemctl restart museum-service и фиксирует событие в Loki. Таким образом стенд остаётся доступным 24/7, а человеку не нужно вмешиваться в рутину.
Технологический стек: Rust + Gajae-Code/LazyCodex
Выбор Rust обусловлен рядом причин. Язык обеспечивает память без garbage‑collector, что критично для небольших встроенных устройств, а его строгая типизация фиксирует ошибки на этапе компиляции. Кроме того, в Rust легко писать безопасный сетевой код и работать с системными вызовами через libc‑обёртки.
В проекте используется Cargo как менеджер пакетов. Минимальный Cargo.toml выглядит так:
toml[package]
name = "claw-agent"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = "4.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
lazycodex = "0.3"
tokio = { version = "1.0", features = ["full"] }
prometheus = "0.13"
Код агента написан в стиле «модульность»: основной цикл находится в src/main.rs, а бизнес‑логика вынесена в отдельные модули (rules.rs, orchestrator.rs). Пример функции, которая проверяет состояние контейнера и инициирует перезапуск:
rustpub async fn check_and_restart(container: &str) {
let usage = prometheus::gather_container_cpu().await;
if usage > 0.0 {
// правило LazyCodex уже сработало, отправляем сигнал перезапуска
println!("Перезапуск контейнера {}", container);
std::process::Command::new("docker")
.args(&["restart", container])
.status()
.expect("не удалось выполнить docker restart");
}
}
Gajae-Code — это higher‑level orchestration layer, которая генерирует Rust‑код из YAML‑описания. Она отвечает за управление зависимостями между сервисами, динамическое изменение конфигураций и интеграцию с systemd. Пример конфигурации, описывающей весь стенд, выглядит так:
yamlservices:
- name: "museum-display"
image: "ultraworkers/claw-display:latest"
ports:
- "8080:80"
depends_on:
- "museum-db"
- name: "museum-db"
image: "postgres:15"
environment:
POSTGRES_USER: "claw"
POSTGRES_PASSWORD: "secret"
volumes:
- "db-data:/var/lib/postgresql/data"
volumes:
db-data:
Запуск этого стека выглядит так:
bashcargo run --release -- --config /etc/claw-agent.yaml
После старта агент регистрирует себя в Prometheus через pushgateway и начинает собирать метрики. В Grafana‑дашборде визуализируются такие показатели, как CPU‑нагрузка, количество запросов к API и состояние диска.
Таким образом, сочетание Rust (производительность и надёжность) и Gajae-Code/LazyCodex (высокоуровневая декларация) даёт возможность построить полностью автономный музейный стенд, который самостоятельно решает возникающие задачи, не требуя постоянного вмешательства оператора.
Разработка и развертывание
### Настройка среды разработки
Во-первых, убедитесь, что на машине установлен Rust Toolchain 1.70 и выше – это минимальная версия, поддерживающая текущие версии lazycodex и gajae-code.
Самый простой способ установить Rust через rustup:
bashcurl https://sh.rustup.rs -sSf | sh -s -- -y
source $HOME/.cargo/env
rustc --version # должно отобразиться 1.70.0 или новее
Создайте новый бинарный проект, который будет служить агентом‑управляющим стендом:
bashcargo new claw-code-museum --bin
cd claw-code-museum
Добавьте зависимости в Cargo.toml. Ниже пример минимального набора:
toml[package]
name = "claw-code-museum"
version = "0.1.0"
edition = "2021"
[dependencies]
gajae-code = "0.3"
lazycodex = "0.5"
tokio = { version = "1.38", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
После изменения файла выполните cargo fetch – Cargo загрузит все необходимые crates в директорию ~/.cargo/registry.
Для удобства отладки удобно добавить скрипт в Makefile:
makefile.PHONY: run build test
run:
cargo run --release
build:
cargo build --release
test:
cargo test
Теперь можно запускать приложение локально:
bashmake run
Если требуется кросс‑компиляция под ARM‑платформы (например, Raspberry Pi), используйте cross:
bashcargo install cross cross build --target aarch64-unknown-linux-gnu --release
Важно: при работе с
lazycodexубедитесь, что в конфигурации указали путь к базе данных модели, иначе агент не сможет генерировать ответы. Подробные настройки см. в документации lazycodex.
Автоматическое развертывание через CI/CD
С практической точки зрения полностью автономный стенд удобно разворачивать через CI/CD. Ниже пример конфигурации workflow для GitHub Actions, который собирает образ Docker, пушит его в GitHub Container Registry и затем применяет его на удалённом сервере через ssh и docker compose.
yamlname: Deploy Claw‑Code Museum
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
run: |
docker build -t ghcr.io/${{ github.repository }}/claw-code-museum:${{ github.sha }} .
docker push ghcr.io/${{ github.repository }}/claw-code-museum:${{ github.sha }}
- name: Deploy to server
env:
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
SSH_KEY: ${{ secrets.SSH_KEY }}
run: |
echo "$SSH_KEY" > /tmp/id_rsa
chmod 600 /tmp/id_rsa
ssh -i /tmp/id_rsa $SERVER_USER@$SERVER_HOST <<'EOF'
set -e
echo "🚀 Pulling new image..."
docker pull ghcr.io/${{ github.repository }}/claw-code-museum:${{ github.sha }}
echo "🛠️ Stopping old container..."
docker compose -f /opt/claw-code-museum/docker-compose.yml down
echo "📦 Deploying new container..."
docker compose -f /opt/claw-code-museum/docker-compose.yml up -d --remove-orphans
EOF
Файл docker-compose.yml для стенда выглядит так:
yamlversion: "3.9"
services:
museum-agent:
image: ghcr.io/your-org/claw-code-museum:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
- RUST_LOG=info
- DB_PATH=/data/museum.db
volumes:
- /opt/claw-code-museum/data:/data
Примечание: в примере использован
docker compose(v2), поэтому на сервере должна быть установлена Docker Compose 2.x. Инструкцию по установке можно найти в официальной документации Docker.
С помощью этого пайплайна каждый push в ветку main автоматически собирает актуальный образ, публикует его в регистрари и без вмешательства человека разворачивает новый стенд. Таким образом достигается полная автономность – именно то, что требуется для museum‑стенда.
Операция и поддержка
Мониторинг и алерты
Когда мы запускаем автономную систему вроде claw-code, предсказуемость становится главным приоритетом. Поскольку агент управляет стендом самостоятельно, вы не можете полагаться на то, что заметите ошибку в консоли в момент её возникновения. Если агент уйдет в бесконечный цикл или начнет потреблять слишком много ресурсов, система должна сообщить об этом до того, как упадет весь узел.
С практической точки зрения, я рекомендую не пытаться мониторить всё подряд, а сфокусироваться на трех метриках: состоянии процесса Rust-бинарника, потреблении памяти и доступности API агента.
Для сбора метрик лучше всего подходит связка Prometheus и Grafana. Если вы разворачиваете claw-code через Docker Compose, добавьте в конфигурацию экспортер для контейнеров. Чтобы убедиться, что агент не «завис», настройте проверку liveness probe.
Пример базового конфига для мониторинга состояния сервиса через systemd, если вы запускаете агент напрямую на хосте:
ini[Unit]
Description=Claw-Code Agent Service
After=network.target
[Service]
ExecStart=/usr/local/bin/claw-code --config /etc/claw-code/config.toml
Restart=always
# Перезапуск через 5 секунд, если процесс упал
RestartSec=5s
# Ограничение ресурсов, чтобы агент не «съел» всю память стенда
MemoryMax=2G
CPUWeight=50
[Install]
WantedBy=multi-user.target
Для настройки алертов в Alertmanager я советую использовать правило на основе rate для ошибок в логах. Если количество строк ERROR в journald превышает пороговое значение за 5 минут, вы должны получить уведомление.
Обновления без простоя
Обновление автономных систем — это всегда риск нарушить логику работы агента. В случае с claw-code, использующим LazyCodex, важно, чтобы процесс обновления не прерывал текущие задачи, которые агент выполняет на стенде.
Если вы используете контейнеризацию, самый надёжный способ — это стратегия Blue-Green. Мы не обновляем работающий контейнер «на лету», а поднимаем новую версию рядом, проверяем её работоспособность и только потом переключаем трафик.
Для реализации этого в домашней лаборатории проще всего использовать Docker Compose с небольшим скриптом автоматизации. Вместо того чтобы делать docker-compose pull && docker-compose up -d, что может вызвать кратковременный простой, используйте следующий подход:
- Разверните новую версию под другим именем сервиса.
- Проверьте статус через
docker inspect. - Переключите конфигурацию Nginx или Caddy на новый контейнер.
- Удалите старую версию.
Пример конфигурации Caddy для плавного переключения между версиями агента:
caddyfile# Основной адрес стенда claw-code.local { # Направляем трафик на текущую рабочую версию reverse_proxy localhost:8080 } # Временный адрес для проверки новой версии перед переключением claw-code-next.local { reverse_proxy localhost:8081 }
Если агент работает как системный сервис, используйте systemd-механизм ExecReload. Это позволит перечитать конфигурацию без полной остановки процесса, если архитектура claw-code это поддерживает. Если же требуется полный перезапуск, всегда делайте бэкап директории с состоянием (state directory) перед выполнением команд обновления. Это единственный способ гарантировать, что после перезапуска агент вернется к той же точке выполнения задачи, с которой он прервался.
Практические примеры
Интеграция с Plex и Nextcloud
Когда мы говорим об автономном агенте вроде claw-code, важно понимать, что он не должен существовать в вакууме. В моей домашней лаборатории он выполняет роль «умного диспетчера», который не просто хранит данные, а управляет их жизненным циклом.
С практической точки зрения, интеграция с Plex и Nextcloud сводится к тому, чтобы агент мог корректно манипулировать файловой структурой, не нарушая логику работы этих сервисов. Если агент решит переместить старый семейный архив в холодное хранилище, Plex должен об этом узнать, иначе мы получим «битые» ссылки в медиатеке.
Для реализации такой предсказуемости я использую связку через API и системные события. Например, чтобы Plex обновил базу после того, как claw-code закончил обработку медиафайлов, можно отправить запрос к Plex Media Scanner.
Если вы разворачиваете это в Docker, убедитесь, что агент находится в той же Docker-сети, что и ваши сервисы, либо имеет доступ к их API по внутренним IP. Пример конфигурации для взаимодействия через сетевой интерфейс в docker-compose.yml:
yamlservices:
claw-code:
image: ultraworkers/claw-code:latest
networks:
- media-network
environment:
- PLEX_API_URL=http://plex-server:32400
- NEXTCLOUD_API_TOKEN=${NEXTCLOUD_TOKEN}
plex:
image: linuxserver/plex
networks:
- media-network
# ... остальные настройки
С Nextcloud ситуация чуть сложнее. Вместо прямого изменения файлов в директории data, что может привести к рассинхронизации базы данных Nextcloud, я настраиваю агента на работу через WebDAV или встроенный API. Это гарантирует, что каждое действие агента будет зафиксировано в системе.
Взаимодействие с TrueNAS
Работа с хранилищем — это критический узел. В моей конфигурации за это отвечает TrueNAS, где на ZFS развернуты основные пулы. Для claw-code наиболее надёжным способом взаимодействия является использование протокола NFS или SMB, но с обязательным контролем прав доступа.
Чтобы избежать ситуации, когда агент создает файлы, которые потом невозможно удалить или переместить из-за конфликта прав (UID/GID), я всегда привожу конфигурацию прав к единому стандарту.
Если вы используете NFS для монтирования разделов TrueNAS на ноду, где запущен агент, проверьте параметры монтирования в /etc/fstab. Для предсказуемости работы лучше использовать явное указание прав:
bash# Пример монтирования сетевого хранилища в /mnt/storage
<IP_адрес_TrueNAS>:/mnt/tank/media /mnt/storage nfs rw,hard,intr,noatime 0 0
С точки зрения логики агента, взаимодействие с TrueNAS можно разделить на три этапа:
- Проверка доступности пула: перед началом любой операции агент должен убедиться, что точка монтирования активна.
- Атомарная запись: сначала данные пишутся во временную директорию на том же пуле, а затем перемещаются в целевую папку. Это исключает появление «битых» файлов при сбое сети.
- Валидация: после завершения операции агент проверяет контрольную сумму (checksum) файла.
Если вы планируете использовать Gajae-Code/LazyCodex для автоматизации этих процессов, рекомендую сначала протестировать сценарии на отдельном пуле с небольшим объёмом данных. Это позволит убедиться, что логика перемещения файлов не конфликтует с политиками snapshot-ов в ZFS.
Часто задаваемые вопросы
Как обеспечить предсказуемость работы агента при сбоях?
Для обеспечения стабильности в архитектуре используется systemd с настроенным параметром WatchdogSec. Если процесс агента зависнет или перестанет отвечать на сигналы, система сама перезапустит его согласно политике Restart=on-failure. Это позволяет минимизировать время простоя и гарантирует, что управляющий процесс всегда будет активен.
Можно ли изменить логику поведения стенда без пересборки всего проекта?
Да, за это отвечает декларативный DSL LazyCodex, который считывает правила из YAML-файла при старте. Вы можете описать новые условия мониторинга или действия, такие как перезапуск сервисов или отправка алертов, просто отредактировав конфигурацию. С практической точки зрения, это позволяет быстро адаптировать стенд под новые требования, не прибегая к изменению исходного кода на Rust.
Какие инструменты используются для мониторинга состояния системы?
Агент собирает метрики напрямую из системы и передаёт их в стек мониторинга, где основную роль играют journald и Alertmanager. Все события, включая критические ошибки и срабатывания правил, фиксируются в системном журнале, что упрощает последующий аудит. Если порог нагрузки превышен, Alertmanager оперативно доставляет уведомление ответственному лицу.
Как агент понимает, что контейнер перестал отвечать на запросы?
В системе реализован механизм проверки метрик, где агент сопоставляет текущие показатели с заданными в правилах LazyCodex. Например, если значение метрики container_cpu_usage или таймер последнего перезапуска не соответствуют норме, срабатывает триггер. После этого агент выполняет предустановленное действие, такое как restart_container, восстанавливая работоспособность сервиса.