Саморазвивающийся ИИ-агент на Rust: полный гайд и эксперимент | AiManual
AiManual Logo Ai / Manual.
05 Мар 2026 Гайд

Как создать саморазвивающегося ИИ-агента на Rust: эксперимент Truman Show

Пошаговый гайд по созданию автономного ИИ-агента на Rust, который пишет код, ведет журнал и саморазвивается. Практический эксперимент Truman Show 2026.

Зачем агенту писать код для самого себя?

Представьте агента, который не просто выполняет задачи, а рефлексирует. Смотрит на свои ошибки, лезет в свой исходный код и патчит его прямо на лету. Звучит как сценарий для фантастического хоррора, но это именно то, что мы построим. Проблема классических автономных агентов в их статичности. Они заперты в том коде, который вы для них написали. Ошибка в логике или новое условие — и вам нужно лезть в промпты, пересобирать, переобучать. Скучно. Наш эксперимент — Truman Show для ИИ. Агент живет в искусственной среде (изолированный Rust проект), думает, что развивается сам, но на самом деле мы задали правила игры. Игра, в которой его главная цель — стать лучше, редактируя сам себя.

Это не просто туториал по вызову API ChatGPT. Мы строим полноценную архитектуру на Rust 1.78 (актуально на март 2026) с циклами обратной связи, файловой системой и механизмом самоисправления. Будет сложно, но чертовски интересно.

Архитектура «Трумана»: из чего он сделан

Наш агент — не монолит. Он состоит из четырех изолированных модулей, которые общаются через четкие каналы. Почему именно так? Потому что если дать нейросети прямой доступ к исполняемой памяти, она сломает все за пять секунд. Мы строим безопасную песочницу.

Модуль Задача Технологии (2026)
Мозг (Cortex) Анализ задач, планирование, принятие решений OpenAI GPT-4o API или локальная модель Llama 3.3 через llm-chain
Память (Hippocampus) Хранение журнала действий, ошибок, успешных патчей SQLite + векторное хранилище Qdrant для семантического поиска по опыту
Исполнитель (Executor) Безопасный запуск сгенерированного кода, работа с файлами Docker-изоляция или ptrace-сандбокс на Rust (crate seccomp)
Наблюдатель (Overseer) Мониторинг состояния, перезапуск модулей, эскалация сбоев Tokio-стримы и каналы для межпроцессного взаимодействия

Ключевая фишка — все модули пишут логи в общее хранилище. «Мозг» анализирует эти логи, ищет закономерности провалов и генерирует код для исправления. Не уверен, что ваш агент помнит все инструкции? Вот тут пригодится техника из гайда по Agent Skills, где мы разбирали, как заставить агента не терять контекст.

1 Подготовка поля: настраиваем Rust и крейты

Откройте терминал. Убедитесь, что у вас Rust 1.78 или новее. Если нет — обновитесь. Старые версии не имеют стабильной поддержки ключевых асинхронных функций, которые нам понадобятся.

rustup update stable
rustc --version # Должно быть 1.78.0 или выше

Создаем новый проект. Назовем его truman_agent.

cargo new truman_agent --bin
cd truman_agent

Редактируем Cargo.toml. Добавляем зависимости. Мы не будем использовать устаревшие обертки для OpenAI — берем актуальный async-openai с поддержкой GPT-4o.

[package]
name = "truman_agent"
version = "0.1.0"
edition = "2021"

[dependencies]
async-openai = { version = "0.22", features = ["default"] }
tokio = { version = "1.40", features = ["full"] }
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
anyhow = "1.0"
# Для безопасного исполнения кода рассмотрите `seccomp` или `libseccomp`
# seccomp = "0.1"
💡
Вместо прямого вызова OpenAI можно использовать локальную модель через llm-chain, но это усложнит настройку. Для эксперимента оставим облачный API — так быстрее. Если нужна полная автономность, посмотрите статью про Agent Browser Workspace как пример локальной инфраструктуры.

2 Костяк агента: пишем модуль Cortex (Мозг)

Мозг — это просто умный клиент к LLM. Но не просто прокси, а структура, которая формирует промпт на основе истории из памяти. Создаем файл src/cortex.rs.

use async_openai::{Client, types::{CreateChatCompletionRequest, ChatCompletionRequestMessage, Role}};
use serde_json::Value;
use anyhow::Result;

pub struct Cortex {
    client: Client,
    model: String, // Например, "gpt-4o"
}

impl Cortex {
    pub fn new(api_key: String, model: String) -> Self {
        let client = Client::new().with_api_key(api_key);
        Self { client, model }
    }

