Skip to main content
Glama

img.png

recall.select

Минималистичная агентная система памяти — передайте один URL любому агенту, и он получит долговременную память с почти нулевой настройкой. Построена на Qdrant + FastMCP + FastAPI/Bootstrap.

Полное описание дизайна и поэтапный план сборки см. в docs/specs/initial_specification.md, а историю заметных изменений — в docs/specs/changelog.md.

Как это работает

Память хранится в виде векторов. Каждое хранилище памяти — это коллекция Qdrant, сопоставленная один к одному с парой (user, project). Метаданные вокруг этих векторов — пользователи, API-ключи, проекты и статистика использования/лимитов по коллекциям — хранятся в MongoDB.

flowchart LR
    agent[Agent] --> web[FastAPI / MCP]
    web <-->qdrant[Qdrant]
    web <--> mongo[MongoDB]
    web <--> embed[Embedding API]

Коллекции Qdrant создаются лениво: ничего не обращается к Qdrant, пока первое воспоминание не будет сохранено в пару (user, project).

Related MCP server: LedgerMem MCP Server

Архитектура

  • app/main.py — приложение FastAPI. Обслуживает Bootstrap-лендинг и при запуске проверяет существование индексов Mongo (устойчиво к холодной/удаленной БД).

  • app/mcp_server.py — MCP-сервер, стоящий за ссылкой на память. MCP-клиент агента указывает на {PUBLIC_BASE_URL}/m/{key} (Streamable HTTP, без состояния, JSON-ответы); API-ключ в пути является единственным учетным данным и ограничивает инструменты проектом по умолчанию владельца ключа. Базовые инструменты: store_memory / recall_memory / delete_memory. Инструменты семантического слоя (см. vector_semantics.py): link_memories / unlink_memories / annotate_memory / memory_connections / recall_connected — подключенный агент выполняет рассуждения о связях на стороне клиента (только по явному запросу), а эти инструменты принимают или обходят результат. Тот же ключ можно отправить как Authorization: Bearer к конечной точке /mcp без ключа, чтобы скрыть секрет из URL/логов. {...}/m/{key}.mdapp/api/connect.py) обслуживает соответствующие инструкции по настройке (обе формы).

  • app/dependencies.py — основной DI-контейнер (injector). Создает общие синглтоны (клиент Qdrant, клиент/БД Mongo, удаленный эмбеддер). Зависимости FastAPI (app/api/deps.py) и код запуска разрешаются из app_container, а не создают клиенты самостоятельно.

  • app/services/ — сервисный слой (без HTTP/кода маршрутов, только ввод-вывод):

    • qdrant_store.py — клиент Qdrant + ensure_collection/upsert_memory/search/delete_memory, а также примитивы уровня точек, необходимые семантическому слою (neighbors, scroll_points, retrieve_points, set_payload).

    • vector_semantics.py — слой утилит векторной памяти: рассматривает хранилище как граф смыслов. Зарезервированное пространство имен _semantics в полезной нагрузке каждой точки содержит дейктические якоря (владелец, время сохранения; записываются при сохранении), извлеченные клиентом сущности и объявленные клиентом типизированные отношения (upsert_relations проверяет и сохраняет их — никаких LLM-вызовов на стороне сервера). Объявленные отношения имеют две качественные оговорки: confidence (0-1], масштабирует силу обхода ребра) и valid_till (ISO 8601; просроченные ребра игнорируются всеми путями чтения, поэтому устаревшая структура самоуничтожается). Гигиена: remove_relations удаляет неверные ребра (корректирующий двойник upsert_relations), а memory.delete_memory вызывает prune_relations_to, чтобы после удаления памяти не осталось висящих ребер. Подключаемые линзы (topical/temporal/entity/declared) выводят типизированные ребра; поверх них работают semantic_graph (мультиграф), spreading_activation (извлечение по связям), concept_clusters (эмерджентная онтология) и infer_relation (сначала объявленная истина, затем геометрические эвристики). Заметка о производительности: поиск входящих ребер (relations_of(include_incoming=True)) сегодня представляет собой ограниченный scroll-and-scan. Если обратный обход станет горячим путем, исправлением будет индекс полезной нагрузки Qdrant на _semantics.relations[].target (create_payload_index, схема ключевого слова) и фильтрованный запрос вместо сканирования — то же хранилище, только индекс; ничего в схеме не меняется.

    • mongo.py — клиент Mongo, get_db() и ensure_indexes() (обеспечивает правило «один к одному» (user, project) с помощью уникального составного индекса).

    • users.pyadd_user, get_user, get_user_by_email, update_user.

    • api_keys.py — ключи, привязанные к пользователю, хранятся в виде хеша SHA-256 (открытый текст возвращается один раз, из add_api_key, и никогда не сохраняется): add_api_key, delete_api_key, delete_user_keys, list_api_keys, get_labeled_key, get_by_key (хеширует представленный токен и сопоставляет с дайджестом; record_use=True на шлюзе аутентификации MCP штампует last_used_at). В состоянии покоя каждый ключ также хранит несекретные подсказки для отображения — key_prefix + key_last4, отображаемые masked() как rs_ab12…wxyz — чтобы ключи можно было перечислить и различить, не раскрывая секрет повторно.

    • projects.pyadd_project, get_project, list_projects, update_project, delete_project.

    • collections.py — реестр соответствия (user, project) ↔ коллекция Qdrant. collection_name(user_id, project_id) — внутренний стандарт именования (rs_{user}_{project}); отслеживает points_count/calls_count для лимитов и статистики.

    • collection_provisioning.py — двухсторонний шаг create_collection / destroy_collection. Коллекция существует только тогда, когда существуют и ее строка в реестре Mongo, и ее резервная коллекция Qdrant; это объединяет реестр collections с qdrant_store в одну атомарную, идемпотентную операцию, чтобы два хранилища никогда не рассинхронизировались. Создание лениво, поэтому его единственный вызывающий — первая запись памяти (memory.store_memory); API коллекции для удаления использует destroy_collection.

    • embeddings.py — абстракция Embedder; embeddings_remote.py — конкретный бэкенд текст→вектор (удаленное API эмбеддингов, например DeepInfra).

    • monobank.py — минимальный клиент эквайринга Monobank (create_invoice, fetch_invoice_status) плюс аутентификация вебхука (fetch_pubkey / verify_signature, ECDSA-SHA256 по сырому телу). Использует тот же мерчант-токен mcp-api.net; recall.select владеет собственными счетом/редиректом/вебхуком.

    • billing.py — каталог тарифов и запись об оплате, привязанная к invoiceId Monobank. record_pending при оформлении заказа; apply_webhook переключает tier покупателя один раз при success (идемпотентно к повторным попыткам/дубликатам); reconcile обрабатывает то, что пропустил вебхук (см. ниже). Тариф ограничен по времени: grant_tier — единственное место, где когда-либо выдается право (оплаченный счет или добрая воля владельца), записывая tier_expires_at плюс строку аудита в tier_grants; effective_tier(user) — то, что должна читать каждая проверка, так как сохраненный paid_2x, срок которого истек, означает бесплатную учетную запись. Также единственный источник истины для разрешений по тарифам: call_allowance(tier) / project_allowance(tier) (None = безлимитно; неизвестные тарифы возвращаются к бесплатному).

    • usage.py — ежемесячный счетчик вызовов и шлюз ценовой модели. Каждый принятый store/recall/delete учитывается в строке usage для (user, календарный-месяц); check_call_allowed отклоняет вызов, когда израсходован месячный call_allowance тарифа, вызывая QuotaExceeded. Применяется в memory.py (так что охвачены как MCP-инструменты, так и HTTP API памяти) и преобразуется в HTTP 429 в app/main.py; транспорт MCP отображает это как ошибку инструмента. Отдельно от общего collections.calls_count.

    • account.py — доступный только для чтения снимок, который показывает страница /account для вошедшего пользователя (тариф, ежемесячное использование, количество сохраненных элементов по проектам и список API-ключей в замаскированной форме с датами создания/последнего использования), составленный из billing/usage/projects/collections/api_keys.

    • docs.py — контент для публичных руководств по интеграции /docs. Собирает конфигурацию MCP-клиента в одном месте (mcp_config / mcp_config_json), повторно используемую как страницами документации, так и app/api/connect.py для .md по ключу, так что они никогда не расходятся. INTEGRATIONS — реестр руководств (добавьте страницу, добавив запись).

