MCP Platform
Entorno de ejecución MCP local con orquestación multiagente, servidores de herramientas distribuidos y recomendaciones multimedia impulsadas por ML.
Documentación
MCP Platform 
Runtime MCP local con orquestación multi-agente, servidores de herramientas distribuidos, recomendaciones multimedia impulsadas por ML, memoria persistente entre sesiones y un programador de agentes proactivo.
⚠️ Experimental — destinado únicamente para uso personal y experimental, no para implementación en producción.
Tabla de Contenidos
- Prerrequisitos
- 1. Inicio Rápido
- 2. Uso de Servidores MCP con Otros Clientes
- 3. Configuración del Cliente
- 4. Agregar Herramientas Personalizadas
- 5. Modo Distribuido (Protocolo A2A)
- 6. Pruebas
- 7. Arquitectura
- 8. RAG y Memoria de Conversación
- 9. Memoria Persistente y Agentes Proactivos
- 10. Patrones de Intención y Solución de Problemas
- Licencia
Prerrequisitos
- Python 3.12+
- 16GB+ de RAM recomendado
- Ollama instalado
1. Inicio Rápido
Instalar Dependencias
cd mcp-platform
python3 -m venv .venv
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows PowerShell
pip install -r requirements.txt
Nota sobre WSL2: crear
.venvdentro de/mnt/c/falla porque NTFS no admite los enlaces simbólicos Unix que requieren los venvs. Crea el venv en el sistema de archivos de Linux en su lugar:python3 -m venv /home/$USER/.virtualenvs/mcp_platform source /home/$USER/.virtualenvs/mcp_platform/bin/activate pip install -r requirements.txt
Backend LLM
curl -fsSL https://ollama.com/install.sh | sh
OLLAMA_HOST=0.0.0.0:11434 ollama serve
ollama pull qwen2.5:14b-instruct-q4_K_M # primary inference (recommended — any tool-calling model works)
ollama pull qwen2.5:0.5b # routing classifier (fast pre-flight intent detection)
ollama pull bge-large # RAG document embeddings
ollama pull qwen3-vl:8b-instruct # vision / image analysis
Alternativa GGUF: los archivos GGUF locales (por ejemplo, de Hugging Face) también funcionan: descarga el archivo y ejecuta
:gguf add <path_to_file>en el prompt. No se necesita editar configuración.Modelos sentence-transformers (embeddings de memoria + reranker RAG) se descargan automáticamente en el primer uso; no se necesita descarga manual.
Iniciar el Cliente
python client.py
Accede a la interfaz web en: http://localhost:9000/client/ui/index.html
2. Uso de Servidores MCP con Otros Clientes
Agrega a tu configuración de cliente MCP (por ejemplo, claude_desktop_config.json):
{
"mcpServers": {
"code_assistant": {
"command": "/path/to/mcp-platform/.venv/bin/python",
"args": ["/path/to/mcp-platform/servers/code_assistant/server.py"]
}
}
}
Rutas de Windows: "command": "C:\\path\\to\\mcp-platform\\.venv\\Scripts\\python.exe"
Servidores disponibles:
code_assistant- Análisis, generación y refactorización de código impulsado por IA (12 herramientas)code_review- Revisión de código, búsqueda y corrección de errores (3 herramientas)code_runner- Sandbox de ejecución de Python/bash (4 herramientas)discord- Notificaciones de canales de Discord mediante webhook (2 herramientas) ⚠️ RequiereDISCORD_WEBHOOK_URLgithub- Clonar, explorar y limpiar repositorios de GitHub (4 herramientas) ⚠️ RequiereGITHUB_TOKENpara repositorios privadosgoogle- Gmail + Google Calendar (13 herramientas) ⚠️ Requiere configuración de Google (Apps Script u OAuth — consulta Configuración de Google)image- Búsqueda, análisis y generación de imágenes con IA (6 herramientas) ⚠️ RequiereSERPER_API_KEYpara búsqueda; la generación es gratuitalocation- Clima, hora, ubicación (3 herramientas) — usa Open-Meteo (gratuito, sin clave); recurre a OpenWeatherMap siOPENWEATHER_API_KEYestá configuradoplex- Biblioteca multimedia + recomendaciones ML (18 herramientas) ⚠️ RequierePLEX_URL,PLEX_TOKENrag- Búsqueda y gestión vectorial (8 herramientas) ⚠️ Requiere Ollama +bge-largesystem- Información del sistema y procesos (3 herramientas)text- Procesamiento de texto y búsqueda web (8 herramientas)trilium- Integración de notas Trilium (11 herramientas) ⚠️ RequiereTRILIUM_URL,TRILIUM_TOKEN
3. Configuración del Cliente
Variables de Entorno
Crea .env en la raíz del proyecto:
# === LLM Backend ===
OLLAMA_BASE_URL=http://127.0.0.1:11434 # Use 127.0.0.1 for local; LAN IP requires OLLAMA_HOST=0.0.0.0 on the server
OLLAMA_VISION_MODEL=qwen3-vl:8b-instruct
MAX_MESSAGE_HISTORY=30
LLM_MESSAGE_WINDOW=15 # Sliding window of messages sent to LLM; older turns fall into Conversation RAG
LLM_TEMPERATURE=0.3
OLLAMA_NUM_CTX=8192 # KV cache / context window size
OLLAMA_NUM_PREDICT=4096 # Max tokens the LLM will generate per response
OLLAMA_REPEAT_PENALTY=1.1 # Penalise token repetition (1.0 = disabled)
IMAGE_MODEL=flux # Model used for AI image generation
# === GGUF Configuration ===
GGUF_GPU_LAYERS=-1
GGUF_CONTEXT_SIZE=4096
GGUF_BATCH_SIZE=512
# === API Keys ===
PLEX_URL=http://localhost:32400
PLEX_TOKEN=your_token_here
TRILIUM_URL=http://localhost:8888
TRILIUM_TOKEN=your_token_here
SHASHIN_BASE_URL=http://localhost:6624/
SHASHIN_API_KEY=your_key_here
SERPER_API_KEY=your_key_here
OLLAMA_TOKEN=your_token_here
LANGSEARCH_API_KEY=your_key_here # fallback search when Ollama weekly limit is reached
OPENWEATHER_API_KEY=your_key_here # fallback weather when Open-Meteo is unavailable
DISCORD_WEBHOOK_URL=your_webhook_url_here
# === Google Apps Script (alternative to OAuth for Gmail/Calendar) ===
# Paste PASTE_INTO_GOOGLE_APPS_SCRIPT.js into script.google.com, deploy as a Web App,
# then set the URL with your SECRET_KEY appended as ?key=...
# When set, ALL Google tools use the script instead of OAuth.
GOOGLE_APPS_SCRIPT_URL=https://script.google.com/macros/s/.../exec?key=<strong-random-secret-32-chars>
# === A2A Protocol ===
A2A_ENDPOINTS=http://localhost:8010
A2A_EXPOSED_TOOLS=
# === Performance Tuning ===
CONCURRENT_LIMIT=3
EMBEDDING_BATCH_SIZE=50
DB_FLUSH_BATCH_SIZE=50
# === Tool Control ===
DISABLED_TOOLS=plex:*
# === Location ===
DEFAULT_CITY=Vancouver
DEFAULT_STATE=BC
DEFAULT_COUNTRY=Canada
DEFAULT_TIMEZONE=America/Vancouver
Configuración Recomendada
ollama pull qwen2.5:14b-instruct-q4_K_M # primary inference — any tool-calling model works; 14b q4 is a good default
ollama pull qwen2.5:0.5b # routing classifier — small/fast; set LLM_ROUTING_MODEL=qwen2.5:0.5b in .env
ollama pull bge-large # required for RAG
ollama pull qwen3-vl:8b-instruct # required for image tools
# sentence-transformers models (memory embeddings + reranker) auto-download on first use
Archivos GGUF: descarga cualquier GGUF (por ejemplo, de Hugging Face) y ejecuta
:gguf add <path_to_file>en el prompt; no se necesita editar configuración.
.env mínimo para comenzar:
OLLAMA_VISION_MODEL=qwen3-vl:8b-instruct
DISABLED_TOOLS=plex:*,image_tools:shashin_analyze,shashin_random,shashin_search
OLLAMA_TOKEN=<token>
SERPER_API_KEY=<key>
Requisitos de Funciones
| Función | Variables de entorno requeridas | Configuración adicional |
|---|---|---|
| Gmail + Google Calendar | — | Configuración de Google — Apps Script (más simple) u OAuth (consulta Configuración de Google) |
| Notificaciones de Discord | DISCORD_WEBHOOK_URL | Crear webhook en la configuración del canal de Discord |
| Ingestión y búsqueda RAG | — | Ollama en ejecución + bge-large descargado |
| Biblioteca multimedia de Plex | PLEX_URL, PLEX_TOKEN | Plex Media Server en ejecución |
| Búsqueda web (principal) | OLLAMA_TOKEN | Cuenta de Ollama + clave API |
| Búsqueda web (respaldo) | LANGSEARCH_API_KEY | Cuenta de LangSearch — panel. Se usa automáticamente cuando se alcanza el límite semanal de Ollama o devuelve vacío. |
| Búsqueda de imágenes | SERPER_API_KEY | Cuenta de Serper + clave API |
| Generación de imágenes con IA | — | Gratis mediante Pollinations.ai — no se requiere clave |
| Notas Trilium | TRILIUM_URL, TRILIUM_TOKEN | Servidor Trilium en ejecución |
| Galería de fotos Shashin | SHASHIN_BASE_URL, SHASHIN_API_KEY | Servidor Shashin en ejecución |
| Modo distribuido A2A | A2A_ENDPOINTS | Servidor A2A remoto en ejecución |
Comandos Disponibles
:jobs - List all scheduled jobs
:jobs pause <label> - Pause a scheduled job
:jobs enable <label> - Resume a scheduled job
:jobs cancel <label> - Delete a scheduled job
:jobs info <label> - Show full job detail
:memory - List all memories
:memory semantic - List permanent memories only
:memory episodic - List session-derived memories
:memory forget <id> - Delete a memory by ID
:memory clear - Clear all episodic memories
:memory clear session <id> - Delete memories from one session
:memory consolidate <id> - Extract memories from a session now
:memory add <fact> - Manually add a permanent memory
:memory dedup - Remove duplicate memories
:commands - List all available commands
:clear sessions - Clear all chat history
:clear session <id> - Clear session
:sessions - List all sessions
:stop - Stop current operation
:stats - Show performance metrics
:tools - List available tools
:tools --all - Show all tools including disabled
:tool <n> - Get tool description
:model - List all available models
:model <n> - Switch to a model
:gguf add <path> - Register a GGUF model
:gguf remove <alias> - Remove a GGUF model
:gguf list - List registered GGUF models
:a2a on/off/status - Control A2A mode
:health - Health overview of all servers
:env - Show environment configuration
Configuración de Google
Opción A — Google Apps Script (configuración más simple)
- Ve a https://script.google.com y crea un nuevo proyecto
- Elimina todo el código existente y pega el contenido de
PASTE_INTO_GOOGLE_APPS_SCRIPT.js - Reemplaza
<SECRET_KEY>en el script con un secreto aleatorio fuerte (32+ caracteres). Genera uno:python3 -c "import secrets; print(secrets.token_urlsafe(32))"Cualquiera que conozca esta clave puede leer tus correos y calendario: trátala como una contraseña. - Haz clic en Implementar > Nueva implementación > Aplicación web
- Ejecutar como: Yo
- Quién tiene acceso: Cualquier persona
- Haz clic en Implementar y copia la URL de la aplicación web
- Agrega a
.env:GOOGLE_APPS_SCRIPT_URL=<Web App URL>?key=<your SECRET_KEY>
En ediciones posteriores del script, usa Implementar > Administrar implementaciones y edita la implementación existente; no crees una nueva o la URL cambiará.
Opción B — Google OAuth
Configuración única. Después de completar estos pasos, el servidor se ejecuta sin intervención.
- Ve a https://console.cloud.google.com/ y crea un proyecto
- Habilita Gmail API y Google Calendar API
- Crea un cliente de Aplicación de escritorio OAuth y descarga
credentials.json - Colócalo en
servers/google/credentials.json - Publica la aplicación en Producción — esto es obligatorio. Las aplicaciones en modo "Prueba" tienen tokens que expiran cada 7 días, lo que causa errores
invalid_granten trabajos programados - Ejecuta:
.venv/bin/python auth_google.py - Reinicia:
python client.py
Si el token expira más tarde: la plataforma lo detecta en la siguiente llamada de herramienta de Google y muestra un banner de reautorización en la interfaz con un enlace. Haz clic en el enlace, aprueba el acceso, pega el código de autorización en el chat — no se necesita reiniciar el servidor. Alternativamente, elimina servers/google/token.json y vuelve a ejecutar el paso 6.
4. Agregar Herramientas Personalizadas
Paso 1: Crea el archivo del servidor
mkdir servers/my_tool && touch servers/my_tool/server.py
Paso 2: Implementa con @tool_meta
from mcp.server.fastmcp import FastMCP
from tools.tool_control import check_tool_enabled
from client.tool_meta import tool_meta
mcp = FastMCP("my-tool-server")
@mcp.tool()
@check_tool_enabled(category="my_tool")
@tool_meta(
tags=["read", "search"],
triggers=["my keyword", "my phrase"],
template='use my_function: arg1=""',
)
def my_function(arg1: str) -> str:
"""Short description."""
return json.dumps({"content": f"Processed {arg1}"})
if __name__ == "__main__":
mcp.run(transport="stdio")
Reinicia el cliente y la herramienta estará activa: enrutada, etiquetada y registrada automáticamente.
Referencia de campos @tool_meta
| Campo | Requerido | Descripción |
|---|---|---|
tags | ✅ | Etiquetas de capacidades |
triggers | ✅ | Frases en lenguaje natural que enrutan a esta herramienta |
template | recomendado | Texto de relleno previo mostrado en el panel de herramientas |
text_fields | si es necesario | Campos de respuesta que contienen el texto principal |
rate_limit | no | "100/hour", "10/day", "ollama", o None |
idempotent | no | True si no tiene efectos secundarios (predeterminado: True) |
intent_category | no | Anula el nombre del grupo de enrutamiento |
Vocabulario de etiquetas
| Etiqueta | Significado |
|---|---|
read | Solo lee datos |
write | Crea o modifica datos |
destructive | Elimina o cambia datos de forma irreversible |
search | El propósito principal es la búsqueda |
external | Llama a una API externa |
vision | Procesa entrada de imágenes |
media | Opera sobre audio/video/imagen |
calendar | Interactúa con datos de calendario |
email | Interactúa con correo electrónico |
notes | Interactúa con sistemas de toma de notas |
code | Opera sobre código fuente |
system | Interactúa con el sistema operativo o hardware |
rag | Interactúa con el almacén vectorial RAG |
ai | Llama a un modelo LLM o ML |
Agregar Servidores MCP Externos
Crea external_servers.json en la raíz del proyecto:
{
"external_servers": {
"deepwiki": {
"transport": "sse",
"url": "https://mcp.deepwiki.com/mcp",
"enabled": true
}
}
}
Transportes admitidos: sse, http, stdio. Convención de variable de entorno para autenticación de encabezado:
ES_SERVERNAME_TOKEN=your_token_here
5. Modo Distribuido (Protocolo A2A)
python a2a_server.py # Terminal 1 — starts on http://localhost:8010
python client.py # Terminal 2
A2A_ENDPOINTS=http://localhost:8010,http://gpu-server:8020
A2A_EXPOSED_TOOLS=plex,location,text # empty = expose all
Todos los endpoints configurados se descubren y registran simultáneamente al inicio mediante asyncio.gather() — los tiempos de espera de conexión para endpoints inalcanzables ya no se bloquean entre sí.
6. Pruebas
Ejecutar Pruebas
Activa tu virtualenv primero, luego ejecuta desde la raíz del proyecto:
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows PowerShell
# WSL2: source /home/$USER/.virtualenvs/mcp_platform/bin/activate
python -m pytest # all tests
python -m pytest -m unit # fast unit tests only
python -m pytest -m integration # integration tests
python -m pytest -m e2e # end-to-end tests
python -m pytest --no-cov # skip coverage (faster)
python -m pytest -x # stop on first failure
python -m pytest -k "session" # filter by name
Estructura de Pruebas
tests/
├── conftest.py
├── unit/ <- fast isolated unit tests
├── integration/ <- multi-component tests
└── e2e/ <- full conversation tests
tests/results/ <- generated after running tests
├── junit.xml
├── coverage.xml
├── test-report.html
└── coverage-report.html
CI/CD
Las pruebas se ejecutan automáticamente en cada push y pull request mediante GitHub Actions (.github/workflows/ci.yml). En caso de fallo, se abre automáticamente un Issue de GitHub con un enlace a la ejecución fallida.
Para subir la cobertura a Codecov, agrega al flujo de trabajo:
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: results/coverage.xml
7. Arquitectura
Diseño Multi-Servidor
servers/
├── code_assistant/ 12 tools - AI-powered code analysis, generation, and refactoring
├── code_review/ 3 tools - Code review, search, and bug fixing
├── code_runner/ 4 tools - Python/bash execution sandbox
├── discord/ 2 tools - Discord channel notifications [requires DISCORD_WEBHOOK_URL]
├── github/ 4 tools - GitHub repo clone, browse, and cleanup
├── google/ 13 tools - Gmail + Google Calendar [requires Google setup]
├── image/ 6 tools - Image search, analysis, AI generation
├── location/ 3 tools - Weather, time, location
├── plex/ 18 tools - Media + ML recommendations [requires PLEX_URL + PLEX_TOKEN]
├── rag/ 8 tools - Vector search and management [requires Ollama + bge-large]
├── system/ 3 tools - System info and processes
├── text/ 8 tools - Text processing and web search
└── trilium/ 11 tools - Trilium notes integration [requires TRILIUM_URL + TRILIUM_TOKEN]
Total: 95 herramientas en 13 servidores
Concurrencia y Paralelismo
La plataforma usa asyncio.gather() en varias capas para ejecutar trabajo no LLM de forma concurrente:
| Capa | Qué se ejecuta en paralelo |
|---|---|
| Inicio — descubrimiento de servidores | Verificaciones de alcance TCP y sondeos OAuth para todos los servidores externos |
| Inicio — registro A2A | Todos los A2A_ENDPOINTS se descubren y registran simultáneamente |
| Ejecución de tareas multi-agente | Las tareas independientes (sin dependencias pendientes) se ejecutan como un lote concurrente en cada ciclo |
| Ejecución de subtareas A2A | Mismo lote agrupado con detección de dependencias que el modo multi-agente |
| Transmisión WebSocket | Todos los clientes conectados reciben mensajes mediante una sola llamada gather() |
Nota de hardware: La inferencia LLM se serializa en la capa GPU de Ollama independientemente de la concurrencia: una inferencia se ejecuta a la vez en una sola GPU. El beneficio del paralelismo está en el trabajo limitado por I/O: llamadas de herramientas HTTP, consultas de bases de datos y solicitudes de red. La ejecución LLM verdaderamente paralela requeriría múltiples GPUs o subagentes alojados en la nube.
Estructura de Directorios
mcp-platform/
├── servers/
├── a2a_server.py
├── client.py
├── client/
│ ├── ui/
│ ├── capability_registry.py <- auto-populated from @tool_meta
│ ├── langgraph.py
│ ├── memory_consolidator.py <- persistent cross-session memory
│ ├── proactive_agent.py <- scheduler + condition triggers
│ ├── query_patterns.py <- auto-populated from @tool_meta triggers
│ ├── tool_meta.py <- single source of truth for tool metadata
│ ├── websocket.py
│ └── ...
├── data/
│ ├── sessions.db <- session + message history
│ ├── memory.db <- persistent memory (created on first run)
│ └── scheduler.db <- scheduled jobs (created on first run)
└── tools/
8. RAG y Memoria de Conversación
Cómo funciona el contexto
Cada llamada LLM recibe contexto ensamblado en este orden exacto:
System prompt
├─ Persistent memory — top 5 by importance (always injected)
├─ Persistent memory — query-relevant (vector search, above threshold)
└─ Original system instructions + session ID
Conversation RAG — turns that scrolled out of the window (if relevant)
Message window — last LLM_MESSAGE_WINDOW turns (always injected)
Current user message
Cadena de búsqueda — qué sucede en cada consulta:
-
Memoria persistente (siempre) — las 5 memorias de mayor importancia se inyectan incondicionalmente en el prompt del sistema, independientemente de la relevancia de la consulta. Esto asegura que los hechos centrales (tu nombre, familia, preferencias) nunca se pierdan incluso cuando ninguna consulta obtiene buena puntuación. Se agregan memorias adicionales relevantes a la consulta mediante búsqueda vectorial.
-
Ventana de mensajes (siempre) — los últimos
LLM_MESSAGE_WINDOWturnos de la sesión actual se incluyen directamente como historial de conversación. -
RAG de conversación (siempre) — se ejecuta una búsqueda semántica contra los turnos que han salido de la ventana. Los resultados por encima del umbral del reranker se inyectan entre el prompt del sistema y el historial.
-
El LLM genera la respuesta usando todo lo anterior.
¿Cuándo te salva cada capa?
| Situación | Qué ayuda |
|---|---|
| Preguntado sobre algo de hace 3 mensajes | Ventana de mensajes |
| Preguntado sobre algo de hace 20 mensajes, misma sesión | RAG de conversación |
| Preguntado sobre tu nombre en una nueva sesión | Memoria persistente (ancla) |
| Preguntado sobre un hecho específico de una sesión antigua | Memoria persistente (relevante a la consulta) |
| Preguntado sobre un documento o artículo específico | RAG de conversación |
| Hecho aún no consolidado en memoria | Nada — dile a la plataforma nuevamente |
Ventana de conversación (LLM_MESSAGE_WINDOW)
Controla cuántos turnos recientes ve el LLM directamente. Se establece en .env:
LLM_MESSAGE_WINDOW=15 # default: 6, recommended: 15
Una ventana de 6 es demasiado ajustada para una conversación normal: la información compartida al inicio de una sesión se sale del contexto antes de que puedas preguntar sobre ella. 15 cubre un intercambio completo sin alcanzar los límites de tokens en qwen2.5:14b. Si compartes algo y el LLM parece olvidarlo unos mensajes después, aumenta este valor.
Ingestión de desbordamiento
Cuando el historial supera la ventana, los turnos más antiguos se ingieren automáticamente en la base de datos vectorial RAG como pares Human + Assistant. Siguen siendo buscables mediante similitud semántica incluso después de salir de la ventana.
Recuperación automática RAG
En cada mensaje, se ejecuta una búsqueda semántica contra todo el almacén RAG utilizando el mensaje actual del usuario como consulta. Los fragmentos coincidentes (de turnos de conversación antiguos, documentos ingeridos, subtítulos de Plex o investigaciones) se inyectan en el contexto automáticamente, sin necesidad de un desencadenante explícito de search rag.
Para qué sirve cada método de recuperación
| Tipo de consulta | Mejor herramienta |
|---|---|
| "¿Qué discutimos sobre X?" | Auto-RAG (semántico) |
| "¿Cuál fue mi primer mensaje?" | session_history_tool (base de datos ordenada) |
| "Resume esta sesión" | session_history_tool |
| "¿Qué decía ese artículo que leímos?" | Auto-RAG |
| "¿Qué pregunté hace 10 mensajes?" | session_history_tool |
session_history_tool
Añadido al servidor rag. Consulta la base de datos SQLite de la sesión directamente para obtener un historial de mensajes ordenado y con marca de tiempo. El ID de sesión actual siempre se inyecta en el mensaje del sistema para que el LLM pueda pasarlo automáticamente.
use session_history_tool: session_id="<id>" [limit="20"] [order="asc"]
Desencadenantes: first prompt, what did I ask, earlier in this session, summarise this session, session history
9. Memoria Persistente y Agentes Proactivos
Cómo funcionan juntos la memoria y el contexto
La plataforma tiene tres capas de contexto, cada una con un propósito diferente:
| Capa | Qué es | Alcance | ¿Sobrevive al borrado de sesión? |
|---|---|---|---|
| Ventana de mensajes | Últimos N turnos en el contexto directo del LLM | Sesión actual | No |
| RAG de conversación | Turnos antiguos ingeridos como vectores | Por sesión | No |
| Memoria persistente | Hechos destilados extraídos por el LLM | Todas las sesiones | Sí: sobrevive al borrado de sesión |
Qué significa esto en la práctica: Si le dices a la plataforma el nombre de tu hijo y preguntas sobre ello 3 mensajes después, la ventana de mensajes lo maneja. Si preguntas 20 mensajes después, RAG lo maneja (normalmente). Si inicias una nueva sesión mañana, solo la memoria persistente lo tiene.
El flujo de trabajo de la memoria
Paso 1: Mantén una conversación. Dile a la plataforma cosas que quieras que recuerde: tu nombre, tu familia, tus proyectos, tus preferencias. Cuanto más declarativo, mejor ("El nombre de mi esposa es Suzy" vs "¿cuál es el nombre de mi esposa?").
Paso 2: La memoria se extrae automáticamente. Después de 15 minutos de inactividad, el InactivityWatcher se activa y ejecuta el LLM sobre la transcripción de tu sesión. Extrae hechos y los almacena en data/memory.db con incrustaciones vectoriales.
La reconsolidación es inteligente: rastrea el recuento de mensajes en la última consolidación y solo se vuelve a ejecutar si se han añadido nuevos mensajes desde entonces. Pasar la noche inactivo activa una extracción, no docenas.
Paso 3: Las memorias se inyectan en cada consulta. En cada nuevo mensaje ocurren dos cosas: las 5 memorias de mayor importancia siempre se inyectan en el mensaje del sistema de forma incondicional (para que tu nombre, familia y preferencias clave nunca se olviden), luego una búsqueda vectorial encuentra memorias adicionales relevantes a la consulta y las añade encima. El bloque combinado se ve así:
## Persistent Memory (from past sessions)
The following facts are KNOWN and TRUE. Use them to answer directly.
◆ The user's name is Bob
○ Bob's wife is Suzy, a nurse and excellent cook
○ Bob's son Sam is 11, plays accordion, excels at hockey
...
Paso 4: Las memorias se acumulan con el tiempo. Las memorias episódicas accedidas 3 o más veces se promueven al nivel semántico (permanente) cada noche. La plataforma se vuelve más útil cuanto más la usas.
Cuando la memoria no se activa automáticamente
El vigilante de inactividad se activa 15 minutos después de tu último mensaje. Si necesitas que las memorias se extraigan inmediatamente:
:memory consolidate <session_id>
Usa :sessions para encontrar el ID de sesión. El comando borra la marca de consolidación y vuelve a ejecutar la extracción independientemente del recuento de mensajes.
Comandos de memoria
:memory — list all memories (sorted by relevance)
:memory semantic — permanent memories only
:memory episodic — session-derived memories
:memory forget <id> — delete one memory by ID
:memory clear — delete all episodic memories
:memory clear session <id> — delete memories from one session
:memory consolidate <id> — extract memories from a session now
:memory add <fact> — manually add a permanent memory
:memory dedup — remove duplicate memories
Las memorias añadidas manualmente (:memory add) se almacenan en el nivel semantic con importancia 1.0: siempre ocupan el primer lugar en la recuperación.
Si el LLM olvida algo a mitad de sesión
Aumenta LLM_MESSAGE_WINDOW en .env. El valor predeterminado de 6 es demasiado ajustado: se recomienda 15. La información compartida al inicio de una sesión se sale de la ventana antes de que puedas preguntar sobre ella.
Una vez que un turno sale de la ventana, pasa a RAG de conversación: sigue ahí, pero ahora se recupera por similitud semántica en lugar de contexto directo. Esto significa que la redacción de la consulta debe ser lo suficientemente cercana al contenido original para que el reranker la muestre. Si el LLM aún no puede encontrar algo que se dijo antes en la misma sesión, intenta reformular la pregunta usando las mismas palabras clave que la declaración original.
Por ejemplo: si dijiste "Mi hijo Sam toca el acordeón" y luego preguntas "¿Qué instrumento toca mi hijo?", la coincidencia semántica es fuerte. Pero "¿Y Sam?" es demasiado vago para que RAG devuelva con confianza el hecho del acordeón: sé específico.
Dos niveles de memoria
| Nivel | Cómo se crea | Persiste |
|---|---|---|
episodic | Extraído automáticamente de las sesiones | Permanente: sobrevive al borrado de sesión |
semantic | Promovido desde episódico (3+ accesos) o añadido mediante :memory add | Permanente |
El umbral de promoción es configurable: MEMORY_PROMOTE_THRESHOLD=3 en .env.
Borrar una sesión no borra sus memorias. Los fragmentos RAG se purgan, pero los hechos extraídos permanecen. Para eliminar memorias de una sesión específica antes de borrarla:
:memory clear session <id>
Lectura de la salida de :memory
PERSISTENT MEMORY
────────────────────────────────────────────────
[11] The user's name is Bob
◆ semantic | importance: 1.0 | accessed 163x
[22] Bob's wife Suzy is a nurse and an excellent cook.
○ episodic | importance: 0.9 | accessed 89x
Cada entrada muestra:
[id]— el ID de memoria, usado con:memory forget <id>para eliminarla- Contenido — el hecho extraído, escrito en inglés sencillo
◆ semantic/○ episodic— nivel: semántico (permanente) o episódico (derivado de la sesión)importance— puntuación asignada por el LLM en el momento de la extracción (0.0–1.0). Mayor = se recupera primeroaccessed Nx— cuántas veces se devolvió esta memoria en una búsqueda de recuperación. Un recuento alto de accesos impulsa la promoción de episódico a semántico
Las memorias se ordenan por puntuación de recuperación (combinación de similitud vectorial e importancia), no por ID o fecha de creación. Las memorias más relevantes para tu última consulta aparecen primero.
Las memorias duplicadas pueden aparecer cuando la misma sesión se consolida varias veces, o cuando dos sesiones contienen hechos similares. No causan errores (el LLM ve ambas), pero desperdician espacio de contexto. Límpialas con:
:memory dedup — removes exact duplicates, keeps highest importance copy
:memory forget <id> — removes near-duplicates manually (same fact, different wording)
Memoria Persistente
La plataforma recuerda hechos, preferencias y resultados entre sesiones. Después de que una sesión termina (o después de 15 minutos de inactividad), el LLM extrae información memorable de la transcripción y la almacena en data/memory.db. En cada nueva sesión, las memorias relevantes se inyectan automáticamente en el mensaje del sistema: no se requiere volver a explicar.
Automatización Agéntica (Programador de Agentes Proactivos)
La plataforma puede actuar de forma autónoma o semiautónoma: ejecutando herramientas según un horario, sondeando condiciones e involucrando al LLM para interpretar los resultados antes de mostrártelos. Esta es la base para cosas como resúmenes diarios, monitoreo de correos electrónicos y, eventualmente, flujos de trabajo totalmente autónomos.
Solo describe lo que quieres:
show me my daily briefing every day at 6am
check for new emails every 5 minutes and summarize anything urgent
show me a random photo from my gallery every morning with a short commentary
alert me when I have unread emails
run the weather check every morning at 7am and write a friendly summary
El LLM clasifica si tu mensaje es una solicitud de programación, analiza la intención en una definición de trabajo y la presenta para confirmación antes de guardar nada. Las solicitudes ambiguas se aclaran con una pregunta en lugar de adivinarse.
Tres tipos de desencadenantes:
| Tipo | Cómo funciona | Ejemplo |
|---|---|---|
cron | Se activa repetidamente en un horario fijo | "todos los días a las 6am" |
condition | Sondea una herramienta de verificación en un intervalo; se activa solo cuando la condición es verdadera | "cuando tenga correos no leídos" |
once | Se activa una vez en una fecha/hora específica y luego se elimina | "a las 8am hoy", "recuérdame mañana al mediodía" |
Participación del LLM: Cada trabajo puede tener un llm_prompt: una instrucción pasada al LLM después de que la herramienta se ejecuta. En lugar de transmitir la salida bruta de la herramienta, el LLM la interpreta primero:
shashin_random_tool+ "escribe un breve comentario sobre esta foto incluyendo la ubicación y el ambiente"gmail_get_unread+ "resume estos correos y marca cualquier cosa que necesite respuesta"get_weather_tool+ "escribe un resumen meteorológico matutino amigable"
Pipelines de herramientas: Encadena múltiples herramientas en secuencia usando >> en el llm_prompt. La salida de cada paso se pasa automáticamente al siguiente paso, sin necesidad de LLM:
use get_day_briefing >> use discord_notify
use gmail_get_unread >> use discord_notify
use calendar_get_today >> use discord_notify
Al programar, solo describe lo que quieres:
Every day at 7am, use get_day_briefing then use discord_notify to send me the result
At 8am today, use get_day_briefing then use discord_notify to send me today's schedule
El analizador del programador producirá automáticamente la sintaxis de cadena >>.
Las expresiones de condición tienen acceso a un contexto rico desde la salida JSON de la herramienta:
total_unread > 0 — fires when Gmail has unread emails
len_results > 0 — fires when a results list is non-empty
result > 10 — fires when a numeric value exceeds 10
result_len > 100 — fires when the raw output is non-trivial
Comandos de gestión de trabajos:
:jobs — list all scheduled jobs
:jobs pause <label> — pause a job without deleting it
:jobs enable <label> — resume a paused job
:jobs cancel <label> — delete a job permanently
:jobs info <label> — full job detail including cron and last run
Archivos:
client/memory_consolidator.py— extracción de memoria, almacenamiento, inyección y comandos:memoryclient/proactive_agent.py— programador, desencadenantes de condición, analizador de intención basado en LLM y comandos:jobs
Dependencia requerida:
pip install apscheduler
Archivos de datos (creados automáticamente en el primer inicio):
data/memory.db — persistent memory store
data/scheduler.db — scheduled jobs store (label, tool, cron, llm_prompt, etc.)
10. Patrones de Intención y Solución de Problemas
Patrones de Intención
El enrutamiento está impulsado por el clasificador LLM y triggers en el decorador @tool_meta de cada herramienta: no se requiere edición manual de patrones. Los desencadenantes admiten cadenas simples y patrones regex con prefijo r:.
Forzar una herramienta específica:
Using shashin_search_tool, find photos of Sam
Using web_image_search_tool, show me a red panda
Derivación conversacional: Las consultas que el LLM clasifica como conversacionales (saludos, opiniones, escritura creativa) omiten el enrutamiento de herramientas por completo: se responden desde el contexto sin herramientas vinculadas.
Detección de programación: El LLM determina si un mensaje es una solicitud de programación/automatización, sin listas de palabras clave. Si se detecta, se enruta al ScheduleParser en lugar del agente.
Solución de Problemas
Ollama no accesible / conexión rechazada:
- Usa
OLLAMA_BASE_URL=http://127.0.0.1:11434en.envpara acceso local - Para WSL2 + acceso LAN, inicia Ollama con
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Añade proxies de puerto de Windows (PowerShell elevado):
# Get WSL2 IP (use ip addr — hostname -I is not available on all WSL2 distros) $wslIp = (wsl -- ip -4 addr show eth0 | Select-String "inet ") -replace '.*inet (\d+\.\d+\.\d+\.\d+).*','$1' netsh interface portproxy add v4tov4 listenport=11434 listenaddress=0.0.0.0 connectport=11434 connectaddress=$wslIp New-NetFirewallRule -DisplayName "WSL2 Ollama" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 11434 - La IP de WSL2 cambia al reiniciar: vuelve a ejecutar los comandos portproxy anteriores después de cada reinicio
Los modelos de Ollama no aparecen:
- Si
OLLAMA_BASE_URLapunta a una IP LAN pero Ollama está vinculado solo a127.0.0.1, la lista de modelos estará vacía - Solución: inicia Ollama con
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Verifica:
ollama list
Interfaz web no accesible desde LAN:
La plataforma vincula los puertos 9000 (HTTP), 8765 y 8766 (WebSockets) a 0.0.0.0 dentro del proceso. Si otros dispositivos en tu LAN pueden alcanzar esos puertos depende de tu entorno.
Linux nativo:
- Debería funcionar sin configuración adicional: verifica que tu firewall permita tráfico entrante en esos puertos:
sudo ufw allow 9000 && sudo ufw allow 8765 && sudo ufw allow 8766
WSL2 en Windows:
WSL2 se ejecuta en una red virtual privada (172.x.x.x) que no es directamente enrutable desde tu LAN. Windows debe reenviar el tráfico LAN a WSL2 usando netsh portproxy. Ejecuta esto en un PowerShell elevado cada vez que la IP de WSL2 cambie (cambia en cada reinicio):
# 1. Find your current WSL2 IP (hostname -I is not available on all WSL2 distros)
$wslIp = (wsl -- ip -4 addr show eth0 | Select-String "inet ") -replace '.*inet (\d+\.\d+\.\d+\.\d+).*','$1'
Write-Host "WSL2 IP: $wslIp"
# 2. Add port forwarding for each port (both 0.0.0.0 and specific LAN IP — both are needed)
netsh interface portproxy add v4tov4 listenport=9000 listenaddress=0.0.0.0 connectport=9000 connectaddress=$wslIp
netsh interface portproxy add v4tov4 listenport=8765 listenaddress=0.0.0.0 connectport=8765 connectaddress=$wslIp
netsh interface portproxy add v4tov4 listenport=8766 listenaddress=0.0.0.0 connectport=8766 connectaddress=$wslIp
$lanIp = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -like "192.168.*" } | Select-Object -First 1).IPAddress
netsh interface portproxy add v4tov4 listenport=9000 listenaddress=$lanIp connectport=9000 connectaddress=$wslIp
netsh interface portproxy add v4tov4 listenport=8765 listenaddress=$lanIp connectport=8765 connectaddress=$wslIp
netsh interface portproxy add v4tov4 listenport=8766 listenaddress=$lanIp connectport=8766 connectaddress=$wslIp
# 3. Allow inbound through Windows Firewall (first time only)
New-NetFirewallRule -DisplayName "MCP Platform 9000" -Direction Inbound -Protocol TCP -LocalPort 9000 -Action Allow
New-NetFirewallRule -DisplayName "MCP Platform 8765" -Direction Inbound -Protocol TCP -LocalPort 8765 -Action Allow
New-NetFirewallRule -DisplayName "MCP Platform 8766" -Direction Inbound -Protocol TCP -LocalPort 8766 -Action Allow
# 4. Allow inbound through WSL2 firewall (first time only — run inside WSL2)
# sudo ufw allow 9000 && sudo ufw allow 8765 && sudo ufw allow 8766
Accede desde dispositivos LAN usando tu IP LAN de Windows (por ejemplo, 192.168.0.x), no la IP de WSL2:
http://192.168.0.x:9000/client/ui/index.html
Para encontrar tu IP LAN de Windows: ipconfig → busca la dirección 192.168.x.x en tu adaptador principal.
Consejo: si deja de funcionar después de un reinicio, solo vuelve a ejecutar los comandos
netsh interface portproxy add: las reglas del firewall persisten, pero la IP de WSL2 habrá cambiado. La interfaz web no carga localmente:
netstat -an | grep LISTEN # check ports 8765, 8766, 9000
La búsqueda web no devuelve resultados / respuestas vacías:
- El nivel gratuito de Ollama tiene un límite de uso semanal: cuando se alcanza, la API devuelve HTTP 200 con un cuerpo vacío
- La plataforma cambia automáticamente a LangSearch cuando esto ocurre
- Establece
LANGSEARCH_API_KEYen.envpara habilitar el respaldo (cuenta gratuita en https://langsearch.com/dashboard) - Para confirmar qué proveedor se está utilizando, revisa los registros en busca de
🔍 Ollama web searchfrente a🔍 LangSearch web search
Problemas con Google / OAuth:
- Confirma que
servers/google/token.jsonexiste; si no, vuelve a ejecutarauth_google.py - Confirma que la API de Gmail y la API de Calendar están habilitadas en Google Cloud Console
- Confirma que la aplicación OAuth está publicada en En producción (los tokens del modo de prueba caducan cada 7 días)
invalid_granten tiempo de ejecución: el banner de re-autenticación aparecerá automáticamente en la interfaz — sigue el enlace, aprueba, pega el código en el chat
RAG no funciona:
- Asegúrate de que Ollama esté en ejecución:
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Descarga bge-large si falta:
ollama pull bge-large
Auto-RAG devolviendo contexto irrelevante:
- Aumenta el umbral de
min_scoreen la llamada arag_search_tooldentro derun_agent - Verifica que el almacén RAG no esté contaminado:
use rag_browse_tool - Elimina una fuente:
use rag_delete_source_tool: source="<source>"
session_history_tool no encuentra mensajes:
- Confirma que el ID de sesión en el prompt del sistema coincida con la sesión activa
- Verifica que
data/sessions.dbexista y sea escribible
Las herramientas de Plex devuelven errores:
curl $PLEX_URL/identity?X-Plex-Token=$PLEX_TOKEN
El modelo GGUF no carga:
- Reduce las capas de GPU:
export GGUF_GPU_LAYERS=20 - Solo CPU:
export GGUF_GPU_LAYERS=0
El servidor A2A no se conecta:
curl http://localhost:8010/.well-known/agent-card.json
El historial de conversación no funciona:
- Cambia a un modelo más grande:
:model qwen2.5:14b-instruct-q4_K_M - Evita
qwen2.5:3b/qwen2.5:7bpara tareas que requieren contexto histórico
Las herramientas no aparecen:
:tools --all # check if disabled
# check DISABLED_TOOLS in .env
python client.py # restart
Licencia
Licencia MIT