Zeroclaw — технический обзор

Repo: https://github.com/zeroclaw-labs/zeroclaw Язык: Rust (edition 2021, MSRV 1.87) Лицензия: MIT / Apache-2.0 Локальная копия: ~/zeroclaw/


Что это

Zeroclaw — autonomous agent runtime на Rust. Единый бинарник, trait-driven архитектура, заточен под минимальное потребление ресурсов (<5MB RAM). Позиционируется как универсальный фреймворк: от серверов до embedded-устройств (ARM, RISC-V).

Ключевая идея — всё через traits + factory: провайдеры, каналы, инструменты, память, безопасность, рантайм. Плагины регистрируют tools и hooks через PluginApi.


Архитектура (модули)

src/
├── agent/          # Оркестрация: loop, session, prompt, research
├── providers/      # LLM-провайдеры (не интересно — мульти-провайдер)
├── channels/       # Транспорт (Telegram, Discord, Slack и т.д.)
├── tools/          # Инструменты + MCP-клиент
├── memory/         # Persistence: SQLite, Postgres, embeddings
├── security/       # Sandbox, pairing, allowlists
├── gateway/        # HTTP/WebSocket сервер (Axum)
├── runtime/        # Адаптеры: native, docker, WASM
├── peripherals/    # Hardware (STM32, Raspberry Pi GPIO)
├── plugins/        # Система плагинов (tools + hooks)
├── observability/  # Метрики, events, tracing
├── coordination/   # Multi-agent message bus
├── hooks/          # Lifecycle event handlers
└── config/         # Конфигурация (TOML)

Ключевые абстракции

Tool trait

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters_schema(&self) -> serde_json::Value;
    async fn execute(&self, args: serde_json::Value) -> Result<ToolResult>;
    fn spec(&self) -> ToolSpec { /* auto from name/desc/schema */ }
}
 
pub struct ToolResult {
    pub success: bool,
    pub output: String,
    pub error: Option<String>,
}

70+ встроенных инструментов: shell, file r/w, web fetch, memory, cron, browser, PDF/XLSX/DOCX, hardware GPIO, delegation, и т.д.

Memory trait

#[async_trait]
pub trait Memory: Send + Sync {
    async fn store(&self, key: &str, content: &str, category: MemoryCategory, session_id: Option<&str>) -> Result<()>;
    async fn recall(&self, query: &str, limit: usize, session_id: Option<&str>) -> Result<Vec<MemoryEntry>>;
    async fn get(&self, key: &str) -> Result<Option<MemoryEntry>>;
    async fn forget(&self, key: &str) -> Result<bool>;
    async fn reindex(&self, ...) -> Result<usize>;
}
 
pub enum MemoryCategory { Core, Daily, Conversation, Custom(String) }

SQLite-бэкенд с гибридным поиском: vector (cosine similarity на embeddings) + keyword (FTS5 BM25), weighted fusion 0.7/0.3. Есть time decay (7 дней half-life) и Core boost (+0.3 score).

Hook система

Два типа хуков:

Void hooks — параллельные, fire-and-forget:

  • on_session_start/end
  • on_llm_input/output
  • on_after_tool_call
  • on_message_sent
  • on_heartbeat_tick

Modifying hooks — последовательные по приоритету, могут отменить операцию:

  • before_prompt_build — модификация промпта
  • before_llm_call — модификация сообщений/модели
  • before_tool_call — модификация имени/аргументов тула
  • on_message_received — модификация входящего сообщения
  • on_message_sending — модификация исходящего
  • before_compaction / after_compaction — контроль сжатия истории
pub enum HookResult<T> {
    Continue(T),  // продолжить с (возможно изменёнными) данными
    Cancel(String),  // отменить операцию
}

Plugin система

pub trait Plugin: Send + Sync {
    fn manifest(&self) -> &PluginManifest;
    fn register(&self, api: &mut PluginApi) -> Result<()>;
}
 
// PluginApi позволяет:
api.register_tool(Box::new(MyTool {}));
api.register_hook(Box::new(MyHook {}));
api.plugin_config();  // JSON config из TOML
api.logger();  // scoped logger

MCP-клиент

Полная реализация MCP (Model Context Protocol):

  • JSON-RPC 2.0 поверх stdio / HTTP / SSE транспортов
  • Автообнаружение tools через tools/list
  • Префикс server_name__tool_name для избежания коллизий
  • Timeout: 180s по умолчанию (max 600s)
  • Non-fatal: сбой сервера логируется и пропускается

