# DuckLM — Текущее состояние проекта **Дата анализа:** 2026-05-21 **Версия:** 0.2.0 **Расположение:** `~/git/ducklm_2` --- ## Последние изменения (Phase 1-7) ### Phase 1: MemoryPolicy — LLM-классификация памяти ✅ - `duck_core/memory/policy.py` — переписан с нуля: LLM-классификация через critic-роль - Роль `memory_policy` добавлена в `config/models.yaml` + промпт - Интегрирован в RuntimeLoop: `_run_memory_policy()` после каждой задачи - События: `memory_policy_decision`, `memory_stored`, `memory_policy_failed` - 6 тестов в `tests/smoke/test_memory_policy.py` ### Phase 2: Рефлексия (Critic) — автоматический вызов ✅ - `_run_reflection()` в RuntimeLoop — transcript из event store → critic → experience - Параметр `reflect: bool = True` в `run_chat()` - `ExperienceRecorder` передаётся через `create_app()` - События: `reflection_completed`, `reflection_failed` - 3 теста в `tests/smoke/test_reflection.py` ### Phase 3-4: ContextBuilder v2 + Summary-роль ✅ - Полностью переписан `duck_core/context_builder.py` - Token budget awareness, приоритизация, суммаризация через summary-роль - `estimate_tokens()`, `estimate_messages_tokens()` утилиты - Подключён model_client для LLM-суммаризации - 11 тестов в `tests/smoke/test_context_builder.py` ### Phase 5: VectorMemory — интеграция ✅ - `VectorMemory` добавлен в RuntimeLoop и `create_app()` - **Локальная модель эмбеддингов**: `all-MiniLM-L6-v2` (384 размерности, sentence-transformers) - При `memory_stored` также сохраняется в Qdrant (graceful fallback при ошибках) - Поддержка двух режимов: локальная модель + remote `/v1/embeddings` endpoint - `sentence-transformers` добавлен в зависимости `pyproject.toml` - 4 теста в `tests/smoke/test_vector_memory_integration.py` ### Embeddings — архитектура ### Phase 6: Recall-роль ✅ - Роль `recall` добавлена в `config/models.yaml` + промпт `prompts/roles/recall.md` - `ContextBuilder.recall_relevant_memory()` — LLM-фильтрация релевантных воспоминаний - Интегрирован в `/v1/chat` endpoint ### Phase 7: Coder-роль — интеграция ✅ - `CoderTool` создан в `duck_core/tools/coder.py` - Зарегистрирован в `ToolGateway.default()` - Описан в `prompts/roles/action.md` ### Статус тестов - **72 из 73** smoke-тестов проходят - 1 тест (`test_llama_server_connection_live_skip_by_env`) требует живой llama-server --- ## 1. Что такое DuckLM DuckLM — это **локальная агентная система (cognitive runtime)**, работающая поверх локальных языковых моделей через `llama-server`. Это не inference-сервер, а полноценный когнитивный цикл: ``` состояние → контекст → мышление → намерение → действие → наблюдение → рефлексия → память → опыт ``` Ключевая идея: DuckLM — это **оркестратор**, который управляет задачами, инструментами, памятью, навыками и рефлексией, используя локальные LLM через OpenAI-совместимый API (`llama-server`). --- ## 2. Архитектурные принципы ### 2.1. Использование готовых компонентов | Компонент | Источник | |-----------|----------| | LLM inference | `llama-server` (llama.cpp, собранный с Vulkan) | | Хранение состояния | SQLite (aiosqlite) | | Векторная память | Qdrant (через docker-compose) | | HTTP API | FastAPI | | Web-интерфейс | Jinja2 + ванильный JS | | Валидация данных | Pydantic | | Конфигурация | PyYAML + python-dotenv | **Не пишется с нуля:** inference server, model scheduler, vector DB, OpenAI API, MCP, песочница, workflow engine. **Пишется с нуля:** Duck Core (runtime loop, context builder, model client, event store, tool gateway, approvals, skills, experience, memory policy, FastAPI API, WebChat). ### 2.2. Web/API first - **WebChat** — интерфейс для человека (порт 8000) - **HTTP API** — для кодера, тестов и внешних агентов - CLI не входит в обязательную часть (если понадобится — тонкий клиент поверх HTTP API) ### 2.3. Роли моделей — логические, не физические Роли: `thinker`, `critic`, `coder`, `action`, `summary`, `recall`, `sys_util`. Все роли в текущей конфигурации указывают на одну физическую модель (`local-main` на порту 8081). Различие между ролями задаётся комбинацией: - system prompt - temperature - max_output_tokens - response_format / structured_output - memory scope - tool permissions ### 2.4. Token budget ``` DUCK_CTX_SIZE=4096 (в .env, хотя в коде дефолт 65536) DUCK_MAX_INPUT_TOKENS=49152 DUCK_MAX_RECENT_EVENTS_TOKENS=12000 DUCK_MAX_MEMORY_TOKENS=8000 DUCK_MAX_SKILL_TOKENS=6000 ``` Output limits по ролям: - thinker: 8192 - critic: 4096 - coder: 16384 - action: 2048 - summary: 4096 --- ## 3. Целевая архитектура (из ТЗ) ``` ┌─────────────┐ │ WebChat │ ← интерфейс человека └──────┬──────┘ │ ▼ ┌─────────────┐ │ FastAPI │ ← интерфейс кодера, тестов и агентов └──────┬──────┘ │ ▼ ┌─────────────────────────────────────┐ │ Duck Core │ │ RuntimeLoop, TaskState, │ │ ContextBuilder, ModelClient, │ │ SkillRegistry, ToolGateway, │ │ ApprovalService, Reflection, │ │ MemoryPolicy, ExperienceRecorder │ └──────┬──────────────┬───────────────┘ │ │ ▼ ▼ ┌────────────┐ ┌──────────────────┐ │llama-server│ │ SQLite/PostgreSQL│ │OpenAI-comp.│ │ events/tasks/ │ └────────────┘ │ approvals │ │ └──────────────────┘ ▼ ┌────────────┐ │ Qdrant │ ← semantic memory └────────────┘ ``` --- ## 4. Что реализовано (текущее состояние) ### 4.1. Полностью реализованные компоненты #### Конфигурация и настройки - **`duck_core/config.py`** — `Settings` dataclass, загрузка из `.env`, кэширование через `lru_cache` - **`config/models.yaml`** — 5 ролей (thinker, critic, coder, action, summary), все на `local-main` (порт 8081) - **`.env`** / **`.env.example`** — полная конфигурация путей, портов, GPU, Qdrant #### ModelClient - **`duck_core/model_client.py`** — ролевая маршрутизация вызовов к llama-server - `chat()` — синхронный вызов с измерением latency, usage - `stream_chat()` — streaming через SSE (reasoning_delta + content_delta) - `ping()` — проверка доступности всех ролей - Автоматическая подстановка system prompt из файла - Автоматический `response_format: json_schema` для action-роли #### Хранение состояния (SQLite) - **`duck_core/tasks/state.py`** — `TaskState` (Pydantic модель) - **`duck_core/tasks/store.py`** — `TaskStore`: create, update_status, complete, fail, cancel, waiting_for_approval, get, list - **`duck_core/events/store.py`** — `EventStore`: append (с авто-increment sequence), list_events, list_by_type - **`duck_core/conversations/store.py`** — `ConversationStore`: create, ensure, get, list, add_message, list_messages, get_conversation_id_for_task - **`duck_core/approvals/service.py`** — `ApprovalService`: create_pending, pending, get, allow_once, allow_forever, deny, is_allowed_forever - **`duck_core/experience/recorder.py`** — `ExperienceRecorder`: record, list_records, get_record, write_skill_update_proposal - **`duck_core/memory/store.py`** — `MemoryStore`: add, list, search (LIKE), relevant (scope-aware), infer_scope, _normalize_scope #### Runtime Loop - **`duck_core/runtime_loop.py`** — ядро когнитивного цикла: - `run_chat()` — полный цикл: создание задачи → action loop → thinker → завершение - `continue_after_approval()` — продолжение после одобрения действия - `_run_action_loop()` — итеративный цикл вызова инструментов (max 4 итерации) - `_run_action_tools()` — парсинг action directive от модели → вызов ToolGateway - `_append_command_audit()` — аудит shell-команд через event store - Обработка requires_approval → пауза с ожиданием решения пользователя #### Context Builder - **`duck_core/context_builder.py`** — `ContextBuilder.build_basic_messages()`: собирает сообщения из memory records, history и текущего user message #### Tools - **`duck_core/tools/base.py`** — `ToolResult` (Pydantic), `Tool` (Protocol) - **`duck_core/tools/gateway.py`** — `ToolGateway`: маршрутизация action → конкретный инструмент - **`duck_core/tools/file_read.py`** — `FileReadTool`: чтение файлов внутри workspace, запрет .env/.ssh/shadow - **`duck_core/tools/file_write.py`** — `FileWriteTool`: запись внутри workspace, защита от перезаписи - **`duck_core/tools/list_dir.py`** — `ListDirTool`: листинг директории внутри workspace - **`duck_core/tools/search_files.py`** — `SearchFilesTool`: текстовый поиск по файлам (glob, case_sensitive) - **`duck_core/tools/shell_exec_safe.py`** — `ShellExecSafeTool`: allowlist + blocklist + approval - **`duck_core/tools/command_policy.py`** — `CommandPolicy`: классификация команд (readonly, system, destructive, dangerous fragments) - **`duck_core/tools/paths.py`** — `resolve_workspace_path()`: защита от path traversal #### Approvals - **`duck_core/approvals/service.py`** — полный цикл согласований: - Создание pending approval с SHA256-хешем действия - Решения: allow_once, allow_forever, deny - Проверка is_allowed_forever по хешу действия - normalized_action хранится в JSON #### Skills - **`duck_core/skills/registry.py`** — `SkillRegistry`: загрузка из `*/skill.yaml`, парсинг procedure/examples/notes, поиск по ключевым словам - **`skills/analyze_project/`** — единственный скилл: анализ структуры проекта #### Experience & Reflection - **`duck_core/experience/recorder.py`** — запись результатов задач, предложения по обновлению скиллов - **`duck_core/reflection.py`** — `Reflection.reflect()`: вызов critic-роли для анализа транскрипта задачи #### Memory - **`duck_core/memory/store.py`** — `MemoryStore`: хранение в SQLite с поддержкой scope (global/workspace/conversation), importance, полнотекстовый поиск (LIKE) - **`duck_core/memory/policy.py`** — `MemoryPolicy`: заглушка (всегда should_store=False) - **`duck_core/memory/vector_memory.py`** — `VectorMemory`: интеграция с Qdrant для семантического поиска (требует embeddings endpoint) #### FastAPI API - **`duck_core/api.py`** — полный HTTP API (878 строк): - `POST /v1/chat` — основной чат с сохранением в conversation - `POST /v1/chat/stream` — streaming чат через SSE - `POST /v1/tasks/{task_id}/continue/stream` — продолжение после одобрения - `POST /v1/tasks/{task_id}/password/stream` — ввод sudo-пароля - `GET/POST /v1/conversations` — управление диалогами - `GET /v1/tasks`, `GET /v1/tasks/{task_id}/events` — инспекция задач - `GET /v1/approvals/pending`, `POST /v1/approvals/{id}/allow_once|allow_forever|deny` - `GET /v1/skills`, `GET /v1/skills/{skill_id}` - `GET /v1/experience` - `POST /v1/memory`, `GET /v1/memory`, `GET /v1/memory/search` - `GET /v1/models/roles`, `GET /v1/models/ping` - `GET /health`, `GET /v1/status` - Веб-страницы: `/`, `/approvals`, `/skills`, `/memory`, `/experience` #### WebChat UI - **`duck_core/web/templates/index.html`** — полноценный WebChat с sidebar, conversation list, activity drawer - **`duck_core/web/static/app.js`** (997 строк) — клиентская логика: - SSE streaming с парсингом reasoning_delta, content_delta, tool_call_started/finished, tool_approval_requested, tool_password_requested - Инлайн-терминалы для отображения вызовов инструментов - Инлайн-кнопки одобрения/запрета действий - Форма ввода sudo-пароля - Activity drawer с вкладками Events/Commands/Memory - Управление диалогами (create, select, load history) - Enter для отправки, Shift+Enter для новой строки - **`duck_core/web/static/style.css`** (1002 строки) — светлая тема, responsive layout #### Скрипты - **`scripts/llama/start_main.sh`** — управление llama-server (start/stop/restart/status/logs) - **`scripts/llama/start_thinker_mtp_experimental.sh`** — экспериментальный MTP endpoint - **`scripts/llama/build_vulkan.sh`** — сборка llama.cpp с Vulkan - **`scripts/llama/healthcheck.sh`** — проверка здоровья llama-server - **`scripts/verify/`** — 7 верификационных скриптов (basic_chat, file_write_read, tool_blocking, models_roles, skills, experience, memory) - **`scripts/bench/bench_runtime.py`** — бенчмарк #### Тесты - 18 smoke-тестов в `tests/smoke/`: - `test_models_config.py`, `test_model_client.py`, `test_api_health.py` - `test_event_log.py`, `test_action_directive_schema.py` - `test_tool_gateway.py`, `test_approvals.py` - `test_skill_registry.py`, `test_experience_recorder.py` - `test_vector_memory.py`, `test_memory_store.py` - `test_chat_api.py`, `test_conversations.py` - `test_runtime_reasoning.py`, `test_runtime_tools.py` - `test_llama_server_connection.py`, `test_llama_service_script.py` #### Документация - 12 файлов в `docs/`: - `architecture.md`, `how_to_run.md`, `how_to_test.md` - `web_api.md`, `model_roles.md`, `tool_gateway.md` - `memory_architecture.md`, `experience_learning.md`, `skills.md` - `local_llama_server.md`, `performance_mtp.md` - `superpowers/plans/2026-05-19-ducklm-runtime.md` — план реализации #### Docker - **`docker-compose.memory.yml`** — Qdrant (порты 6333/6334) #### Сборка и запуск - **`pyproject.toml`** — зависимости: fastapi, uvicorn, httpx, pydantic, pyyaml, jinja2, python-dotenv, jsonschema, aiosqlite, qdrant-client - **`Makefile`** — цели: duck-up, duck-llama-main, duck-api, duck-dev, duck-smoke, duck-test, duck-verify - **`data/duck.sqlite3`** — рабочая БД SQLite - **`workspace/`** — рабочая директория для инструментов --- ## 5. План разработки (из `docs/superpowers/plans/2026-05-19-ducklm-runtime.md`) План состоит из 4 задач: ### Task 1: Tests First - Написать smoke tests для всех компонентов - ✅ **Выполнено** — 18 тестов созданы ### Task 2: Runtime Core - pyproject.toml, .env.example, config/models.yaml - config.py, model_client.py, events/store.py, tasks/store.py, tasks/state.py - context_builder.py, runtime_loop.py, api.py - ✅ **Выполнено** — все компоненты реализованы ### Task 3: Stage Adapters - tools/*, approvals/service.py, skills/registry.py - experience/recorder.py, reflection.py, memory/* - schemas/action_directive.schema.json - ✅ **Выполнено** — все компоненты реализованы ### Task 4: Project Surface - scripts/llama/*, scripts/verify/*, scripts/bench/* - web/templates/*, web/static/* - skills/analyze_project/* - docker-compose.memory.yml, Makefile, README.md, docs/* - ✅ **Выполнено** — все компоненты реализованы --- ## 6. Отступления от плана / дополнительные возможности ### 6.1. Что добавлено сверх плана 1. **ConversationStore** — полноценное управление диалогами (conversations + conversation_messages таблицы), чего не было явно в плане Task 2. План упоминал только tasks и events. 2. **Streaming API** — `POST /v1/chat/stream` с SSE, `POST /v1/tasks/{id}/continue/stream`, `POST /v1/tasks/{id}/password/stream`. В плане не было явно указано streaming. 3. **Password flow** — полный цикл запроса sudo-пароля: `requires_password` → `tool_password_requested` → `/v1/tasks/{id}/password/stream`. В плане не было детализировано. 4. **Activity Drawer в WebChat** — боковая панель с вкладками Events/Commands/Memory, инлайн-терминалы для инструментов, инлайн-одобрения. Значительно больше, чем «пустая WebChat-страница» из этапа 1 ТЗ. 5. **Command Audit** — отдельный тип события `command_audit` для shell-команд с полной метаданной (action_type, risk_level, blocked, approved, returncode). 6. **Scope-aware Memory** — трёхуровневая система скоупов (global/workspace/conversation) с автоматическим infer_scope. 7. **Skill update proposals** — автоматическая запредложений по обновлению скиллов в `skills/_proposals/`. 8. **18 тестов вместо 11 запланированных** — добавлены: `test_chat_api`, `test_conversations`, `test_runtime_reasoning`, `test_runtime_tools`, `test_llama_server_connection`, `test_llama_service_script`, `test_memory_store`. ### 6.2. Что не реализовано (или реализовано частично) 1. **MemoryPolicy — заглушка.** `MemoryPolicy.classify()` всегда возвращает `should_store=False`. Нет LLM-классификации для автоматического сохранения памяти. 2. **ContextBuilder — минимальный.** Нет суммаризации старых events, нет обрезки по token budget, нет приоритизации контекста. Просто склеивает memory + history + user message. 3. **Critic не вызывается автоматически.** `Reflection.reflect()` есть, но не интегрирован в RuntimeLoop — нет автоматической рефлексии после завершения задачи. 4. **Summary роль не используется.** Нет автоматической суммаризации контекста при превышении budget. 5. **Coder роль не используется в основном потоке.** RuntimeLoop вызывает только action и thinker. 6. **Recall роль не определена в конфиге.** В ТЗ упоминается recall, но в `config/models.yaml` её нет. 7. **Sys_util роль не определена в конфиге.** Аналогично. 8. **VectorMemory не интегрирован в RuntimeLoop.** Qdrant-поиск не подключён к основному циклу (MemoryStore использует LIKE-поиск, а не векторный). 9. **WebChat — светлая тема.** В памяти пользователя указано предпочтение тёмной темы, но CSS реализован светлый (`color-scheme: light`, белый фон). 10. **Нет CLI.** Упомянуто в ТЗ как необязательное, но если понадобится — нужно делать. 11. **Нет автоматического применения skill patches.** Предложения пишутся в `skills/_proposals/`, но не применяются автоматически. ### 6.3. Технические заметки - **Модель:** Qwen3.6 35B A3B, два варианта — nonMTP (основной, порт 8081) и MTP (экспериментальный, порт 8085) - **GPU:** Radeon RX580, Vulkan backend, 20 GPU layers - **llama-server бинарник:** `./vendor/llama.cpp/build/bin/llama-server` - **ctx_size в .env:** 4096 (хотя в коде Settings дефолт 65536) - **reasoning-budget:** 512 в .env, `--reasoning-budget 512 --cache-ram 0` - **Python:** 3.13 (по путям `__pycache__`) --- ## 7. Структура базы данных (SQLite) Таблицы: - **tasks** — задачи (task_id, status, user_message, workspace, debug, final_response, created_at, updated_at) - **events** — события (id, task_id, sequence, event_type, payload_json, created_at) - **conversations** — диалоги (id, conversation_id, title, workspace, created_at, updated_at) - **conversation_messages** — сообщения диалогов (id, conversation_id, role, content, reasoning_content, task_id, status, created_at) - **approvals** — согласования (id, approval_id, task_id, action_hash, normalized_action_json, status, decision, created_at, updated_at) - **experience_records** — записи опыта (id, task_id, skill_id, summary, result, what_worked_json, what_failed_json, reusable_lesson, suggested_skill_patch, confidence, created_at) - **memories** — память (id, memory_id, text, scope, workspace, conversation_id, memory_type, importance, metadata_json, created_at, updated_at) --- ## 8. Когнитивный цикл (как работает RuntimeLoop) 1. Пользователь отправляет сообщение → `POST /v1/chat` или `/v1/chat/stream` 2. Создаётся Task + событие `task_created` 3. ContextBuilder собирает сообщения (memory + history + user message) 4. **Action loop** (до 4 итераций): - Модель `action` генерирует JSON directive (schema: action_directive.schema.json) - ToolGateway выполняет каждый action через соответствующий инструмент - Если команда требует approval → пауза, создание Approval, ожидание решения - Если sudo → запрос пароля - Результаты собираются как tool_observations 5. Thinker получает все tool_observations и формирует финальный ответ 6. Задача завершена → `task_completed` 7. (Опционально) Reflection через critic — **не автоматизировано** --- ## 9. Статус готовности | Компонент | Статус | |-----------|--------| | Конфигурация | ✅ Готово | | ModelClient | ✅ Готово | | TaskStore / EventStore | ✅ Готово | | ConversationStore | ✅ Готово | | RuntimeLoop | ✅ Готово | | ContextBuilder | ⚠️ Минимальный | | ToolGateway + Tools | ✅ Готово | | ApprovalService | ✅ Готово | | SkillRegistry | ✅ Готово | | ExperienceRecorder | ✅ Готово | | Reflection | ⚠️ Не интегрирован в loop | | MemoryStore (SQLite) | ✅ Готово | | MemoryPolicy | ✅ LLM-based (Phase 1) | | VectorMemory (Qdrant) | ✅ Интегрирован (Phase 5) | | FastAPI API | ✅ Готово | | WebChat UI | ✅ Готово (светлая тема) | | Streaming | ✅ Готово | | Password flow | ✅ Готово | | Smoke tests | ✅ 74 теста | | Docs | ✅ 12 файлов | | Scripts | ✅ Готово | --- ## Архитектура эмбеддингов ### Локальная модель (основной режим) - **Модель**: `all-MiniLM-L6-v2` (sentence-transformers, 384 размерности) - **Расположение**: `./models/all-MiniLM-L6-v2/` (safetensors формат) - **Библиотека**: `sentence-transformers` (добавлен в pyproject.toml) - **Использование**: `VectorMemory._local_embed()` — загрузка модели через `SentenceTransformer`, кодирование в thread pool ### Remote endpoint (fallback) - **Endpoint**: `/v1/embeddings` на llama-server или OpenAI-совместимом сервере - **Использование**: `VectorMemory._remote_embed()` — HTTP POST запрос ### Поток данных 1. Зача завершена → `_run_memory_policy()` → LLM классифицирует → `MemoryDecision` 2. Если `should_store=True` → `MemoryStore.add()` (SQLite) + `VectorMemory.add_memory()` (Qdrant) 3. При следующем запросе → `MemoryStore.relevant()` (SQLite LIKE) + `VectorMemory.search_memory()` (semantic) 4. Recall-роль фильтрует релевантные воспоминания через LLM ### Зависимости - `sentence-transformers` — для локальной модели - `qdrant-client` — для Qdrant (уже был) - Qdrant запускается через `docker-compose.memory.yml` (порт 6333) | Docker (Qdrant) | ✅ Готово | **Общий вывод:** Все 4 задачи плана реализованы. Система представляет собой работающий skeleton с полным когнитивным циклом. Основные направления для дальнейшего развития: интеграция рефлексии и summary в основной цикл, LLM-based MemoryPolicy, векторная память, тёмная тема, расширение ContextBuilder.