1. Qué es MEDUSA
MEDUSA es un frontend personal sobre Claude Code y otros LLMs (Anthropic, OpenAI, OpenRouter, Ollama). Te da:
- Una cara visible: un orbe animado que reacciona a estados (escuchando, pensando, hablando), o un escenario pixel-art con personajes que representan los módulos del sistema.
- Voz bidireccional: TTS expresivo (ElevenLabs) + STT del navegador, con wake-word opcional.
- Multi-dispositivo: el server escucha en tu Mac y se accede desde el iPhone, otra pantalla, etc. (Tailscale recomendado).
- Proactividad: te avisa solo cuando llega un correo importante o tienes una reunión inminente — siempre y cuando NO estés en "no molestar".
- Un workspace con proyectos persistentes, cada uno con su propia sesión de Claude Code.
El nombre "JARVIS" vive en el código como referencia interna; lo visible se llama MEDUSA.
2. Interfaz
2.1 El orbe
Vive en el centro de la pantalla principal. Sus estados:
- idle: respira tranquilo. Modo reposo.
- listening: te está oyendo (STT activo). Reacciona al volumen del micro.
- thinking: el LLM está procesando. Glifos rotando.
- speaking: respondiendo / TTS activo. Pulsa al ritmo de la voz.
Cuando se ejecuta un sub-agente Claude Code (Task), aparece un mini-orbe orbitando con su propia paleta. Cuando una skill se invoca, aparece un glifo con el color/símbolo declarado en su frontmatter.
2.2 El HUD (barra superior)
- 🔔 / 🌙 (Proactividad): visible solo si el módulo proactivity está activado. Click → toggle DND. Color cambia según estado (silencio manual, quiet hours, snooze).
- 📚 SecondBrain: abre el panel de la wiki personal.
- $0.00 (coste): gasto del mes en USD. Si el provider es Claude Code, muestra tokens consumidos. Click → dashboard. Se pone amarillo en soft-cap, rojo en hard-cap.
- STATE: replica del estado del orbe en texto.
- ≡ panel toggle: muestra/oculta el panel de chat lateral.
- ⚙ ajustes: panel completo de configuración.
2.3 El panel de chat
Lateral colapsable. Cada burbuja del asistente tiene botones 📋 copiar y ▶ leer (TTS bajo demanda). Drag & drop archivos sobre la pantalla → suben a workspace/uploads/ y aparecen en el autocompletado @.
2.4 El subtítulo flotante
Bajo el orbe, en grande, el último mensaje del asistente. Se auto-oculta a 10s. Cuando el panel de chat está abierto, el subtítulo desaparece (no duplicar).
3. Vistas alternativas
Todas comparten el mismo WebSocket — lo que pase en una se ve en las demás:
/— vista principal con orbe + HUD + chat./chat— chat-only fullscreen, sin orbe. Útil para tener el orbe en una pantalla y el chat en el iPhone, por ejemplo./pixels— escenario pixel-art con una oficina y personajes que reaccionan. Cada agente/skill/provider está representado en un mueble; cuando trabajan, animan typing en su PC./diagram— grafo SVG de los módulos del sistema y sus dependencias./sandbox/<módulo>— invoca tools de un módulo concreto en aislamiento./manual— esta página.
Coordinación cross-cliente: cuando hay un /chat abierto en otra pantalla, la vista principal oculta su panel local automáticamente.
4. Voz
4.1 TTS (texto → voz)
Síntesis vía ElevenLabs. Configurable en Ajustes → Voz:
- API key de ElevenLabs (gratis con free tier de caracteres/mes).
- Voz: selector con todas las voces de tu cuenta (premade + cloned + generated).
- Modelo:
eleven_multilingual_v2,eleven_turbo_v2_5,eleven_v3(alpha — soporta tags expresivos[laughs] [curious]). - auto_speak: lee automáticamente las respuestas cuando la última entrada fue por voz. También controla los proactivos con
is_voice=true. - stream_tts_by_sentence: empieza a hablar antes de que termine la respuesta — divide por frases y encadena.
- stability / similarity_boost / style: ajustes de timbre y expresividad.
4.2 STT (voz → texto)
Web Speech API del navegador. Idioma configurable (stt_language, default es-ES). En iOS solo funciona desde HTTPS — usa tailscale serve --https=8000 http://localhost:8000 para acceder por Tailscale con TLS.
4.3 Wake word
Si activas wake_word_enabled, MEDUSA escucha pasivamente y se activa cuando dices alguna de las wake words configurables (default: "medusa"). Validación deferida 1.2s para evitar falsos positivos. Anti-eco: el STT no abre micro mientras el TTS está reproduciendo.
4.4 Modo conversación
Tras una respuesta vuelve a abrir el micro automáticamente — diálogo continuo sin tocar nada.
5. Providers
El "cerebro" detrás de cada respuesta. Selección en Ajustes → Provider:
- anthropic — API directa de Claude (Sonnet, Opus, Haiku). Requiere API key.
- claude_code — el binario
claudeheadless. Permite ejecución de tools, lectura de archivos, sub-agentes Task. Sin coste por API key (consume tu suscripción de Claude). Permission modes:read_only(allowed-tools whitelist),confirm_writes,bypass_all(--dangerously-skip-permissions, solo desde 127.0.0.1). - openai — GPT vía API.
- openrouter — multi-modelo proxy (cientos de modelos).
- ollama — modelos locales (Llama, Mistral, Qwen…). Cero coste, cero red.
- orchestrator — encadena providers según una
chainconfigurada (ej. router → coder → reviewer). - smart_router — un LLM rápido (Haiku/Ollama) clasifica el intent (chat / code / reasoning / etc.) y delega al provider óptimo de los
intentsconfigurados.
Los tools declaradas por skills y módulos las puede invocar cualquier provider con stream_reply_with_tools — no hay allowlist hardcoded.
6. Targets y sesiones
Un target es el "espacio de trabajo activo" para Claude Code. Cada uno mantiene una sesión persistente independiente en ~/medusa-workspace/sessions/<target>.session.
- orchestrator — tu conversación principal. Vive en
~/medusa-workspace/. - secondbrain — sub-agente dedicado a la wiki personal.
- project:<slug> — sub-agente con su propio CLAUDE.md, su propio cwd, su propia sesión.
- module:<id> — sub-agente sandboxed de un módulo (ej.
module:googletrabaja con gws).
Cambiar de target en el dropdown del HUD; el comando /projects lo lista; el atajo @ en el input lo permite mencionar.
7. Workspace y proyectos
El workspace vive fuera del repo en ~/medusa-workspace/ (configurable con workspace_dir). Crítico: si estuviese dentro del repo, Claude Code subiría buscando CLAUDE.md y se contaminaría con el meta-CLAUDE.md del propio frontend.
Estructura:
~/medusa-workspace/
├── CLAUDE.md ← auto-generado, índice de proyectos
├── .claude/skills/jarvis-projects/ (auto-instalado)
├── projects/<slug>/
│ ├── PROJECT.md ← metadatos (título, descripción)
│ ├── STATUS.md ← estado dinámico (frontmatter YAML + cuerpo)
│ ├── .claude/settings.json
│ └── (tus archivos)
├── repos/ ← clones git desde la UI
├── uploads/ ← drag & drop
├── secondbrain/ ← wiki personal
└── sessions/ ← sesiones persistentes Claude Code
STATUS.md
Cada proyecto tiene un STATUS.md con frontmatter: state, last_activity, pending_tasks (lista), blockers, last_milestone. La auto-actualización post-turno (Haiku) parsea el último intercambio y patchea el STATUS si detecta cambios. Toggle en Ajustes → Sistema.
8. Módulos
El sistema modular permite añadir capacidades sin tocar el core. Cada módulo es una carpeta en modules/<id>/ con module.yaml, código Python (o sub-agente Claude Code), tools, rutas REST, hooks, settings_schema, healthcheck.
Módulos del sistema
| id | qué hace |
|---|---|
voice | TTS ElevenLabs + voice-formatter post-proceso |
mail | Triage de Gmail vía sub-agente (compose, send, summarize, daily_briefing) |
google | Base CLI gws para Gmail/Calendar/Drive/Docs/Sheets |
gcalendar | Eventos próximos, hueco libre, schedule_meeting con detección de conflictos |
logs | Buffer de eventos del sistema (300 últimos, no persiste a disco) |
costs | Tracker de gasto LLM por mes/día/modelo + soft/hard caps |
secondbrain | Wiki personal mantenida por sub-agente |
scheduler | Cronjobs (APScheduler) + webhooks entrantes |
projects | CRUD de proyectos del workspace |
web_search | Búsqueda web Tavily (primario) + Brave (fallback) + fetch_url |
proactivity | Watchers mail/calendar + DND + cola de aplazados (ver §12) |
Cómo gestionarlos
Ajustes → Módulos. Cada tarjeta muestra:
- Estado (enabled / disabled / pending_dep / error).
- Healthcheck en vivo (verde / amarillo / rojo).
- Botones: enable, disable, reload, ver logs, settings, sandbox (para tools tester).
El healthcheck se cachea 30s. Algunos verifican APIs externas (ElevenLabs, gws), otros estado interno (event count, schedules activos).
Crear un módulo nuevo
Ver GUIA/modulos.md en el repo para el contrato completo. Patrón mínimo:
modules/mi_modulo/
├── module.yaml (id, kind, tools, routes, hooks)
├── settings_schema.yaml
└── python/
├── __init__.py
├── lifecycle.py (enable + healthcheck)
├── routes.py (APIRouter)
└── tools.py (handlers de tools)
Ojo: el ID no puede llevar guiones (-) — Python no acepta guiones en nombres de paquete. Usa underscores: web_search, no web-search.
9. Skills
Las skills son capacidades discretas en formato Claude Code (frontmatter YAML + cuerpo Markdown). El cuerpo entero es la description que ve el LLM. Pueden tener handler.py Python (ejecutables) o ser directive_only (solo contexto inyectable).
Skills del repo
get_current_time— devuelve hora local. Glifo reloj 🕐.get_weather— meteorología. Glifo nube ☁.workspace_status— directive_only, contexto sobre cómo consultar/actualizar STATUS.md.update_project_status— actualizar STATUS.md programáticamente.
Crear / editar / eliminar
Ajustes → Skills tiene un editor: nombre, descripción, color (hex), glifo, handler Python opcional. También se puede generar una skill nueva por LLM (botón ✨ "generar skill"). Hot-reload: cada skill se importa con nombre único para evitar caché de Python.
10. SecondBrain
Tu wiki personal mantenida por un sub-agente Claude Code dedicado. Workflow:
- Subes archivos vía Ajustes → SecondBrain (drag&drop) o pasas texto en conversación con
POST /api/secondbrain/ingest-text. Aparecen enworkspace/secondbrain/raw/. - Cambias el target a
secondbrainy pides "ingesta lo último de raw/". - El sub-agente lee, sintetiza y crea/actualiza páginas en
workspace/secondbrain/wiki/. - Búsqueda con
GET /api/secondbrain/query?q=...— grep + scoring naive (NO pasa por LLM, es rápido).
Ajustes → SecondBrain también permite ver páginas, log de operaciones, borrar fuentes raw, reset destructivo.
11. Programación
Ajustes → Programación. Dos cosas distintas:
11.1 Cronjobs (proactivos programados)
Disparan un prompt a una hora fija. El resultado se broadcastea por WebSocket — aparece como burbuja proactiva en cualquier vista abierta y, si tienes auto_speak + is_voice=true, lo lee en alto.
Tipos de schedule:
- daily — todos los días a HH:MM.
- weekly — días de la semana específicos a HH:MM.
- hourly — cada N horas.
- cron — expresión cron cruda.
Cada schedule tiene target (orchestrator / project:slug / module:id), is_voice, enabled, last_run, last_result_preview. Botón ▶ para disparar manualmente.
11.2 Webhooks entrantes
URL pública POST /api/trigger/<id> con header X-Token: .... Cualquiera con el token puede disparar el prompt. Útil para Zapier, iOS Shortcuts, Home Assistant.
Cada webhook: name, token (uuid 32 chars, comparación constante-tiempo), prompt, target, is_voice. El body opcional {"extra": "..."} se appendea al prompt. El token se muestra UNA SOLA VEZ al crearlo (después solo enmascarado).
Ejemplo desde Shortcuts iOS:
curl -X POST https://medusa.tailnet.ts.net:8000/api/trigger/abc123 \
-H "X-Token: tu-token-aqui" \
-H "Content-Type: application/json" \
-d '{"extra": "y dime el tiempo"}'
12. Proactividad
Hace que MEDUSA hable sola cuando algo importante pasa, sin que tú lo pidas. Activable en Ajustes → Módulos → proactivity → enabled = true (opt-in explícito).
12.1 Watchers
- mail watcher (cada 3 min, configurable): consulta Gmail vía gws, filtra
is:unread newer_than:1h, pasa cada nuevo correo por la heurística. Si pasa el umbral, dispara proactivo. - calendar watcher (cada 2 min): mira eventos en los próximos 30 min. Avisa a 15 min antes (
notice) y otra vez a 5 min (critical).
Primer arranque hace bootstrap: registra los IDs existentes SIN emitir, para no spammear con todo lo no leído al activarlo.
12.2 Heurística de importancia (correo)
Score 0–100. Ajustable en settings:
- +40 si
Fromestá envip_senders(acepta substrings, ej.@familia.comcoge a todos del dominio). - +25 si el asunto matchea
important_keywords(default: urgente, asap, deadline, factura, contrato). - +15 si el correo va directo a ti (no list-id, no bcc, no
no-reply). - −20 si
Fromennoisy_senderso si tieneList-Id(mailing list). - −30 si el asunto matchea
noisy_keywords(newsletter, no-reply, digest, boletín).
Umbral default 50: bajo eso → ignorar. Entre 50 y 75 → notice. Por encima → critical.
12.3 Modo no molestar (DND)
Tres mecanismos que se combinan:
- Quiet hours: bloques de horarios. Default 22:00–08:00 todos los días. Cruzan medianoche correctamente. Editables vía
PUT /api/proactivity/quiet-hours. - Toggle manual: botón 🔔/🌙 en el HUD. Click → silencio total hasta nuevo click.
- Snooze: silencio temporal con duración (30 min, 1h, 4h, custom). Caduca solo.
Si dnd_critical_passes=true (default), las reuniones a 5 min siempre suenan aunque haya DND. Si lo apagas, ni eso.
12.4 Cola de aplazados
Cuando llega un proactivo durante DND, NO se pierde — se aplaza en una cola. Cuando el DND termina (snooze caducado, sales de quiet hours, o desactivas el toggle manual), MEDUSA emite UN proactivo-resumen consolidado del tipo "mientras silencio: 3 correos importantes (de Jefe, Mamá y Hacienda) y 1 reunión".
Se puede forzar el resumen ya con POST /api/proactivity/deferred/flush.
12.5 Throttling
- min_gap_seconds: mínimo entre proactivos (default 300s = 5 min).
- max_per_hour: ventana móvil 1h (default 6).
- max_per_day: ventana móvil 24h (default 20).
Si supera caps, drop con warning en logs.
12.6 Endpoints útiles
GET /api/proactivity # estado completo
POST /api/proactivity/dnd # {mode: on|off|snooze|clear, minutes?}
POST /api/proactivity/test # disparar proactivo de prueba
GET /api/proactivity/deferred # ver aplazados
POST /api/proactivity/deferred/flush # forzar resumen
PUT /api/proactivity/quiet-hours # {blocks: [...]}
12.7 Notificaciones nativas (macOS)
Si os_native_notify=true y el server corre en macOS, dispara una notificación nativa con osascript. Llega aunque la pestaña esté cerrada. iOS / móvil sigue requiriendo la pestaña abierta (no hay PWA aún).
13. Búsqueda web
Módulo web_search con dos tools:
search(query, max_results)— devuelve lista de{title, url, snippet}.fetch_url(url)— descarga la URL y devuelve el texto plano (HTML stripped). Limitado porfetch_max_charspara no quemar tokens.
Configurar API keys en Ajustes → Módulos → web_search:
- Tavily (primario, recomendado) — free tier 1000 búsquedas/mes en tavily.com.
- Brave Search (fallback) — free tier en brave.com/search/api.
Si Tavily falla y hay Brave configurado, cae automáticamente al fallback. Cuando MEDUSA no sabe algo o necesita información actual, invocará search sin que tengas que pedirlo explícitamente.
14. Costes
El módulo costs registra cada llamada al LLM en config/cost_log.json con tokens × precio (tabla en config/pricing.py). Para Claude Code usa el total_cost_usd reportado por el binario (incluye cache hits + sub-agents).
- HUD: gasto del mes en USD (o tokens si provider es Claude Code).
- Soft cap: warning visual cuando se alcanza, no bloquea.
- Hard cap: bloquea llamadas. Aparece "tope mensual alcanzado" en chat.
Configurar en Ajustes → Sistema. Endpoint GET /api/costs devuelve resumen mes / día / top modelos / recent.
15. Diagnóstico
15.1 Event log
Buffer en memoria de 300 eventos con niveles (debug/info/warn/error) y categorías (api/voice/provider/router/skill/cost/ws/system). NO persiste a disco. Visible en Ajustes → Sistema.
15.2 Healthchecks
Cada módulo expone GET /api/modules/<id>/healthcheck. Algunos hacen ping real a APIs externas (ElevenLabs, gws). Cache 30s para no saturar.
15.3 /diagram
Visualización SVG de los módulos cargados, sus dependencias declaradas (depends_on) y sus tools. Útil para entender el grafo del sistema de un vistazo.
15.4 Token Dashboard
Solo si usas Claude Code: integra el ccusage dashboard para ver consumo por sesión, modelo, etc. GET /api/token-dashboard/status.
16. Atajos y comandos
16.1 Atajos de teclado
- F — focus mode (oculta HUD y panel, solo orbe).
- Cmd/Ctrl + Shift + R — recarga forzada (útil tras cambios en CSS/JS).
16.2 Slash commands en el input
Empieza un mensaje con / para abrir el palette:
/clear— limpia chat visual./reset— reinicia memoria del LLM./focus— focus mode./voice— toggle voz./help— ayuda rápida./skills— lista skills./agents— sub-agentes./projects— proyectos del workspace./cost— gasto del mes./status— estado de proyectos.
16.3 @-mentions
Empieza con @ para autocompletar:
- Uploads recientes (drag&drop previo).
- Proyectos del workspace.
17. Configuración avanzada
17.1 Variables de entorno (.env)
JARVIS_HOST=127.0.0.1— restringe el bind a loopback (default0.0.0.0, escucha en todas las interfaces incluido Tailscale/LAN).JARVIS_DEV=1— hot-reload Python en cambios.ANTHROPIC_BASE_URL=...— apunta Claude Code a un proxy (claude-code-router, etc.).- API keys:
ANTHROPIC_API_KEY,OPENAI_API_KEY,OPENROUTER_API_KEY, etc. También editables desde Ajustes → Variables (UI persistente).
17.2 Acceso desde móvil con HTTPS
iOS bloquea el micrófono fuera de localhost salvo HTTPS. Para STT desde el iPhone vía Tailscale:
tailscale serve --https=8000 http://localhost:8000
Luego desde el iPhone: https://<tu-mac>.tailnet.ts.net:8000/chat.
17.3 Permission modes Claude Code
read_only— allowed-tools whitelist mínima.confirm_writes— pide confirmación humana antes de escribir.bypass_all—--dangerously-skip-permissions. Solo se puede activar desde 127.0.0.1 (no Tailscale, no LAN).
17.4 Personalidad y nombre
Ajustes → Personalidad: edita el system prompt del orquestador. Default: amigo cercano, tutea siempre, NO formal "señor". assistant_name (default "MEDUSA") es lo que aparece en el HUD y notificaciones.
17.5 Apariencia
- Tema: paletas de color (cyan, ámbar, magenta…).
- orb_size: small / medium / large / huge.
- Modo Medusa (beta): 14 tentáculos sinuosos animados con onda transversal en lugar del orbe estándar.
17.6 Wake words
Editables desde Ajustes → Voz con chips removibles. Default: "medusa". Puedes añadir variantes ("hey medusa", "ok medusa").
18. Privacidad
- API keys nunca en disco como texto plano: se guardan en
config/runtime.jsoncon flag de secreto (la UI las enmascara comosk-ant-•••••XXXX). Backend ignora valores con•al actualizar (no se sobrescriben con la versión enmascarada). - Event log no persiste: buffer en memoria. Los detalles con campo
api_keyse sanean antes de loggear. - Workspace fuera del repo: tu trabajo en
~/medusa-workspace/, gitignored. - Endpoints de escritura (POST/PUT/DELETE) solo desde loopback + Tailscale (100.x) + LAN privada. Los GET son públicos a la red bindeada.
- bypass_all de Claude Code SOLO se puede activar desde 127.0.0.1 (ni Tailscale).
- Webhooks: el token se muestra UNA VEZ al crearlo. Después solo masked. Comparación constante-tiempo (HMAC).
19. Problemas frecuentes
"No veo X / aún veo la versión vieja"
Caché del navegador. Cmd+Shift+R (Mac) o Ctrl+Shift+R (Win/Linux). En iOS Safari: cierra la pestaña entera y vuelve a abrir. Antes de pensar que es bug, verifica que el server sirve la versión nueva con curl http://localhost:8000/static/<archivo>.
"El provider activo no responde"
Falta API key o caducada. Mira la badge del HUD y Ajustes → Provider. Para Ollama: que esté corriendo (ollama serve).
"gws auth caducada / mail watcher falla"
El healthcheck de proactivity / mail / google lo detecta. Resuelve con gws auth login en terminal. Tras 3 fallos consecutivos, los watchers hacen backoff (no spammean errores).
"Puerto 8000 ocupado"
lsof -ti:8000 | xargs kill -9
"iOS no me deja usar el micro"
Necesita HTTPS — usa tailscale serve --https=8000 http://localhost:8000 y conéctate por la URL TLS de Tailscale.
"Subí un archivo al panel y no aparece"
Tamaño máximo por archivo: 10 MB. Para más grandes, usa Ajustes → Repos para clonar repositorios completos via git.
"Las notificaciones del navegador no llegan"
Tienes que aceptar el permiso la primera vez (banner del navegador). Solo aparecen cuando la pestaña NO está visible. Para que lleguen aunque la pestaña esté cerrada: macOS con os_native_notify=true en proactivity (no funciona en iOS aún).
20. Para desarrolladores
Arrancar y reiniciar
pip install -r requirements.txt
python3 run.py # bind 0.0.0.0:8000
# Reiniciar tras cambio Python (no hay hot-reload por defecto):
lsof -ti:8000 | xargs kill -9; sleep 1; nohup python3 run.py > /tmp/jarvis.log 2>&1 &
Cambios en web/: solo recargar navegador. Cambios Python: reiniciar (o JARVIS_DEV=1).
Tests
python3 -m pip install -r requirements-dev.txt # una vez
python3 -m pytest -q # ~1s, ~170 tests
Los tests usan TestClient de FastAPI (no levanta uvicorn real). Si añades feature, añade test. Tests del módulo en modules/<id>/tests/ se descubren automáticamente (pytest.ini: testpaths = tests modules).
Validar JS sin ejecutar
node -c web/orb.js
node -c web/app.js
node -c web/pixels.js
Imprescindible tras refactors grandes. El conteo de paréntesis a ojo es engañoso por strings.
Verificación rápida del backend
python3 -c "from core.server import app; print('OK')"
Idiosincrasia del loader de módulos
Importa archivos como <id>.python.X (sin modules. prefix); el resto del repo usa modules.<id>.python.X. Python ve dos módulos distintos en sys.modules aunque sean el mismo archivo.
- Los singletons globales (
events,tracker,workspace,secondbrain,scheduler) viven encore/para evitar doble carga. - Los tests que mockean del módulo deben parchear AMBOS paths — patrón
dual_patchenmodules/proactivity/tests/conftest.py.
Roadmap
Lo pendiente vive en tareas.md en la raíz del repo. Mover de "En curso" a "Hechas" al terminar.