diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..e753a56 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,226 @@ +# spark-mind — DESIGN: заземлённый контур и автономия по заслугам + +Обновление плана от 2026-09-23. Заменяет «шесть пунктов P1–P6» на три контура +плюс механизм автономии. Источник идей — разбор iLands (см. `ilands-razbor.txt` +в workspace), но реализация — своя. + +## 1. Тезис + +iLands для «судьи без автора» строит общество на 80 000 агентов. Нам это не нужно: +у нас есть **мир, который отвечает сам** — Метрика, GSC, Директ, Вордстат, +реакция клиентов. Это последствия, которые не сбрасываются и которых никто не +авторит. + +Отсюда два принципа, которых нет у iLands в рабочем виде: + +1. **Заземление.** Действие агента оценивается не его же рассуждением, + а замером метрики в реальном мире до/после, с отложенным вердиктом. +2. **Автономия по заслугам.** Агент получает права не сразу, а по подтверждённому + трек-рекорду. Провал — права сужаются. Границы из persona `values` непреодолимы + всегда, независимо от уровня. + +## 2. Три контура + +``` +A. КОНТУР СДЕЛОК «кому можно верить» + counterparties → deals → deal_events + +B. КОНТУР ЗАЗЕМЛЕНИЯ «стало ли лучше на самом деле» ← ядро уникальности + outcomes (метрики до/после) → capabilities (автономия) → ledger (энергия) + +C. КОНТУР МЕТОДОВ «как делать хорошо» + skills (методы, retrieval по эмбеддингу) + reflection (ночная консолидация) +``` + +Плюс сквозная микро-правка P5 (`noAction`) и тонкий `Channel` (бывший P7). + +## 3. Модель данных (SQLite, тот же файл БД) + +### A. Контур сделок + +```sql +CREATE TABLE counterparties ( + id TEXT PRIMARY KEY, + kind TEXT NOT NULL CHECK(kind IN ('client','contact','platform','external')), + name TEXT NOT NULL, + notes TEXT, + trust_score REAL NOT NULL DEFAULT 0, -- derived, не задаётся руками + created_at INTEGER NOT NULL +); + +CREATE TABLE deals ( + id TEXT PRIMARY KEY, + counterparty_id TEXT NOT NULL REFERENCES counterparties(id), + task TEXT NOT NULL, + price REAL, + currency TEXT DEFAULT 'RUB', + status TEXT NOT NULL CHECK(status IN + ('offered','accepted','delivered','paid','refused','defaulted')), + created_at INTEGER NOT NULL, + closed_at INTEGER +); + +-- append-only: нельзя переписать задним числом +CREATE TABLE deal_events ( + id TEXT PRIMARY KEY, + deal_id TEXT NOT NULL REFERENCES deals(id), + type TEXT NOT NULL, -- offered|accepted|delivered|paid|refused|note + payload_json TEXT, + created_at INTEGER NOT NULL +); +``` + +### B. Контур заземления + +```sql +-- Метрики РАЗНОГО ТИПА. Один outcome = одно действие + один замер. +CREATE TABLE outcomes ( + id TEXT PRIMARY KEY, + deal_id TEXT REFERENCES deals(id), -- nullable: действие без сделки + domain TEXT NOT NULL, -- seo|ads_budget|content_publish|client_comms|research|code + metric_kind TEXT NOT NULL CHECK(metric_kind IN + ('reach','position','engagement','conversion','sale','revenue','cost','speed','quality')), + metric_name TEXT NOT NULL, -- 'ctr', 'avg_position', 'cpa', 'leads', 'revenue_rub'... + unit TEXT, -- '%','руб','шт','сек','место' + direction TEXT NOT NULL CHECK(direction IN ('up_good','down_good')), + value_before REAL, + value_after REAL, + source TEXT NOT NULL CHECK(source IN ('metrika','gsc','direct','wordstat','manual','client')), + horizon_days INTEGER NOT NULL, -- через сколько замеряем + due_at INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT 'pending' + CHECK(status IN ('pending','measured','missed','unavailable')), + measured_at INTEGER, + score REAL -- нормализованный вердикт [-1..+1] +); + +-- Автономия по заслугам: уровень НА КАЖДУЮ ОБЛАСТЬ, не общий. +CREATE TABLE capabilities ( + domain TEXT PRIMARY KEY, + tier INTEGER NOT NULL DEFAULT 0 CHECK(tier BETWEEN 0 AND 4), + positive_outcomes INTEGER NOT NULL DEFAULT 0, + negative_outcomes INTEGER NOT NULL DEFAULT 0, + updated_at INTEGER NOT NULL +); + +-- Энергия (бывший P4): не деньги, а единица активности. +CREATE TABLE ledger ( + id TEXT PRIMARY KEY, + ts INTEGER NOT NULL, + delta REAL NOT NULL, + reason TEXT NOT NULL, + balance_after REAL NOT NULL +); +``` + +### C. Контур методов + +```sql +CREATE TABLE skills ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + method_md TEXT NOT NULL, + source TEXT NOT NULL CHECK(source IN ('builtin','learned')), + validated INTEGER NOT NULL DEFAULT 0, + uses INTEGER NOT NULL DEFAULT 0, + success_rate REAL, + embedding BLOB -- bge-m3, dim=1024 +); +``` + +## 4. Правила + +### 4.1 Нормализация метрики в score + +Один вердикт на outcome, диапазон `[-1..+1]`: + +``` +raw = (value_after - value_before) / max(|value_before|, eps) +score = clamp(raw * (direction == 'up_good' ? 1 : -1), -1, 1) +``` + +Для `quality` (ручной вердикт клиента) — дискретно: `+1 / 0 / -1`. +Для `speed` — `direction = 'down_good'` (быстрее = лучше). +Если `status != 'measured'` → score не участвует в трек-рекорде. + +### 4.2 Автономия по заслугам + +Уровни (по области, отдельно для каждой): + +| tier | Имя | Что разрешено | +|------|-----|----------------| +| 0 | observer | только думает и пишет в память, внешних действий нет | +| 1 | suggest | предлагает действия человеку, сам не делает | +| 2 | ask | делает после подтверждения (кроме своих каналов) | +| 3 | act+report | делает и отчитывается; человек может откатить | +| 4 | autonomous | делает без подтверждения в пределах границ persona | + +Переходы: +- `+1 тир` за **3 нетто-положительных** outcome в области (score > 0). +- `-1 тир` за **1 нетто-отрицательный** (score < 0) — асимметрия: падать легче. +- Затухание: outcome старше 90 дней весит 0.5. +- Новая область всегда стартует с tier 0. +- **Границы persona `values` непреодолимы на любом тире.** Тир расширяет + свободу внутри границ, но не снаружи. + +### 4.3 Энергия + +- Расход: каждый тик, каждое внешнее действие, каждый вызов саб-агента. +- Приход: **подтверждённые положительные outcomes** (особенно `revenue`), плюс + донат человека. +- Низкий баланс → реже просыпается (интервал растёт), затем Deep Rest. +- Ключевое отличие от iLands: энергия восполняется **результатом**, а не покупкой. + Дефицит мотивирует делать полезное, а не имитировать активность. + +## 5. Триггеры heartbeat (добавляются к существующим) + +| Триггер | Что делает | +|---------|------------| +| `{type:'measure'}` | наступил `due_at` → тянет метрику из источника → пишет `value_after`, `score` | +| `{type:'reflection'}` | ночной цикл: обобщает deals + outcomes + thoughts → в long-term и `self_observations` | +| `{type:'task'}` | **наконец используется** — cron-планировщик (нужен для measure/reflection) | + +Плюс в `decision` появляется `noAction: true` (P5) — агент может решить, +что действие не требуется, и не писать thought. + +## 6. Тонкий Channel (бывший P7) + +Не строим plugin-реестр рантаймов. Только: + +```ts +interface Channel { send(text: string): Promise; } +``` + +Реализации: `ConsoleChannel`, `TelegramChannel`, `FileChannel` (лог для заземления). +`LLMClient` уже есть — движок меняли Spark→Ornith без переписывания Mind. +Полный harness-реестр откладываем: абстракция окупается с ≥2 реализациями, у мира +пока одна. `Channel` станет дверью к BYOA, если решишь подключаться к чужим мирам. + +## 7. Порядок внедрения + +| Шаг | Что | Критерий приёмки | +|-----|-----|------------------| +| 0 | P5 `noAction` + `Channel` (console/telegram/file) | агент умеет не действовать; вывод через интерфейс | +| 1 | Планировщик (`task` + `measure`): проверка due-записей в SQLite | задача с датой выполняется в срок | +| 2 | Контур A: counterparties + deals + deal_events | сделка проходит offered→paid, события в журнале | +| 3 | Контур B: outcomes + `measure` | метрика замеряется через horizon, score пишется | +| 4 | Контур B: capabilities (автономия по заслугам) | тир растёт после 3 успехов, падает после провала | +| 5 | Контур C: reflection | ночью опыт уходит в self_observations, мёртвый слой оживает | +| 6 | Контур C: skills + retrieval по bge-m3 | нужный метод подгружается по задаче, не всё сразу | +| 7 | Контур B: ledger (энергия) | результат пополняет энергию, дефицит замедляет тики | + +Зависимости: 3 требует 1 (замер по расписанию) и 2 (deal_id). 4 требует 3. +7 требует 3. Остальное независимо. + +## 8. Что НЕ делаем + +- **Соцсеть/ленту/подписки** внутри агента — бессмысленно без массы участников. +- **Токены как валюту** — Тамагочи-монетизация, не наша польза. +- **Полный harness-реестр** (Codex/Claude Code/OpenClaw) — оверинжиниринг для соло. +- **«Суперразум из общества»** — нарратив без измерений. + +## 9. Открытые вопросы + +- Источник метрик по умолчанию для каждой области (Метрика/GSC/Директ — какие API уже есть). +- Кто ставит `horizon_days` — агент или человек (по умолчанию предлагаю агент, человек правит). +- Как считать `quality`-вердикт: явный ответ клиента или оценка Ника вручную. diff --git a/README.md b/README.md index 676bd3e..0e68445 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,13 @@ const telegram = new TelegramMessenger({ ## Roadmap (P6+) -Полный список незакрытых задач — в [TODO.md](./TODO.md). +- **План** — [DESIGN.md](./DESIGN.md): заземлённый контур + автономия по заслугам +- **Чек-лист** — [TODO.md](./TODO.md) -Кратко: -- P6: sleep & energy, cron-планировщик, рабочие слои памяти (notepads / core self) в цикле mind, tool-runtime для под-агентов -- P7: Web UI (persona setup), конфиг/.env, retry+timeout, тесты -- Каналы: Telegram — обход SSL-проблемы bot.qw4.ru -- Движок: Spark X2.5-4B (unknown architecture в Ollama) +Суть программы: агент оценивается не своими же рассуждениями, а замером метрик +реального мира (охват, конверсии, продажи, деньги, скорость) до/после действия, +с отложенным вердиктом. Права (автономия) выдаются по подтверждённому трек-рекорду, +а не сразу; границы persona непреодолимы на любом уровне. ## License diff --git a/TODO.md b/TODO.md index 27459cd..a67c59b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,63 +1,79 @@ # spark-mind — Оставшиеся TODO -Статус: **P0–P5 завершены** (heartbeat→mind, JSON output, embeddings, sub-agents, -SQLite, Telegram). Ниже — реально незакрытые пункты, проверенные по коду -(2026-09-15). Порядок — по приоритету. +Полный план — в [DESIGN.md](./DESIGN.md) (заземлённый контур + автономия по заслугам). +Здесь — рабочий чек-лист. Порядок обязателен: зависимости между шагами. -## P6 — Функциональная полнота +Статус: **P0–P5 базовые завершены** (heartbeat→mind, JSON output, bge-m3 +embeddings, sub-agents, SQLite, Telegram+fallback). Далее — новая программа. -- [ ] **Sleep & energy (циркадный ритм).** Сейчас heartbeat тикает всегда - с фиксированным интервалом 5 мин. Нужно: окна активности/сна, накопление - «энергии», снижение частоты тиков в пассивные периоды. -- [ ] **Планировщик задач (cron).** Тип триггера `{ type: 'task' }` объявлен - в `heartbeat.ts`, но **ничто его не эмитит** — планировщика нет - (`scheduleNext` — это idle-таймер, не cron). Нужен движок отложенных задач. -- [ ] **Рабочая память (notepads) в цикле mind.** Таблица `notepads` и методы - `updateNotepad()`/`getNotepad()` реализованы, но **вне memory.ts не - вызываются** — слой не участвует в `handleTick`. -- [ ] **Core self (self_observations) в цикле mind.** Аналогично: `addSelfObservation()` / - `getAllSelfObservations()` реализованы, но из mind не вызываются. - Самосознательность не накапливается автоматически. -- [ ] **Реальное исполнение инструментов под-агентов.** `subagent.ts` объявляет - список tools (web_search, terminal, file_write…), но это **строки-декларации** — - исполнения нет. Нужен tool-runtime для под-агентов. +## Шаг 0 — Основа (дёшево, чистит память) +- [ ] P5: `decision.noAction: true` — агент может решить, что действие не нужно, + и не писать thought. Сейчас heartbeat всегда «работает» → шум в памяти. +- [ ] Тонкий `Channel`-интерфейс: `ConsoleChannel`, `TelegramChannel`, `FileChannel`. + `sendReply` сейчас жёстко зашит на Telegram+console. -## P7 — Инфраструктура +## Шаг 1 — Планировщик (нужен для measure/reflection) +- [ ] Cron/otlozhennye задачи: триггер `{type:'task'}` объявлен, но никто не эмитит. +- [ ] Проверка due-записей в SQLite → эмит `{type:'measure'}`. +- [ ] Ночной `{type:'reflection'}`. +- Приёмка: задача с датой выполняется в срок. -- [ ] **Web UI (persona setup).** Настройка 8 слоёв persona через интерфейс - вместо `DEFAULT_PERSONA` в коде. -- [ ] **Конфиг / .env.** URL-ы Ollama (`localhost:11434`), dbPath, Telegram - захардкожены в `index.ts`. Вынести в конфиг/`.env`. -- [ ] **Retry + timeout для LLM-запросов.** `fetch` в `mind.ts` и `embedding.ts` - без retry и без `AbortController` — один сетевой сбой роняет тик. -- [ ] **Тесты.** Их нет вообще. Минимум: unit на `parseSparkOutput` (nested JSON, - fences, fallback), на `Database` CRUD, на cosine-поиск. +## Шаг 2 — Контур A: сделки и контрагенты +- [ ] Таблицы `counterparties`, `deals`, `deal_events` (append-only). +- [ ] `trust_score` выводится из истории, не задаётся руками. +- [ ] Mind умеет открывать/двигать сделку (offered→accepted→delivered→paid|refused|defaulted). +- Приёмка: сделка проходит полный цикл, все переходы в журнале. + +## Шаг 3 — Контур B: outcomes (ядро уникальности) +- [ ] Таблица `outcomes`: метрики разного типа (reach, position, engagement, + conversion, sale, revenue, cost, speed, quality), `direction`, `horizon_days`, `due_at`. +- [ ] Замер через `{type:'measure'}`: тянем метрику из источника (Метрика/GSC/Директ/Вордстат/manual). +- [ ] Нормализация в `score` [-1..+1] (формула в DESIGN.md §4.1). +- Приёмка: метрика замеряется через horizon, score пишется. + +## Шаг 4 — Контур B: автономия по заслугам +- [ ] Таблица `capabilities` (уровень НА ОБЛАСТЬ: seo, ads_budget, content_publish, + client_comms, research, code). +- [ ] Тиры 0..4 (observer → suggest → ask → act+report → autonomous). +- [ ] +1 тир за 3 нетто-положительных; −1 за 1 отрицательный; затухание 90 дней. +- [ ] Границы persona `values` непреодолимы на любом тире. +- Приёмка: тир растёт после 3 успехов и падает после провала. + +## Шаг 5 — Контур C: reflection +- [ ] Ночной цикл: обобщает deals + outcomes + thoughts → long-term. +- [ ] Пишет в `self_observations` — **сейчас мёртвый слой**, оживляется здесь. +- [ ] Дешёвая модель (не основная). + +## Шаг 6 — Контур C: skills registry +- [ ] Таблица `skills` (name, method_md, source, validated, uses, success_rate, embedding). +- [ ] Retrieval нужного метода по эмбеддингу bge-m3 (не грузить всё сразу). +- [ ] Заменяет 4 захардкоженные роли sub-agents. +- [ ] Tool-runtime для под-агентов: tools сейчас — только строки-декларации. + +## Шаг 7 — Контур B: энергия (бывший P6 sleep & energy) +- [ ] Таблица `ledger`. +- [ ] Расход: тик, внешнее действие, саб-агент. Приход: положительные outcomes + донат. +- [ ] Низкий баланс → интервал тиков растёт → Deep Rest. +- Приёмка: результат пополняет энергию, дефицит замедляет тики. + +## Инфраструктура (по мере надобности) +- [ ] Конфиг / `.env`: URL-ы Ollama, dbPath, Telegram захардкожены в `index.ts`. +- [ ] Retry + timeout (AbortController) для LLM/embedding fetch — один сбой роняет тик. +- [ ] Тесты: `parseSparkOutput`, `Database` CRUD, cosine-поиск, нормализация score. +- [ ] Web UI (persona setup) вместо `DEFAULT_PERSONA` в коде. ## Каналы вывода - -- [ ] **Telegram: обход SSL-проблемы bot.qw4.ru.** Сервер отдаёт - `tlsv1 unrecognized name` (SSL alert 112) при TLS-handshake — проблема - на стороне сервера, не клиента (проверено curl-ом и Node https). - Агент сейчас работает в console-only fallback. Варианты: починить - SNI/сертификат на qw4.ru, поднять локальный MAX-релей, или другой канал - (Telegram Bot API напрямую). +- [ ] Telegram: обход SSL-проблемы bot.qw4.ru (`tlsv1 unrecognized name`, серверная). + Сейчас console-only fallback. Варианты: починить SNI на qw4.ru, локальный + MAX-релей, или Telegram Bot API напрямую. ## Движок - -- [ ] **Spark X2.5-4B.** Не грузится в текущей Ollama: - `error loading model: unknown model architecture: 'spark2_5'`. - Варианты: собрать совместимый GGUF, поднять через llama.cpp напрямую - (OpenAI-сервер на :8080), или оставить Ornith-1.5-9B как основной движок. +- [ ] Spark X2.5-4B: не грузится в Ollama (`unknown architecture 'spark2_5'`). + Варианты: совместимый GGUF / llama.cpp напрямую / остаться на Ornith-1.5-9B. **Важно:** локальные модели пумолкают (пустой ответ), если `subAgent` - присутствует в JSON-схеме structured output — учитывать при правках промпта. + в JSON-схеме structured output. ## Косметика - -- [ ] Устаревший doc-комментарий в `mind.ts`: класс `SparkLLMClient` всё ещё - описан как «Реальный LLM-клиент для Spark X2.5-4B. В MVP — заглушка», - хотя работает с Ornith-9B и без заглушки. -- [ ] Дефолтное имя модели в конструкторе `SparkLLMClient` = `'spark-x2.5-4b'` - (index.ts переопределяет на `ornith-1.5-9b:latest`, но дефолт вводит в заблуждение). - ---- -Проверено `grep`-ом по коду: dead-code слои, отсутствие cron/retry/tests/.env, SSL-ошибка Telegram. +- [ ] Устаревший doc-комментарий в `mind.ts` (класс `SparkLLMClient` описан как + «клиент для Spark X2.5-4B, в MVP заглушка»). +- [ ] Дефолт модели в конструкторе `SparkLLMClient` = `'spark-x2.5-4b'` (вводит в заблуждение).