- 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":
1. **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`.
2. **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.
3. **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.
4. 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):**
```bash
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):
1. Script Python sobre `%LOCALAPPDATA%\hermes\cron\jobs.json`: listar jobs con `last_status` no-ok (`id`, `name`, `schedule.expr`, `model`, `deliver`, `last_run_at`).
2. El error REAL está en el último fichero de `%LOCALAPPDATA%\hermes\cron\output\<job_id>\*.md`, sección `## Error`.
3. `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
```bash
cd C:/Users/d_ant/Projects/MasterMind
python scripts/consultar-skills.py "consulta" --json # búsqueda semántica
python scripts/indexar-skills.py [--reset] # indexar (tras crear skills)
python scripts/doctor.py [--json] # health check
python scripts/test-doctor.py [--json] # tests del doctor (bug-inyección, 9 casos sandbox)
bash scripts/run-stars-explorer.sh --batch 3 --json # explorar stars manual
```
### 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:
```bash
# consulta descriptiva en castellano, no keywords sueltas
python scripts/consultar-skills.py "<descripción de la tarea>" --json
# cargar los 2-5 top por score (score > 0.25) con skill_view
```
**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:
1. **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.
Voir sur GitHub