Skip to main content
Glama

img.png

recall.select

Un sistema de memoria agéntico mínimo: proporciona una URL a cualquier agente y este obtiene memoria a largo plazo con una configuración casi nula. Construido sobre Qdrant + FastMCP + FastAPI/Bootstrap.

Consulta docs/specs/initial_specification.md para el diseño completo y el plan de construcción incremental, y docs/specs/changelog.md para un registro continuo de cambios notables.

Cómo funciona

La memoria se almacena como vectores. Cada almacén de memoria es una colección de Qdrant, mapeada uno a uno a un par (usuario, proyecto). Los metadatos alrededor de esos vectores (usuarios, claves API, proyectos y estadísticas de uso/límite por colección) residen en MongoDB.

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

Las colecciones de Qdrant se crean de forma diferida: nada toca Qdrant hasta que la primera memoria se almacena en un par (usuario, proyecto).

Related MCP server: LedgerMem MCP Server

Arquitectura

  • app/main.py - Aplicación FastAPI. Sirve la página de inicio Bootstrap y, al iniciarse, garantiza que los índices de Mongo existan (tolerante a una base de datos fría/remota).

  • app/mcp_server.py - El servidor MCP detrás del enlace de memoria. El cliente MCP de un agente apunta a {PUBLIC_BASE_URL}/m/{key} (HTTP Streamable, sin estado, respuestas JSON); la clave API en la ruta es la credencial completa y limita las herramientas al proyecto predeterminado del propietario de la clave. Herramientas básicas: store_memory / recall_memory / delete_memory. Herramientas de capa semántica (ver vector_semantics.py): link_memories / unlink_memories / annotate_memory / memory_connections / recall_connected - el agente conectado realiza el razonamiento de relaciones del lado del cliente (solo bajo demanda explícita) y estas ingieren o recorren el resultado. La misma clave también se puede enviar como Authorization: Bearer contra el endpoint /mcp sin clave, para mantener el secreto fuera de la URL/logs. {...}/m/{key}.md (en app/api/connect.py) sirve las instrucciones de configuración correspondientes (ambas formas).

  • app/dependencies.py - El contenedor DI central (injector). Construye los singletons compartidos (cliente Qdrant, cliente/BD Mongo, el incrustador remoto). Las dependencias de FastAPI (app/api/deps.py) y el inicio se resuelven desde app_container en lugar de construir los clientes ellos mismos.

  • app/services/ - La capa de servicios (sin código HTTP/ruta, solo E/S):

    • qdrant_store.py - Cliente Qdrant + ensure_collection/upsert_memory/search/delete_memory, más las primitivas a nivel de punto que necesita la capa semántica (neighbors, scroll_points, retrieve_points, set_payload).

    • vector_semantics.py - La capa de utilidad de memoria vectorial: trata un almacén como un grafo de significado. Un espacio de nombres reservado _semantics en la carga útil de cada punto contiene anclajes deícticos (propietario, almacenado en; escritos en el momento del almacenamiento), entidades extraídas por el cliente y relaciones tipadas declaradas por el cliente (upsert_relations las valida y almacena - sin llamadas LLM del lado del servidor). Las relaciones declaradas llevan dos indicadores de calidad: confidence (0-1], escala la fuerza de recorrido del borde) y valid_till (ISO 8601; los bordes caducados son ignorados por todas las rutas de lectura, por lo que la estructura obsoleta se retira sola). Higiene: remove_relations elimina bordes incorrectos (el gemelo correctivo de upsert_relations), y memory.delete_memory llama a prune_relations_to para que ningún borde suelto sobreviva a la eliminación de una memoria. Lentes conectables (topical/temporal/entity/declared) derivan bordes tipados; sobre ellos se sitúan semantic_graph (multigrafo), spreading_activation (recuperación por conexión), concept_clusters (ontología emergente) e infer_relation (verdad declarada primero, heurísticas geométricas después). Nota de rendimiento: la búsqueda de bordes entrantes (relations_of(include_incoming=True)) es hoy un desplazamiento y escaneo acotado. Si el recorrido inverso se vuelve intensivo, la solución es un índice de carga útil de Qdrant en _semantics.relations[].target (create_payload_index, esquema de palabra clave) y una consulta filtrada en lugar del escaneo - el mismo almacén, solo un índice; nada en el esquema cambia.

    • mongo.py - Cliente Mongo, get_db() y ensure_indexes() (aplica la regla uno a uno (usuario, proyecto) con un índice compuesto único).

    • users.py - add_user, get_user, get_user_by_email, update_user.

    • api_keys.py - Claves limitadas por usuario, almacenadas como un hash SHA-256 (el texto plano se devuelve una vez, desde add_api_key, y nunca se persiste): add_api_key, delete_api_key, delete_user_keys, list_api_keys, get_labeled_key, get_by_key (hashea el token presentado y coincide con el resumen; record_use=True en la puerta de autenticación MCP marca last_used_at). En reposo, cada clave también mantiene sugerencias de visualización no secretas - key_prefix + key_last4, renderizadas por masked() como rs_ab12…wxyz - para que las claves puedan listarse y distinguirse sin reexponer el secreto.

    • projects.py - add_project, get_project, list_projects, update_project, delete_project.

    • collections.py - El registro (usuario, proyecto) ↔ colección Qdrant. collection_name(user_id, project_id) es el estándar de nomenclatura interna (rs_{user}_{project}); rastrea points_count/calls_count para límites y estadísticas.

    • collection_provisioning.py - El paso de dos caras create_collection / destroy_collection. Una colección solo existe una vez que tanto su fila de registro en Mongo como su colección Qdrant de respaldo existen; esto compone el registro collections con qdrant_store en una operación atómica e idempotente para que los dos almacenes nunca se desincronicen. La creación es diferida, por lo que su único llamante creador es la primera escritura de memoria (memory.store_memory); la eliminación de la API de colecciones usa destroy_collection.

    • embeddings.py - La abstracción Embedder; embeddings_remote.py - el backend concreto texto→vector (API de incrustación remota, p. ej., DeepInfra).

    • monobank.py - Cliente de adquisición mínimo de Monobank (create_invoice, fetch_invoice_status) más autenticación de webhook (fetch_pubkey / verify_signature, ECDSA-SHA256 sobre el cuerpo sin procesar). Reutiliza el token de comerciante de mcp-api.net; recall.select posee su propia factura/redirección/webhook.

    • billing.py - El catálogo de planes y el registro de pago claveado por invoiceId de Monobank. record_pending al finalizar la compra; apply_webhook cambia el tier del comprador una vez en success (idempotente contra reintentos/duplicados); reconcile resuelve lo que el webhook perdió (abajo). Un nivel tiene límite de tiempo: grant_tier es el único lugar donde se otorga el derecho (factura pagada o buena voluntad del propietario), escribiendo tier_expires_at más una fila de auditoría en tier_grants; effective_tier(user) es lo que toda verificación debe leer, ya que un paid_2x almacenado cuya fecha ha pasado es una cuenta gratuita. También es la fuente única de verdad para las asignaciones por nivel: call_allowance(tier) / project_allowance(tier) (None = ilimitado; niveles desconocidos caen en el gratuito).

    • usage.py - El medidor de llamadas mensual y la puerta del modelo de precios. Cada store/recall/delete aceptado se contabiliza en una fila usage por (usuario, mes-calendario); check_call_allowed rechaza una llamada una vez que se ha gastado la call_allowance mensual del nivel, lanzando QuotaExceeded. Se aplica en memory.py (por lo que tanto las herramientas MCP como la API de memoria HTTP están cubiertas) y se mapea a HTTP 429 por app/main.py; el transporte MCP lo muestra como un error de herramienta. Separado del calls_count de collections de todos los tiempos.

    • account.py - La instantánea de solo lectura que muestra la página /account con sesión iniciada (plan, uso mensual, recuentos almacenados por proyecto y la lista de claves API en forma enmascarada con fechas de creación/último uso), compuesta a partir de billing/usage/projects/collections/api_keys.

    • docs.py - Contenido para las guías de integración públicas /docs. Construye la configuración del cliente MCP en un solo lugar (mcp_config / mcp_config_json), reutilizado tanto por las páginas de documentación como por el .md por clave de app/api/connect.py, para que los dos nunca diverjan. INTEGRATIONS es el registro de guías (agrega una página añadiendo una entrada).

