Waku — личный AI-ассистент на локальной машине: харнес, цикл, память и eval

· 2 мин чтения
ai-agents harness memory eval self-hosted
📂 Исходный код на GitHub

Waku — local-first персональный AI-ассистент, показывающий четыре столпа серьёзного агента: харнес, цикл, память и eval/LLM-Ops. Цикл ~95 строк чистого Python, память в одном SQLite-файле, локальный дашборд, deterministic + LLM-as-judge eval с release-гейтом. MIT.

Waku — личный AI-ассистент на локальной машине: харнес, цикл, память и eval

Waku — это local-first персональный AI-ассистент, который можно запустить на своём ноутбуке и прочитать как код. Проект ShenSeanChen намеренно показывает четыре столпа любого серьёзного агента: харнес, цикл, память и eval/LLM-Ops — без фреймворков, скрывающих «хорошие части». Это не продукт, которым вы пользуетесь, а кодовая база, которой владеете: цикл, схема памяти, гейт и eval-харнесс — всё можно читать и менять.

Что это такое

Waku — это ассистент, где память — главный герой: семантическая + эпизодическая + процедурная, с гейтом, который решает, запоминать ли вообще, и проходом, решающим, что именно сохранить. Цикл агента занимает всего ~95 строк чистого Python (без LangGraph и скрытого control flow), а вся память хранится в одном SQLite-файле .waku/state.db, который всегда можно открыть и прочитать.

Ключевые идеи:

  • Local-first — память в одном SQLite-файле, ничего не уходит с ноутбука
  • Читаемый цикл — Reason → Act → Repeat, ~95 строк
  • Встроенный дашборд — браузерный «кокпит», подсвечивающий каждое сообщение по мере прохождения через харнес
  • Eval с гейтом релиза — детерминированные тесты и LLM-as-judge бок о бок

Быстрый старт

Быстрее всего — установить пакет:

pip install waku-agent
waku                                    # чат в терминале
waku dashboard                          # браузерный кокпит → localhost:7777

Если хотите читать код и вносить вклад — клонируйте репозиторий:

git clone https://github.com/ShenSeanChen/waku-agent && cd waku-agent
uv venv && uv pip install -e .
cp .env.example .env                    # выберите провайдера, вставьте ОДИН ключ
uv run waku                             # чат в терминале
uv run waku dashboard                   # браузерный кокпит

waku и waku dashboard — две двери в одного и того же Waku. Дашборд — это крошечный веб-сервер на вашей машине: браузер лишь UI, а тот же процесс исполняет каждый ход. Поддерживаются Anthropic (по умолчанию), OpenAI, Gemini, DeepSeek, MiniMax, Kimi, GLM, OpenRouter, OpenCode Zen и OpenCode Go — настраивается переменной WAKU_PROVIDER=.

Дашборд — как наблюдать за харнесом

waku dashboard          # локальный сервер → http://localhost:7777

Крошечный веб-сервер, которым вы владеете (127.0.0.1, без облака). Каждая вкладка — один из столпов, привязанный к реальным файлам:

Вкладка Что показывает
Overview стоимость, задержка, split «skip/retrieve» у гейта, кликабельная карта архитектуры
Gateway один разговор через все каналы, каждое сообщение помечено источником (dashboard / telegram / voice / cli)
Loop каждый ход с решением гейта, вызовами инструментов, токенами и стоимостью
Graph графовые воркфлоу: живая топология триажа + какая дверь вела к каждому ходу
Memory под-вкладки по столпам — семантические факты, эпизоды, редактируемые навыки + SOUL
Tools доступные инструменты (по происхождению), результаты и MCP-коннекторы
Data живой SQLite-браузер: таблицы, схема и read-only SQL-консоль над state.db
Ops вердикт eval + история, решения гейта, самые медленные ходы, инлайн JSONL-трейсы

Цикл — Reason → Act → Repeat

Настоящий агентный цикл, ~95 строк, без скрытого control flow:

while not done:
    response = llm(messages, tools)      # reason
    if response wants tools:
        results = run(tool_calls)        # act
        messages += results              # observe
    else:
        done                             # reply to the human

Два guardrail'а завершают каждый ход: модель сама перестаёт запрашивать инструменты (естественный конец) или достигает max_iterations (жёсткий стоп — цикл никогда не крутится вечно). Это и есть «loop engineering»: условия выхода, round-trip инструментов и возврат результатов в рабочую память.

Multi-tool loop («денежный кадр»). Один инструмент — это цикл; связывание инструментов — вот где loop engineering проявляется. Например, «Найди оставшиеся матчи ЧМ и добавь каждый в календарь»: агент проходит несколько итераций в одном ходе — search_web × N → create_event × N (на демо до 8 итераций).

Графовые воркфлоу — когда ходу нужна форма

