Skip to main content

Catálogo core y chat — el plan que se aprueba antes de escribir código

Encargo: dejar el MCP como la única fuente de capacidades de Brein, borrar lo que no se usa, estandarizar lo que queda, añadir Secuencias como primera capacidad nueva y definir el chat que lo opera. Base: mcp-estandar-y-catalogo.md (la auditoría: 53 herramientas), 01-interfaz-luna.md, 03-lexico.md, 04-harness-y-evals.md (seis benches medidos), apps/mcp/src/tools/tool-registry.ts, apps/api/src/cognitive/tool-registry.ts, apps/api/src/email-campaigns/email-campaign-tools.ts. Referencias externas: Apollo, Clay, HubSpot, Outreach, Instantly, lemlist, Vercel AI SDK, Claude, Cursor. Regla de lectura: [REQUISITO] = contrato congelable y evaluable; [SUGERENCIA] = técnica sustituible.

1. Veredicto en cinco líneas

  1. El MCP es la fuente de verdad; Luna lo consume en proceso y su copia de herramientas se borra. La costura ya existe y nadie la usó: McpModule.register() exporta ToolRegistry y AppModule ya lo importa. 53 → 34 herramientas.
  2. Se borran 22 hoy: las 18 retiradas más context_share, contact_prepare, members y context_delete. No hay borrado desde el chat ni desde el MCP, y las notas mueren: los topics se escriben directo.
  3. Mercado no se toca. Sus 13 se quedan como están; la estandarización a 8 espera al chat. Eso elimina el riesgo más caro del plan: los nombres que las skills públicas ya tienen guardados.
  4. Secuencias son la primera capacidad nueva: seis herramientas de borrador y previsualización más un preflight de estatutos. Autorizar y activar no es una herramienta — es una decisión que la persona resuelve en sesión.
  5. El chat es de Brein y de nada más. Rechaza lo de afuera por alcance del cinturón, no por prohibición; muestra cada llamada, cada costo y cada decisión antes de gastar.

2. Qué se elimina ya

Difiere de la auditoría, que contaba 25: ahí las sintetizadas contaban como herramientas y context_delete estaba en KEEP. “Broker fuera”, en concreto. DRIFTLESS_BROKER_ENABLED a false por default, fijo en todos los entornos; driftless_broker sale del array de registro y deja de resolverse en tools/call; connectorTools() sale de paginateFor — una línea — y tool-synthesis.ts queda inerte; se borra references/broker.md (9 menciones en 2 archivos, espejadas en .driftless/) y su fila de ruteo; docs/mcp/integrations.mdx se marca no disponible. Conectar proveedores sigue siendo flujo humano. “Las notas mueren”, en concreto. propose desaparece de context_create (hoy su default es true y manda el topic a Cambios pendientes). El topic nace como conocimiento del workspace. Lo que sustituye a la cola: las seis reglas validadas al escribir — área obligatoria, patrón validado contra el checkout, bloqueo de secretos — y, en el chat, la tarjeta de un toque.

3. El estándar de una herramienta

Estado: ya está · parcial · + nuevo.

4. El catálogo core objetivo

34 públicas = 21 core + 13 de Mercado sin tocar. El registro tiene 36 definiciones: las dos de sesión (board_read, selection_update) viven ahí con external:false — nunca salen en tools/list, pero comparten tipo, sobre y presupuesto. Costo: gratis · lectura (cuenta en el techo horario) · créditos.

Empresas y personas — 8

CRM — 2

Cerebro — 4

Cuenta — 2

Secuencias — 6 · nueva

No existen en el MCP. El motor sí (EMAIL_CAMPAIGN_TOOL_CONTRACT, autorizaciones inmutables, dispatcher por Nylas) tras EMAIL_CAMPAIGNS_ENABLED. Se envuelve, no se reescribe. Todas gratis. No hay sequence_activate. Nadie en la industria expone envío masivo: el patrón universal es borrador → destinatarios → un solo cuello de botella de activación. El nuestro es la tarjeta revisar_secuencia, con una casilla por paso, resuelta en sesión; assertHumanSession se cumple igual que en el dashboard.

Mercado — 13, sin tocar

market_capabilities, market_search_suppliers · _opportunities · _awards · _permits · _risks, market_get_supplier · _opportunity, market_count_suppliers, market_compare_segments, market_aggregate_awards, market_get_supplier_history, market_screen_risks. Se estandarizan (13→8, con kind y filters cerrado por kind) después de que el chat esté vivo. Total: 21 core + 13 Mercado = 34 públicas · 36 definiciones.