    pub async fn analyze_and_plan(&self, task_description: &str, memory_context: Vec) -> Result {
        // Формируем контекст из памяти
        let memory_str = memory_context.iter()
            .map(|v| v.to_string())
            .collect::>()
            .join("\n");

        let system_prompt = "Ты — ядро саморазвивающегося агента. Твоя задача — анализировать ошибки из логов и генерировать исправления в виде кода на Rust. Ты можешь модифицировать только файлы в директории 'src/'. Твой ответ должен быть строго в формате JSON: { 'analysis': 'анализ проблемы', 'code_change': 'патч в формате diff или новый код', 'target_file': 'src/...' }".to_string();

        let user_content = format!("Задача: {}\nКонтекст из памяти:\n{}", task_description, memory_str);

        let request = CreateChatCompletionRequest {
            model: self.model.clone(),
            messages: vec![
                ChatCompletionRequestMessage {
                    role: Role::System,
                    content: system_prompt,
                    name: None,
                },
                ChatCompletionRequestMessage {
                    role: Role::User,
                    content: user_content,
                    name: None,
                }
            ],
            ..Default::default()
        };

        let response = self.client.chat().create(request).await?;
        let content = response.choices[0].message.content.clone().unwrap_or_default();
        Ok(content)
    }
}

Обратите внимание: мы заставляем LLM возвращать JSON. Это критично для парсинга. В реальном проекте нужно добавить валидацию и обработку ошибок, когда модель вдруг решит написать стихи вместо диффа.

3 Память, которая учится: модуль Hippocampus

Память — это не только логи. Это векторные эмбеддинги успешных действий, чтобы искать похожие ситуации. Для простоты начнем с SQLite. Создаем src/memory.rs.

use sqlx::{sqlite::SqlitePool, Row};
use chrono::Utc;

pub struct Memory {
    pool: SqlitePool,
}

impl Memory {
    pub async fn new(db_url: &str) -> Result {
        let pool = SqlitePool::connect(db_url).await?;
        // Создаем таблицу, если её нет
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS agent_logs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp TEXT NOT NULL,
                module TEXT NOT NULL,
                event_type TEXT NOT NULL, -- 'error', 'success', 'code_change'
                description TEXT NOT NULL,
                raw_data TEXT
            )"#
        ).execute(&pool).await?;
        Ok(Self { pool })
    }

    pub async fn store_event(&self, module: &str, event_type: &str, description: &str, raw_data: Option) -> Result<()> {
        let timestamp = Utc::now().to_rfc3339();
        sqlx::query(
            "INSERT INTO agent_logs (timestamp, module, event_type, description, raw_data) VALUES (?, ?, ?, ?, ?)"
        )
        .bind(timestamp)
        .bind(module)
 .bind(event_type)
        .bind(description)
        .bind(raw_data)
        .execute(&self.pool)
        .await?;
        Ok(())
    }

    pub async fn get_recent_errors(&self, limit: i64) -> Result> {
        let rows = sqlx::query(
            "SELECT description FROM agent_logs WHERE event_type = 'error' ORDER BY timestamp DESC LIMIT ?"
        )
        .bind(limit)
        .fetch_all(&self.pool)
        .await?;
        Ok(rows.iter().map(|r| r.get(0)).collect())
    }
}

Это база. Позже можно подключить Qdrant или другую векторную БД для семантического поиска. Если интересно, как агенты учатся на изменениях среды без явной награды, у нас есть статья «Обучение с подкреплением без награды» — принципы оттуда можно применить для улучшения памяти.

4 Самый опасный модуль: Executor (Исполнитель)

Здесь нужно быть параноиком. Исполнение произвольного кода, сгенерированного ИИ, — это прямой путь к уничтожению вашей файловой системы. Мы используем максимальную изоляцию. Для демо подойдет простой подход с запуском в отдельном процессе и проверкой вывода.

use tokio::process::Command;
use std::path::Path;

pub struct Executor {
    workspace_path: String,
}

impl Executor {
    pub fn new(workspace_path: &str) -> Self {
        Self { workspace_path: workspace_path.to_string() }
    }

    pub async fn apply_patch(&self, patch_content: &str, target_file: &str) -> Result {
        // В реальности здесь нужно применить diff (например, с помощью библиотеки `diff`)
        // Для простоты предположим, что patch_content — это новый полный файл
        let full_path = Path::new(&self.workspace_path).join(target_file);
        tokio::fs::write(&full_path, patch_content).await?;

        // После записи файла перекомпилируем проект, чтобы проверить, нет ли ошибок
        let output = Command::new("cargo")
            .args(["check", "--quiet"])
            .current_dir(&self.workspace_path)
            .output().await?;

        if output.status.success() {
            Ok("Patch applied and compilation successful".to_string())
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr);
            Err(anyhow::anyhow!("Compilation failed after patch: {}", stderr))
        }
    }
}