Цикл покрывает чат, но у некоторых задач есть форма: шаги, которые могут идти параллельно, и явная маршрутизация «если это, то туда». Графовый воркфлоу делает эту форму первоклассной. Это расширение столпа Loop, а не замена: loop/agent.py не меняется ни строкой — граф выстраивает вызовы вокруг него. Весь движок — один читаемый файл.

Поставляемый пример — триаж. С WAKU_GRAPH_WORKFLOWS=1 каждое сообщение сначала входит в граф триажа: маленькая модель классифицирует сообщение, пока параллельно грузится календарь. «Спасибо!» получает быстрый ответ маленькой модели и не будит большую; «назначь заплыв в субботу» маршрутизируется в тот же самый цикл. Любой сбой — классификатор, движок, что угодно — fail-open в обычный цикл, так что флаг может только экономить время и токены.

Два ключевых момента

1. Retrieval-гейт. Большинство агентов обращаются к памяти на каждом ходе. Это медленно и, хуже, нерелевантные воспоминания смещают ответы. Здесь дешёвая модель сначала отвечает на один вопрос: нужна ли этому сообщению память вообще?

you > what's 2+2?
  gate · skip — pure math
you > when am I meeting Alex?
  gate · retrieve — references user's plans

2. Deterministic eval vs LLM-as-judge. «Создал ли он правильное событие календаря?» — это unit-тест, 0 или 1, без судей-моделей (make eval). «Был ли ответ полезным?» — судимая оценка с порогом (make eval-judge). Смешивать эти два понятия — самая частая ошибка в eval; здесь это отдельные сьюты, которые можно диффать. make gate запускает оба как гейт релиза.

Eval, трейсинг и ловля багов

Три команды, два вида eval — это LLM-Ops половина системы:

make eval          # deterministic: «сработал ли нужный инструмент?» — 0 или 1
make eval-judge    # LLM-as-judge: «был ли ответ полезным?» — оценка в %, нужен ключ
make gate          # release-гейт: deterministic должен пройти 100%, judge — порог

Детерминированные тесты — это обычный pytest в evals/deterministic/; судимые используют DeepEval в evals/judge/. Держать их раздельно — и есть смысл.

Воркфлоу ловли багов: когда вы ловите баг при живом использовании, вы чините его и добавляете детерминированный кейс, чтобы он не вернулся. Реальный пример из репозитория: агент не знал текущее время и спрашивал о нём перед планированием «через 30 минут» → исправлено в session.py, навсегда закреплено тестом test_working_memory.py.

Расход постоянен: каждый вызов LLM дописывает токены в .waku/usage.jsonl — append-only журнал, который не стирается демо-сбросом. Трейсинг всегда включён: каждый ход дописывает читаемые строки в .waku/traces/<date>.jsonl (ноль настройки) — трейс это просто «что произошло, по порядку».

Управление памятью и навыки

У агента есть инструменты, чтобы поддерживать себя полезным, без чёрного ящика:

  • manage_memory — исправить или забыть факт, когда вы говорите, что он неверен
  • update_soul — сохранить постоянное предпочтение (живёт в SOUL.md)
  • create_skill — когда вы учите его повторяемому воркфлоу, он предлагает сохранить его как навык (в .waku/skills/)

Навыки — это процедурная память: markdown-инструкции, загружаемые только при релевантности.

python -m waku skill install https://github.com/<someone>/<repo>/blob/main/skills/<skill>/SKILL.md

Внести свой навык — это просто markdown-файл: скопируйте skills/TEMPLATE.md, отправьте PR в skills/community/. CI валидирует frontmatter.

Upgrade paths и подключение

Память локальна по умолчанию. Когда вы перерастаете дефолты:

Дефолт (ноль настройки) Апгрейд Как
SQLite FTS5 keyword memory Supabase pgvector semantic search WAKU_SEMANTIC_STORE=supabase
Mock-календарь (ICS + SQLite) Apple / Google Calendar WAKU_APPLE_CALENDAR=1 или WAKU_GOOGLE_CALENDAR=1
Hand-built memory pillars mem0 / Zep / LangMem pip install -e '.[arena]' — и гоняйте их друг против друга

Голос, Telegram, Apple Calendar и Mail, Google Calendar, MCP-серверы — каждый подключается опционально, за своим extra, и ни один не меняет цикл. Памятью можно делиться между агентами через удалённый MCP-сервер — тогда она становится просто ещё одним набором инструментов.

Итог

Waku — поучительный пример для тех, кто хочет понять, как устроены серьёзные агенты изнутри: читаемый цикл, продуманная схема памяти с гейтом, встроенный дашборд и eval-дисциплина с release-гейтом. В отличие от крупных опенсорсных ассистентов (OpenClaw, Hermes) с той же архитектурой — это 1/100 кода: не «продукт», а читаемый чертёж. Код распространяется по лицензии MIT.

Источник: https://github.com/ShenSeanChen/waku-agent