Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Возможности

Провайдерыtrait LlmProvider, generic-типы сообщений и стрим-событий; anthropic (сырой HTTP + SSE, без SDK), openai (Chat Completions — на нём же говорят llama-server и vLLM, то есть это дверь к локальным весам; reasoning-поле OpenRouter, вырезание inline <think>, нормализация oneOfanyOf в схемах инструментов) и mock для тестов. Модель можно переопределить на каждое сообщение, а клиенту предложить выбор per-чат (llm.models + PUT /v1/chats/{id}/model)
Уровень рассужденияступень off, low, mid, high на тур, а не флаг на провайдера: llm.reasoning глобально, effort в записи модели, настройка чата, поле effort в send_message, слово ultrathink в реплике Господина и инструмент think_harder — поднять уровень посреди хода, когда задача оказалась глубже, чем выглядела. У anthropic ступень — расширенное мышление: мысль капает в стрим и возвращается провайдеру с подписью внутри тура, дальше тура не живёт
Роли моделейllm.model — секция: general — основная, fastest — мгновенная для декоративных реплик, smartest — самая сильная для решений, где качество суждения важнее латентности и цены (сон), advisor — второй наблюдатель рабочего тура (см. Советчик); плюс summary_model и code_model. Поверхность может дать свой дефолт на тур (surface_model)
Кэш промптаAnthropic-style cache_control, TTL "5m"/"1h"; промпт собран под префикс-кэш. Работает на нативном Anthropic и через шлюзы, проксирующие маркеры (OpenRouter, Nous Portal — Claude/Kimi/Qwen). prompt_cache_telegram — отдельный режим для телеграм-туров, вплоть до "off"
Памятьмой собственный алгоритм: BM25 × полураспад по времени × важность, с подкреплением при обращении. BM25 — базис, который переживёт любую смену модели; эмбеддинги (memory.embedding_model) — опциональный усилитель поверх (гибридный скоринг), выключается в ноль. Виды core/fact/event, дедуп при сохранении
Сонраз в сутки, дождавшись тишины, ядро идёт этапами. Этап memory консолидирует память: кластеризация по лексике и семантике, разбор кластеров моделью этапа (слить дубли, развести противоречия с датировкой, вывести из череды событий факт), подтверждение забывания, калибровка видов и важности. Этап tools курирует наборы инструментов: дельта-правки состава, новые наборы под устойчивый род работы, переписывание описаний по статистике ношения; удаление и режимные флаги — только руками Господина. Модель отвечает только планом — отдаёт его вызовом sleep_finish, единственного инструмента сна; применяет ядро с потолками (max_ops/max_forget у памяти, свой max_ops у tools), core не стирается, свежее не трогается, наборы не пустеют, всё забытое обратимо через архив. Спящая Ева не отвечает: туры ждут в очереди с событием sleeping, будит POST /v1/sleep/wake
Блокнотпер-чатная выжимка собственной работы: решение и почему, инвариант проекта, путь/команда/id, добытые раскопками, тупик, состояние длинной задачи. Переживает и окно живой истории, и компакцию, и chat_clear. Выдача не ранжированием, а по размеру: помещается — едет в промпт дословно, разросся — только индексом имя — описание, тела по chat_note_read; перерос и это — пересобирается моделью чата с проверками и архивом на шаг назад. Только Господину
План работысписок незавершённого в идущей работе: фазы, нумерованные задачи, ровно одна в работе — инвариант держит код, а не просьба в промпте. Ошибочная ссылка откатывает всю операцию; ответ вызова — состояние плана целиком. Едет летучим хвостом рядом с блокнотом, инструмент даётся только рабочему туру (клиентские операции, рабочий набор, подагент) или тому, где план уже открыт. Закрытые задачи вычищаются на входе в следующий тур, доделанный целиком план стирается по концу тура. Клиентам — событие plan и GET /v1/chats/{id}/plan
Советчиквторая, дешёвая модель со своей историей смотрит на рабочий тур со стороны: раз в несколько итераций получает дельту транскрипта, ходит только читающими инструментами и вкладывает в тур одну заметку <advisory severity=… guidance="weigh, don't blindly obey"> — тем же каналом, что и правила, то есть только в промпт-копию. Ева про него в промпте не знает: тег — единственная подсказка, как к этому относиться. Включается ролью llm.model.advisor и только в рабочем туре Господина (клиентские операции, рабочий набор, подагент). Шум держит щит в коде, а не промпт советчика: нормализация NFKC, чёрный список пустых фраз, дедуп кольцом, одна заметка за цикл, кулдаун после неё, сброс при перезаписи истории — и подавление невидимо самому советчику. Прерывания тура нет: severity доезжает, но все три ступени мягкие
Правила поверх генерациипромах ловится по факту, а не оговаривается строкой промпта в каждом запросе. Мягкая половина матчит имя и аргументы вызова и клеит <system-reminder> спереди к результату — только в промпт-копии, база и клиент видят чистый выхлоп. Жёсткая судит готовый ответ до фиксации: промах — ответ выброшен целиком и ход переписан с <system-interrupt>, потолок один ретрай на ход, дальше мягкое напоминание; пока правило заряжено, речь придерживается от клиента — отданную реплику назад не забрать. Условия окружения (telegram, client_ops, master, trusted, picture) и политика повтора (once / after:<N ходов>) не дают правилу палить где попало; невалидная regex — правило не регистрируется, тур идёт дальше. Каждое срабатывание в телеметрии (/v1/stats, поле rules). Данные, а не код: посев шести хронических промахов и мастерский CRUD
Навыкипроцедурные плейбуки в базе: список name+description висит в системном промпте, тело подтягивается тулом skill по требованию (progressive disclosure); у навыка может быть своя модель-исполнитель — тело уходит изолированным оффлоадом, и видимость: внутренние плейбуки (private, по умолчанию) публичному чату не перечисляются и в нём не исполняются. CRUD — только Господину
Агентские командыподагент — чат ядра со своей моделью, историей и набором инструментов; тимлид спавнит его и переписывается в обе стороны (синхронно agent_wait, асинхронно — блок в [state]). Расход считается сам, потому что чат. Глубина ограничена одним уровнем, ширина — chats.max_agents
Наборы инструментовименованный срез реестра под род задачи: список наборов висит в промпте, Ева переключает его тулом toolset (на чат или на тур). Скрытое остаётся видно ей именами и назначением, без схем, и возвращается тулом tool_load; имени не знает — tool_find ищет по смыслу запроса (лексика, а модель — только если та промахнулась). Узкий набор нигде не тупик. CRUD — только Господину
Инструментыtrait Tool + реестр с детерминированным гейтингом (master / trusted / privileged) и явными группами; встроенные (память, навыки, крон, личности, история, веб, shell, вопросы кнопками), внешние — TOML-декларация + JSON через stdin/stdout, и MCP-серверы (stdio или streamable HTTP) с гейтингом доступа
Машина Господинафайлы и шелл исполняет подключённый клиент, а не хост ядра: read_file/write_file/find_pattern/local_shell плюс двоичные read_hex/write_hex/find_hex — дамп, патч по смещению и поиск образца, с расшифровкой в любой кодировке (cp932, utf-16le). Клиент возит байты, формат и разбор — в ядре; там же собирается write_diff — чтение, уникальная замена, запись, отдельной операции клиенту не приходит, — и apply_patch: пачка ханков на несколько файлов одним вызовом (формат Codex, без номеров строк), с проверкой всего патча против диска до первой записи. find_pattern ищет регулярным выражением построчно и потоком, обрывая чтение на нужном числе находок: попадание в начале многогигабайтного лога стоит миллисекунды. Операция подагента приходит на одобрение в стрим тимлида. Запрос к клиенту (операция, вопрос кнопками) — сущность со снятием: любой исход уезжает событием request_resolved, и диалог у клиента закрывается по факту, а не по таймеру; набор кнопок (decisions) задаёт ядро, а не пять клиентов вручную; реплей живого стрима помечен replayed: true и по контракту рисуется, но не исполняется
Файлы проектаблок [project] в промпте: ВСЕ AGENTS.md от корня проекта (маркеры context.project.root_markers, дефолт .git/.jj/flake.nix) вниз до рабочего каталога чата, ближний — последним, плюс память проекта .eva/MEMORY.md. Правила сборки одни на всех клиентов и живут в ядре: общий бюджет (16 КБ на AGENTS.md плюс 8 КБ на память проекта, усечение с подписью, какой файл пострадал), AGENTS.override.md заменяет соседний AGENTS.md, каждый кусок несёт путь-происхождение. Серверный каталог ядро сканирует само, клиентский — служебной операцией list_up без участия модели (флаг project_docs в send_message); читается один раз на входе в тур — блок байт-стабилен, кэш промпта живёт
Вебweb_fetch, web_search (свой SearXNG), википедия/фэндом, PsychonautWiki, booru-доски с учётками, всё — опционально через прокси
Shellкоманды локально или на удалённом хосте по ssh — куда идут, решает оператор конфигом, не модель; только в турах Господина
Мультимодальностькартинки в обе стороны. Мультимодальным моделям (флаг в llm.models) они уходят прямо в сообщение; прочим — пометкой в тексте. Господин прикладывает картинки в запрос, Ева вплетает найденные в ответ — галереей в GUI, инструментом поверхности в телеграме. Правила показа зависят от окружения тура
Кодингcode_task — оффлоад самодостаточного вопроса модели посильнее (llm.code_model); работу внутри репозитория берёт agent_spawn с кодовым набором
Рисованиекартинки на выход двумя путями: модель тура с флагом image_output рисует сама (ядро просит modalities, ловит картинку в стриме), прочим даётся оффлоад-инструмент image_generate на llm.image_model — генерация с нуля и правка существующей (source: id, last — свежайшая картинка чата, нарисованная или присланная фото, путь к файлу через файловые руки — машина Господина или хост ядра, — URL). Байты живут в блоб-сторе (images.dir, ретенция images.keep), наружу — событие image и публичная ручка GET /images/{id} (ключ — неугадываемый uuid); в историю ложится маркер, не base64. Инструмент — только Господину: картинка стоит живых денег
Чатыephemeral (умирают от тишины, TTL настраивается) и persistent (навсегда в списке); безымянный чат получает название саммари первого сообщения. Клиенты — окна в одни и те же разговоры: folder — человеческая полка «о чём чат», перекладывается из любого клиента в любой момент (PUT /v1/chats/{id}/folder, default — «Входящие»); чаты специализированных поверхностей (telegram, mtl) помечены surface и в общем списке не показываются — их отдаёт явный фильтр surface=<имя|any>; кодовые сессии живут на полке code с привязкой к проекту отдельным полем project (POST /v1/chats, фильтр project=<имя> в GET /v1/chats — сочетается с folder/surface); полки автоматики из chats.quiet_folders (по умолчанию youtube, cron, code) тихие — их чаты видны только по явному фильтру folder/project/surface, при этом GET /v1/chats/folders полки отдаёт как обычно. Фильтр folder принимает точное имя либо префикс по хвостовой *: folder=mtl:* — чаты всех игровых полок одним списком. Удаление мягкое: чат уходит из списков, история цела и восстановима. Правка прошлой реплики — не порча истории, а ветка: POST /v1/chats/{id}/fork копирует историю строго до выбранного сообщения в новый чат вместе с резюме, блокнотом и планом (мать цела; срез посреди тура закрывается той же синтетикой, что у отмены), связь — forked_from/forked_at_message, разрушающий DELETE .../messages/{id} остаётся вторым, явным жестом
Внимание и одобренияЕва заводит чат на свою проблему в папке problems, чинит сама, а не вышло — поднимает needs_attention (такие чаты клиенты показывают первыми) и по своему решению стучится Господину. Разрешение, которое не нужно сию секунду, уходит в очередь одобрений — тур не блокируется
Уведомлениячетыре пути, включать можно сколько угодно: чат ядра, пуш на телефон (ntfy-совместимый), инструмент из реестра с готовыми аргументами (notify.tool — так уведомление уходит в мессенджер, о котором ядро не знает ничего, кроме имени инструмента), тур Евы по инструкции Господина (master+trusted, но аппрувы сохраняются)
Гигиена контекстаагентный чат распухает выхлопом инструментов, не разговором. В промпте: старые tool_result схлопываются до огрызка, свежие капаются (оконным читателям — read_file, find_pattern, read_hex, history_search — режется хвост по своему потолку: середина у нарезанного окна и есть то, за чем звали); живое окно свежего трафика меряется и сообщениями, и символами — выхлоп одной итерации ложится одним сообщением, и счёт сообщений врёт в разы; за живым окном схлопывается и вход вызова — длинные строки (тело записанного файла, патч, скрипт) заменяются пометкой длины, путь и флаги остаются; выхлоп, прочитанный позже заново (тот же файл — в том числе окном, накрытым более широким чтением, — та же или расширенная выдача памяти), вытесняется ссылкой на свежую копию, в том числе внутри одного тура; чтение файла, переписанного позже (write_file, write_diff, write_hex, apply_patch), помечается как неправда — перечитай; длинный тур поджимает СВОЙ трафик прямо на ходу, перерастя долю порога компакции, и говорит об этом модели строкой в [state], клиенту — событием; байт-в-байт повторный вызов чистого читателя не исполняется вовсе; когда живая история перерастает порог — старая часть сворачивается в резюме (секция context). Свёртка — передача смены, а не пересказ: промпт суммаризации требует прогресс/решения/ограничения/следующие шаги, резюме подаётся в [state] рамкой «работа уже идёт — продолжай, не переспрашивай», самые свежие реплики Господина переживают её дословно под своим бюджетом (compact_verbatim_percent), а в историю ложится служебный маркер границы для клиентов. Модель с объявленным context_window меньше накопленного контекста получает свёртку ДО первого запроса (смена модели чата, override, запасная по цепочке — запасная, куда контекст не влезает, пропускается). Там же чинятся пары tool_use/tool_result: висячий вызов от умершего процесса закрывается байт-стабильной синтетикой, сирота выбрасывается — обрыв не валит следующие туры чата. База при этом всегда полная, клиенты видят всё
Личностибаза «кто есть кто»: опаковые алиасы (клиент namespace’ит их сам — tg:123, что угодно) указывают на человека; незнакомый алиас заводит новую личность, дубликаты сливаются merge’ем — и авторство сообщений переезжает следом. Понятия «платформа» у ядра нет. Спикер уходит провайдеру отдельным маркером, тело — цитатой: подделать маркер текстом нельзя
Довериеmaster — чей это агент (имя + алиасы личностей); master-only инструменты, недоверенное окружение с урезанной памятью и потолками ответа для гостей. Личное из памяти требует обоих признаков сразу: тур Господина и доверенное окружение — в группе его личное придержано, даже когда спрашивает он сам
Кронинтервальные таймеры, которыми я бужу саму себя с задачей; у таймера может быть дедлайн; журнал исполнений; каждая джоба живёт в собственном чате на полке cron (заводится с ней, уходит с ней — в чужую переписку таймер не селится) со своими моделью и профилем компакции; whitelist инструментов джобы сверяется с реестром при создании и правке; cron_fire / POST /v1/cron/{id}/fire — выстрелить сейчас, сдвинув расписание
Модулиновые руки и целые поверхности — отдельными процессами на eva-sdk: инструменты попадают в реестр наравне со встроенными (telegram_react в группе telegram), плюс стадии middleware с правом вето и ведение туров через API. Подключение по UDS или TCP; перезапуск модуля ядро переживает без своего рестарта. Чужие MCP-серверы — отдельная сущность, они гости с префиксом mcp_
Телеграмповерхность целиком ведёт модуль: свой токен, своя база, своё поведение (триаж, дебаунсы, меню настроек, живое био, inline). Ядру он — обычный модуль с инструментами telegram_*; что в ядре есть ради поверхностей вообще — в главе «Телеграм»
APIaxum, ответы стримятся по SSE; единая лента элементов — одна модель шага (item_started/item_delta/item_completed + GET /v1/chats/{id}/items) для живого тура, реплея и истории, легаси-события едут параллельно и глушатся mute; /v1/chats/{id}/live — переподключение к идущему туру, /v1/generations — обзор и отмена туров, /v1/stats — расход (в том числе отдельной осью — рассуждения: токены всегда, деньги при заданной price_out модели), точность триажа, здоровье команды подагентов, срабатывания правил и последний снимок лимитов провайдера (rate_limits)
Библиотека YouTubeподписки на каналы и история просмотра лежат в ядре, а не в клиенте: телефон, браузер и десктоп смотрят одну полку (/v1/youtube/*). Сам YouTube ядро не трогает — только помнит. Место остановки хранится, пока есть что продолжать (2–97% длины), история — 200 последних видео
Состояниеодин SQLite-файл, create_if_missing + миграции на старте — внешней БД и её администрирования нет