2184 lines
47 KiB
Markdown
2184 lines
47 KiB
Markdown
# DuckLM — техническое задание на разработку локальной агентной системы
|
||
|
||
## 0. Назначение проекта
|
||
|
||
`DuckLM` — локальная агентная система, которая работает как самостоятельный runtime поверх локальных языковых моделей.
|
||
|
||
Система должна уметь:
|
||
|
||
- принимать сообщения от человека через WebChat;
|
||
- принимать задачи от внешних агентов и тестов через HTTP API;
|
||
- использовать локальные LLM через `llama-server`;
|
||
- вести состояние задач;
|
||
- записывать события выполнения;
|
||
- безопасно запускать инструменты;
|
||
- работать с навыками;
|
||
- сохранять опыт;
|
||
- использовать память;
|
||
- анализировать собственные ошибки;
|
||
- постепенно улучшать поведение через опыт и предложения по обновлению навыков.
|
||
|
||
Главная идея:
|
||
|
||
```text
|
||
DuckLM — это не inference server.
|
||
|
||
DuckLM — это когнитивный runtime:
|
||
состояние → контекст → мышление → намерение → действие → наблюдение → рефлексия → память → опыт.
|
||
```
|
||
|
||
---
|
||
|
||
# 1. Архитектурные принципы
|
||
|
||
## 1.1. Использовать готовые компоненты
|
||
|
||
DuckLM должна использовать готовые решения там, где это разумно.
|
||
|
||
```text
|
||
llama-server → inference
|
||
SQLite/PostgreSQL → события, задачи, approvals, experience records
|
||
Qdrant → semantic memory
|
||
FastAPI → HTTP API
|
||
WebChat → интерфейс человека
|
||
ToolGateway → безопасный запуск инструментов
|
||
Duck Core → когнитивный цикл
|
||
```
|
||
|
||
Не писать с нуля:
|
||
|
||
- LLM inference server;
|
||
- model scheduler;
|
||
- vector database;
|
||
- OpenAI-compatible API;
|
||
- MCP-протокол;
|
||
- production-grade sandbox;
|
||
- сложный workflow engine;
|
||
- бесконечный JSON repair loop.
|
||
|
||
Писать с нуля:
|
||
|
||
- Duck Core;
|
||
- ModelClient;
|
||
- ContextBuilder;
|
||
- RuntimeLoop;
|
||
- EventStore;
|
||
- TaskStore;
|
||
- ToolGateway;
|
||
- ApprovalService;
|
||
- SkillRegistry;
|
||
- ExperienceRecorder;
|
||
- MemoryPolicy;
|
||
- FastAPI API;
|
||
- WebChat;
|
||
- verification scripts;
|
||
- smoke tests;
|
||
- документацию.
|
||
|
||
---
|
||
|
||
## 1.2. Web/API first
|
||
|
||
Основные интерфейсы:
|
||
|
||
```text
|
||
WebChat → для человека
|
||
HTTP API → для кодера, тестов и внешних агентов
|
||
```
|
||
|
||
CLI в обязательную часть не входит.
|
||
|
||
Если позже понадобится CLI, он должен быть тонким клиентом поверх HTTP API.
|
||
|
||
---
|
||
|
||
## 1.3. Роли моделей логические
|
||
|
||
Роли моделей:
|
||
|
||
```text
|
||
thinker
|
||
critic
|
||
coder
|
||
action
|
||
recall
|
||
summary
|
||
sys_util
|
||
```
|
||
|
||
являются логическими ролями, а не обязательно разными физическими моделями.
|
||
|
||
Одна физическая модель может использоваться сразу для всех ролей:
|
||
|
||
```text
|
||
thinker = local-main
|
||
critic = local-main
|
||
coder = local-main
|
||
action = local-main
|
||
recall = local-main
|
||
summary = local-main
|
||
```
|
||
|
||
Различие между ролями задаётся комбинацией:
|
||
|
||
- system prompt;
|
||
- temperature;
|
||
- max_output_tokens;
|
||
- response_format;
|
||
- structured_output;
|
||
- memory scope;
|
||
- tool permissions;
|
||
- context builder mode;
|
||
- inference endpoint.
|
||
|
||
Пример:
|
||
|
||
```text
|
||
thinker — свободное рассуждение, temperature 0.4
|
||
critic — проверка и рефлексия, temperature 0.1
|
||
coder — code-oriented prompt, temperature 0.2
|
||
action — strict JSON schema, temperature 0.0
|
||
summary — сжатие контекста, temperature 0.1
|
||
```
|
||
|
||
Код не должен предполагать, что разные роли используют разные модели.
|
||
|
||
Правильно:
|
||
|
||
```python
|
||
await model_client.chat(role="thinker", ...)
|
||
await model_client.chat(role="critic", ...)
|
||
await model_client.chat(role="coder", ...)
|
||
await model_client.chat(role="action", response_format=...)
|
||
```
|
||
|
||
`ModelClient` по конфигу решает:
|
||
|
||
```text
|
||
какой base_url использовать
|
||
какое имя модели передать
|
||
какую температуру поставить
|
||
какой system prompt применить
|
||
какой max_output_tokens поставить
|
||
нужен ли response_format
|
||
```
|
||
|
||
---
|
||
|
||
# 2. Параметры модели
|
||
|
||
## 2.1. Request-level параметры
|
||
|
||
Эти параметры можно менять на каждый запрос без перезапуска модели:
|
||
|
||
- system prompt;
|
||
- messages;
|
||
- temperature;
|
||
- top_p;
|
||
- top_k;
|
||
- min_p;
|
||
- max_output_tokens;
|
||
- stop;
|
||
- response_format;
|
||
- JSON schema;
|
||
- tool definitions.
|
||
|
||
Одна загруженная модель в одном `llama-server` может обслуживать разные роли с разными prompt, temperature и output limits.
|
||
|
||
---
|
||
|
||
## 2.2. Backend-level параметры
|
||
|
||
Эти параметры обычно требуют отдельного запуска сервера:
|
||
|
||
- путь к GGUF-модели;
|
||
- ctx-size;
|
||
- GPU layers / offload;
|
||
- flash-attn;
|
||
- KV cache configuration;
|
||
- speculative decoding / MTP;
|
||
- server port / host;
|
||
- parallel slots;
|
||
- chat template startup config;
|
||
- quant/offload mode.
|
||
|
||
Пример:
|
||
|
||
```text
|
||
8081 local-main обычный
|
||
8085 local-main-mtp экспериментальный
|
||
```
|
||
|
||
MTP/speculative decoding не включать по умолчанию для `action` JSON endpoint.
|
||
|
||
---
|
||
|
||
# 3. Token budget и context budget
|
||
|
||
Нужно явно разделять:
|
||
|
||
```text
|
||
ctx_size
|
||
общий размер контекстного окна модели
|
||
|
||
max_output_tokens
|
||
сколько модель может сгенерировать за один вызов
|
||
|
||
max_input_tokens
|
||
сколько токенов можно собрать во входной prompt
|
||
|
||
recent_events_tokens
|
||
сколько истории событий можно включить
|
||
|
||
memory_tokens
|
||
сколько памяти можно включить
|
||
|
||
skill_tokens
|
||
сколько текста skill/procedure/examples можно включить
|
||
```
|
||
|
||
Пример `.env.example`:
|
||
|
||
```env
|
||
DUCK_CTX_SIZE=65536
|
||
DUCK_MAX_INPUT_TOKENS=49152
|
||
DUCK_MAX_RECENT_EVENTS_TOKENS=12000
|
||
DUCK_MAX_MEMORY_TOKENS=8000
|
||
DUCK_MAX_SKILL_TOKENS=6000
|
||
```
|
||
|
||
Рекомендуемые output limits:
|
||
|
||
```text
|
||
thinker: 8192
|
||
critic: 4096
|
||
coder: 16384
|
||
action: 2048
|
||
recall: 2048
|
||
summary: 4096
|
||
```
|
||
|
||
`action` может иметь небольшой output limit, потому что action directive должен быть коротким.
|
||
|
||
`thinker` и `coder` должны иметь более крупный output limit.
|
||
|
||
---
|
||
|
||
# 4. ContextBuilder
|
||
|
||
`ContextBuilder` не должен бездумно добавлять всю историю общения в каждый запрос.
|
||
|
||
Контекст должен собираться из:
|
||
|
||
- текущего user message;
|
||
- active task state;
|
||
- selected skill;
|
||
- compact task summary;
|
||
- recent relevant events;
|
||
- relevant tool observations;
|
||
- retrieved memory;
|
||
- system prompt текущей роли.
|
||
|
||
Если контекст превышает budget:
|
||
|
||
1. сохранить текущий user message;
|
||
2. сохранить active task state;
|
||
3. сохранить selected skill summary;
|
||
4. сохранить последние важные observations;
|
||
5. суммаризировать старые events;
|
||
6. обрезать низкорелевантную memory;
|
||
7. не превышать context window молча.
|
||
|
||
---
|
||
|
||
# 5. Целевая архитектура
|
||
|
||
```text
|
||
┌─────────────────────────────────────────────┐
|
||
│ WebChat │
|
||
│ интерфейс человека к DuckLM │
|
||
└─────────────────────┬───────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────┐
|
||
│ FastAPI │
|
||
│ интерфейс кодера, тестов и агентов │
|
||
└─────────────────────┬───────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────┐
|
||
│ Duck Core │
|
||
│ │
|
||
│ RuntimeLoop │
|
||
│ TaskState │
|
||
│ ContextBuilder │
|
||
│ ModelClient │
|
||
│ SkillRegistry │
|
||
│ ToolGateway │
|
||
│ ApprovalService │
|
||
│ Reflection │
|
||
│ MemoryPolicy │
|
||
│ ExperienceRecorder │
|
||
└───────────────┬───────────────┬─────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌───────────────────────┐ ┌────────────────────────┐
|
||
│ llama-server │ │ SQLite/PostgreSQL │
|
||
│ OpenAI-compatible API │ │ events/tasks/approvals │
|
||
└───────────────────────┘ └────────────────────────┘
|
||
│
|
||
▼
|
||
┌───────────────────────┐
|
||
│ Qdrant / Vector Store │
|
||
│ semantic memory │
|
||
└───────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
# 6. Структура проекта
|
||
|
||
Создать структуру:
|
||
|
||
```text
|
||
ducklm/
|
||
duck_core/
|
||
__init__.py
|
||
api.py
|
||
config.py
|
||
model_client.py
|
||
runtime_loop.py
|
||
context_builder.py
|
||
|
||
events/
|
||
__init__.py
|
||
store.py
|
||
|
||
tasks/
|
||
__init__.py
|
||
store.py
|
||
state.py
|
||
|
||
tools/
|
||
__init__.py
|
||
base.py
|
||
gateway.py
|
||
file_read.py
|
||
file_write.py
|
||
shell_exec_safe.py
|
||
|
||
approvals/
|
||
__init__.py
|
||
service.py
|
||
|
||
skills/
|
||
__init__.py
|
||
registry.py
|
||
|
||
experience/
|
||
__init__.py
|
||
recorder.py
|
||
|
||
memory/
|
||
__init__.py
|
||
vector_memory.py
|
||
policy.py
|
||
|
||
schemas/
|
||
action_directive.schema.json
|
||
|
||
web/
|
||
templates/
|
||
index.html
|
||
task.html
|
||
approvals.html
|
||
skills.html
|
||
memory.html
|
||
experience.html
|
||
static/
|
||
app.js
|
||
style.css
|
||
|
||
prompts/
|
||
roles/
|
||
thinker.md
|
||
action.md
|
||
critic.md
|
||
coder.md
|
||
summary.md
|
||
|
||
skills/
|
||
analyze_project/
|
||
skill.yaml
|
||
procedure.md
|
||
examples.md
|
||
notes.md
|
||
|
||
config/
|
||
models.yaml
|
||
|
||
scripts/
|
||
llama/
|
||
start_main.sh
|
||
start_thinker_mtp_experimental.sh
|
||
healthcheck.sh
|
||
|
||
verify/
|
||
verify_basic_chat.sh
|
||
verify_file_write_read.sh
|
||
verify_tool_blocking.sh
|
||
verify_models_roles.sh
|
||
verify_skills.sh
|
||
verify_experience.sh
|
||
verify_memory.sh
|
||
|
||
bench/
|
||
bench_runtime.py
|
||
|
||
tests/
|
||
smoke/
|
||
|
||
docs/
|
||
|
||
data/
|
||
workspace/
|
||
|
||
.env.example
|
||
docker-compose.memory.yml
|
||
Makefile
|
||
pyproject.toml
|
||
README.md
|
||
```
|
||
|
||
---
|
||
|
||
# 7. Этап 1 — базовый проект и конфигурация
|
||
|
||
## 7.1. Цель
|
||
|
||
Создать запускаемый skeleton проекта с конфигурацией, зависимостями, `.env.example`, `config/models.yaml`, базовым FastAPI и пустой WebChat-страницей.
|
||
|
||
---
|
||
|
||
## 7.2. pyproject.toml
|
||
|
||
Минимальные зависимости:
|
||
|
||
```toml
|
||
[project]
|
||
name = "ducklm"
|
||
version = "0.1.0"
|
||
description = "Local agent runtime with WebChat, API, tools, memory and experience"
|
||
requires-python = ">=3.11"
|
||
|
||
dependencies = [
|
||
"fastapi",
|
||
"uvicorn",
|
||
"httpx",
|
||
"pydantic",
|
||
"pyyaml",
|
||
"jinja2",
|
||
"python-dotenv",
|
||
"jsonschema",
|
||
"aiosqlite",
|
||
"qdrant-client"
|
||
]
|
||
|
||
[project.optional-dependencies]
|
||
dev = [
|
||
"pytest",
|
||
"pytest-asyncio",
|
||
"ruff"
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 7.3. .env.example
|
||
|
||
Создать:
|
||
|
||
```env
|
||
DUCK_LLAMA_SERVER_BIN=/usr/local/bin/llama-server
|
||
DUCK_MAIN_MODEL_PATH=/models/main.gguf
|
||
|
||
DUCK_MAIN_PORT=8081
|
||
DUCK_CTX_SIZE=65536
|
||
DUCK_N_GPU_LAYERS=99
|
||
DUCK_HOST=127.0.0.1
|
||
|
||
DUCK_API_HOST=127.0.0.1
|
||
DUCK_API_PORT=8000
|
||
|
||
DUCK_WORKSPACE=./workspace
|
||
DUCK_DB_PATH=./data/duck.sqlite3
|
||
|
||
DUCK_MAX_INPUT_TOKENS=49152
|
||
DUCK_MAX_RECENT_EVENTS_TOKENS=12000
|
||
DUCK_MAX_MEMORY_TOKENS=8000
|
||
DUCK_MAX_SKILL_TOKENS=6000
|
||
|
||
QDRANT_URL=http://127.0.0.1:6333
|
||
|
||
DUCK_SKIP_LIVE_LLM_TESTS=0
|
||
```
|
||
|
||
По умолчанию API и `llama-server` должны слушать только `127.0.0.1`.
|
||
|
||
Если пользователь явно указывает `0.0.0.0`, в логах должно быть предупреждение:
|
||
|
||
```text
|
||
WARNING: DuckLM API is listening on 0.0.0.0. This may expose local tool execution endpoints.
|
||
```
|
||
|
||
---
|
||
|
||
## 7.4. config/models.yaml
|
||
|
||
Создать:
|
||
|
||
```yaml
|
||
default_provider: llama_server
|
||
|
||
models:
|
||
thinker:
|
||
provider: llama_server
|
||
base_url: http://127.0.0.1:8081/v1
|
||
model: local-main
|
||
purpose: free_cognition
|
||
structured_output: false
|
||
temperature: 0.4
|
||
max_output_tokens: 8192
|
||
system_prompt: prompts/roles/thinker.md
|
||
|
||
critic:
|
||
provider: llama_server
|
||
base_url: http://127.0.0.1:8081/v1
|
||
model: local-main
|
||
purpose: reflection
|
||
structured_output: false
|
||
temperature: 0.1
|
||
max_output_tokens: 4096
|
||
system_prompt: prompts/roles/critic.md
|
||
|
||
coder:
|
||
provider: llama_server
|
||
base_url: http://127.0.0.1:8081/v1
|
||
model: local-main
|
||
purpose: code_generation
|
||
structured_output: false
|
||
temperature: 0.2
|
||
max_output_tokens: 16384
|
||
system_prompt: prompts/roles/coder.md
|
||
|
||
action:
|
||
provider: llama_server
|
||
base_url: http://127.0.0.1:8081/v1
|
||
model: local-main
|
||
purpose: action_directive
|
||
structured_output: true
|
||
temperature: 0.0
|
||
max_output_tokens: 2048
|
||
system_prompt: prompts/roles/action.md
|
||
response_schema: duck_core/schemas/action_directive.schema.json
|
||
|
||
summary:
|
||
provider: llama_server
|
||
base_url: http://127.0.0.1:8081/v1
|
||
model: local-main
|
||
purpose: context_summary
|
||
structured_output: false
|
||
temperature: 0.1
|
||
max_output_tokens: 4096
|
||
system_prompt: prompts/roles/summary.md
|
||
```
|
||
|
||
---
|
||
|
||
# 8. Этап 2 — llama-server integration и ModelClient
|
||
|
||
## 8.1. Скрипт запуска llama-server
|
||
|
||
Создать:
|
||
|
||
```text
|
||
scripts/llama/start_main.sh
|
||
```
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
: "${DUCK_MAIN_MODEL_PATH:?DUCK_MAIN_MODEL_PATH is required}"
|
||
|
||
"${DUCK_LLAMA_SERVER_BIN:-llama-server}" \
|
||
-m "${DUCK_MAIN_MODEL_PATH}" \
|
||
--alias local-main \
|
||
--host "${DUCK_HOST:-127.0.0.1}" \
|
||
--port "${DUCK_MAIN_PORT:-8081}" \
|
||
-c "${DUCK_CTX_SIZE:-65536}" \
|
||
-ngl "${DUCK_N_GPU_LAYERS:-99}" \
|
||
--flash-attn on \
|
||
--cache-prompt \
|
||
--metrics
|
||
```
|
||
|
||
Создать:
|
||
|
||
```text
|
||
scripts/llama/healthcheck.sh
|
||
```
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
BASE_URL="${1:-http://127.0.0.1:8081/v1}"
|
||
|
||
curl -fsS "${BASE_URL}/models" >/dev/null
|
||
|
||
echo "OK: ${BASE_URL}"
|
||
```
|
||
|
||
---
|
||
|
||
## 8.2. ModelClient
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/model_client.py
|
||
```
|
||
|
||
Требования:
|
||
|
||
1. Читать `config/models.yaml`.
|
||
2. Вызывать модель по логической роли.
|
||
3. Работать через OpenAI-compatible API.
|
||
4. Поддерживать role-specific `system_prompt`.
|
||
5. Поддерживать role-specific `temperature`.
|
||
6. Поддерживать role-specific `max_output_tokens`.
|
||
7. Поддерживать `response_format`.
|
||
8. Логировать latency.
|
||
9. Логировать usage tokens, если backend их возвращает.
|
||
10. Корректно обрабатывать ошибки соединения.
|
||
11. Не требовать уникальности моделей для ролей.
|
||
|
||
Интерфейс:
|
||
|
||
```python
|
||
from dataclasses import dataclass
|
||
from typing import Any
|
||
|
||
|
||
@dataclass
|
||
class ModelResponse:
|
||
role: str
|
||
model: str
|
||
content: str
|
||
raw: dict[str, Any]
|
||
latency_ms: float
|
||
prompt_tokens: int | None = None
|
||
completion_tokens: int | None = None
|
||
total_tokens: int | None = None
|
||
|
||
|
||
class ModelClient:
|
||
def __init__(self, config_path: str = "config/models.yaml"):
|
||
...
|
||
|
||
async def chat(
|
||
self,
|
||
role: str,
|
||
messages: list[dict[str, str]],
|
||
temperature: float | None = None,
|
||
max_output_tokens: int | None = None,
|
||
response_format: dict | None = None,
|
||
) -> ModelResponse:
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
# 9. Этап 3 — Web/API runtime loop
|
||
|
||
## 9.1. Цель
|
||
|
||
Сделать минимальный живой вертикальный срез:
|
||
|
||
```text
|
||
человек пишет в WebChat
|
||
↓
|
||
FastAPI создаёт task
|
||
↓
|
||
Duck Core вызывает llama-server
|
||
↓
|
||
ответ пишется в SQLite event log
|
||
↓
|
||
WebChat показывает ответ и event timeline
|
||
```
|
||
|
||
На этом этапе не делать:
|
||
|
||
- tools;
|
||
- approvals;
|
||
- skills;
|
||
- experience;
|
||
- Qdrant;
|
||
- MTP.
|
||
|
||
---
|
||
|
||
## 9.2. SQLite schema
|
||
|
||
Создать EventStore и TaskStore.
|
||
|
||
Минимальные таблицы:
|
||
|
||
```sql
|
||
create table if not exists tasks (
|
||
task_id text primary key,
|
||
status text not null,
|
||
user_message text not null,
|
||
workspace text,
|
||
debug integer not null default 0,
|
||
final_response text,
|
||
created_at text not null,
|
||
updated_at text not null
|
||
);
|
||
|
||
create table if not exists events (
|
||
id integer primary key autoincrement,
|
||
task_id text not null,
|
||
sequence integer not null,
|
||
event_type text not null,
|
||
payload_json text not null,
|
||
created_at text not null
|
||
);
|
||
|
||
create unique index if not exists idx_events_task_sequence
|
||
on events(task_id, sequence);
|
||
```
|
||
|
||
Минимальные статусы задач:
|
||
|
||
```text
|
||
running
|
||
completed
|
||
failed
|
||
cancelled
|
||
```
|
||
|
||
Минимальные события:
|
||
|
||
```text
|
||
task_created
|
||
model_call_started
|
||
cognition_response
|
||
model_call_finished
|
||
task_completed
|
||
task_failed
|
||
```
|
||
|
||
---
|
||
|
||
## 9.3. RuntimeLoop
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/runtime_loop.py
|
||
```
|
||
|
||
Минимальный цикл:
|
||
|
||
```text
|
||
POST /v2/chat
|
||
↓
|
||
create task
|
||
↓
|
||
write task_created
|
||
↓
|
||
build basic context
|
||
↓
|
||
call thinker
|
||
↓
|
||
write cognition_response
|
||
↓
|
||
save final_response
|
||
↓
|
||
write task_completed
|
||
↓
|
||
return response
|
||
```
|
||
|
||
---
|
||
|
||
## 9.4. FastAPI endpoints
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/api.py
|
||
```
|
||
|
||
Минимальные endpoints:
|
||
|
||
```text
|
||
GET /health
|
||
GET /v1/status
|
||
|
||
GET /v1/models/roles
|
||
GET /v1/models/ping
|
||
|
||
POST /v1/chat
|
||
|
||
POST /v1/tasks
|
||
GET /v1/tasks
|
||
GET /v1/tasks/{task_id}
|
||
GET /v1/tasks/{task_id}/events
|
||
GET /v1/tasks/{task_id}/stream
|
||
```
|
||
|
||
`POST /v1/chat` — основной человекоподобный вход.
|
||
|
||
Пример запроса:
|
||
|
||
```json
|
||
{
|
||
"message": "Скажи коротко, что ты DuckLM",
|
||
"workspace": "./workspace",
|
||
"debug": true
|
||
}
|
||
```
|
||
|
||
Пример ответа:
|
||
|
||
```json
|
||
{
|
||
"task_id": "task_20260519_001",
|
||
"status": "completed",
|
||
"final_response": "Я DuckLM, локальная агентная система с Web/API-интерфейсом."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9.5. WebChat
|
||
|
||
Сделать минимальный WebChat.
|
||
|
||
Допустимо:
|
||
|
||
- FastAPI templates;
|
||
- static HTML;
|
||
- простой JS через `fetch`;
|
||
- SSE для event timeline.
|
||
|
||
Главная страница `/` должна содержать:
|
||
|
||
- поле сообщения;
|
||
- поле workspace;
|
||
- checkbox debug;
|
||
- кнопку Run;
|
||
- блок final response;
|
||
- блок event timeline.
|
||
|
||
---
|
||
|
||
## 9.6. Проверка этапа
|
||
|
||
Запуск:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# прописать DUCK_MAIN_MODEL_PATH
|
||
|
||
bash scripts/llama/start_main.sh
|
||
```
|
||
|
||
Во втором терминале:
|
||
|
||
```bash
|
||
python -m duck_core.api
|
||
```
|
||
|
||
Проверка:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8000/health
|
||
curl http://127.0.0.1:8000/v1/models/roles
|
||
curl http://127.0.0.1:8000/v1/models/ping
|
||
```
|
||
|
||
Запуск задачи:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:8000/v1/chat \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"message": "Скажи коротко, что ты DuckLM",
|
||
"workspace": "./workspace",
|
||
"debug": true
|
||
}'
|
||
```
|
||
|
||
Проверить events:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8000/v1/tasks/<task_id>/events
|
||
```
|
||
|
||
Ожидаемые события:
|
||
|
||
```text
|
||
task_created
|
||
model_call_started
|
||
cognition_response
|
||
model_call_finished
|
||
task_completed
|
||
```
|
||
|
||
---
|
||
|
||
# 10. Этап 4 — cognition/action split
|
||
|
||
## 10.1. Цель
|
||
|
||
Разделить свободное мышление и машинное намерение.
|
||
|
||
```text
|
||
cognition_response
|
||
свободный текст, понимание задачи, план, риски
|
||
|
||
action_directive
|
||
строгий JSON для ToolGateway
|
||
```
|
||
|
||
Модель не должна думать в JSON.
|
||
|
||
JSON используется только как форма внешнего действия.
|
||
|
||
---
|
||
|
||
## 10.2. Action directive schema
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/schemas/action_directive.schema.json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": ["kind", "intent", "risk_level", "actions"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"kind": {
|
||
"type": "string",
|
||
"enum": ["action_directive"]
|
||
},
|
||
"intent": {
|
||
"type": "string",
|
||
"minLength": 1
|
||
},
|
||
"risk_level": {
|
||
"type": "string",
|
||
"enum": ["none", "low", "medium", "high", "critical"]
|
||
},
|
||
"actions": {
|
||
"type": "array",
|
||
"minItems": 0,
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["tool", "args"],
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"tool": {
|
||
"type": "string",
|
||
"minLength": 1
|
||
},
|
||
"args": {
|
||
"type": "object"
|
||
},
|
||
"reason": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"memory_hints": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"expected_observations": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
},
|
||
"stop_reason": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10.3. Structured output и retry
|
||
|
||
Правила:
|
||
|
||
1. `action_directive` генерируется через structured output, если backend это поддерживает.
|
||
2. Если backend не поддерживает JSON schema, явно записать это в event log.
|
||
3. Fallback на plain JSON допускается только если включён в config.
|
||
4. После генерации directive валидируется локально.
|
||
5. Разрешён максимум один retry.
|
||
6. Retry чинит только directive.
|
||
7. Бесконечный JSON repair loop запрещён.
|
||
|
||
Запрещено:
|
||
|
||
```python
|
||
while not valid_json:
|
||
call_model_to_fix_json()
|
||
```
|
||
|
||
---
|
||
|
||
# 11. Этап 5 — ToolGateway
|
||
|
||
## 11.1. Цель
|
||
|
||
Добавить безопасное выполнение действий через tools.
|
||
|
||
Модель не запускает инструменты напрямую.
|
||
|
||
Модель создаёт `action_directive`.
|
||
|
||
`ToolGateway`:
|
||
|
||
1. принимает action directive;
|
||
2. проверяет tool;
|
||
3. проверяет risk level;
|
||
4. нормализует действие;
|
||
5. проверяет permissions;
|
||
6. выполняет разрешённое действие;
|
||
7. пишет observation в event log;
|
||
8. возвращает результат в runtime loop.
|
||
|
||
---
|
||
|
||
## 11.2. Tool interface
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/tools/base.py
|
||
```
|
||
|
||
```python
|
||
from typing import Protocol, Any
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
class ToolResult(BaseModel):
|
||
ok: bool
|
||
output: str | None = None
|
||
error: str | None = None
|
||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||
|
||
|
||
class Tool(Protocol):
|
||
name: str
|
||
risk_level: str
|
||
|
||
async def run(self, args: dict[str, Any]) -> ToolResult:
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## 11.3. Минимальные tools
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/tools/file_read.py
|
||
duck_core/tools/file_write.py
|
||
duck_core/tools/shell_exec_safe.py
|
||
```
|
||
|
||
### file_read
|
||
|
||
Требования:
|
||
|
||
- читать только внутри workspace;
|
||
- запретить path traversal;
|
||
- запретить чтение `/etc/shadow`;
|
||
- запретить чтение `~/.ssh` без explicit approval;
|
||
- запретить чтение `.env` без explicit approval;
|
||
- ограничить максимальный размер файла.
|
||
|
||
### file_write
|
||
|
||
Требования:
|
||
|
||
- писать только внутри workspace;
|
||
- запретить path traversal;
|
||
- не перезаписывать существующий файл без backup или approval;
|
||
- создавать каталоги только внутри workspace;
|
||
- возвращать metadata: path, bytes_written, created/updated.
|
||
|
||
### shell_exec_safe
|
||
|
||
Allowlist:
|
||
|
||
```text
|
||
pwd
|
||
ls
|
||
cat
|
||
head
|
||
tail
|
||
grep
|
||
find
|
||
python -m pytest
|
||
pytest
|
||
git status
|
||
git diff
|
||
git log
|
||
```
|
||
|
||
Blocklist:
|
||
|
||
```text
|
||
rm
|
||
sudo
|
||
su
|
||
dd
|
||
mkfs
|
||
mount
|
||
umount
|
||
chmod -R
|
||
chown -R
|
||
curl | sh
|
||
wget | sh
|
||
shutdown
|
||
reboot
|
||
poweroff
|
||
systemctl
|
||
service
|
||
apt install
|
||
apt remove
|
||
pacman -S
|
||
pacman -R
|
||
pip install
|
||
npm install -g
|
||
```
|
||
|
||
Команды вне allowlist требуют approval.
|
||
|
||
---
|
||
|
||
# 12. Этап 6 — approvals и resume
|
||
|
||
## 12.1. Цель
|
||
|
||
Добавить подтверждение рискованных действий и продолжение задачи после решения пользователя.
|
||
|
||
---
|
||
|
||
## 12.2. Таблица approvals
|
||
|
||
```sql
|
||
create table if not exists approvals (
|
||
id integer primary key autoincrement,
|
||
approval_id text not null unique,
|
||
task_id text not null,
|
||
action_hash text not null,
|
||
normalized_action_json text not null,
|
||
status text not null,
|
||
decision text,
|
||
created_at text not null,
|
||
updated_at text not null
|
||
);
|
||
```
|
||
|
||
Статусы задачи:
|
||
|
||
```text
|
||
running
|
||
waiting_for_approval
|
||
completed
|
||
failed
|
||
cancelled
|
||
```
|
||
|
||
Если действие требует approval:
|
||
|
||
1. создать pending approval;
|
||
2. перевести task в `waiting_for_approval`;
|
||
3. показать approval в Web UI;
|
||
4. позволить approve/deny через API;
|
||
5. после allow_once/allow_forever продолжить задачу через `/continue`.
|
||
|
||
---
|
||
|
||
## 12.3. Approval API
|
||
|
||
Добавить:
|
||
|
||
```text
|
||
GET /v1/approvals/pending
|
||
POST /v1/approvals/{approval_id}/allow_once
|
||
POST /v1/approvals/{approval_id}/allow_forever
|
||
POST /v1/approvals/{approval_id}/deny
|
||
POST /v1/tasks/{task_id}/continue
|
||
POST /v1/tasks/{task_id}/cancel
|
||
```
|
||
|
||
Инвариант:
|
||
|
||
```text
|
||
Allow forever = только exact normalized action hash.
|
||
```
|
||
|
||
Это не широкое разрешение на похожие действия.
|
||
|
||
---
|
||
|
||
## 12.4. Approval UI
|
||
|
||
Web UI должен показывать pending approval:
|
||
|
||
```text
|
||
DuckLM хочет выполнить действие:
|
||
|
||
tool: shell_exec_safe
|
||
command: pytest tests/smoke -v
|
||
risk: low
|
||
reason: Need to run tests
|
||
|
||
[Allow once]
|
||
[Allow forever for exact action]
|
||
[Deny]
|
||
```
|
||
|
||
---
|
||
|
||
# 13. Этап 7 — Skills
|
||
|
||
## 13.1. Цель
|
||
|
||
Добавить процедурную память.
|
||
|
||
Skill — это не if/else-автомат.
|
||
|
||
Skill — это описание способа решения типа задач:
|
||
|
||
- какие tools нужны;
|
||
- какие риски есть;
|
||
- какие шаги обычно полезны;
|
||
- какие критерии успеха;
|
||
- какие ошибки уже известны;
|
||
- какие примеры есть.
|
||
|
||
---
|
||
|
||
## 13.2. Структура skill
|
||
|
||
Создать:
|
||
|
||
```text
|
||
skills/analyze_project/
|
||
skill.yaml
|
||
procedure.md
|
||
examples.md
|
||
notes.md
|
||
```
|
||
|
||
Пример `skill.yaml`:
|
||
|
||
```yaml
|
||
id: analyze_project
|
||
title: Analyze project structure
|
||
description: Inspect repository structure and summarize architecture.
|
||
version: 1
|
||
|
||
tags:
|
||
- code
|
||
- repository
|
||
- analysis
|
||
|
||
required_tools:
|
||
- file_read
|
||
- shell_exec_safe
|
||
|
||
risk_level: low
|
||
|
||
inputs:
|
||
- workspace_path
|
||
|
||
outputs:
|
||
- architecture_summary
|
||
- risks
|
||
- suggested_next_steps
|
||
|
||
success_criteria:
|
||
- repository structure inspected
|
||
- major modules identified
|
||
- no destructive commands executed
|
||
- summary is grounded in actual files
|
||
```
|
||
|
||
---
|
||
|
||
## 13.3. SkillRegistry
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/skills/registry.py
|
||
```
|
||
|
||
Интерфейс:
|
||
|
||
```python
|
||
class SkillRegistry:
|
||
def load_skills(self) -> list[Skill]:
|
||
...
|
||
|
||
def get_skill(self, skill_id: str) -> Skill | None:
|
||
...
|
||
|
||
async def find_candidate_skills(
|
||
self,
|
||
user_request: str,
|
||
limit: int = 3,
|
||
) -> list[SkillCandidate]:
|
||
...
|
||
```
|
||
|
||
На первом этапе допустимо:
|
||
|
||
- keyword prefilter по title/tags/description;
|
||
- LLM selection через thinker/action.
|
||
|
||
Не делать огромный if/else-router.
|
||
|
||
---
|
||
|
||
## 13.4. Skills API
|
||
|
||
Добавить:
|
||
|
||
```text
|
||
GET /v1/skills
|
||
GET /v1/skills/{skill_id}
|
||
```
|
||
|
||
Web UI:
|
||
|
||
```text
|
||
/skills
|
||
```
|
||
|
||
---
|
||
|
||
# 14. Этап 8 — Experience и Reflection
|
||
|
||
## 14.1. Цель
|
||
|
||
Добавить самоулучшение через опыт.
|
||
|
||
Не через автоматическое изменение кода.
|
||
|
||
А через:
|
||
|
||
```text
|
||
task
|
||
↓
|
||
reflection
|
||
↓
|
||
experience record
|
||
↓
|
||
skill update proposal
|
||
↓
|
||
human approval later
|
||
```
|
||
|
||
---
|
||
|
||
## 14.2. Reflection
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/reflection.py
|
||
```
|
||
|
||
Reflection должна отвечать:
|
||
|
||
1. Что пытались сделать?
|
||
2. Получилось ли?
|
||
3. Что сработало?
|
||
4. Что не сработало?
|
||
5. Были ли лишние model calls?
|
||
6. Были ли лишние tool calls?
|
||
7. Застревала ли система?
|
||
8. Была ли проблема с JSON/action directive?
|
||
9. Нужно ли что-то запомнить?
|
||
10. Нужно ли предложить изменение skill?
|
||
|
||
Reflection использует роль `critic`.
|
||
|
||
`critic` может быть той же физической моделью, что и `thinker`.
|
||
|
||
---
|
||
|
||
## 14.3. ExperienceRecord
|
||
|
||
Добавить таблицу:
|
||
|
||
```sql
|
||
create table if not exists experience_records (
|
||
id integer primary key autoincrement,
|
||
task_id text not null,
|
||
skill_id text,
|
||
summary text not null,
|
||
result text not null,
|
||
what_worked_json text,
|
||
what_failed_json text,
|
||
reusable_lesson text,
|
||
suggested_skill_patch text,
|
||
confidence real,
|
||
created_at text not null
|
||
);
|
||
```
|
||
|
||
Формат:
|
||
|
||
```json
|
||
{
|
||
"task_id": "...",
|
||
"skill_id": "optional",
|
||
"summary": "What was attempted",
|
||
"result": "success/failure/partial",
|
||
"what_worked": ["..."],
|
||
"what_failed": ["..."],
|
||
"reusable_lesson": "...",
|
||
"suggested_skill_patch": "optional",
|
||
"confidence": 0.7
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 14.4. Skill update proposals
|
||
|
||
Если reflection считает, что skill надо улучшить, создать файл:
|
||
|
||
```text
|
||
skills/_proposals/<timestamp>_<skill_id>.patch.md
|
||
```
|
||
|
||
Формат:
|
||
|
||
```markdown
|
||
# Skill update proposal
|
||
|
||
Skill: analyze_project
|
||
|
||
## Reason
|
||
|
||
...
|
||
|
||
## Proposed changes
|
||
|
||
...
|
||
|
||
## Evidence
|
||
|
||
Task id: ...
|
||
|
||
## Risk
|
||
|
||
Low / medium / high.
|
||
|
||
## Requires human approval
|
||
|
||
Yes.
|
||
```
|
||
|
||
Запрещено автоматически применять skill patch без approval.
|
||
|
||
---
|
||
|
||
## 14.5. Experience API
|
||
|
||
Добавить:
|
||
|
||
```text
|
||
GET /v1/experience
|
||
GET /v1/experience/{id}
|
||
```
|
||
|
||
Web UI:
|
||
|
||
```text
|
||
/experience
|
||
```
|
||
|
||
---
|
||
|
||
# 15. Этап 9 — Semantic memory
|
||
|
||
## 15.1. Цель
|
||
|
||
Добавить semantic memory через готовый vector store.
|
||
|
||
---
|
||
|
||
## 15.2. Qdrant compose
|
||
|
||
Создать:
|
||
|
||
```text
|
||
docker-compose.memory.yml
|
||
```
|
||
|
||
```yaml
|
||
services:
|
||
qdrant:
|
||
image: qdrant/qdrant:latest
|
||
ports:
|
||
- "6333:6333"
|
||
- "6334:6334"
|
||
volumes:
|
||
- qdrant_storage:/qdrant/storage
|
||
|
||
volumes:
|
||
qdrant_storage:
|
||
```
|
||
|
||
---
|
||
|
||
## 15.3. VectorMemory adapter
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/memory/vector_memory.py
|
||
```
|
||
|
||
Интерфейс:
|
||
|
||
```python
|
||
from typing import Any
|
||
|
||
|
||
class VectorMemory:
|
||
async def add_memory(
|
||
self,
|
||
text: str,
|
||
metadata: dict[str, Any] | None = None,
|
||
) -> str:
|
||
...
|
||
|
||
async def search_memory(
|
||
self,
|
||
query: str,
|
||
limit: int = 5,
|
||
) -> list[dict[str, Any]]:
|
||
...
|
||
```
|
||
|
||
Embeddings:
|
||
|
||
1. Если `llama-server /v1/embeddings` доступен — использовать его.
|
||
2. Если embeddings пока недоступны — сделать явный adapter stub и xfail-test.
|
||
3. Не писать самодельный embedding algorithm.
|
||
|
||
---
|
||
|
||
## 15.4. MemoryPolicy
|
||
|
||
Создать:
|
||
|
||
```text
|
||
duck_core/memory/policy.py
|
||
```
|
||
|
||
Типы памяти:
|
||
|
||
```text
|
||
event
|
||
semantic_fact
|
||
preference
|
||
procedure
|
||
experience
|
||
skill_update_candidate
|
||
```
|
||
|
||
Пример результата:
|
||
|
||
```json
|
||
{
|
||
"should_store": true,
|
||
"memory_type": "experience",
|
||
"summary": "The action directive schema failed because reasoning and JSON were mixed.",
|
||
"importance": 0.8,
|
||
"metadata": {
|
||
"task_id": "...",
|
||
"source": "reflection"
|
||
}
|
||
}
|
||
```
|
||
|
||
Допустима LLM-классификация через `action` role со structured JSON.
|
||
|
||
Не делать жёстких эвристик вида:
|
||
|
||
```python
|
||
if "remember" in text:
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## 15.5. Memory API
|
||
|
||
Добавить:
|
||
|
||
```text
|
||
GET /v1/memory/search?q=...
|
||
```
|
||
|
||
Web UI:
|
||
|
||
```text
|
||
/memory
|
||
```
|
||
|
||
---
|
||
|
||
# 16. Этап 10 — Performance и MTP experiments
|
||
|
||
## 16.1. Цель
|
||
|
||
Добавить экспериментальные режимы ускорения inference.
|
||
|
||
MTP/speculative decoding — уровень inference backend, а не Duck Core.
|
||
|
||
---
|
||
|
||
## 16.2. MTP script
|
||
|
||
Создать:
|
||
|
||
```text
|
||
scripts/llama/start_thinker_mtp_experimental.sh
|
||
```
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
: "${DUCK_MAIN_MODEL_PATH:?DUCK_MAIN_MODEL_PATH is required}"
|
||
|
||
LLAMA_BIN="${DUCK_LLAMA_SERVER_BIN:-llama-server}"
|
||
|
||
if ! "${LLAMA_BIN}" --help | grep -qi "spec"; then
|
||
echo "This llama-server build does not expose speculative/MTP flags."
|
||
exit 1
|
||
fi
|
||
|
||
"${LLAMA_BIN}" \
|
||
-m "${DUCK_MAIN_MODEL_PATH}" \
|
||
--alias local-main-mtp \
|
||
--host "${DUCK_HOST:-127.0.0.1}" \
|
||
--port "${DUCK_MAIN_MTP_PORT:-8085}" \
|
||
-c "${DUCK_CTX_SIZE:-65536}" \
|
||
-ngl "${DUCK_N_GPU_LAYERS:-99}" \
|
||
--flash-attn on \
|
||
--cache-prompt \
|
||
--metrics \
|
||
${DUCK_MTP_FLAGS:-}
|
||
```
|
||
|
||
MTP не включать по умолчанию для action JSON endpoint.
|
||
|
||
---
|
||
|
||
## 16.3. Benchmark
|
||
|
||
Создать:
|
||
|
||
```text
|
||
scripts/bench/bench_runtime.py
|
||
```
|
||
|
||
Метрики:
|
||
|
||
- total runtime seconds;
|
||
- LLM calls count;
|
||
- latency per LLM call;
|
||
- prompt tokens;
|
||
- completion tokens;
|
||
- total tokens;
|
||
- tool calls count;
|
||
- JSON directive validity;
|
||
- retry count;
|
||
- memory writes count;
|
||
- experience record created yes/no;
|
||
- selected skill;
|
||
- model role mapping.
|
||
|
||
Тестовые задачи:
|
||
|
||
```text
|
||
1. "Скажи коротко, что ты DuckLM."
|
||
2. "Создай tmp/duck_test_note.md с текстом hello duck и прочитай его обратно."
|
||
3. "Посмотри структуру проекта и кратко опиши модули."
|
||
4. "Найди TODO/FIXME в проекте."
|
||
5. "Запусти тесты и кратко объясни результат."
|
||
```
|
||
|
||
Бенчмарк должен выводить:
|
||
|
||
```text
|
||
role -> base_url/model
|
||
```
|
||
|
||
---
|
||
|
||
# 17. Verification scripts
|
||
|
||
Создать:
|
||
|
||
```text
|
||
scripts/verify/
|
||
verify_basic_chat.sh
|
||
verify_file_write_read.sh
|
||
verify_tool_blocking.sh
|
||
verify_models_roles.sh
|
||
verify_skills.sh
|
||
verify_experience.sh
|
||
verify_memory.sh
|
||
```
|
||
|
||
Скрипты должны использовать HTTP API, а не CLI.
|
||
|
||
Пример `verify_basic_chat.sh`:
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
BASE_URL="${DUCK_API_URL:-http://127.0.0.1:8000}"
|
||
|
||
curl -fsS "${BASE_URL}/health"
|
||
|
||
curl -fsS -X POST "${BASE_URL}/v1/chat" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"message": "Скажи коротко, что ты DuckLM",
|
||
"debug": true
|
||
}'
|
||
```
|
||
|
||
Пример `verify_file_write_read.sh`:
|
||
|
||
```bash
|
||
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
BASE_URL="${DUCK_API_URL:-http://127.0.0.1:8000}"
|
||
|
||
RESPONSE="$(curl -fsS -X POST "${BASE_URL}/v1/chat" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"message": "Создай tmp/duck_test_note.md с текстом hello duck и прочитай его обратно",
|
||
"workspace": "./workspace",
|
||
"debug": true
|
||
}')"
|
||
|
||
echo "${RESPONSE}"
|
||
```
|
||
|
||
---
|
||
|
||
# 18. Makefile
|
||
|
||
Создать:
|
||
|
||
```makefile
|
||
duck-up:
|
||
docker compose -f docker-compose.memory.yml up -d
|
||
@echo "Memory services started."
|
||
@echo "Start llama-server:"
|
||
@echo "bash scripts/llama/start_main.sh"
|
||
|
||
duck-llama-main:
|
||
bash scripts/llama/start_main.sh
|
||
|
||
duck-llama-health:
|
||
bash scripts/llama/healthcheck.sh http://127.0.0.1:8081/v1
|
||
|
||
duck-api:
|
||
python -m duck_core.api
|
||
|
||
duck-dev:
|
||
docker compose -f docker-compose.memory.yml up -d
|
||
@echo "Start llama-server in another terminal:"
|
||
@echo "bash scripts/llama/start_main.sh"
|
||
@echo "Then run:"
|
||
@echo "make duck-api"
|
||
@echo "Open:"
|
||
@echo "http://127.0.0.1:8000/"
|
||
|
||
duck-open:
|
||
@echo "Open web UI:"
|
||
@echo "http://127.0.0.1:8000/"
|
||
|
||
duck-smoke:
|
||
python -m pytest tests/smoke -v
|
||
|
||
duck-test:
|
||
python -m pytest -v
|
||
|
||
duck-verify:
|
||
bash scripts/verify/verify_basic_chat.sh
|
||
bash scripts/verify/verify_file_write_read.sh
|
||
bash scripts/verify/verify_tool_blocking.sh
|
||
bash scripts/verify/verify_models_roles.sh
|
||
```
|
||
|
||
---
|
||
|
||
# 19. Smoke tests
|
||
|
||
Создать:
|
||
|
||
```text
|
||
tests/smoke/test_models_config.py
|
||
tests/smoke/test_model_client.py
|
||
tests/smoke/test_llama_server_connection.py
|
||
tests/smoke/test_api_health.py
|
||
tests/smoke/test_chat_api.py
|
||
tests/smoke/test_event_log.py
|
||
tests/smoke/test_action_directive_schema.py
|
||
tests/smoke/test_tool_gateway.py
|
||
tests/smoke/test_approvals.py
|
||
tests/smoke/test_skill_registry.py
|
||
tests/smoke/test_experience_recorder.py
|
||
tests/smoke/test_vector_memory.py
|
||
```
|
||
|
||
Live LLM tests должны пропускаться, если:
|
||
|
||
```text
|
||
DUCK_SKIP_LIVE_LLM_TESTS=1
|
||
```
|
||
|
||
---
|
||
|
||
# 20. Документация
|
||
|
||
Создать:
|
||
|
||
```text
|
||
docs/architecture.md
|
||
docs/how_to_run.md
|
||
docs/how_to_test.md
|
||
docs/local_llama_server.md
|
||
docs/model_roles.md
|
||
docs/web_api.md
|
||
docs/tool_gateway.md
|
||
docs/skills.md
|
||
docs/experience_learning.md
|
||
docs/memory_architecture.md
|
||
docs/performance_mtp.md
|
||
```
|
||
|
||
## docs/how_to_run.md
|
||
|
||
Описать:
|
||
|
||
1. как установить зависимости;
|
||
2. как указать путь к GGUF-модели;
|
||
3. как запустить `llama-server`;
|
||
4. как запустить DuckLM API;
|
||
5. как открыть WebChat;
|
||
6. как отправить первую задачу;
|
||
7. как смотреть task events;
|
||
8. как смотреть approvals;
|
||
9. как остановить сервисы.
|
||
|
||
## docs/model_roles.md
|
||
|
||
Описать:
|
||
|
||
1. роль модели — логическая роль;
|
||
2. thinker/critic/coder/action могут использовать одну модель;
|
||
3. разные роли могут отличаться prompt/temperature/schema/context;
|
||
4. как настроить одну модель на все роли;
|
||
5. как настроить разные модели на разные роли;
|
||
6. какие параметры request-level;
|
||
7. какие параметры backend-level.
|
||
|
||
## docs/web_api.md
|
||
|
||
Описать endpoints:
|
||
|
||
```text
|
||
GET /health
|
||
GET /v1/status
|
||
GET /v1/models/roles
|
||
GET /v1/models/ping
|
||
POST /v1/chat
|
||
POST /v1/tasks
|
||
GET /v1/tasks
|
||
GET /v1/tasks/{task_id}
|
||
GET /v1/tasks/{task_id}/events
|
||
GET /v1/tasks/{task_id}/stream
|
||
GET /v1/approvals/pending
|
||
POST /v1/approvals/{approval_id}/allow_once
|
||
POST /v1/approvals/{approval_id}/allow_forever
|
||
POST /v1/approvals/{approval_id}/deny
|
||
GET /v1/skills
|
||
GET /v1/skills/{skill_id}
|
||
GET /v1/experience
|
||
GET /v1/experience/{id}
|
||
GET /v1/memory/search?q=...
|
||
```
|
||
|
||
---
|
||
|
||
# 21. Критерии готовности по этапам
|
||
|
||
## Этап 1 готов, если:
|
||
|
||
- создана структура проекта;
|
||
- есть `pyproject.toml`;
|
||
- есть `.env.example`;
|
||
- есть `config/models.yaml`;
|
||
- есть базовый FastAPI;
|
||
- есть пустая WebChat-страница;
|
||
- проект запускается без синтаксических ошибок.
|
||
|
||
## Этап 2 готов, если:
|
||
|
||
- `llama-server` запускается через `scripts/llama/start_main.sh`;
|
||
- `/v1/models` отвечает;
|
||
- `ModelClient` читает `config/models.yaml`;
|
||
- одна модель может быть назначена на все роли;
|
||
- `GET /v1/models/roles` показывает роли;
|
||
- `GET /v1/models/ping` проверяет доступность backend-а.
|
||
|
||
## Этап 3 готов, если:
|
||
|
||
- `POST /v1/chat` работает;
|
||
- WebChat позволяет отправить сообщение;
|
||
- task создаётся;
|
||
- events пишутся в SQLite;
|
||
- task timeline отображается в WebChat;
|
||
- final response отображается в WebChat.
|
||
|
||
## Этап 4 готов, если:
|
||
|
||
- `cognition_response` отделён от `action_directive`;
|
||
- action directive schema создана;
|
||
- action directive валидируется;
|
||
- бесконечного JSON repair loop нет;
|
||
- разрешён максимум один retry.
|
||
|
||
## Этап 5 готов, если:
|
||
|
||
- ToolGateway существует;
|
||
- file_read работает внутри workspace;
|
||
- file_write работает внутри workspace;
|
||
- shell_exec_safe работает для allowlist;
|
||
- опасные команды блокируются;
|
||
- tool observations пишутся в event log.
|
||
|
||
## Этап 6 готов, если:
|
||
|
||
- approvals table создана;
|
||
- waiting_for_approval status работает;
|
||
- pending approvals видны в Web UI;
|
||
- allow_once работает;
|
||
- allow_forever работает только для exact normalized action hash;
|
||
- deny работает;
|
||
- `/continue` продолжает задачу после approval.
|
||
|
||
## Этап 7 готов, если:
|
||
|
||
- каталог `skills/` существует;
|
||
- SkillRegistry грузит skills;
|
||
- Runtime выбирает candidate skill;
|
||
- Skills API работает;
|
||
- Web UI показывает skills.
|
||
|
||
## Этап 8 готов, если:
|
||
|
||
- Reflection работает через critic role;
|
||
- ExperienceRecord создаётся после задачи;
|
||
- Experience API работает;
|
||
- Web UI показывает experience records;
|
||
- skill update proposals создаются;
|
||
- proposals не применяются автоматически.
|
||
|
||
## Этап 9 готов, если:
|
||
|
||
- Qdrant поднимается через docker-compose;
|
||
- VectorMemory adapter существует;
|
||
- add_memory работает или явно xfail, если embeddings недоступны;
|
||
- search_memory работает или явно xfail;
|
||
- MemoryPolicy существует;
|
||
- Memory API работает;
|
||
- Web UI имеет memory page.
|
||
|
||
## Этап 10 готов, если:
|
||
|
||
- MTP experimental script есть;
|
||
- MTP не включён по умолчанию для action JSON endpoint;
|
||
- benchmark script есть;
|
||
- benchmark показывает role → base_url/model;
|
||
- benchmark считает LLM calls, latency, retries, tool calls.
|
||
|
||
---
|
||
|
||
# 22. Что запрещено
|
||
|
||
Запрещено:
|
||
|
||
1. превращать DuckLM в обычный workflow-runner;
|
||
2. заменять когнитивный цикл набором if/else эвристик;
|
||
3. писать самописный inference server;
|
||
4. писать самописный model scheduler;
|
||
5. писать самописную vector database;
|
||
6. делать бесконечный JSON repair loop;
|
||
7. давать модели прямой shell без ToolGateway;
|
||
8. включать MTP/speculative для action JSON endpoint по умолчанию;
|
||
9. делать self-modifying code без approval;
|
||
10. смешивать cognition_response и action_directive;
|
||
11. считать, что thinker/critic/coder/action — обязательно разные модели;
|
||
12. считать, что каждая роль требует отдельный llama-server;
|
||
13. хардкодить пути к моделям в коде;
|
||
14. делать CLI обязательной частью системы;
|
||
15. делать сложный frontend раньше рабочего Web/API loop.
|
||
|
||
---
|
||
|
||
# 23. Финальный отчёт исполнителя
|
||
|
||
В конце работы по каждому этапу исполнитель должен предоставить:
|
||
|
||
1. что реализовано;
|
||
2. что не реализовано и почему;
|
||
3. список изменённых файлов;
|
||
4. как запустить `llama-server`;
|
||
5. как запустить DuckLM API;
|
||
6. как открыть WebChat;
|
||
7. как отправить первую задачу через WebChat;
|
||
8. как отправить задачу через curl;
|
||
9. как посмотреть task events;
|
||
10. как проверить одну модель на все роли;
|
||
11. как проверить разные модели на разные роли;
|
||
12. как проверить file_write/file_read;
|
||
13. как проверить блокировку опасной команды;
|
||
14. как проверить approvals;
|
||
15. как запустить smoke tests;
|
||
16. как запустить verification scripts;
|
||
17. какие ограничения остались;
|
||
18. что делать следующим этапом.
|
||
|
||
Финальные команды запуска должны быть примерно такими:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# прописать DUCK_MAIN_MODEL_PATH
|
||
|
||
bash scripts/llama/start_main.sh
|
||
```
|
||
|
||
Во втором терминале:
|
||
|
||
```bash
|
||
python -m duck_core.api
|
||
```
|
||
|
||
Проверка:
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8000/health
|
||
curl http://127.0.0.1:8000/v1/models/roles
|
||
curl http://127.0.0.1:8000/v1/models/ping
|
||
```
|
||
|
||
Запуск задачи:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:8000/v1/chat \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"message": "Скажи коротко, что ты DuckLM",
|
||
"workspace": "./workspace",
|
||
"debug": true
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
# 24. Главная мысль проекта
|
||
|
||
DuckLM должна быть не набором скриптов и не inference-сервером.
|
||
|
||
DuckLM должна быть локальным когнитивным runtime:
|
||
|
||
```text
|
||
состояние
|
||
контекст
|
||
модельное мышление
|
||
намерение
|
||
действие
|
||
наблюдение
|
||
рефлексия
|
||
память
|
||
опыт
|
||
навыки
|
||
```
|
||
|
||
Первый результат должен быть маленьким, но живым:
|
||
|
||
```text
|
||
WebChat
|
||
↓
|
||
FastAPI
|
||
↓
|
||
Duck Core
|
||
↓
|
||
llama-server
|
||
↓
|
||
SQLite event timeline
|
||
↓
|
||
WebChat показывает ответ и ход выполнения
|
||
```
|
||
|
||
После этого постепенно добавляются:
|
||
|
||
```text
|
||
tools
|
||
approvals
|
||
skills
|
||
experience
|
||
semantic memory
|
||
MTP
|
||
benchmark
|
||
hardening
|
||
``` |