Принятые решения
Решения, которые мы зафиксировали (см. Открытые вопросы — что нет).
| Тема | Решение |
|---|---|
| Позиция | Greenfield — не портируем 1.x; старая реализация только как референс |
| Облачная стратегия | Hetzner-first; AWS по триггеру. Стартуем 100% на Hetzner; managed-сервисы AWS (S3/SES/RDS/KMS) подключаем по одному только на реальный сигнал масштаба/надёжности. Компьют никогда не покидает Hetzner. См. Облачную стратегию |
| Топология кластера | Три нодпула: выделенный tainted control-plane (1 dev / 3 prod-HA) · core (всегда онлайн, HPA) · workers (эфемерные Job'ы, scale-to-zero). См. Кластер и ноды |
| Автоскейлинг | HPA (реплики api/app/admin) + VPA (right-size requests, in-place на k3s ≥1.33) + cluster-autoscaler (ноды по пулам; workers 0→N). Без KEDA для MVP |
| Тенант | = team (субъект биллинга, владеет агентами/ключами; соло-юзер = команда из одного) |
| Топология приложений | 4 приложения: api · app · admin · worker. Нет runner, нет отдельного runtime-приложения — эфемерный агент = образ api; worker = руки по запросу |
| Слоистость | 8 групп, зависимости только вниз (строгий инвариант) |
| Имя L1-группы | platform → переименовано в system (= usage · setting · notification · llm · file) |
| Auth | Drop Cognito → своя JWT (issuer = Core) в user/auth; та же схема минтит короткоживущие runtime session tokens для worker (один механизм) |
| k8s-примитив | Native k8s Jobs, ttlSecondsAfterFinished для scale-to-zero. Без KEDA/Knative для MVP |
| Браузерное исполнение | Внутри worker'а — образ worker несёт Playwright + Chromium и поднимает браузер in-pod на каждую задачу. Без отдельного пула Browserless (проще: одно приложение, полная изоляция; trade-off = более тяжёлый образ + cold start Chrome на каждую задачу) |
| Очередь | BullMQ на Redis |
| Хранилище данных | DynamoDB → Postgres |
| LLM | Bedrock → Claude API (system/llm); ретайр openai |
Resend на MVP → AWS SES на объёмах (сменяемо за notification/email) | |
| Знания | LightRAG библиотека, отдельным Python-сервисом; agent/knowledge = тонкий gateway |
| Файлы | system/file (managed-файлы поверх infra/storage); влил runtime artifacts |
| Позиция billing | Top sink; setting читает team.planId (инверсия), billing никто не импортирует |
| publicApi | Распущен → rate-limit в core, доставка вебхуков в notification, ключи в user/apiKey |
| orchestrator | Мозг — живёт в agent, не в runtime |
| mcp | Остаётся в setup (транспорт-экспозиция, не способность system) |
| Канал тулов worker'а | = MCP. worker — это эфемерный MCP-сервер; api — это MCP host, который регистрирует его на ready и отключает на release. Транспорт = MCP поверх WebSocket'а, инициированного worker'ом (обратного) (вариант A — сохраняет «no pod IP / no inbound to the worker»; trade-off = небольшой кастомный MCP-транспорт вместо штатного Streamable-HTTP). MCP sampling отключён; единая модель тулов для brain-MCP · worker-MCP · connector-MCP. См. Use → протокол · Канал инструментов (контракт провода) |
| Короткая память | Рабочий контекст регидратируется из Postgres каждый ход (мозг эфемерный) + running summary, персистимый на чате + окно с защищёнными головой/хвостом; компактизация = дешёвый пре-прун tool-result'ов → memory-flush в agent/memory → суммаризация середины дешёвой вспомогательной моделью. Snapshot system-промпта держит prefix cache тёплым. Короткая ≠ долгая память. См. Короткую память |
| Тип агента | У агента есть тип (standard / concierge), и одна декларация говорит, что этому типу разрешено; её читают и каждый путь записи, и интерфейс, а не по копии каждый. Тип назначает только сервер — никогда не из DTO и не из манифеста пакета. См. Состав |
| Системный промпт | Слоистый: платформенная база + указания по инструментам + душа агента дословно. Душа — один слой, а не весь промпт; всё, что зависит от хода, собирается в другом месте. См. Состав |
| Значения секретов | Никогда не покидают систему. Ни один эндпоинт их не отдаёт, а пакет несёт только имена ключей. Запись сливается со своим scope, а не заменяет его. См. Секреты |
| Автозапуск | Один глобальный обходчик раз в 60 с на всех агентов — никогда не таймер на агента, — поэтому созревший тик срабатывает ровно один раз на весь парк. Тик — это полноценный ход, поэтому у интервала есть пол в 10 минут, проверяемый на пути записи: пока нет учёта потребления, этот пол и есть единственное ограничение расхода. Занятый агент пропускается, а не прерывается; тик с NO_REPLY не пишет ничего. См. Автозапуск |
| Отмена хода | Ход отменяет закрытие сокета (закрытая вкладка, перезагрузка, кнопка «стоп»); SPA-навигация — намеренно нет. Остановленный ход сохраняет доставленное с пометкой «остановлено», ход с ошибкой не сохраняет ничего. См. Модель рантайма |
| История чата | Только курсорная пагинация — непрозрачный курсор, limit ≤ 200; ни один вызов не отдаёт тред целиком. Один тред на (агента, канал, человека): «начать заново» проводит черту внутри него, а не открывает второй, и заметки агента эту черту переживают. Консьерж — обычная строка Agent, поэтому его переписка хранится как любая другая. См. Хранилище |
| Разметка в чате | Ответ модели рендерится из белого списка типов узлов, а не «HTML плюс санитайзер»: никакого v-html, никаких картинок и сырого HTML, ссылки — только http/https/mailto и объявленные внутренние пути |
| Собственный автозапуск агента | Содержимое — всегда; частота и включённость — только в ходе, который открыл человек, никогда в собственном тике: опасна была петля, а не правка. Ускорение ограничено (≤ 60 мин, ≤ 3 за 24 ч), никогда не уходит ниже пола и гаснет арифметикой, а не тем, что кто-то должен сработать. Разовая строка вычёркивается после выполнения; опустевший список возвращает частоту и оставляет агента включённым. См. Автозапуск |
| Руки | Может ли агент поднять воркер — это колонка у агента, по умолчанию none, и релиз её никогда не расширяет. Какие инструменты достанутся этому воркеру — это грант, по умолчанию пустой: воркер без инструментов, а не воркер со всеми. Ничего не провижинится, пока модель действительно не вызовет инструмент воркера; одно поручение просит один раз; а инструменты приезжают в следующий ход, а не в тот, который их поднял. См. Руки |
| Архивация против удаления | Консьерж может заархивировать агента разговором и вернуть его из архива; удалить не может никогда. Граница — обратимость: архивацию разворачивает одно движение, а удаление уносит переписку, заметки, навыки и секреты агента. Удаление — за человеком, в кабинете, за подтверждением, которое называет агента по имени. См. Состав |
| Что ход помнит | Один композитор, один вызов за ход у обоих мозгов, возвращающий и слои промпта, и префикс сообщений, и потраченное — потому что торговать одним слоем против другого может только тот, кто видит сумму. Один потолок на всё; закреплённый порядок вытеснения (персона → последний обмен → сводка → заметки → выдержки → более глубокое окно), в котором первые два не режутся никогда. Считается в знаках, потому что блок едет в каждом ходу. См. Память |
| Запись в память | У заметки есть потолок, и более длинная отвергается, а не подрезается. Всё, что пишет заметку, обязано её переиндексировать — заметка, которую нельзя вспомнить, худший вид поломки, потому что ничего не падает, — а удаление заметки убирает её вектор, чтобы человек, удаливший заметку, не остался с агентом, который её всё ещё помнит. Припоминание по смыслу построено и не запускалось ни разу: ключа эмбеддингов не задаёт ни одно окружение, поэтому заметки идут по дате. См. Память |
| Основатель | Самая ранняя учётная запись становится администратором платформы один раз, и это фиксирует строка «установка основана», а не правило «повышать самого раннего, когда администраторов нет» — второе тихо отдало бы роль в тот день, когда отзовут последнего администратора. Все выдачи после этого — команда в оболочке; ни эндпоинта, ни кнопки нет. См. Роль администратора |
| Запись промптов | Выключена по умолчанию, по агенту, включает владелец агента — в промпте лежит чья-то переписка и написанные о человеке заметки, поэтому соглашаться должен тот, чей это текст. Хранится 7 дней. Читается только в панели. История работы агента — обратный размен: включена по умолчанию, дешёвая, хранится 30 дней, и промпта в ней нет никогда. См. Что агент делал |
| Контроллеры и гейтвеи | Контроллер зависит от сервиса; ему нельзя импортировать ни *Gateway, ни что-либо из data/. Оставленное на ревью, это расползлось по шести контроллерам при зелёном гейте; теперь проверка границ такое отвергает. Порядок групп берётся из конфигурации каждого приложения, поэтому один скрипт обслуживает все. См. Как мы это строим |
| Приём пакета | Ограничен по объявленному и измеренному размеру (25 МБ загрузка · 20 000 записей · 128 МБ в распакованном виде), только STORE/DEFLATE, у каждого отказа код, а не фраза, и весь импорт атомарен. См. Импорт и экспорт |
| Каналы | Отдельного приложения gateway нет — каналы живут внутри api, за транспортно-независимым швом. agent/channel/channel держит таблицу привязок, встраиваемый токен и мост к оркестратору и не знает ни одного протокола; провод лежит в подслайсе на транспорт (agent/channel/bridle). Api — это рантайм агента, а не хаб: он сам звонит НАРУЖУ в /ws/agent Bridle, поэтому наружу не открывается ничего. Внешний разговор — тот же разговор, просто с другим channel: та же таблица, то же окно, та же компакция. Посетитель не субъект: ход исполняется от владельца агента, в его команде, через ту же проверку членства, что и у участника, а идентификатор посетителя — только channelUserId. У такого хода нет рук, и он ничего не пишет в долгую память. Токен для браузера выпускает только участник команды, он истекает (15 мин по умолчанию, потолок 24 ч) и несёт id агента в sub — хаб не проверяет, для какого агента выпущен токен, поэтому проверяем мы. См. Состав и AGNT2-222 |
| Аноним → узнанный | СЛИВАТЬ, по предъявленному самим браузером билету, со следом и с отменой. Решение владельца от 28 августа 2026, принятое с названным и осознанно принятым риском: человек, который писал анонимно, а потом пришёл опознанным, получает свои прошлые разговоры без каких-либо подтверждений. Основание — только наша подпись: слияние происходит лишь там, где браузер предъявляет в ОДНОМ обмене и новый билет, и тот, которым он был до сих пор, и оба вышли из JWT, подписанного этим api. Никогда по почте, введённой в чате (чужой адрес может написать кто угодно), и никогда по имени или телефону: узнанным человека делает только externalId, который прислал его собственный сайт со своим ключом, сервер серверу (AGNT2-234). Четыре отказа, и все молчаливые для посетителя: один и тот же человек, разные команды, никто не узнан, и прошлое, которое сайт уже называл по имени. Принятый риск в том, что анонимный идентификатор живёт в браузере — общий компьютер или гостевой режим приклеят чужой разговор, а это показ чужой переписки, — поэтому слияние выкуплено двумя условиями, от которых решение неотделимо: остаётся СЛЕД (LeadMerge: кто, к кому, какие разговоры и когда), видимый словами на экране лидов; и его может ОТМЕНИТЬ участник команды, вернув ровно те разговоры, которые переехали, и ничего из сказанного после. Отмена помечает запись, а не удаляет её: «было и отменили» и «этого не было» — разные ответы. Ручки, сливающей двух лидов по id, нет и быть не должно: подпись через ручку не предъявишь. См. AGNT2-236 | | Собственные навыки агента | Агент может НАПИСАТЬ навык; включить его может только человек. Навык — это инструкция, поэтому ошибочный меняет поведение агента со всеми, с кем он говорит, и указать на фразу, которая это вызвала, нельзя — из-за этого довод, решивший вопрос про заметки памяти («пусть пишет свободно, очередь, которую никто не разбирает, всё равно ничего не делает»), здесь даёт обратный ответ. Это не очередь: навык попадает в общий список команды с пометкой, какой агент его написал, и не подключён ни к одному агенту, включая автора — то же состояние, в котором начинает навык, написанный ЧЕЛОВЕКОМ. Он ни на что не влияет и не уезжает в пакет, пока его не подключат, а на экране самого агента видно, что он написал, — чтобы решение стояло перед тем, кто его принимает. Пишется внутри хода, а не отдельным проходом после: проход стоит вызова модели на каждый разговор ради вывода «ничего», а навык, в отличие от долгой заметки, обычно просят вслух — и человек рядом, чтобы услышать, как он назван. В собственном тике отказ, потому что там некому это услышать; посетителю сайта инструмент не выдаётся вовсе — это обеспечивает способность, под которой он зарегистрирован, а не проверка внутри него. Править и удалять навык агент не может. См. AGNT2-270 |
Принципы размещения
infra= адаптеры ·setup= экспозиция/framework (без способности) ·system= сервисы-способности.systemvsruntime= состояние/способность vs механизм/действие.- членство в
system= L1-лист, используемый многими слоями (держать строго). - ломать рёбра вверх через инверсию (порт, событие или чтение через Prisma), не ломая слой.