| name | mastermind-system-ops |
| description | Usar al operar Mastermind en Windows: repo, ChromaDB, crons, gateway Telegram (colapso por token revocado y su restauración), backups y monitorización. |
| version | 1.0.0 |
| tags | ["mastermind","sistema","chromadb","cron","windows","nan"] |
Mastermind System Ops (v4.2, Windows local)
Sistema personal de agente IA de David Antizar: Hermes Desktop (Windows) + NaN.builders + ChromaDB + GitHub (Ntizar/MasterMind). Este skill cubre SU OPERACIÓN en el PC local — la documentación de producto vive en el repo (README.md, mastermind/stars-explorer.md).
Ubicaciones reales
| Qué | Dónde |
|---|
| Repo (fuente de verdad) | C:\Users\d_ant\Projects\MasterMind → github.com/Ntizar/MasterMind |
| Instalación Hermes | %LOCALAPPDATA%\hermes\ (config.yaml, .env, skills/, memories/) |
| ChromaDB | ~/.mastermind/chromadb — colección mastermind-skills, embebida sin servidor |
| Gateway | Autoarranque al login (Hermes_Gateway.vbs en Startup); comprobar con hermes gateway status |
Estructura del repo: agent/ (skills + memories + SOUL.md), scripts/ (motor), notes/, data/ (stars-registry.json), mastermind/ (docs internos).
Python: cuál usar (PITFALL #1)
El python del PATH es el venv de Hermes y NO tiene pip ni chromadb. Para ChromaDB y scripts del motor usar SIEMPRE:
C:/Users/d_ant/AppData/Local/Programs/Python/Python312/python.exe
NaN API: User-Agent obligatorio (PITFALL #2)
Toda petición Python (urllib/requests) a api.nan.builders/v1 sin header User-Agent custom devuelve 403 (curl sí funciona sin él). Añadir p.ej. "User-Agent": "MastermindIndexer/2.0" a cada Request.
Modelos disponibles en NaN (verificado 2026-08-31, /v1/models + smoke test): qwen3.8-flash (vivo; default global hasta 2026-09-03, sigue pinneado en los crons), deepseek-v4-flash (vivo, el más barato; nuevo default global desde 2026-09-03 — smoke test de texto+tools+visión superado), glm5.3-flash (vivo hasta agotar cuota), qwen3.6 (vivo), qwen3-embedding (embeddings, dim 4096, coseno, threshold score > 0.25); minimax-h3 responde 401 = fuera de plan. Cambiar modelo global: hermes config set model.default <modelo> — AVISA de los crons "unpinned" con model_snapshot divergente que fallarán closed: re-anclar TODOS con hermes cron edit <job_id> --model <m> --provider openai-api (ambas flags obligatorias juntas).
PITFALL #2c — crons qwen3.6 caen por protocolo /responses (verificado 2026-09-05): el overlay built-in del provider openai-api fuerza transport=codex_responses, así que Hermes le habla a NaN por la Responses API (/v1/responses). Para qwen3.6 NaN NO emite el evento de cierre response.output_text.done → Hermes reconstruye el message vacío → RuntimeError: Invalid API response after 3 retries (el cron de 08:00/22:00/04:30 falló en cadena; los crons con qwen3.8-flash/deepseek en la misma conexión NO caen). Fix: hermes config set model.api_mode chat_completions (campo reconocido; el fallback para provider no-custom lo honra). Verificar en logs/agent.log el cambio de codex_stream_request → chat_completion_request. Diagnóstico completo + evidencia en references/nan-api-qwen36-responses-protocol.md. NO es límite de tokens ni cómo se escribe el modelo (qwen3.6 es id válido; el id interno ...-nvfp4 da 401).
PITFALL #2b — cuota de glm5.3 y fallos silenciosos de cron (verificado 2026-08-31): glm5.3-flash agota los tokens del mes ANTES de fin de mes → los crons asignados a él revientan con RuntimeError: HTTP 402. Como el digest/scout/doctor entregan a local, el error queda enterrado en cron/output/<job_id>/*.md sin aviso (ese día fallaron 3 de 9 crons sin que nadie se enterara). Reglas: 1) ante 402/429 el fallback natural es reintentar/anclar a qwen3.8-flash; 2) para detectar fallos ajenos, revisar cronjob list (campo last_status) o leer las salidas en cron/output/ — un error de modelo NO es un fallo del script, comprobar el .md del run antes de tocar el prompt; 3) solución activa: job vigia-cron (no_agent, cada 30min, deliver telegram) con scripts/vigia-cron.py de esta skill — lee jobs.json, alerta SOLO fallos NUEVOS (estado no-ok + hash name|last_run_at en vigia-estado.json; stdout vacío = silencio). Si se pierde el job: hermes cron create "*/30 * * * *" --name vigia-cron --no-agent --script vigia-cron.py --deliver telegram (--script exige ruta RELATIVA a ~/.hermes/scripts/, un path absoluto falla en silencio).
Gateway caído / token de Telegram revocado (verificado 2026-09-02)
Síntoma del colapso: hermes gateway status = "No gateway process" y en logs/gateway.log: Telegram bot token rejected ... non-retryable startup conflict → el gateway MUERE al arrancar si el token es inválido (no reintenta). Los crons siguen corriendo y entregando error Telegram send failed: Unauthorized. Diagnóstico en 3 comandos: hermes gateway status, tail gateway.log, curl -s https://api.telegram.org/bot<TOKEN>/getMe (401 = token revocado → David pide /token o /newbot en @BotFather).
Procedimiento de restauración del bot: 1) validar token nuevo contra getMe ANTES de nada; 2) escribirlo en .env (TELEGRAM_BOT_TOKEN); 3) hermes gateway restart; 4) verificar telegram connected en gateway.log; 5) prueba REAL de entrega con sendMessage al chat 7288273982. Si el bot es NUEVO (@/newbot), David debe hacer /start al bot antes de que pueda escribirle, y re-añadirlo al grupo -1004341345827.
Pitfalls: a) getUpdates devuelve 409 "terminated by other getUpdates" cuando el gateway ya está polling — es BUENA señal, no borrar nada; b) curl desde git-bash con emojis/tildes a Telegram da strings must be encoded in UTF-8 — usar texto plano ASCII o python; c) copiar un token viejo de BotFather parece nuevo pero la ID de bot (prefijo numérico) delata: bot nuevo = prefijo nuevo; d) NUNCA pegar el token en chat sin verificar — puede ser el revocado.
Watchdog (auto-curación): tarea Hermes_Gateway_Watchdog del Task Scheduler (cada 10 min, scripts/vigia-gateway.ps1 en instalación + repo scripts/) — si no hay gateway: lo relanza, comprueba en gateway.log si "telegram connected", escribe en logs/vigia-gateway.log y AVISA por Telegram (lee token del .env; si Telegram no conecta, el aviso alerta del token revocado). Vive FUERA del gateway a propósito: un cron de Hermes no puede vigilar al gateway que lo ejecuta. Registro: powershell -File scripts/registrar-vigia-gateway.ps1.
Cambiar el modelo global del bot: flujo de reinicio (verificado 2026-09-03)
hermes config set model.default <m> NO relee el proceso gateway en marcha: el bot sigue con el modelo que leyó al arrancar; solo las sesiones nuevas tras un reinicio estrenan el default. Flujo completo para "que lo estrene ya":
- Smoke test ANTES de cambiar contra
https://api.nan.builders/v1/chat/completions (con User-Agent custom): texto, tools+tool_choice (el bot sin tool calling no opera) y visión — imagen 1×1 roja como data URL PNG en un content part image_url, preguntar el color y esperar «Rojo». Catálogo: GET /v1/models.
- Reiniciar: el gateway es un login item (.vbs), NO un servicio —
hermes gateway restart no lo gestiona. Matar PID: taskkill /PID <pid> /F (con un slash; //PID lo rechaza taskkill — no aplicar la doble-barra de cygwin aquí). Luego el watchdog tarda hasta 10 min → no esperar: invocar powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "%LOCALAPPDATA%\hermes\scripts\vigia-gateway.ps1" y verificar hermes gateway status con PID nuevo en ~20s.
- Verificar en
logs/agent.log: el reinicio hace session_reset del DM de Telegram (nace sesión nueva → atrapa el default); confirmar línea model=<nuevo> o el session_reset + respuesta enviada. Las sesiones de escritorio ya abiertas NO cambian (prompt caching) — es comportamiento esperado, no un bug.
- Recordar al usuario: los crons pinneados no se mueven con el default;
/model <m> revierte por sesión.
Crons (viven si el gateway está vivo)
PITFALL — el tool cronjob NO persiste el modelo (verificado 2026-09-01)
Crear un job vía el tool cronjob (action=create) deja model: null → el run usa el default (glm5.3 → 402). cronjob action=update con solo model falla con "No updates provided" (no es un campo editable ahí). Fix SIEMPRE por CLI: hermes cron edit <job_id> --model qwen3.8-flash --provider openai-api. Tras crear cualquier cron, verificar model en cronjob list y re-anclar si sale null.
Cron "ahora mismo" (one-shot inmediato): schedule en ISO (2026-09-01T00:17:00) a 2-3 min vista + repeat: 1; reprogramar el fuego con hermes cron edit <id> --schedule <ISO>; confirmar arranque con hermes cron runs <job_id> (estado running).
PITFALL — sub-agentes hermes en paralelo dentro de un cron → 429 max_parallel_requests (verificado 2026-09-06)
Los crons de Gobierno IA lanzan ministros con hermes -p <perfil> chat -q "<instrucción>" en background. La API de NaN limita a max_parallel_requests = 5 por api_key: si un cron lanza 3+ sub-agentes a la vez, cada uno consume slots y se desborda el límite → HTTP 429: Rate limit exceeded ... Limit type: max_parallel_requests. Current limit: 5, Remaining: 0. Como los sub-agentes ocupan los 5 slots, también tumba la LLAMADA DEL PROPIO COORDINADOR, marcando el job entero como error (racha de fallos) aunque los sub-agentes escriban sus ficheros.
Fix: en el prompt del cron, lanzar los sub-agentes SECUENCIALMENTE (uno tras otro, esperando a que cada uno termine) y añadir reintento: "si un ministro falla por 429 o transitorio 402/5xx, relánzalo UNA vez tras una breve espera". El cron de Hermes NO tiene knob de retry (verificado en references/background-systems.md de hermes-agent), así que la robustez se cuece en el prompt. Aplicado a "Pase de lista matinal" (7f86939758e2) y "Consejo de Ministros" (d8c606f0f8da) — ambos pasados a secuencial + reintento el 2026-09-06.
PITFALL — avalancha de catch-up tras días con el PC apagado → 429 EN MASA (verificado 2026-09-15)
Síntoma: el PC/gateway lleva días apagado y al arrancar fallan casi todos los crons LLM a la vez (mismo minuto, last_status: error). En logs/agent.log aparecen las líneas Job '<x>' missed its scheduled time (... grace=7200s). Running now; re-anchored on completion. El scheduler lanza TODOS los jobs vencidos en paralelo y sin límite por defecto → con 9+ jobs LLM se desbordan los 5 slots de NaN → RuntimeError: HTTP 429 ... Limit type: max_parallel_requests. Current limit: 5, Remaining: 0 en cada uno. Los jobs no_agent (script) pasan sin problema porque no llaman al LLM. No son N errores distintos: es 1 causa con N víctimas — diagnosticar por causa, no job a job.
Fix de raíz (persistente):
hermes config set cron.max_parallel_jobs 1
Clave real leída en hermes-agent/cron/scheduler.py (cron_cfg.get("max_parallel_jobs"); env equivalente HERMES_CRON_MAX_PARALLEL=1; por defecto: unbounded). Se aplica en el siguiente tick, sin reiniciar el gateway. Con 1 worker desaparecen de golpe las DOS clases de fallo de arranque: el 429 por saturación de NaN y el TimeoutError: TERMINAL_CWD write lock (jobs con workdir corriendo a la vez — el fallo del Café informal del 2026-09-09). Ponerlo a 1 es preferible a 2: los crons de Gobierno IA ya lanzan sub-agentes hermes propios que consumen slots.
Diagnóstico en 3 lecturas (el last_status de cron/jobs.json solo dice error, sin detalle):
- Script Python sobre
%LOCALAPPDATA%\hermes\cron\jobs.json: listar jobs con last_status no-ok (id, name, schedule.expr, model, deliver, last_run_at).
- El error REAL está en el último fichero de
%LOCALAPPDATA%\hermes\cron\output\<job_id>\*.md, sección ## Error.
grep 'missed its scheduled time' logs/agent.log confirma la avalancha de catch-up y da las horas originales perdidas.
Tras el catch-up los jobs ya quedan re-anclados a su siguiente hora: el fallo es histórico y NO se reintenta solo. Para recuperar un run concreto, relanzarlo con el tool cronjob(action='run', job_id=...) (dispara en background), no con hermes cron run <id> por CLI (se queda esperando la ejecución y agota el timeout del terminal). No relanzar de golpe los jobs de contenido seriado (Gobierno IA): duplicaría sesiones del serial — dejar que reanuden en su horario.
Detalle del incidente y lista de víctimas: references/cron-catchup-429.md.
Cron con ventana horaria (maratones de N batches en M horas)
Pedido tipo "tira crons de aprendizaje durante 6 horas" → UN solo cron con expr de ventana + repeat: N: p.ej. */25 1-6 1 9 * = cada 25 min entre 01:00-06:59 del 1 de septiembre, 18 fuegos. El prompt de cada batch debe ser autocontenido: dedup contra registry/estado persistente (así los batches no se pisan), commit+push por batch, append a un notes/ compartible (### Batch — HH:MM, nunca borrar secciones ajenas), y reporte final ≤5 líneas (llega de madrugada, el usuario duerme). hermes cron edit <id> --repeat N sí funciona para ajustar el número de batches tras crear el job.
PITFALL — orden de campos en expr cron con fecha fija: es min hora día mes dow. Escribir */25 1-6 9 1 * queriendo "1 de septiembre" da día=9, mes=1 → next_run_at: 2027-01-09. SIEMPRE verificar next_run_at en la respuesta de creación (o cronjob list) tras programar Anything fechado, y corregir con hermes cron edit <id> --schedule "<expr>".
Colisión con cron regular (obligatorio): un maratón sobre el mismo repo NO puede solaparse con el scout regular (push cruzado "fetch first"). Pausar el regular (hermes cron pause <id>) y crear un one-shot no_agent con script que haga hermes cron resume <id> y avise por telegram a la hora de fin del maratón (ej. 07:15). Nunca depender de reactivar a mano. Script de ejemplo: scripts/reactivar-scout.py (subprocess → hermes cron resume, stdout solo si algo va mal o para confirmación única).
| Job | ID | Schedule |
|---|
| mastermind-scout (stars→skills→push) | bc390c1bf06a | cada 6h |
| mastermind-weekly-digest | d8e9eb7ce270 | lunes 9:00 |
| mastermind-doctor (health check + autocura) | cceb83c1026c | diario 10:00 |
Operación
cd C:/Users/d_ant/Projects/MasterMind
python scripts/consultar-skills.py "consulta" --json
python scripts/indexar-skills.py [--reset]
python scripts/doctor.py [--json]
python scripts/test-doctor.py [--json]
bash scripts/run-stars-explorer.sh --batch 3 --json
Descubrimiento de skills antes de cargar (RECALL)
Para tareas NO triviales no basta con escanear el catálogo inyectado en el prompt (recorta las descripciones a 57 chars y puede ocultar skills relevantes). Ejecutar la búsqueda semántica y cargar el top de resultados ANTES de empezar:
python scripts/consultar-skills.py "<descripción de la tarea>" --json
Formato de --json (verificado 2026-09-15): devuelve una LISTA plana
[{"name": "...", "path": "media/voicebox", "distance": 0.1102, "score": 0.8898, "relevant": true}, ...]
— NO un dict con resultados/results. Un script que asuma dict saca None=None para todos los
repos y parece que el dedup no encuentra nada. score = similitud (1 = idéntico); ≥0.8 suele ser
"ya cubierto". Para barridos masivos (dedup de un backlog de stars, p.ej. 30+ repos), ver
references/bulk-backlog-stars.md.
Reglas:
- Consulta descriptiva de la tarea, no palabras sueltas.
- Cargar 2-5 top por score; no cargar 10 de golpe.
- Complementa al catálogo, no lo sustituye: catálogo para triggers claros, búsqueda para temas transversales.
- Si el top no incluye un skill que el catálogo sí sugiere, vale la pena cargarlo también.
- Si un skill se carga y no aporta, es señal de descripción débil u obsoleto — revisar con las herramientas de abajo.
Herramientas de este dominio (creadas 2026-09-08):
scripts/registro-skills.py — registro REAL de uso de skills leyendo state.db (llamadas a skill_view), por semana y por skill. --weeks N, --skill X, --json.
scripts/auditar-descripciones-skills.py — detecta descripciones cuyo trigger en la ventana de 57 chars es débil (pocas palabras de contenido específico). Parsear frontmatter con YAML, no con regex (los description: >- en bloque scalar cuelan el >).
scripts/skills-nunca-usados.py — skills NO cargados en la ventana de state.db + clusters de casi-duplicados por similitud Jaccard de tokens de descripción. --sim N (umbral, def 0.4), --json.
Consolidar skills duplicados (fusión)
Cuando el reporte marca clusters casi-duplicados, y David pide fusionar:
- Leer cada SKILL.md del cluster y decidir si son duplicados REALES o solo comparten tema. El detector Jaccard da falsos positivos:
agent-browser (CLI Rust de Vercel) y page-agent-browser-automation (librería PageAgent de Alibaba) se clusterizan pero son herramientas distintas — NO fusionar.