MCP Platform
Runtime MCP local com orquestração multiagente, servidores de ferramentas distribuídos e recomendações de mídia baseadas em aprendizado de máquina.
Documentação
MCP Platform 
Runtime MCP local com orquestração multiagente, servidores de ferramentas distribuídos, recomendações de mídia com ML, memória persistente entre sessões e um agendador proativo de agentes.
⚠️ Experimental — destinado apenas para uso pessoal e experimental, não para implantação em produção.
Sumário
- Pré-requisitos
- 1. Início Rápido
- 2. Usando Servidores MCP com Outros Clientes
- 3. Configuração do Cliente
- 4. Adicionando Ferramentas Personalizadas
- 5. Modo Distribuído (Protocolo A2A)
- 6. Testes
- 7. Arquitetura
- 8. RAG e Memória de Conversa
- 9. Memória Persistente e Agentes Proativos
- 10. Padrões de Intenção e Solução de Problemas
- Licença
Pré-requisitos
- Python 3.12+
- 16GB+ de RAM recomendado
- Ollama instalado
1. Início Rápido
Instalar Dependências
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: criar
.venvdentro de/mnt/c/falha porque o NTFS não suporta os symlinks Unix que os venvs exigem. Crie o venv no sistema de arquivos Linux:python3 -m venv /home/$USER/.virtualenvs/mcp_platform source /home/$USER/.virtualenvs/mcp_platform/bin/activate pip install -r requirements.txt
Backend de 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: arquivos GGUF locais (ex.: do Hugging Face) também funcionam — baixe o arquivo e execute
:gguf add <path_to_file>no prompt. Não é necessário editar a configuração.Modelos sentence-transformers (embeddings de memória + reranker RAG) são baixados automaticamente no primeiro uso — não é necessário pull manual.
Iniciar o Cliente
python client.py
Acesse a interface web em: http://localhost:9000/client/ui/index.html
2. Usando Servidores MCP com Outros Clientes
Adicione à configuração do seu cliente MCP (ex.: 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"]
}
}
}
Caminhos do Windows: "command": "C:\\path\\to\\mcp-platform\\.venv\\Scripts\\python.exe"
Servidores disponíveis:
code_assistant- Análise, geração e refatoração de código com IA (12 ferramentas)code_review- Revisão de código, busca e correção de bugs (3 ferramentas)code_runner- Sandbox de execução Python/bash (4 ferramentas)discord- Notificações de canal Discord via webhook (2 ferramentas) ⚠️ RequerDISCORD_WEBHOOK_URLgithub- Clonar, navegar e limpar repositórios GitHub (4 ferramentas) ⚠️ RequerGITHUB_TOKENpara repositórios privadosgoogle- Gmail + Google Calendar (13 ferramentas) ⚠️ Requer configuração do Google (Apps Script ou OAuth — veja Configuração do Google)image- Busca de imagens, análise e geração com IA (6 ferramentas) ⚠️ RequerSERPER_API_KEYpara busca; geração é gratuitalocation- Clima, hora, localização (3 ferramentas) — usa Open-Meteo (gratuito, sem chave); usa OpenWeatherMap como fallback seOPENWEATHER_API_KEYestiver definidoplex- Biblioteca de mídia + recomendações com ML (18 ferramentas) ⚠️ RequerPLEX_URL,PLEX_TOKENrag- Busca e gerenciamento vetorial (8 ferramentas) ⚠️ Requer Ollama +bge-largesystem- Informações do sistema e processos (3 ferramentas)text- Processamento de texto e busca na web (8 ferramentas)trilium- Integração com notas Trilium (11 ferramentas) ⚠️ RequerTRILIUM_URL,TRILIUM_TOKEN
3. Configuração do Cliente
Variáveis de Ambiente
Crie .env na raiz do projeto:
# === 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
Configuração 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
Arquivos GGUF: baixe qualquer GGUF (ex.: do Hugging Face) e execute
:gguf add <path_to_file>no prompt — não é necessário editar a configuração.
.env mínimo para começar:
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 Recursos
| Recurso | Variáveis de ambiente necessárias | Configuração adicional |
|---|---|---|
| Gmail + Google Calendar | — | Configuração do Google — Apps Script (mais simples) ou OAuth (veja Configuração do Google) |
| Notificações do Discord | DISCORD_WEBHOOK_URL | Crie webhook nas configurações do canal Discord |
| Ingestão e busca RAG | — | Ollama em execução + bge-large baixado |
| Biblioteca de mídia Plex | PLEX_URL, PLEX_TOKEN | Plex Media Server em execução |
| Busca na web (principal) | OLLAMA_TOKEN | Conta Ollama + chave de API |
| Busca na web (fallback) | LANGSEARCH_API_KEY | Conta LangSearch — painel. Usado automaticamente quando o limite semanal do Ollama é atingido ou retorna vazio. |
| Busca de imagens | SERPER_API_KEY | Conta Serper + chave de API |
| Geração de imagens com IA | — | Gratuito via Pollinations.ai — sem chave necessária |
| Notas Trilium | TRILIUM_URL, TRILIUM_TOKEN | Servidor Trilium em execução |
| Galeria de fotos Shashin | SHASHIN_BASE_URL, SHASHIN_API_KEY | Servidor Shashin em execução |
| Modo distribuído A2A | A2A_ENDPOINTS | Servidor A2A remoto em execução |
Comandos Disponíveis
: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
Configuração do Google
Opção A — Google Apps Script (configuração mais simples)
- Acesse https://script.google.com e crie um novo projeto
- Exclua todo o código existente e cole o conteúdo de
PASTE_INTO_GOOGLE_APPS_SCRIPT.js - Substitua
<SECRET_KEY>no script por um segredo aleatório forte (32+ caracteres). Gere um:python3 -c "import secrets; print(secrets.token_urlsafe(32))"Qualquer pessoa que conheça esta chave pode ler seus e-mails e calendário — trate-a como uma senha. - Clique em Implantar > Nova implantação > Aplicativo web
- Executar como: Eu
- Quem tem acesso: Qualquer pessoa
- Clique em Implantar e copie a URL do Aplicativo Web
- Adicione ao
.env:GOOGLE_APPS_SCRIPT_URL=<Web App URL>?key=<your SECRET_KEY>
Em edições subsequentes do script, use Implantar > Gerenciar implantações e edite a implantação existente — não crie uma nova, ou a URL mudará.
Opção B — Google OAuth
Configuração única. Após concluir estas etapas, o servidor roda sem intervenção.
- Acesse https://console.cloud.google.com/ e crie um projeto
- Ative Gmail API e Google Calendar API
- Crie um cliente OAuth Desktop app e baixe
credentials.json - Coloque em
servers/google/credentials.json - Publique o aplicativo como Em produção — isso é obrigatório. Aplicativos em modo "Teste" têm tokens que expiram a cada 7 dias, causando erros
invalid_grantem tarefas agendadas - Execute:
.venv/bin/python auth_google.py - Reinicie:
python client.py
Se o token expirar depois: a plataforma detecta na próxima chamada de ferramenta do Google e mostra um banner de reautorização na interface com um link. Clique no link, aprove o acesso, cole o código de autorização no chat — não é necessário reiniciar o servidor. Alternativamente, exclua servers/google/token.json e execute novamente o passo 6.
4. Adicionando Ferramentas Personalizadas
Passo 1: Crie o arquivo do servidor
mkdir servers/my_tool && touch servers/my_tool/server.py
Passo 2: Implemente com @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")
Reinicie o cliente e a ferramenta estará ativa — roteada, marcada e registrada automaticamente.
Referência de campos @tool_meta
| Campo | Obrigatório | Descrição |
|---|---|---|
tags | ✅ | Tags de capacidade |
triggers | ✅ | Frases em linguagem natural que roteiam para esta ferramenta |
template | recomendado | Texto pré-preenchido mostrado no painel de ferramentas |
text_fields | se necessário | Campos de resposta que contêm o texto principal |
rate_limit | não | "100/hour", "10/day", "ollama" ou None |
idempotent | não | True se não tiver efeitos colaterais (padrão: True) |
intent_category | não | Substitui o nome do grupo de roteamento |
Vocabulário de tags
| Tag | Significado |
|---|---|
read | Apenas leitura de dados |
write | Cria ou modifica dados |
destructive | Exclui ou altera dados de forma irreversível |
search | Propósito principal é busca |
external | Chama uma API externa |
vision | Processa entrada de imagem |
media | Opera em áudio/vídeo/imagem |
calendar | Interage com dados de calendário |
email | Interage com e-mail |
notes | Interage com sistemas de anotações |
code | Opera em código-fonte |
system | Interage com SO ou hardware |
rag | Interage com o armazenamento vetorial RAG |
ai | Chama um LLM ou modelo de ML |
Adicionando Servidores MCP Externos
Crie external_servers.json na raiz do projeto:
{
"external_servers": {
"deepwiki": {
"transport": "sse",
"url": "https://mcp.deepwiki.com/mcp",
"enabled": true
}
}
}
Transportes suportados: sse, http, stdio. Convenção de variável de ambiente para autenticação por cabeçalho:
ES_SERVERNAME_TOKEN=your_token_here
5. Modo Distribuído (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 os endpoints configurados são descobertos e registrados simultaneamente na inicialização via asyncio.gather() — timeouts de conexão para endpoints inacessíveis não bloqueiam mais uns aos outros.
6. Testes
Executando Testes
Ative seu virtualenv primeiro e execute a partir da raiz do projeto:
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
Estrutura de Testes
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
Os testes são executados automaticamente a cada push e pull request via GitHub Actions (.github/workflows/ci.yml). Em caso de falha, uma Issue do GitHub é aberta automaticamente com um link para a execução com falha.
Para enviar cobertura ao Codecov, adicione ao workflow:
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: results/coverage.xml
7. Arquitetura
Design 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 ferramentas em 13 servidores
Concorrência e Paralelismo
A plataforma usa asyncio.gather() em várias camadas para executar trabalho não-LLM simultaneamente:
| Camada | O que roda em paralelo |
|---|---|
| Inicialização — descoberta de servidores | Verificações de alcance TCP e sondas OAuth para todos os servidores externos |
| Inicialização — registro A2A | Todos os A2A_ENDPOINTS são descobertos e registrados simultaneamente |
| Execução de tarefas multiagente | Tarefas independentes (sem dependências não atendidas) rodam como um lote concorrente a cada ciclo |
| Execução de subtarefas A2A | Mesmo agrupamento concorrente com consciência de dependências do modo multiagente |
| Transmissão WebSocket | Todos os clientes conectados recebem mensagens via uma única chamada gather() |
Nota de hardware: a inferência de LLM serializa na camada GPU do Ollama independentemente da concorrência — uma inferência roda por vez em uma única GPU. O benefício do paralelismo está no trabalho limitado por I/O: chamadas de ferramentas HTTP, consultas de banco de dados e requisições de rede. Execução LLM verdadeiramente paralela exigiria múltiplas GPUs ou subagentes hospedados em nuvem.
Estrutura de Diretórios
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 e Memória de Conversa
Como o contexto funciona
Cada chamada de LLM recebe contexto montado nesta ordem exata:
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
Cadeia de consulta — o que acontece a cada pergunta:
-
Memória persistente (sempre) — as 5 memórias de maior importância são injetadas incondicionalmente no prompt do sistema, independentemente da relevância da consulta. Isso garante que fatos essenciais (seu nome, família, preferências) nunca sejam perdidos, mesmo quando nenhuma consulta pontua bem. Memórias adicionais relevantes à consulta são adicionadas por cima via busca vetorial.
-
Janela de mensagens (sempre) — as últimas
LLM_MESSAGE_WINDOWturnos da sessão atual são incluídas diretamente como histórico de conversa. -
RAG de conversa (sempre) — uma busca semântica é executada contra turnos que saíram da janela. Resultados acima do limite do reranker são injetados entre o prompt do sistema e o histórico.
-
O LLM gera a resposta usando tudo acima.
Quando cada camada ajuda você:
| Situação | O que ajuda |
|---|---|
| Perguntou sobre algo de 3 mensagens atrás | Janela de mensagens |
| Perguntou sobre algo de 20 mensagens atrás, mesma sessão | RAG de conversa |
| Perguntou seu nome em uma nova sessão | Memória persistente (âncora) |
| Perguntou um fato específico de uma sessão antiga | Memória persistente (relevante à consulta) |
| Perguntou sobre um documento ou artigo específico | RAG de conversa |
| Fato ainda não consolidado na memória | Nada — informe a plataforma novamente |
Janela de conversa (LLM_MESSAGE_WINDOW)
Controla quantas interações recentes o LLM vê diretamente. Definido em .env:
LLM_MESSAGE_WINDOW=15 # default: 6, recommended: 15
Uma janela de 6 é apertada demais para conversas normais — informações compartilhadas no início de uma sessão saem da janela antes que você possa perguntar sobre elas. 15 cobre uma troca completa sem atingir os limites de tokens no qwen2.5:14b. Se você compartilhar algo e o LLM parecer esquecer algumas mensagens depois, aumente esse valor.
Ingestão de excedente
Quando o histórico excede a janela, interações mais antigas são automaticamente ingeridas no banco de vetores RAG como pares Human + Assistant. Elas permanecem pesquisáveis por similaridade semântica mesmo após saírem da janela.
Recuperação automática via RAG
A cada mensagem, uma busca semântica é executada contra todo o armazenamento RAG usando a mensagem atual do usuário como consulta. Trechos correspondentes (de interações antigas, documentos ingeridos, legendas do Plex ou pesquisas) são injetados no contexto automaticamente — sem necessidade de gatilho explícito de search rag.
Para que serve cada método de recuperação
| Tipo de consulta | Melhor ferramenta |
|---|---|
| "O que discutimos sobre X?" | Auto-RAG (semântico) |
| "Qual foi meu primeiro prompt?" | session_history_tool (banco ordenado) |
| "Resuma esta sessão" | session_history_tool |
| "O que tinha naquele artigo que lemos?" | Auto-RAG |
| "O que perguntei há 10 mensagens?" | session_history_tool |
session_history_tool
Adicionado ao servidor rag. Consulta o banco SQLite da sessão diretamente para obter histórico de mensagens ordenado e com carimbo de data/hora. O ID da sessão atual é sempre injetado no prompt do sistema para que o LLM possa passá-lo automaticamente.
use session_history_tool: session_id="<id>" [limit="20"] [order="asc"]
Gatilhos: first prompt, what did I ask, earlier in this session, summarise this session, session history
9. Memória Persistente e Agentes Proativos
Como memória e contexto funcionam juntos
A plataforma tem três camadas de contexto, cada uma com um propósito diferente:
| Camada | O que é | Escopo | Sobrevive à exclusão da sessão? |
|---|---|---|---|
| Janela de mensagens | Últimas N interações no contexto direto do LLM | Sessão atual | Não |
| RAG de conversa | Interações antigas ingeridas como vetores | Por sessão | Não |
| Memória persistente | Fatos destilados extraídos pelo LLM | Todas as sessões | Sim — sobrevive à exclusão da sessão |
O que isso significa na prática: Se você disser o nome do seu filho à plataforma e perguntar sobre isso 3 mensagens depois, a janela de mensagens resolve. Se perguntar 20 mensagens depois, o RAG resolve (geralmente). Se iniciar uma nova sessão amanhã, apenas a memória persistente terá essa informação.
O fluxo de memória
Passo 1 — Tenha uma conversa. Diga à plataforma coisas que você quer que ela lembre: seu nome, sua família, seus projetos, suas preferências. Quanto mais declarativo, melhor ("O nome da minha esposa é Suzy" vs "qual é o nome da minha esposa?").
Passo 2 — A memória extrai automaticamente. Após 15 minutos de inatividade, o InactivityWatcher dispara e executa o LLM sobre a transcrição da sua sessão. Ele extrai fatos e os armazena em data/memory.db com embeddings vetoriais.
A reconsolidação é inteligente: ela rastreia a contagem de mensagens na última consolidação e só reexecuta se novas mensagens foram adicionadas desde então. Ficar inativo durante a noite dispara uma extração, não dezenas.
Passo 3 — Memórias são injetadas em cada consulta. A cada nova mensagem, duas coisas acontecem: as 5 memórias de maior importância são sempre injetadas no prompt do sistema incondicionalmente (para que seu nome, família e preferências principais nunca sejam esquecidos), e então uma busca vetorial encontra memórias adicionais relevantes à consulta e as adiciona por cima. O bloco combinado fica assim:
## 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
...
Passo 4 — Memórias se acumulam com o tempo. Memórias episódicas acessadas 3+ vezes são promovidas ao nível semântico (permanente) durante a noite. A plataforma fica mais útil quanto mais você a usa.
Quando a memória não dispara automaticamente
O observador de inatividade dispara 15 minutos após sua última mensagem. Se você precisar que as memórias sejam extraídas imediatamente:
:memory consolidate <session_id>
Use :sessions para encontrar o ID da sessão. O comando limpa o sinalizador de consolidação e reexecuta a extração independentemente da contagem de mensagens.
Comandos de memória
: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
Memórias adicionadas manualmente (:memory add) são armazenadas no nível semantic com importância 1.0 — elas sempre ficam em primeiro lugar na recuperação.
Se o LLM esquecer algo no meio da sessão
Aumente LLM_MESSAGE_WINDOW em .env. O padrão de 6 é apertado demais — 15 é recomendado. Informações compartilhadas no início de uma sessão saem da janela antes que você possa perguntar sobre elas.
Quando uma interação sai da janela, ela passa para o RAG de conversa — ela ainda está lá, mas agora é recuperada por similaridade semântica em vez de contexto direto. Isso significa que a formulação da consulta precisa estar próxima o suficiente do conteúdo original para que o reranker a encontre. Se o LLM ainda não conseguir encontrar algo que foi dito antes na mesma sessão, tente reformular a pergunta usando as mesmas palavras-chave da declaração original.
Por exemplo: se você disse "Meu filho Sam toca acordeão" e depois perguntar "Qual instrumento meu filho toca?", a correspondência semântica é forte. Mas "E o Sam?" é vago demais para o RAG retornar com confiança o fato do acordeão — seja específico.
Dois níveis de memória
| Nível | Como é criado | Persiste |
|---|---|---|
episodic | Extraído automaticamente das sessões | Permanente — sobrevive à exclusão da sessão |
semantic | Promovido do episódico (3+ acessos) ou adicionado via :memory add | Permanente |
O limite de promoção é configurável: MEMORY_PROMOTE_THRESHOLD=3 em .env.
Excluir uma sessão não exclui suas memórias. Os trechos RAG são removidos, mas os fatos extraídos permanecem. Para remover memórias de uma sessão específica antes de excluí-la:
:memory clear session <id>
Lendo a saída 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 mostra:
[id]— o ID da memória, usado com:memory forget <id>para excluí-la- Conteúdo — o fato extraído, escrito em inglês simples
◆ semantic/○ episodic— nível: semântico (permanente) ou episódico (derivado da sessão)importance— pontuação atribuída pelo LLM no momento da extração (0.0–1.0). Maior = recuperado primeiroaccessed Nx— quantas vezes esta memória foi retornada em uma busca de recuperação. Alta contagem de acessos impulsiona a promoção de episódico → semântico
Memórias são ordenadas por pontuação de recuperação (combinação de similaridade vetorial e importância), não por ID ou data de criação. As memórias mais relevantes para sua última consulta aparecem primeiro.
Memórias duplicadas podem aparecer quando a mesma sessão é consolidada várias vezes, ou quando duas sessões contêm fatos semelhantes. Elas não causam erros — o LLM vê ambas — mas desperdiçam espaço de contexto. Limpe-as com:
:memory dedup — removes exact duplicates, keeps highest importance copy
:memory forget <id> — removes near-duplicates manually (same fact, different wording)
Memória Persistente
A plataforma lembra fatos, preferências e resultados entre sessões. Após o término de uma sessão (ou após 15 minutos de inatividade), o LLM extrai informações memoráveis da transcrição e as armazena em data/memory.db. Em cada nova sessão, memórias relevantes são injetadas no prompt do sistema automaticamente — sem necessidade de reexplicar.
Automação Agêntica (Agendador de Agentes Proativos)
A plataforma pode agir de forma autônoma ou semiautônoma — executando ferramentas em um cronograma, verificando condições e envolvendo o LLM para interpretar resultados antes de apresentá-los a você. Esta é a base para coisas como resumos diários, monitoramento de e-mail e, eventualmente, fluxos de trabalho totalmente autônomos.
Basta descrever o que você quer:
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
O LLM classifica se sua mensagem é uma solicitação de agendamento, interpreta a intenção em uma definição de tarefa e a apresenta para confirmação antes de salvar qualquer coisa. Solicitações ambíguas são esclarecidas com uma pergunta em vez de adivinhar.
Três tipos de gatilho:
| Tipo | Como funciona | Exemplo |
|---|---|---|
cron | Dispara repetidamente em um cronograma fixo | "todos os dias às 6h" |
condition | Verifica uma ferramenta de checagem em um intervalo; dispara apenas quando a condição é verdadeira | "quando eu tiver e-mails não lidos" |
once | Dispara uma vez em uma data/hora específica e depois se remove | "hoje às 8h", "me lembre amanhã ao meio-dia" |
Envolvimento do LLM: Cada tarefa pode ter um llm_prompt — uma instrução passada ao LLM após a execução da ferramenta. Em vez de transmitir a saída bruta da ferramenta, o LLM a interpreta primeiro:
shashin_random_tool+ "escreva um breve comentário sobre esta foto incluindo local e clima"gmail_get_unread+ "resuma estes e-mails e sinalize qualquer coisa que precise de resposta"get_weather_tool+ "escreva um resumo meteorológico matinal amigável"
Pipelines de ferramentas: Encadeie várias ferramentas em sequência usando >> no llm_prompt. A saída de cada etapa é automaticamente passada para a próxima — sem necessidade de LLM:
use get_day_briefing >> use discord_notify
use gmail_get_unread >> use discord_notify
use calendar_get_today >> use discord_notify
Ao agendar, basta descrever o que você quer:
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
O analisador do agendador produzirá automaticamente a sintaxe de cadeia >>.
Expressões de condição têm acesso a contexto rico da saída JSON da ferramenta:
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 gerenciamento de tarefas:
: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
Arquivos:
client/memory_consolidator.py— extração, armazenamento, injeção de memória e comandos:memoryclient/proactive_agent.py— agendador, gatilhos de condição, analisador de intenção baseado em LLM e comandos:jobs
Dependência necessária:
pip install apscheduler
Arquivos de dados (criados automaticamente na primeira execução):
data/memory.db — persistent memory store
data/scheduler.db — scheduled jobs store (label, tool, cron, llm_prompt, etc.)
10. Padrões de Intenção e Solução de Problemas
Padrões de Intenção
O roteamento é conduzido pelo classificador LLM e triggers no decorador @tool_meta de cada ferramenta — sem necessidade de edição manual de padrões. Os gatilhos suportam strings simples e padrões regex prefixados com r:.
Forçar uma ferramenta específica:
Using shashin_search_tool, find photos of Sam
Using web_image_search_tool, show me a red panda
Desvio conversacional: Consultas que o LLM classifica como conversacionais (saudações, opiniões, escrita criativa) ignoram o roteamento de ferramentas completamente — respondidas a partir do contexto sem ferramentas vinculadas.
Detecção de agendamento: O LLM determina se uma mensagem é uma solicitação de agendamento/automação — sem listas de palavras-chave. Se detectado, é roteado para o ScheduleParser em vez do agente.
Solução de Problemas
Ollama inacessível / conexão recusada:
- Use
OLLAMA_BASE_URL=http://127.0.0.1:11434em.envpara acesso local - Para WSL2 + acesso LAN, inicie o Ollama com
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Adicione proxies de porta do 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 - O IP do WSL2 muda na reinicialização — reexecute os comandos portproxy acima após cada reinicialização
Modelos do Ollama não aparecendo:
- Se
OLLAMA_BASE_URLaponta para um IP LAN, mas o Ollama está vinculado apenas a127.0.0.1, a lista de modelos ficará vazia - Correção: inicie o Ollama com
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Verifique:
ollama list
Interface web não acessível pela LAN:
A plataforma vincula as portas 9000 (HTTP), 8765 e 8766 (WebSockets) a 0.0.0.0 dentro do processo. Se outros dispositivos na sua LAN podem alcançar essas portas depende do seu ambiente.
Linux nativo:
- Deve funcionar imediatamente — verifique se seu firewall permite entrada nessas portas:
sudo ufw allow 9000 && sudo ufw allow 8765 && sudo ufw allow 8766
WSL2 no Windows:
O WSL2 roda em uma rede virtual privada (172.x.x.x) que não é diretamente roteável a partir da sua LAN. O Windows deve encaminhar o tráfego da LAN para o WSL2 usando netsh portproxy. Execute isso em um PowerShell elevado toda vez que o IP do WSL2 mudar (ele muda a cada reinicialização):
# 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
Acesse a partir de dispositivos LAN usando seu IP LAN do Windows (ex.: 192.168.0.x), não o IP do WSL2:
http://192.168.0.x:9000/client/ui/index.html
Para encontrar seu IP LAN do Windows: ipconfig → procure o endereço 192.168.x.x no seu adaptador principal.
Dica: se parar de funcionar após uma reinicialização, basta reexecutar os comandos
netsh interface portproxy add— as regras de firewall persistem, mas o IP do WSL2 terá mudado. A interface web não carrega localmente:
netstat -an | grep LISTEN # check ports 8765, 8766, 9000
Pesquisa na web retornando sem resultados / respostas vazias:
- O nível gratuito do Ollama tem um limite de uso semanal — quando atingido, a API retorna HTTP 200 com corpo vazio
- A plataforma automaticamente recorre ao LangSearch quando isso acontece
- Defina
LANGSEARCH_API_KEYem.envpara habilitar o fallback (conta gratuita em https://langsearch.com/dashboard) - Para confirmar qual provedor está sendo usado, verifique os logs para
🔍 Ollama web searchvs🔍 LangSearch web search
Problemas com Google / OAuth:
- Confirme se
servers/google/token.jsonexiste — se não, execute novamenteauth_google.py - Confirme se a API do Gmail e a API do Google Agenda estão habilitadas no Google Cloud Console
- Confirme se o aplicativo OAuth está publicado como Em produção (tokens do modo de teste expiram a cada 7 dias)
invalid_grantem tempo de execução: o banner de reautenticação aparecerá automaticamente na interface — siga o link, aprove, cole o código no chat
RAG não funcionando:
- Garanta que o Ollama esteja em execução:
OLLAMA_HOST=0.0.0.0:11434 ollama serve - Baixe o bge-large se estiver faltando:
ollama pull bge-large
Auto-RAG retornando contexto irrelevante:
- Aumente o limite de
min_scorena chamadarag_search_tooldentro derun_agent - Verifique se o armazenamento RAG não está poluído:
use rag_browse_tool - Remova uma fonte:
use rag_delete_source_tool: source="<source>"
session_history_tool não encontrando mensagens:
- Confirme se o ID da sessão no prompt do sistema corresponde à sessão ativa
- Verifique se
data/sessions.dbexiste e é gravável
Ferramentas Plex retornando erros:
curl $PLEX_URL/identity?X-Plex-Token=$PLEX_TOKEN
O modelo GGUF não carrega:
- Reduza as camadas de GPU:
export GGUF_GPU_LAYERS=20 - Somente CPU:
export GGUF_GPU_LAYERS=0
Servidor A2A não conectando:
curl http://localhost:8010/.well-known/agent-card.json
Histórico de conversa não funcionando:
- Mude para um modelo maior:
:model qwen2.5:14b-instruct-q4_K_M - Evite
qwen2.5:3b/qwen2.5:7bpara tarefas que dependem do histórico
Ferramentas não aparecendo:
:tools --all # check if disabled
# check DISABLED_TOOLS in .env
python client.py # restart
Licença
Licença MIT