5. Cómo Luna consume el MCP en proceso

La costura ya existe: McpModule.register() exporta ToolRegistry y AppModule ya lo importa con el adaptador in-process (app.module.ts:334). Sin HTTP ni JSON-RPC.
  1. Handle compartido. Extraer apps/api/src/mcp.dynamic.ts con la referencia del DynamicModule e importar esa misma desde AppModule y LunaModule (Nest deduplica por identidad de objeto). LunaToolsService inyecta ToolRegistry.
  2. Lista y llamada. registry.catalog(): DriftlessTool[] y registry.call(name, args, ctx), con un ToolCallContext armado desde el Principal de la sesión, sin authorization.
  3. Subconjunto por etapa: stages en la definición, no allowlist en Luna [REQUISITO]. Hoy LUNA_TOOL_STAGES es una tabla paralela por nombre que hay que tocar por cada herramienta nueva. MAX_VISIBLE_TOOLS se queda en Luna: es regla de la conversación. market_capabilities va sin etapas — la cobertura llega compilada en el TurnInput.
Base = context_retrieve, context_get, usage, board_read, market_count_suppliers (5, visibles en todas). [Resuelto 2026-09-10] El tope de 12 era una heurística de S1 (MAX_VISIBLE_TOOLS), nunca una decisión del founder; subió a 20 y buscando ve Mercado completo (17 herramientas), con_lista ve las Secuencias (17). Lo que sigue es histórico. El tope de 12 no cabe con Mercado en 13. Las de análisis (compare_segments, screen_risks, get_supplier_history, search_permits/_risks) quedan fuera de buscando, y el banco viejo depende de ellas. Es el costo concreto de aplazar el 13→8: o el tope sube a 14 solo en buscando, o Mercado se estandariza antes (§11). Luna conserva: luna-manual.ts, luna-input.compiler.ts, luna-criterion.ts, luna-intent.ts (las seis compuertas), el cosechador de refs, stripRefTokens, luna-stage.ts y el cobro. Luna borra: luna-tools.ts (551 líneas) salvo MEXICAN_STATES, que sube a libs/market-data-contracts con su guardia; y de luna-tools.service.ts (862) las ~600 de despacho, mapeo acción→ruta, recorte por kind y proyección. anchorRow, capOutput y toLunaToolError se mudan al MCP. next_actions y el presupuesto contra el validador. Luna hoy quita next_action a propósito; con el sobre estándar lo verán los gates. Regla: next_actions[], warnings[] y hint son instrucción, no material citable — el cosechador los ignora por nombre y el validador los trata como no-citables, una línea junto a REF_FIELDS. cost{} sí es citable: “20 créditos” es una cifra que la narración dirá, y entra al set con el ref de la llamada. La cifra derivada, el defecto que mata turnos buenos. Bench 6: 5 de 6 defectos y los 2 rechazos fueron promedios calculados bien que no pudieron anclarse porque no venían en ningún payload. [REQUISITO] todo agregado devuelve promedio y total con su ref. [SUGERENCIA] el gate acepta además una cifra derivada por suma, promedio o porcentaje sobre cifras del mismo ref.

6. El chat, comportamiento por comportamiento

