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