Observer (observability)

pub trait Observer: Send + Sync + 'static {
    fn record_event(&self, event: &ObserverEvent);
    fn record_metric(&self, metric: &ObserverMetric);
}
 
// Events: AgentStart, LlmRequest, LlmResponse, ToolCallStart, ToolCall, 
//         AgentEnd, ChannelMessage, Error, HeartbeatTick
// Metrics: RequestLatency, TokensUsed, ActiveSessions, QueueDepth

Бэкенды: LogObserver, Prometheus, OpenTelemetry, CostObserver, MultiObserver.

Session management

pub trait SessionManager: Send + Sync {
    async fn get_history(&self, session_id: &str) -> Result<Vec<ChatMessage>>;
    async fn set_history(&self, session_id: &str, history: Vec<ChatMessage>) -> Result<()>;
    async fn delete(&self, session_id: &str) -> Result<()>;
    async fn cleanup_expired(&self) -> Result<usize>;
}
 
pub enum AgentSessionStrategy {
    Main,        // одна сессия на всех
    PerChannel,  // по каналу
    PerSender,   // по (канал, отправитель)
}

In-memory (TTL) и SQLite реализации.

Channel trait (транспорт)

#[async_trait]
pub trait Channel: Send + Sync {
    fn name(&self) -> &str;
    async fn send(&self, message: &SendMessage) -> Result<()>;
    async fn listen(&self, tx: mpsc::Sender<ChannelMessage>) -> Result<()>;
    
    // Draft message updates (streaming в Telegram/Discord)
    fn supports_draft_updates(&self) -> bool;
    async fn send_draft(&self, message: &SendMessage) -> Result<Option<String>>;
    async fn update_draft(&self, recipient: &str, message_id: &str, text: &str) -> Result<Option<String>>;
    async fn finalize_draft(&self, recipient: &str, message_id: &str, text: &str) -> Result<()>;
    
    // Approval prompts для tool calls
    async fn send_approval_prompt(&self, recipient: &str, request_id: &str, tool_name: &str, arguments: &Value, thread_ts: Option<String>) -> Result<()>;
}

Security

  • Sandbox trait: wrap_command() для isolating shell commands (landlock, bubblewrap)
  • Pairing guard для HTTP gateway
  • Credential scrubbing: regex-based redaction из tool output перед отправкой LLM
  • Deny-by-default allowlists

Agent Loop (оркестрация)

Основной цикл:

  1. Получить сообщение из канала
  2. Resolve session (Main / PerChannel / PerSender)
  3. Загрузить релевантные memories (hybrid search)
  4. Построить system prompt
  5. Вызвать LLM через Provider
  6. Если ответ содержит tool_calls → выполнить tools (параллельно или последовательно)
  7. Loop detection: 3 стратегии:
    • No-progress repeat (один tool+args+output 3+ раз) → warning → hard stop
    • Ping-pong (A→B→A→B 2+ цикла) → warning
    • Failure streak (один tool фейлит 3+ раз) → warning
  8. Credential scrubbing на выходе tools
  9. Добавить результаты в историю, вернуться к шагу 5
  10. Финальный ответ → отправить в канал
  11. Обновить memory и session

History compaction:

  • Перед компакцией — извлечь durable facts в Core memories
  • Max 12,000 символов для summarization, max 2,000 summary
  • Сохраняет 20 последних не-system сообщений
  • Никогда не разрезает пару assistant(tool_calls) → tool results

Что полезно для tagopi

Сравнение с текущим состоянием tagopi (Go, Telegram↔Claude CLI bridge)

1. Hook/lifecycle система — высокий приоритет

Tagopi сейчас: монолитный handleMessagerunTurn без точек расширения.

Zeroclaw предлагает: чёткие lifecycle hooks с двумя режимами (void / modifying). Это даёт:

  • before_tool_call — логирование, фильтрация опасных tools
  • on_message_received — пре-процессинг входящих (команды, авто-перевод, фильтры)
  • on_message_sending — пост-процессинг ответов (форматирование, цензура)
  • on_after_tool_call — аналитика по использованию tools

Реализация для tagopi: Не нужен полноценный plugin API. Достаточно:

type Hook interface {
    // Void hooks
    OnToolStart(ctx context.Context, name string, args json.RawMessage)
    OnToolDone(ctx context.Context, name string, result string, dur time.Duration)
    OnTurnComplete(ctx context.Context, sessionKey string, result TurnResult)
    
    // Modifying hooks
    BeforePrompt(ctx context.Context, prompt string) (string, error)
    BeforeResponse(ctx context.Context, text string) (string, error)
}