Base: assistant-ui con External Store (instalado, 0.15.8) sobre el contrato de eventos de 01-interfaz-luna.md §6. El servidor es la única autoridad. 6.1 Llamadas visibles [REQUISITO]. La máquina de estados de tool-part del Vercel AI SDK es el contrato de renderizado; nuestros eventos mapean 1:1. Implementación: makeAssistantToolUI por familia — seis componentes, no treinta y cuatro. La etiqueta sale de TOOL_LABELS con el léxico corregido (COUNT_LABEL del bench cambia en lockstep). 6.2 Regenerar. Variantes hermanas, nunca destructivo (Claude, Cursor): el turno se re-emite con el mismo parent_turn_id y un variant nuevo, y assistant-ui lo pinta con su BranchPicker. [REQUISITO] las herramientas de la variante anterior no se re-ejecutan con argumentos idénticos — se reutiliza el resultado por search_id/ref, así que regenerar no cuesta créditos ni lecturas. 6.3 Borrar un turno. No borra nada: el log de la Work Session es la autoridad. Se escribe turn.retracted {turn_id} y la proyección deja de mostrarlo. [REQUISITO] los efectos ya ejecutados no se deshacen, y el evento lo dice con esas palabras. 6.4 Compactar — la escalera. Un peldaño por turno, como máximo. [REQUISITO] compactar no puede romper el anclaje: el peldaño 1 conserva refs, labels y cifras para que unresolved_refs siga en 0. 6.5 Recordar. Topics editables con aprobación antes de guardar. Luna propone dejar_nota; la tarjeta muestra el cuerpo editable; al tocar, context_create escribe directo. [REQUISITO] la memoria de equipo es Cerebro y nada más. Lo que solo sirve en esta sesión vive en snapshot.facts y muere con ella. 6.6 Rechazar lo que no es de Brein. Por alcance, no por prohibición: el cinturón por etapa define lo que existe, sin un system prompt que enumere prohibiciones. Si el mensaje no cae en ninguna capacidad, el turno termina en limite {tipo, detalle} más una redirección a la acción más cercana: “No puedo abrir tu bandeja de Gmail — ese conector no existe hoy. Sí puedo dejarte la secuencia lista para que la autorices.” 6.7 Interrupción de dos niveles. Detener (Esc): el bucle corta en el siguiente límite de paso; las herramientas en vuelo terminan — no se cancelan a medias, no se pierde lo cobrado — y el turno cierra como cierre parcial con lo anclado. Dirigir sin detener: el mensaje entra como steering al mismo turno. [REQUISITO] un mensaje durante una decisión pendiente es steering, no una resolución: resolver la tarjeta exige tocarla. 6.8 Tarjetas: cotizar → tarjeta → resolver. Toda tarjeta nace de una cotización real, nunca de una estimación del modelo, y lleva qué, cuánto cuesta, cuánto queda, cuándo vence (10 min) y qué pasa si aceptas o no. Aprobación por lote: la fatiga de aprobación mide ~93 % de sellos automáticos cuando cada acción pide un clic, así que una tarjeta por lote — “Revelar 10 contactos · 20 créditos · te quedan 140” es UNA tarjeta con diez casillas. Compuertas duras solo para gasto e irreversible; el resto es nivel 1 con vista previa o nivel 0 con deshacer. El modelo nunca ve quote_token, idempotency_key ni correlation_id.

7. Memoria y compresión (S2)

8. Los nuevos briefs del bench

Reemplazan el set actual, que mide Mercado. Cubren el flujo core: Empresas → enrichment → lista → CRM → secuencia → Cerebro. Distribución esperada: 8 cierre, 6 proponer_decision, 3 responder, 3 limite. Este set mide por primera vez los evals 1, 2 y 8 de 01-interfaz-luna.md §9. Los ocho medidores actuales se conservan y se añade uno: intento 1 vs intento 2 — bench 6 mostró que la reparación puede cambiar un cierre con tres anclas por un párrafo vacío que pasa todas las reglas.

9. Orden de ejecución y tamaño

≈19 días de un ingeniero con Claude (A 2 · B 3 · C 3 · D 2,5 · E 3,5 · F 5). A→B→C secuenciales; D en paralelo a C; E necesita B+C+D; F necesita E. −3 230 / +4 000 líneas, ~93 archivos. Lo que NO se toca: las 13 de Mercado (esquemas, cursores sellados, cobro, la cabecera X-Driftless-Model-Exposure: abstracted), el motor de secuencias y su ledger de autorizaciones inmutables, CommercialUsageExecutionService, el CLI (habla HTTP contra la API: borrar del MCP no puede romperlo), la Work Session en base de datos y el gateway de modelos.

10. Riesgos y mitigaciones

11. Decisiones que quedan para el founder

  1. El tope de herramientas visibles en buscando Resuelto el 2026-09-10: el tope era mío, no del founder; subió a 20.
  2. Escribir a Cerebro desde el MCP, fuera del chat: ¿directo (lo que dice este doc), o con tarjeta también para agentes externos? Borra la única cola de revisión que existe.
  3. Guardar al CRM: ¿nivel 1 con tarjeta, o nivel 0 con deshacer? Este doc lo deja en nivel 1 porque toca el sistema de registro del equipo; nivel 0 es más rápido y defendible.
  4. people_reveal en el catálogo público: hoy un agente externo puede gastar créditos del workspace con confirm:true y un quote_token vivo. ¿Se queda (los clientes ya la usan) o sale y vive solo como resolutor del chat?
  5. El nombre “Luna”: en producto es el modelo gestionado; en código gpt-5.6-luna es el fallback caro y el primario es deepseek-v4-flash. Pendiente desde 01-interfaz-luna.md.