16 KiB
DuckLM — Текущее состояние проекта
1. Что это
DuckLM — локальный event-driven multi-model AI agent runtime. Система принимает пользовательскую задачу, извлекает релевантную память, собирает контекст, принимает orchestration-решение, при необходимости строит план, исполняет шаги через tools и coder, оценивает результаты через critic, сохраняет полезное в долговременную память, публикует события и поддерживает streaming клиенту.
Ключевой принцип: центр системы — RuntimeLoop. Все execution transitions проходят через него. Router, Orchestrator, ExecutionEngine — decision-producing компоненты, которые только возвращают структурированные объекты (ExecutionDirective), но не исполняют действия напрямую.
2. Архитектура
Client / CLI / API
│
▼
RuntimeLoop (runtime_loop.py)
│
├── State Store / Checkpoints (SQLite)
├── ContextBuilder
├── AsyncRouter (Thinker → JSON Compiler)
├── ExecutionEngine / ExecutionScheduler
│ ├── ToolRegistry / ToolSandbox
│ ├── CoderAdapter
│ └── CriticAdapter
├── PermissionService
├── MemoryRecallService
├── MemoryWritePolicy
├── MemoryInterface (SQLite + hnswlib)
└── EventBus → SQLiteEventStore
│
▼
StreamingManager → WebSocket
3. Структура проекта
ducklm/
main.py # Точка входа (импорт app.api.server.app)
app/
api/
server.py # FastAPI: POST /chat, WS /stream, GET /health, etc.
static/index.html # Веб-чат (dark theme, Enter=отправить, Shift+Enter=новая строка)
cli/__init__.py # Пока пустой
core/
contracts.py # Pydantic модели: UserTask, PlanStep, ToolResult, CriticScore, ...
config.py # AppConfig, load_app_config()
async_router.py # AsyncRouter: Thinker + JSON Compiler pipeline
context_builder.py # ContextBuilder: сборка контекста с бюджетами
execution_engine.py # ExecutionEngine: исполнение plan/tool/respond/coder
execution_scheduler.py # ExecutionScheduler: парсинг плана, граф задач, цикл выполнения
intent_parser.py # IntentParser: извлечение tool intents из текста
permission_service.py # PermissionService: проверка и разрешений команд
permission_resolution.py # Pydantic модели для API разрешений
events/
event_bus.py # EventBus: per-task ordered publishing
event_store.py # SQLiteEventStore: append-only log
event_types.py # Константы типов событий
memory/
interface.py # MemoryInterface: insert/search/get/delete/reindex/cleanup
store.py # MemoryStore: SQLite хранение MemoryEntry + embeddings
vector_index.py # VectorIndex: hnswlib L2 index
recall.py # MemoryRecallService: LLM-based решение о необходимости recall
write_policy.py # MemoryWritePolicy: детерминированное решение о записи
models/
adapters.py # create_adapter/create_llama_adapter (llama-cpp-python)
async_adapters.py # AsyncOrchestratorAdapter, AsyncCoderAdapter, AsyncCriticAdapter
orchestrator.py # OrchestratorAdapter: обёртка над Llama
coder.py # CoderAdapter
critic.py # CriticAdapter
embeddings.py # EmbeddingsAdapter (sentence-transformers)
permissions/
approval_store.py # SQLiteApprovalStore
runtime/
runtime_loop.py # RuntimeLoop: центральный цикл (sync)
async_runtime_loop.py # AsyncRuntimeLoop: альтернативная async версия
runtime_controller.py # RuntimeController: composition root, инициализация всего
services/__init__.py # Пустой
state/
task_state_store.py # SQLiteTaskStateStore
checkpoint_store.py # SQLiteCheckpointStore
streaming/
manager.py # StreamingManager: подписка на события → WebSocket
tools/
base.py, registry.py, sandbox.py, discover.py
shell_exec.py, file_read.py, file_write.py, memory_tools.py
plugins/ # Plugin discovery: shell_exec, file_read, file_write, memory_tools
config/
models.json # Конфигурация моделей
runtime.json # Таймауты, retry limits, context budgets
permissions.json # Категории команд, пути
prompts/ # Markdown промпты для каждой роли
thinker.md, json_compiler.md, coder.md, critic.md, sys_util.md, orchestrator.md, planning.md, system.md
data/
events/events.sqlite3 # Event store
state/task_state.sqlite3 # Task state
state/checkpoints.sqlite3 # Checkpoints
permissions/approvals.sqlite3 # Permission cache
memory/memory.sqlite3 # Memory store
memory/index.bin # Vector index
models/ # GGUF модели и sentence-transformers
tests/
test_contracts.py # 6 тестов: контракты, router
test_runtime_loop.py # 2 теста: runtime loop events, permission flow
test_tools_flow.py # 7 тестов: file read/write, shell, recovery, permissions
test_api_handlers.py # 6 тестов: health, events, chat, permissions, feedback
4. Модели и их роли
| Роль | Модель | Backend | Конфиг |
|---|---|---|---|
| Thinker (orchestrator) | Qwen3.5-9B-GLM5.1-Distill-v1-Q4_K_M.gguf | vulkan (GPU) | max_tokens=2048, temp=0.3 |
| JSON Compiler | gemma-4-E4B-it-Q4_K_M.gguf | cpu | max_tokens=1024, temp=0.1 |
| Critic | gemma-4-E4B-it-Q4_K_M.gguf (shared с compiler) | cpu | max_tokens=1024, temp=0.1 |
| Coder | X-Coder-SFT-Qwen3-8B.Q6_K.gguf | cpu | max_tokens=2048, temp=0.2 |
| Sys Utility | Menlo_Lucy-Q4_K_M.gguf | cpu | max_tokens=1024, temp=0.1 |
| Embeddings | all-MiniLM-L6-v2 (sentence-transformers) | — | dim=384 |
Важно: Critic и JSON Compiler используют одну и ту же модель (gemma-4B), но разные экземпляры адаптеров. Модели не дублируются в памяти — используется кэширование через _get_or_create_llm() с ключом (path, backend, n_gpu_layers, n_ctx).
5. Конфигурация
Все настройки в config/:
- models.json — пути к GGUF файлам, backend, GPU layers, max_tokens, temperature
- runtime.json — таймауты (step=30s, task=5min), retry limits, context budgets, retrieval_top_k
- permissions.json — hard_stop команды (rm -rf /, dd, mkfs), no_always команды (shutdown, killall), normal команды
- prompts/*.md — системные промпты для каждой роли модели
6. API
FastAPI сервер на порту 8000 (scripts/server.sh):
| Метод | Путь | Описание |
|---|---|---|
| GET | / |
Веб-чат (index.html) |
| GET | /health |
Health check |
| GET | /events |
Список последних событий |
| POST | /chat |
Отправить задачу (UserTask) → получить результат |
| POST | /permissions/resolve |
Разрешить/запретить команду |
| POST | /secrets/resolve |
Передать sudo-пароль |
| POST | /password/resolve |
Передать пароль (альтернативный путь) |
| POST | /critic/feedback |
Обратная связь от пользователя |
| WS | /stream/{task_id} |
Streaming событий по задаче |
7. Поток выполнения задачи
- Клиент → POST /chat →
RuntimeController.handle_task() RuntimeLoop.run_task():- Проверка hard-stop команд через PermissionService
- Создание task state в SQLiteTaskStateStore
- Публикация TASK_RECEIVED
- Checkpoint: received
- ContextBuilder.build() — сборка контекста (memory, tools, budgets)
- MemoryRecallService.recall() — LLM решает, нужно ли искать в памяти
- AsyncRouter.decide() — Thinker → JSON Compiler → ExecutionDirective
- ExecutionEngine.execute() — исполнение directive:
- plan → парсинг шагов → граф → последовательное выполнение
- tool → проверка разрешений → ToolSandbox → ToolResult
- respond → прямой ответ
- coder → CoderAdapter
- Critic оценка каждого шага (correctness, usefulness, safety)
- Recovery при неудачных шагах (retry/continue/respond/fail)
- MemoryWritePolicy — решение о записи в долговременную память
- Checkpoint: final state
- Публикация TASK_COMPLETED / TASK_FAILED / TASK_AWAITING_PERMISSION
- Результат возвращается клиенту + события доступны через WebSocket
8. Что реализовано и работает
Core (полностью)
- Модульная структура проекта (app/, config/, data/, tests/)
- Typed contracts (Pydantic модели для всех сущностей)
- RuntimeLoop — центральный цикл
- RuntimeController — composition root
- EventBus + SQLiteEventStore (append-only, per-task ordering)
- TaskStateStore + CheckpointStore (SQLite)
- ContextBuilder с token budgets
- AsyncRouter: Thinker → JSON Compiler pipeline с retry и JSON fix
- IntentParser: извлечение tool intents из естественного языка
- ExecutionEngine: plan/tool/respond/coder/fail
- ExecutionScheduler: парсинг плана, DAG граф, cycle detection
- PermissionService: hard_stop/no_always/normal категории, кэш разрешений
- ToolSandbox: timeout, cwd restrictions
- ToolRegistry + Plugin Discovery
- Tools: shell_exec, file_read, file_write, memory_insert/search/list
- CriticAdapter с retry и recovery (continue/retry/respond/fail)
- MemoryInterface: SQLite + hnswlib vector index
- MemoryRecallService: LLM-based решение о необходимости recall
- MemoryWritePolicy: детерминированное решение о записи
- EmbeddingsAdapter (sentence-transformers)
- FastAPI API: /chat, /health, /events, /permissions/resolve, /secrets/resolve, /critic/feedback
- WebSocket streaming (/stream/{task_id})
- Веб-чат (dark theme, Enter=отправить, Shift+Enter=новая строка, панель событий, permission controls, feedback dialog)
- 21 тест (все проходят)
Известные баги (исправлены)
- RECALL_PROMPT_TEMPLATE format string escaping — фигурные скобки в JSON примерах нужно двоить
- VectorIndex._get_memory_id возвращал неправильный ID (hash вместо хранения mapping)
- recall_model по умолчанию был sys_util, изменён на json_compiler
9. Что ещё нужно сделать
Приоритет 1 — Доработка до полного MVP
- Resume из checkpoint — после падения/перезапуска восстанавливать задачу из последнего checkpoint
- CLI интерфейс — отправка задач, просмотр событий, поиск в памяти из терминала (app/cli/ пока пустой)
- Structured logging — вместо print() использовать logging с форматированием
- WS /stream — доработать (сейчас базово работает, но нет подписки на новые события в реальном времени при длительных задачах)
Приоритет 2 — Улучшения
- Retry/recovery policy — более надёжная обработка ошибок tool execution
- Replay из event store — воспроизведение истории задачи для отладки
- Параллельное выполнение шагов — сейчас только sequential DAG, можно добавить parallel для независимых шагов
- Веб-чат: отображение streaming ответа — сейчас ответ приходит целиком, можно добавить потоковую передачу
- Веб-чат: отображение tool output — более красивый рендер результатов shell/file операций
- Memory cleanup — автоматическая очистка старых/низко-весовых записей (базовая логика есть в MemoryInterface.cleanup, но не вызывается автоматически)
Приоритет 3 — Расширения
- web_search / web_fetch tools — второй приоритет по TASK_3.md
- Telegram bot stub — thin клиент для удалённого управления
- Coder integration в план — пока coder adapter есть, но не интегрирован в планирование как отдельный step kind
- Модели: загрузка при старте — load_models_at_startup() вызывается из lifespan, но если модели не загружены, runtime работает в fallback mode (respond only)
- Документация API — OpenAPI схема генерируется FastAPI, но можно добавить примеры
10. Запуск
cd ~/git/ducklm
./scripts/server.sh
# или
uvicorn main:app --host 0.0.0.0 --port 8000
Веб-чат: http://localhost:8000/
11. Тестирование
cd ~/git/ducklm
python -m pytest tests/ -v
21 тест, все проходят. Покрытие: контракты, runtime loop, tool flow, API handlers.
12. Технологии
- Python 3.13, FastAPI, uvicorn, websockets
- llama-cpp-python — локальный инференс GGUF моделей (Vulkan/CPU)
- sentence-transformers — эмбеддинги (all-MiniLM-L6-v2)
- hnswlib — векторный поиск (L2 метрика)
- SQLite — event store, task state, checkpoints, memory, permissions
- Pydantic — все контракты
- pytest — тестирование