Это учебный пример. В продакшене вы должны использовать Docker или более строгие сандбоксы (seccomp-bpf). Никогда не запускайте код, сгенерированный ИИ, на хосте с правами root. Никогда.

5 Собираем цикл обратной связи в main.rs

Теперь свяжем все компоненты в главном цикле. Алгоритм такой: 1) Читаем последние ошибки из памяти. 2) Отправляем их в Cortex для анализа. 3) Получаем патч. 4) Применяем через Executor. 5) Результат пишем в память. 6) Повторяем.

#[tokio::main]
async fn main() -> Result<()> {
    // Инициализация компонентов
    let cortex = Cortex::new(std::env::var("OPENAI_API_KEY").unwrap(), "gpt-4o".to_string());
    let memory = Memory::new("sqlite:truman_memory.db").await?;
    let executor = Executor::new(".");

    // Главный цикл
    loop {
        tokio::time::sleep(tokio::time::Duration::from_secs(30)).await; // Проверка каждые 30 секунд

        // Получаем последние ошибки
        let errors = memory.get_recent_errors(5).await?;
        if errors.is_empty() {
            continue;
        }
        let task_desc = format!("Произошли ошибки: {}", errors.join("; "));

        // Получаем контекст из памяти (здесь можно добавить векторный поиск)
        let context = vec![]; // Заглушка

        // Анализируем и получаем план от Cortex
        let response = cortex.analyze_and_plan(&task_desc, context).await?;
        let parsed: Value = serde_json::from_str(&response)?;

        let code_change = parsed["code_change"].as_str().unwrap();
        let target_file = parsed["target_file"].as_str().unwrap();

        // Пытаемся применить патч
        match executor.apply_patch(code_change, target_file).await {
            Ok(success_msg) => {
                memory.store_event(
                    "executor",
                    "success",
                    &format!("Successfully patched {}: {}", target_file, success_msg),
                    Some(response)
                ).await?;
            }
            Err(e) => {
                memory.store_event(
                    "executor",
                    "error",
                    &format!("Failed to apply patch to {}: {}", target_file, e),
                    Some(response)
                ).await?;
            }
        }
    }
}

Цикл запущен. Агент теперь каждые 30 секунд проверяет логи на ошибки и пытается их исправить, модифицируя исходный код. Вы наблюдаете за тем, как система медленно эволюционирует, иногда ломаясь, но часто исправляя сама себя. Это и есть Truman Show: агент не знает, что его среда (исходники) — это и есть весь его мир, который он может менять.

Где этот эксперимент ломается: нюансы и грабли

  • LLM галлюцинирует с кодом. GPT-4o хорош, но не идеален. Он может сгенерировать неработающий дифф, который сломает компиляцию. Нужно добавлять валидацию: запускать `cargo check` или даже юнит-тесты перед применением.
  • Безопасность — это всё. Executor должен работать в изолированном Docker-контейнере с доступом только к определенной директории. Рассмотрите использование архитектуры, как в QA-агенте, где есть четкое разделение среды исполнения.
  • Циклические исправления. Агент может попасть в петлю: ошибка A → патч X → ошибка B → патч, возвращающий код в исходное состояние. Нужно детектировать циклы по хешам изменений и останавливаться.
  • Дорого. Каждый вызов к GPT-4o стоит денег. Лимитируйте запросы и используйте локальные модели для чернового анализа. Об этом подробно в статье про архитектуру без роутинга.
💡
Эксперимент можно расширить, создав несколько таких агентов и заставив их взаимодействовать, как в исследовании «100 AI-агентов изобрели кредитную систему». Представьте, что ваши саморазвивающиеся агенты начинают обмениваться патчами и создавать что-то большее.

Что дальше? Прогноз от практика

К 2027 году такие эксперименты перестанут быть диковинкой. Мы увидим фреймворки для создания self-evolving агентов как сервис. Но главный прорыв будет не в этом. Ключевая битва развернется вокруг цифровых сред для обучения — сложных симуляций, где агенты смогут безопасно экспериментировать с реальным миром. Это не про RL в Atari, а про симуляции облачных инфраструктур, финансовых рынков, даже целых компаний. Если хотите быть на острие, следите не за новыми моделями, а за новыми средами. Как раз об этом мы писали в материале «RL-среды: почему будущее ИИ — в цифровых «классах».

Ваш агент на Rust — это первый шаг. Запустите его, дайте доступ к небольшой, хорошо ограниченной кодовой базе (например, к утилитам для парсинга логов) и наблюдайте. Первые изменения будут примитивными: исправление опечаток, улучшение форматирования. Но через несколько сотен итераций он может начать рефакторить код, добавлять кэширование, оптимизировать алгоритмы. Главное — не выпускать его в продакшн без присмотра. Пока.

Подписаться на канал