Публичные страницы (обслуживаются из app/main.py, Bootstrap + Jinja, i18n через app/translations/*.yml): лендинг /, /plans, /account (для вошедших) и руководства /docs/integrations. Встроенная документация API FastAPI перемещена с /docs на /api/docs (/api/redoc, /api/openapi.json), чтобы публичный сайт владел /docs.

Платежи работают через HTTP-слой в app/api/payments.py: POST /api/me/checkout (для вошедших) создает счет и возвращает pay_url Monobank; проверенный POST /webhooks/monobank предоставляет тариф; GET /payment/success|fail — это косметические страницы возврата в браузере (право предоставляется через вебхук, никогда через них).

Право не зависит только от вебхука. Monobank отправляет каждое изменение статуса один раз и никогда не пересылает его, поэтому обратный вызов, потерянный из-за перезапуска или сбоя прокси, оставил бы платящего клиента на его старом тарифе, и на нашей стороне не было бы механизма это заметить. Поэтому приложение также выполняет опрос: каждые PAYMENT_RECONCILE_MINUTES фоновый обход (billing.reconcile_with_monobank, запускаемый в жизненном цикле app/main.py) получает реальный статус каждого платежа, все еще находящегося в обработке после пяти минут, и пропускает его через тот же переход apply_webhook. Push и pull идемпотентны друг к другу — какой бы ни сработал первым, предоставляет тариф, другой является no-op. Строка в payments записывается, когда оформление начинается, поэтому created означает «открыл страницу оплаты», а не «оплатил»; /admin/payments показывает это различие явно.

Покупка дает один месяц (SUBSCRIPTION_DAYS), не навсегда: grant_tier штампует tier_expires_at, тот же фоновый цикл возвращает просроченные учетные записи на бесплатный тариф (downgrade_expired), а страница учетной записи показывает дату окончания тарифа. Ничто не продлевается автоматически — пользователь покупает снова, и покупка, пока еще есть кредит, продлевает окно, а не перезапускает его. Право читается через effective_tier, поэтому истекший грант перестает действовать немедленно, даже до того, как обход перезапишет сохраненное поле.

Поскольку ничто не продлевается само по себе, приложение спрашивает: billing.renewal_state(user) отображает подсказку на /account — предупреждение с кнопкой «Продлить» в один клик в последние RENEWAL_WARNING_DAYS (7) действия тарифа и подсказку «ваш тариф закончился, продлите его» в течение LAPSED_PROMPT_DAYS (30) после этого (обход понижения записывает lapsed_tier / tier_lapsed_at, чтобы страница все еще могла показать, что закончилось). Продление отправляет POST на тот же /api/me/checkout, который использует страница тарифов, с предустановленным тарифом, который был у пользователя. Пока нет email — подсказка доходит только до пользователей, посещающих сайт.

Каждая CRUD-функция принимает необязательный аргумент db=/client=, чтобы ее можно было использовать в тестах без работающего бэкенда.

Конфигурация

Задается через окружение (локальный .env загружается автоматически; никогда не коммитьте его — см. .env.example):

Переменная

По умолчанию

Назначение

MONGODB_URI

(обязательно)

Строка подключения к удаленной управляемой MongoDB.

MONGODB_DB

recall_select

Имя базы данных.

QDRANT_URL

http://qdrant:6333

Конечная точка Qdrant (внутренняя сеть Compose).

QDRANT_API_KEY

(нет локально; обязательно в продакшене)

Общий секрет между приложением и Qdrant. Compose устанавливает QDRANT__SERVICE__API_KEY Qdrant из него, и приложение отправляет его в каждом запросе. Это единственный шлюз на панели управления qdrant.recall.select, у которой нет собственной аутентификации.

VECTOR_SIZE

768

Размерность вектора для каждой коллекции. У удаленного эмбеддера запрашивается (через параметр API dimensions) возвращать векторы именно этого размера, чтобы они оставались синхронизированными.

EMBEDDING_API_KEY

(обязательно)

Ключ API для удаленного API эмбеддинга.

EMBEDDING_BASE_URL

https://api.deepinfra.com/v1

Базовый URL API эмбеддингов, совместимого с OpenAI.

GOOGLE_CLIENT_ID

(обязательно для входа)

Идентификатор веб-клиента Google OAuth 2.0.

GOOGLE_CLIENT_SECRET

(обязательно для входа)

Секрет клиента Google OAuth 2.0.

SESSION_SECRET

(запасной вариант для разработки)

Подписывает cookie сессии. Установите стабильное значение в продакшене.

PUBLIC_BASE_URL

http://localhost:8000

Публичный источник; формирует ссылку на память + URI перенаправления OAuth.

FORWARDED_ALLOW_IPS

172.25.0.0/16 (compose) / 127.0.0.1 (uvicorn)

Пиры, чьим X-Forwarded-Proto/-For доверяет uvicorn. Compose устанавливает значение по умолчанию на подсеть caddy_net, чтобы перенаправления сохраняли схему https, а логи видели реальный IP клиента; проверьте с помощью docker network inspect caddy_net, если эта сеть была пересоздана.

MONOBANK_API_KEY

(обязательно для платежей)

Токен мерчанта Monobank. Общий с платформой mcp-api.net - один мерчант, один аккаунт; счета различаются по reference.

MONOBANK_REDIRECT_URL

{PUBLIC_BASE_URL}/payment/success

Куда браузер покупателя возвращается после оплаты.

MONOBANK_WEBHOOK_URL

{PUBLIC_BASE_URL}/webhooks/monobank

Серверный обратный вызов, который предоставляет уровень доступа. Должен быть общедоступным.

MONOBANK_WEBHOOK_VERIFY

1

Проверять X-Sign вебхука по публичному ключу мерчанта. Оставляйте включенным везде, где движутся деньги; 0 только для локальной разработки.

PAYMENT_RECONCILE_MINUTES

15

Как часто запрашивать реальный статус текущих платежей у Monobank, чтобы потерянный вебхук не оставил платящего клиента в подвешенном состоянии. 0 отключает проверку.

ADMIN_SECRET

(не задан - область отключена)

Разблокирует область администратора владельца по адресу /admin. Если не задан, каждый маршрут /admin возвращает 404.

ADMIN_SESSION_HOURS

12

Как долго длится разблокированная сессия администратора, прежде чем она снова заблокируется.

Область администратора владельца (/admin)

Окно только для чтения в личную область любого пользователя для поддержки и просмотра того, что видит пользователь. Установите ADMIN_SECRET (сгенерировать: python -c "import secrets; print(secrets.token_urlsafe(32))"), пересоздайте веб-контейнер, затем откройте {PUBLIC_BASE_URL}/admin и введите ключ один раз за сессию. /admin/users выводит список всех учетных записей - доступен поиск по email, имени или ID пользователя - и каждая строка открывает тарифный план этого пользователя, использование за текущий период, проекты с количеством записей памяти и ссылки на память в замаскированной форме.

Границы продуманы: ключ отправляется через POST (никогда не параметр URL, поэтому он не попадает в историю и журналы доступа), повторные неверные попытки блокируют клиента на пять минут, сессия повторно блокируется через ADMIN_SESSION_HOURS, и ни один маршрут здесь ничего не записывает и не раскрывает текст памяти или секреты ключей - владелец видит структуру учетной записи, а не ее содержимое. Если ADMIN_SECRET не задан, область вообще не существует.

Аутентификация (вход через Google)

Вход открывает доступ к ссылке на память: пользователь входит через Google, затем нажимает Copy memory link, чтобы подготовить свой проект по умолчанию + коллекцию + ключ API и получить URL для передачи агенту. Секрет показывается ровно один раз (сохраняется только его хэш): после этого целевая страница показывает ссылку замаскированной (через GET /api/me/link), а кнопка превращается в явное подтвержденное действие "получить новую ссылку" - перегенерация аннулирует старую ссылку, никогда не делая этого молча. Ключами можно управлять на странице /account: замаскированный список, даты создания/последнего использования, создание с меткой (однократное отображение) и отзыв. Чтобы настроить учетные данные Google:

  1. Google Cloud Console → APIs & Services → OAuth consent screen - настройте его (External; добавьте свой email как тестового пользователя, пока приложение не проверено).

  2. Credentials → Create credentials → OAuth client ID → Web application.

  3. Добавьте Authorized redirect URI: {PUBLIC_BASE_URL}/auth/callback - например, http://localhost:8000/auth/callback для локальной разработки и https://recall.select/auth/callback для продакшена (добавьте оба, если тестируете локально).

  4. Скопируйте Client ID и Client secret в .env (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET) и установите стабильный SESSION_SECRET (python -c "import secrets; print(secrets.token_urlsafe(48))").

Локальный запуск

Полный стек (веб + Qdrant) через Docker Compose:

cp .env.example .env   # then fill in MONGODB_URI
docker compose up --build
# open http://localhost:8000

Или только приложение, используя свой собственный Qdrant/Mongo:

pip install -e ".[dev]"
uvicorn app.main:app --reload

Тесты

pip install -e ".[dev]"
pytest

CRUD-тесты запускаются с MongoDB в памяти (mongomock), а клиенты Qdrant/эмбеддинга имитируются - не требуются работающие бэкенды.

Развертывание

./deploy/deploy.sh

Одна и та же команда работает из двух мест - она определяет, где запущена:

  • С машины разработчика (или из агента): отправляет локальные коммиты, затем запускает развертывание на сервере через SSH-алиас recall-server.

  • На самом сервере (setti@setti-server:~/recall_select$ ./deploy/deploy.sh): развертывает на месте, без SSH-перехода.

Оба пути запускают один и тот же скрипт - deploy/_server_deploy.sh: синхронизация master через git, пересборка стека Compose (FastAPI web + Qdrant), перезагрузка общего прокси Caddy (автоматический HTTPS для recall.select), удаление старых образов. MongoDB является удаленной/управляемой, поэтому переменная окружения auth/MONGODB_URI (см. .env) должна присутствовать на сервере.

Тот, кто запускает это на сервере, должен иметь доступ на чтение к репозиторию GitHub (авторизованный SSH-ключ в своем ~/.ssh) и быть членом группы docker - оба условия верны для claude-agent и setti. Скрипт автоматически регистрирует репозиторий как safe.directory в git, чтобы развертывающий, не являющийся владельцем репозитория, не был заблокирован ошибкой "dubious ownership".

Автоматические развертывания (CI)

Каждый push в master автоматически развертывается через GitHub Actions (.github/workflows/deploy.yml) - тот же процесс, что и выше, только запускаемый CI, а не человеком. Задача подключается по SSH к серверу и передает deploy/_server_deploy.sh через stdin, таким образом выполняя логику развертывания отправленного коммита. Развертывания сериализованы (concurrency), а кнопка Run workflow (workflow_dispatch) позволяет развернуть по требованию.

Одноразовая настройка - добавьте в Settings → Secrets and variables → Actions:

Секрет

Обязательно

Назначение

DEPLOY_SSH_KEY

да

Приватный ключ, чья публичная половина находится в ~/.ssh/authorized_keys пользователя развертывания.

DEPLOY_HOST / DEPLOY_USER

да

Адрес сервера и пользователь SSH для развертывания.

DEPLOY_PORT

нет

Порт SSH (по умолчанию 22).

DEPLOY_KNOWN_HOSTS

нет

Зафиксировать хост-ключ сервера; если не задано, CI доверяет ему при первом использовании через ssh-keyscan.

Секреты приложения (MONGODB_URI, OAuth и т.д.) остаются в .env на сервере - CI их никогда не видит.

Лицензия

Лицензировано в соответствии с GNU Affero General Public License v3.0. Если вы запускаете модифицированную версию в качестве сетевого сервиса, AGPL требует предоставить ее исходный код вашим пользователям. Copyright © 2026 Sergii Setti.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Provides persistent, local-first AI memory across sessions via MCP tools for storing, searching, and retrieving context from past interactions.
    1
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Provides persistent memory for AI agents via 10 MCP tools that map to the AgentRAM REST API, enabling store, retrieve, search, and share memories across personal and shared namespaces.
    10
    191
    MIT

View all related MCP servers

Related MCP Connectors

  • Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.

  • Shared long-term memory vault for AI agents with 20 MCP tools.

  • Your memory, everywhere AI goes. Build knowledge once, access it via MCP anywhere.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SergeySetti/recall_select'

If you have feedback or need assistance with the MCP directory API, please join our Discord server