Just A While Loop (JAWL): асинхронный Python-фреймворк для событийно-управляемых ИИ-агентов с гибридным RAG

от автора

В марте я уже выкладывал материал про свой ранний проект — AAF (Autonomous Agent Framework). Это был базовый эксперимент, на котором я обкатывал концепции локального контекста и вызова инструментов. За прошедшие месяцы я переписал архитектуру с нуля, учтя накопленный опыт, и сформировал новый фреймворк — JAWL (Just A While Loop).

Если посмотреть на актуальный ландшафт опенсорсных автономных агентов, выделяются два интересных проекта: OpenClaw (созданный Питером Штайнбергером) и Hermes Agent (от Nous Research).

  • OpenClaw — это готовое локальное приложение-ассистент на Node.js. Оно выступает гейтвеем между мессенджерами и вашим ПК, закрывая вопросы привязки сессий (Pairing) и авторизации пользователей. Но это монолитный пользовательский софт, который, к тому же, часто страдает от проблем с безопасностью.

  • Hermes Agent — продвинутый ассистент на Python с механизмом самообучения и формированием профиля пользователя. Однако его память базируется преимущественно на логах сессий и плоском векторном поиске, без жесткой реляционной структуры и графов знаний.

JAWL решает задачу с точки зрения системной инженерии на Python. Это полностью асинхронный фреймворк, работающий через механизм Event-Driven ReAct Loop, предоставляющий агенту гибридную векторно-графовую память Vector-Graph RAG, дедукцию по теореме Байеса, делегирование задач фоновым воркерам и транзакционную самомодификацию кода с автоматическим откатом.


🏗 Архитектура слоев (L0 — L3)

Система спроектирована с четким разграничением ответственности по слоям (SOLID):

┌─────────────────────────────────────────────────────────────┐│ 🔴 L3: AGENT CORE (ReAct Loop, Heartbeat, LLM Executor)     │└──────────────────────────────┬──────────────────────────────┘                               │ (вызовы инструментов + сбор контекста)┌──────────────────────────────▼──────────────────────────────┐│ 🟠 L2: INTERFACES (Host OS, Telegram, GitHub, Web, Voice)   │└──────────────────────────────┬──────────────────────────────┘                               │ (пассивные хранилища данных)┌──────────────────────────────▼──────────────────────────────┐│ 🟢 L0: STATE LAYER (Пассивные кэши интерфейсов)             │└──────────────────────────────┬──────────────────────────────┘                               │ (Долгосрочная память)┌──────────────────────────────▼──────────────────────────────┐│ 🟡 L1: DATABASES (SQLite + Qdrant + KuzuDB)                 │└─────────────────────────────────────────────────────────────┘
  1. L0 State: Пассивный кэш данных. Они хранят “снимки состояния” интерфейсов (сообщения, метрики хост-ПК, открытые файлы) в памяти. Задумка L0: стейты не выполняют I/O операций при каждой сборке промпта, что устраняет задержки на сетевые запросы при каждом запросе к модели.

  2. L1 Databases Layer: Гибридная память.

    • SQL DB (SQLite): Матрица Эйзенхауэра для рабочих задач, гипотезы Байеса, встроенные математические мотиваторы, заметки и CRM субъектов.

    • Vector DB (Qdrant + FastEmbed): Семантическая память: Knowledge (знания) и Thoughts (фоновая рефлексия).

    • Graph DB (KuzuDB): Граф знаний и граф навигации по кодовой базе (Code Graph).

  3. L2 Interfaces Layer: Изолированные плагины взаимодействия с внешним миром (Host OS, Telegram, GitHub, Email, Web, Voice, Calendar и др.). Собираются динамически через паттерн Plugin Discovery.

  4. L3 Agent Layer: Вся высокоуровневая логика (ReactLoop, Heartbeat, LLMExecutor с ротацией ключей, SwarmManager, SubconsciousOrchestrator).


⚡ Событийно-управляемый Heartbeat