2. Loop detection — высокий приоритет

Tagopi сейчас: нет защиты от зацикливания. Claude CLI имеет свой max_turns, но внутри одного turn agent может зациклиться на tool calls.

Zeroclaw: три стратегии — no-progress repeat, ping-pong, failure streak. Сначала inject warning в промпт, потом hard stop.

Для tagopi это менее актуально, т.к. Claude CLI сам управляет tool loop. Но можно добавить:

  • Timeout на весь turn (уже есть IdleTimeout, но это per-output, а не global)
  • Счётчик tool calls в одном turn с hard limit

3. Observability — средний приоритет

Tagopi сейчас: slog.Info/Error в main.go.

Zeroclaw: Observer trait с events и metrics, pluggable бэкенды.

Для tagopi достаточно:

type Observer interface {
    RecordEvent(event Event)
}
 
type Event struct {
    Type     string  // "tool_call", "turn_complete", "error"
    Tool     string
    Duration time.Duration
    Success  bool
    Tokens   TokenUsage
}

Даже простой файловый лог events даст полезную аналитику.

4. MCP-клиент (нативный) — средний приоритет

Tagopi сейчас: Claude CLI сам управляет MCP серверами через --mcp-config.

Zeroclaw: собственный MCP-клиент (JSON-RPC 2.0 поверх stdio/HTTP/SSE).

Для tagopi: пока не критично, т.к. CLI справляется. Но если перейти с CLI на прямой API — понадобится. Архитектура zeroclaw (transport abstraction + registry + prefix naming) — хороший референс.

5. Memory система — низкий/средний приоритет

Tagopi сейчас: session store (SQLite) хранит только session mapping и token usage.

Zeroclaw: полноценная semantic memory с embeddings, hybrid search, categories, time decay.

Для tagopi интересны:

  • Персистентная история разговоров (tagopi делегирует это Claude CLI через --session-id)
  • Auto-save ключевых фактов из разговора
  • Recall при старте нового turn

6. Draft message updates — уже реализовано

Tagopi statusMsg уже делает то, что zeroclaw называет “draft updates”: progressive message editing с throttling. Реализация в tagopi даже лучше заточена под Telegram (escape, split, fallback).

7. Session strategies — низкий приоритет

Tagopi сейчас: per-chat/thread (единственный нужный вариант).

Zeroclaw: Main / PerChannel / PerSender. Добавлять другие стратегии в tagopi пока нет смысла.

8. Credential scrubbing — хорошая идея

Zeroclaw: regex-based redaction (token=xxx, api_key="...") из tool output перед LLM.

Для tagopi: полезно добавить в пост-процессинг ответов, хотя бы базовый regex.

9. Approval system — интересно

Zeroclaw: send_approval_prompt — канал может запросить у пользователя подтверждение перед выполнением tool.

Для tagopi: можно реализовать inline-кнопки в Telegram для опасных операций (delete files, git push и т.д.).


Что НЕ интересно (по запросу)

  • Мульти-провайдеры (20+ LLM-бэкендов) — tagopi работает только с Claude
  • Мульти-каналы (27+ реализаций Channel) — tagopi = Telegram only
  • Provider capability negotiation / tool format conversion
  • Model routing, fallback, quota management
  • Gateway HTTP API / OpenAI-compatible endpoints
  • Hardware peripherals
  • WASM runtime
  • Multi-agent coordination

Архитектурные принципы (из CLAUDE.md zeroclaw)

Полезные для любого проекта:

  • KISS: простой control flow, explicit matching, localized errors
  • YAGNI: никаких спекулятивных фич без конкретного use case
  • Fail Fast: явные ошибки, никогда silent fallback
  • Secure by Default: deny-by-default, least privilege
  • Determinism: воспроизводимые билды и поведение

Резюме

Zeroclaw — зрелый, хорошо спроектированный фреймворк с trait-driven архитектурой. Для tagopi наиболее ценны:

  1. Hooks — точки расширения в lifecycle (реализуемо за день)
  2. Loop detection — защита от зацикливания (частично покрыто CLI)
  3. Observability — structured events для аналитики
  4. Credential scrubbing — базовая гигиена безопасности
  5. Approval flow — inline Telegram кнопки для подтверждения

Остальное (мульти-провайдеры, мульти-каналы, semantic memory) — overkill для текущего scope tagopi.