07 — Tarjetas de resultado y el “super chat” de Ada
Propuesta, 2026-09-11 (corregida el mismo día). Estado: borrador para decisión del fundador. No hay código detrás. Lo que pide el fundador: un chat que muestre datos como componentes (listas, conteos, pipeline, secuencias, contexto) y actúe sobre ellos, explotando al máximo el proveedor que usamos, sobre las cinco primitivas del producto: CRM, enriquecimiento, señales, secuencias de correo, contexto.0. Diagnóstico en tres líneas
- El modelo no es el problema. Verificado en
model_usagede staging: cada turno de la sesión92da0b12corrió conopenai/gpt-5.6-luna, tier primario, sin escalar. (Los defaults demanaged-models.tsyrender.yamlapuntan a DeepSeek, pero el entorno de Render los sobreescribe; no inferir el roster del código.) Lo que falla es el harness. - El harness solo sabe narrar. El contrato de salida es
{intent, narration}. La única tarjeta de resultado en línea es la de selección, y solo para tres herramientas (company_search,market_search_suppliers,market_get_supplier). Un conteo, un pipeline por etapa, una secuencia o un análisis se aplastan en un párrafo con las tres advertencias que el payload de mercado pega en cada llamada. - Los componentes ya existen y el chat no los usa.
DataArtifactRenderer(apps/dashboard/src/redesign/) pinta seis vistas (DataTableView,MetricGridView,BarChartView,LineChartView,EvidenceListView,ComparisonView) sobreDataArtifactSpec, un contrato versionado que el servidor valida antes de salir (apps/api/src/investigations/data-artifact.contract.ts, decisión endocs/architecture/data-artifact-views.md). Hoy solo lo usa Investigaciones. Las nueve tarjetas de decisión de Luna son para pedir permiso, no para mostrar datos; cuatro devuelven 501 yonResolvedescarta lo que la persona edita.
1. Norte
Cada respuesta de Ada es una tarjeta y una frase. La tarjeta es unDataArtifactSpec que el servidor construye a partir del resultado real de la herramienta, valida con el gate que ya existe y el cliente pinta con el renderer que ya existe. La frase la produce el modelo y lee la tarjeta, no la repite. Toda tarjeta tiene acciones por fila y una en lote, y esas acciones son las herramientas y decisiones que ya existen.
Esto respeta la decisión ya tomada en data-artifact-views.md: dataset persistido → spec validado → renderer nuestro. Nunca HTML del modelo. Y es el estado del arte de 2026 (partes tipadas del hilo, vocabulario cerrado de componentes) sin adoptar react-generative-ui, que el spike de agosto descartó.
2. Las tarjetas (vocabulario v1)
Todas se expresan comoDataArtifactSpec con un datasetRef nuevo { kind: 'luna_run', run_id } (hoy solo existe investigation), y se pintan con DataArtifactRenderer dentro del hilo. Lo nuevo es el mapeo herramienta → vista y las acciones, no los componentes.
Las seis primeras las construye el código, deterministas. Análisis es la única que compone el modelo: emite un
DataArtifactSpec (no HTML) con json_schema estricto contra el contrato existente, y pasa por validateDataArtifactSpec como cualquier spec. Si no valida, cae a la tabla canónica, que es la degradación que el renderer ya define.
Regla del manual que acompaña: “Cuando el turno produce una tarjeta, la narración es una o dos frases que la leen. No repitas sus cifras, no listes sus filas, no agregues advertencias: la fuente va al pie de la tarjeta.” Las tres advertencias fijas de mercado salen del payload que ve el modelo y entran como pie de tarjeta, en español, una vez.
3. Proveedor: qué exprimimos de OpenAI
Ya usamos: streaming SSE, tools +tool_choice, json_schema negociado, prompt caching (los turnos de staging muestran ~11k tokens en caché de ~12k), razonamiento apagado.
Lo que empezamos a usar:
json_schemaconstrict: truepara el contrato de turno y para la tarjeta Análisis. La plomería existe (types.ts:37,openai-compatible.ts:41); nadie la enciende.reasoning_effort: lowsolo en intents de análisis y cierre, apagado en búsquedas y conteos.- Llamadas de herramienta en paralelo (
parallel_tool_calls) para “conteo + contexto del equipo” en un solo paso. - Narración incremental: hoy
narration.deltase emite una vez tras validar. Con la tarjeta ya en pantalla, la frase puede llegar en streaming sin riesgo de cifras sin ancla. - Responses API más adelante, cuando toque migrar el adaptador. No es requisito.
- Agents API solo para “Investigar”. No para el chat.
4. Plan por fases (tres semanas, tres PRs)
Fase 0 — Ver lo que pasa (1 día)
- Persistir los argumentos de cada llamada en
tool.finished(hoy no se guardan; el 32 contra 13 fue indepurable). board.runsdeja de estar vacío: cada llamada registra{run_id, tool, args, ref_label}. Habilita “muéstramelos hereda los filtros”.- Alinear los defaults del código con el roster real (OpenAI primario) para que local, staging y producción digan lo mismo, y que el bench corra contra ese roster.
Fase 1 — Las seis tarjetas (1 semana)
- Servidor:
cardFromToolResult(name, parsed, harvest): DataArtifactSpec | nulljunto aselectionFromToolResultenluna-board.ts;datasetRef.kind = 'luna_run'; validación con el gate existente; nuevo eventocard.shown {spec, payload}(migración: el CHECK deluna_session_events.kindlista exactamente 10 kinds). - Cliente: parte
data-luna-result-cardque montaDataArtifactRenderercon una barra de acciones por tarjeta. Precedente:BoardCard.tsx. - Manual: regla “tarjeta y una frase”; advertencias de mercado al pie de tarjeta.
Fase 2 — Tarjetas que actúan (1 semana)
onResolvelleva payload: filas elegidas, texto editado, colección elegida. Hoy se descarta.- Las cuatro decisiones que devuelven 501 se habilitan o salen del catálogo.
- Tarjeta Cuenta: mover etapa, elegir persona, agendar, desde la tarjeta, con recibo.
- Tarjeta Análisis: el modelo emite
DataArtifactSpecconjson_schemaestricto. Caso de prueba: “dame un análisis de mi CRM” sobre la sesión92da0b12(40 registros, 3 etapas).
Fase 3 — Señales y secuencias desde el chat (1 semana)
- Herramienta
account_signals {collection_id, record_id}que junta oportunidad + evidencia + estado de contacto en una lectura. - Secuencia desde una selección:
sequence_create_draftalimentado porpeople_acquired, tarjeta Secuencia, decisión “Revisar y activar” concheck_idfirmado (hoy es un tokensc1.sin firma). - Streaming de narración.
5. Lo que NO hacemos
- No cambiamos de modelo: el que responde ya es
gpt-5.6-luna. - No rehacemos el harness sobre el Agents API de OpenAI. Se evalúa solo para “Investigar”.
- No dejamos que el modelo componga HTML ni markdown libre. Solo
DataArtifactSpecvalidado. - No creamos componentes nuevos donde
DataArtifactRendererya tiene la vista. - No abrimos panel derecho. Decisión 21 se mantiene.
- No tocamos el gate de anclajes: las tarjetas lo alimentan, no lo esquivan.
6. Decisiones que necesita el fundador
- Reusar
DataArtifactRenderercomo motor de tarjetas del chat (en vez de componentes nuevos por tarjeta). Sí/no. - Tarjeta Análisis compuesta por el modelo como
DataArtifactSpecestricto. Sí/no. Sin ella, “dame un análisis” sigue siendo texto. - Orden de fases como está, o Fase 2 antes que Fase 1 si lo urgente es operar el CRM desde el chat.
7. Evidencia usada
- Modelo real:
model_usageen staging,run_id= turn ids de la sesión92da0b12(provider_class=openai,model_id=gpt-5.6-luna,tier=primary). - Componentes existentes:
apps/dashboard/src/redesign/DataArtifactRenderer.tsx:310-316,apps/api/src/investigations/data-artifact.contract.ts:86-138,data-artifact.builder.ts,docs/architecture/data-artifact-views.md. - Seam del hilo:
luna.contracts.ts:347-399,749,luna-turn.service.ts:700-768,1020,luna-board.ts:110,142,lunaRuntime.ts:171,280,430,747-780,ThreadParts.tsx:216-255, migración1715200000185-AddLunaSessions.ts:49-62. - Primitivas:
apps/mcp/src/tools/families/{crm,empresas,mercado,secuencias,cerebro}.ts;apps/api/src/luna-decisions/luna-decisions.service.ts(cinco kinds resueltos, cuatro enUNAVAILABLE_KINDS). - Estado del arte: assistant-ui Tool UI y generative UI, AI SDK v6 partes tipadas, MCP Apps (2026-01-26), OpenAI structured outputs estrictos.