Агент в JAWL, как и во многих других фреймворках, крутится в ReAct цикле. Для того, чтобы уменьшить затраты на запросы к модели, я написал локальный Event Bus, который менеджерит просыпания модели. Все L2-интерфейсы подключены к единой асинхронной шине событий (EventBus). Каждое входящее событие имеет свой уровень приоритета (от BACKGROUND и LOW до CRITICAL). Heartbeat на лету умножает оставшееся время сна на коэффициент ускорения:

\text{Time}_{\text{remaining}} = \text{Time}_{\text{remaining}} \times \text{Multiplier}_{\text{event}}

Если поступает событие уровня CRITICAL (например, прямое сообщение от пользователя или вебхук), коэффициент равен 0.0 — остаток времени мгновенно обнуляется, и агент просыпается без задержки.

    [Сон Heartbeat: 600 сек]                  │    Входящий ивент (MEDIUM, x0.6)                  │    [Сон сокращен: 360 сек]                  │    Входящий ивент (CRITICAL, x0.0)                  │        ⚡ МОМЕНТАЛЬНЫЙ ВЫЗОВ

🧠 Гибридный Vector-Graph RAG

Векторный поиск отлично ищет похожие по смыслу фрагменты текста, но не способен отслеживать жесткие логические цепочки. Графовая база (KuzuDB) хранит причинно-следственные связи и структуры.

В JAWL реализован гибридный поиск:

  1. Из текста вызова извлекаются ключевые сущности.

  2. Параллельно запрашиваются векторы из Qdrant и связанные узлы из KuzuDB.

  3. Кросс-резолвинг: Найденный векторный факт проверяет наличие своих узлов в графе и подтягивает их соседние связи. И наоборот — текстовые описания найденных графовых узлов отправляются в новый векторный поиск.

Для корректной работы с русским языком и падежами в систему встроен поиск сущностей на базе RapidFuzz и алгоритма Ахо-Корасик FlashText.


🐝 Оркестрация субагентами

Выполнять тяжелые и рутинные задачи (парсинг 20 сайтов, глубокий рефакторинг кода) именно основной, дорогой моделью давно стало признаком плохого распределения токенов. В JAWL, как и в некоторых других агентских фреймворках главный агент выступает в роли оркестратора. Он формулирует ТЗ и спавнит фоновых субагентов со специализированными ролями (CODER, WEB_RESEARCHER, ARCHIVIST, QA_ENGINEER, SYSADMIN):

       ┌────────────────────────┐       │      Main Agent        │       └───────────┬────────────┘                   │ Делегирование рутины         ┌─────────┼─────────┐         ▼         ▼         ▼         │     Subagents     │         │         │         │         └─────────┴─────────┘                   │ Отформатированные Markdown репорты                   ▼       [SUBAGENT_TASK_COMPLETED] -> Пробуждение главного агента

Субагенты работают в изолированных Stateless ReAct циклах на более дешевых и быстрых моделях (например, Gemini 3.5 Flash Lite). Они не имеют доступа к основной памяти и возвращают результат в виде итогового Markdown-отчета. Нагрузка на API регулируется через asyncio.Semaphore.


🛡 Безопасная самомодификация

При наличии уровня доступа OPERATOR агент имеет право модифицировать исходный код самого фреймворка. Чтобы защитить систему от падения в незапускаемое состояние, внедрен механизм под названием Deploy Sessions:

  1. Перед правками агент открывает деплой-сессию.

  2. Система создает транзакционный Copy-on-Write бэкап всех изменяемых файлов и ведут манифест новых файлов.

  3. При коммите сессии принудительно запускаются синтаксический анализатор compileall и набор тестов pytest.

  4. Если тесты провалены, агент получает Traceback ошибки и списывает одну попытку.

  5. Если лимит попыток исчерпан, система автоматически выполняет Rollback — восстанавливает исходные файлы из бэкапа и удаляет части нерабочего кода.


🚀 Заключение

Фреймворк распространяется под лицензией MIT. Исходный код, архитектурные схемы и документация доступны на GitHub:

👉 GitHub: https://github.com/th0r3nt/JAWL

Буду рад отзывам и пулл-реквестам в репозитории.

ссылка на оригинал статьи https://habr.com/ru/articles/1079990/