Páginas públicas (servidas desde app/main.py, Bootstrap + Jinja, i18n a través de app/translations/*.yml): / página de inicio, /plans, /account (con sesión iniciada) y las guías /docs/integrations. La documentación de API incorporada de FastAPI se mueve de /docs a /api/docs (/api/redoc, /api/openapi.json) para que el sitio público sea propietario de /docs.

Los pagos se manejan en la capa HTTP en app/api/payments.py: POST /api/me/checkout (con sesión iniciada) crea la factura y devuelve el pay_url de Monobank; el POST /webhooks/monobank verificado otorga el nivel; GET /payment/success|fail son las páginas de retorno del navegador cosméticas (el derecho se basa en el webhook, nunca en estas).

El derecho no depende solo del webhook. Monobank envía cada cambio de estado una vez y nunca lo reenvía, por lo que una devolución de llamada perdida por un reinicio o un problema de proxy dejaría a un cliente que pagó en su nivel anterior, sin que nada de nuestro lado lo note. Por lo tanto, la aplicación también extrae: cada PAYMENT_RECONCILE_MINUTES un barrido en segundo plano (billing.reconcile_with_monobank, iniciado en el ciclo de vida de app/main.py) obtiene el estado real de cada pago aún en vuelo después de cinco minutos y lo empuja a través de la misma transición apply_webhook. Push y pull son idempotentes entre sí: el que llegue primero otorga el nivel, el otro es un no-op. Se escribe una fila en payments cuando comienza el proceso de pago, por lo que created significa "abrió la página de pago", no "pagó"; /admin/payments muestra esa distinción explícitamente.

Una compra compra un mes (SUBSCRIPTION_DAYS), no para siempre: grant_tier marca tier_expires_at, el mismo bucle en segundo plano devuelve las cuentas vencidas a gratuitas (downgrade_expired), y la página de cuenta muestra la fecha en que el plan se agota. Nada se renueva automáticamente todavía: el usuario compra de nuevo, y comprar mientras aún tiene crédito extiende la ventana en lugar de reiniciarla. El derecho se lee a través de effective_tier, por lo que una concesión vencida deja de pagar inmediatamente incluso antes de que el barrido reescriba el campo almacenado.

Debido a que nada se renueva por sí solo, la aplicación pregunta: billing.renewal_state(user) impulsa un aviso en /account: una advertencia con un botón de Renovar con un solo clic en los últimos RENEWAL_WARNING_DAYS (7) de un plan, y un aviso de "tu plan terminó, renueva" durante LAPSED_PROMPT_DAYS (30) después (el barrido de degradación registra lapsed_tier / tier_lapsed_at para que la página aún pueda decir qué se agotó). Renovar publica en el mismo /api/me/checkout que usa la página de planes, preestablecido al plan que tenían. No hay correo electrónico todavía: el aviso solo llega a los usuarios que visitan.

Cada función CRUD toma un argumento opcional db=/client= para que pueda ser impulsada en pruebas sin un backend en vivo.

Configuración

Se configura a través del entorno (un .env local se carga automáticamente; nunca lo confirmes - ver .env.example):

Variable

Default

Propósito

MONGODB_URI

(obligatorio)

String de conexión remota a MongoDB gestionada.

MONGODB_DB

recall_select

Nombre de la base de datos.

QDRANT_URL

http://qdrant:6333

Endpoint de Qdrant (red interna de compose).

QDRANT_API_KEY

(ninguno local; obligatorio en producción)

Secreto compartido entre la aplicación y Qdrant. Compose establece QDRANT__SERVICE__API_KEY de Qdrant a partir de él, y la aplicación lo envía en cada solicitud. Es la única puerta de acceso al panel de qdrant.recall.select, que no tiene autenticación propia.

VECTOR_SIZE

768

Dimensión del vector para cada colección. Se solicita al embedder remoto (mediante el parámetro dimensions de la API) que devuelva vectores exactamente de este tamaño, para que ambos estén sincronizados.

EMBEDDING_API_KEY

(obligatorio)

Clave API para la API de embeddings remota.

EMBEDDING_BASE_URL

https://api.deepinfra.com/v1

URL base de la API de embeddings compatible con OpenAI.

GOOGLE_CLIENT_ID

(obligatorio para iniciar sesión)

ID de cliente web de Google OAuth 2.0.

GOOGLE_CLIENT_SECRET

(obligatorio para iniciar sesión)

Secreto de cliente de Google OAuth 2.0.

SESSION_SECRET

(fallback de desarrollo)

Firma la cookie de sesión. Establece un valor estable en producción.

PUBLIC_BASE_URL

http://localhost:8000

Origen público; construye el enlace de memoria + la URI de redirección de OAuth.

FORWARDED_ALLOW_IPS

172.25.0.0/16 (compose) / 127.0.0.1 (uvicorn)

Pares de los que uvicorn confía en X-Forwarded-Proto/-For. Compose lo establece por defecto a la subred caddy_net para que las redirecciones mantengan el esquema https y los registros vean la IP real del cliente; compruébalo con docker network inspect caddy_net si esa red se recrea.

MONOBANK_API_KEY

(obligatorio para pagos)

Token de comercio de adquisición de Monobank. Compartido con la plataforma mcp-api.net - mismo comercio, una cuenta; las facturas se distinguen por reference.

MONOBANK_REDIRECT_URL

{PUBLIC_BASE_URL}/payment/success

Donde vuelve el navegador del comprador después de pagar.

MONOBANK_WEBHOOK_URL

{PUBLIC_BASE_URL}/webhooks/monobank

Callback de servidor a servidor que concede el nivel. Debe ser accesible públicamente.

MONOBANK_WEBHOOK_VERIFY

1

Verificar la X-Sign del webhook con la clave pública del comercio. Mantener activo donde haya movimiento de dinero; 0 solo para desarrollo local.

PAYMENT_RECONCILE_MINUTES

15

Cada cuánto tiempo se obtiene el estado real de los pagos en curso desde Monobank, para que un webhook perdido no deje a un cliente de pago varado. 0 desactiva la verificación periódica.

ADMIN_SECRET

(sin establecer - área deshabilitada)

Desbloquea el área de administración del propietario en /admin. Sin establecer significa que cada ruta /admin devuelve 404.

ADMIN_SESSION_HOURS

12

Cuánto dura una sesión de administración desbloqueada antes de volverse a bloquear.

Área de administración del propietario (/admin)

Una ventana de solo lectura al área personal de cualquier usuario, para soporte y para ver lo que ve un usuario. Establece ADMIN_SECRET (genera: python -c "import secrets; print(secrets.token_urlsafe(32))"), recrea el contenedor web, luego abre {PUBLIC_BASE_URL}/admin e ingresa la clave una vez por sesión. /admin/users lista todas las cuentas -buscables por correo, nombre o ID de usuario- y cada fila abre el plan de ese usuario, el uso de este período, los proyectos con sus recuentos de memoria y los enlaces de memoria en forma enmascarada.

Los límites son deliberados: la clave se envía mediante POST (nunca un parámetro de URL, por lo que no queda en el historial ni en los registros de acceso), los intentos incorrectos repetidos bloquean a un cliente durante cinco minutos, la sesión se vuelve a bloquear después de ADMIN_SESSION_HOURS, y ninguna ruta aquí escribe nada ni revela texto de memoria o secretos clave - el propietario ve la forma de una cuenta, no su contenido. Con ADMIN_SECRET sin establecer, el área no existe en absoluto.

Autenticación (inicio de sesión con Google)

El inicio de sesión controla el enlace de memoria: un usuario inicia sesión con Google, luego hace clic en Copiar enlace de memoria para aprovisionar su proyecto predeterminado + colección + clave API y obtener la URL para alimentar a un agente. El secreto se muestra exactamente una vez (solo se almacena su hash): después, la página de inicio muestra el enlace enmascarado (a través de GET /api/me/link) y el botón se convierte en un explícito y confirmado "obtener un nuevo enlace": la regeneración invalida el enlace anterior, nunca de forma silenciosa. Las claves se gestionan en /account: lista enmascarada, fechas de creación/último uso, crear con etiqueta (revelar una vez) y revocar. Para configurar las credenciales de Google:

  1. Consola de Google Cloud → APIs y servicios → Pantalla de consentimiento de OAuth - configúrala (Externa; agrega tu correo como usuario de prueba mientras no esté verificado).

  2. Credenciales → Crear credenciales → ID de cliente de OAuth → Aplicación web.

  3. Agrega una URI de redirección autorizada: {PUBLIC_BASE_URL}/auth/callback - por ejemplo, http://localhost:8000/auth/callback para desarrollo local y https://recall.select/auth/callback en producción (agrega ambas si pruebas localmente).

  4. Copia el ID de cliente y el Secreto de cliente en .env (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET), y establece un SESSION_SECRET estable (python -c "import secrets; print(secrets.token_urlsafe(48))").

Ejecutar localmente

El stack completo (web + Qdrant) mediante Docker Compose:

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

O solo la aplicación, contra tu propio Qdrant/Mongo:

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

Pruebas

pip install -e ".[dev]"
pytest

Las pruebas CRUD se ejecutan contra una Mongo en memoria (mongomock) y los clientes de Qdrant/embedding son simulados; no se requieren backends en vivo.

Desplegar

./deploy/deploy.sh

El mismo comando funciona desde dos lugares: detecta dónde se ejecuta:

  • Desde una máquina de desarrollo (o la caja del agente): envía confirmaciones locales, luego ejecuta el despliegue en el servidor a través del alias SSH recall-server.

  • En el propio servidor (setti@setti-server:~/recall_select$ ./deploy/deploy.sh): despliega in situ, sin salto SSH.

Ambas rutas ejecutan el mismo trabajador - deploy/_server_deploy.sh: sincronización git de master, reconstrucción del stack de Compose (FastAPI web + Qdrant), recarga del proxy Caddy compartido (HTTPS automático para recall.select), limpieza de imágenes antiguas. MongoDB es remoto/gestionado, por lo que la autenticación/MONGODB_URI env (ver .env) debe estar presente en el servidor.

Quien lo ejecute en el servidor necesita acceso de extracción de GitHub al repositorio (una clave SSH autorizada en su ~/.ssh) y pertenencia al grupo docker - ambas son ciertas para claude-agent y setti. El trabajador registra automáticamente el repositorio como un safe.directory de git para que un desplegador que no sea el propietario del repositorio no se vea bloqueado por "propiedad dudosa".

Despliegues automatizados (CI)

Cada push a master se despliega automáticamente mediante GitHub Actions (.github/workflows/deploy.yml) - el mismo flujo que arriba, solo que activado por CI en lugar de una persona. El trabajo se conecta por SSH al servidor y canaliza deploy/_server_deploy.sh a través de stdin, por lo que ejecuta la lógica de despliegue de la confirmación enviada. Los despliegues están serializados (concurrency), y un botón Run workflow (workflow_dispatch) te permite desplegar bajo demanda.

Configuración única: agrega en Settings → Secrets and variables → Actions:

Secreto

Obligatorio

Propósito

DEPLOY_SSH_KEY

Clave privada cuya mitad pública está en el ~/.ssh/authorized_keys del usuario de despliegue.

DEPLOY_HOST / DEPLOY_USER

Dirección del servidor y el usuario SSH para realizar el despliegue.

DEPLOY_PORT

no

Puerto SSH (por defecto 22).

DEPLOY_KNOWN_HOSTS

no

Fijar la clave de host del servidor; si no se establece, CI confía en él en el primer uso mediante ssh-keyscan.

Los secretos de la aplicación (MONGODB_URI, OAuth, etc.) permanecen en el .env del servidor - CI nunca los ve.

Licencia

Licenciado bajo la GNU Affero General Public License v3.0. Si ejecuta una versión modificada como servicio de red, la AGPL requiere que ofrezca su código fuente a sus usuarios. 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