Partners — un agente para cada uno de tus clientes
Actualizado 2026-10-07
ghosty partner es para cuando tu producto le quiere dar un asistente a cada uno de sus clientes. Tú tienes un espacio de partner; cada negocio que usa tu producto es un tenant (lo identificas con tu propio externalId) y cada persona que le escribe a ese negocio es un usuario final (groupId).
- Hay un solo agente compartido, en modo solo-MCP: no tiene máquina ni disco; sus únicas herramientas son las de tus servidores MCP por HTTPS.
- Cada turno lleva un token de corta vida que Ghosty le pasa a tu MCP. Nunca va al prompt, a la memoria ni a los registros.
- La memoria es por tenant y usuario final: un negocio nunca ve la de otro.
Tu espacio de partner
Estos comandos los corre la dueña del espacio, con su sesión (ghosty login).
createimprimeGHOSTY_PARTNER_KEY=gpk_…yGHOSTY_PARTNER_KEY_SECRET=gps_…. Guárdalas en tu servidor en ese momento: el secreto no se vuelve a enseñar. Para que no salga en pantalla:--out-env RUTA(lo escribe con permisos 600) o--format env(sólo las dos líneas, para mandarlas a otro comando).rotateacepta lo mismo.
--out-envantes se llamaba--env-file; Node 22 se come ese nombre como flag suyo (node: ./.env.partner: not found). El viejo sigue como alias.
rotate --grace MINdeja válido el secreto anterior esos minutos (default 60) para que cambies tu servidor sin cortes.credses alias decredentials;revoketambién se escriberm.- Si tienes más de un espacio de partner, elige cuál con
--workspace <slug>en cualquier comando.
El agente compartido
Un partner puede tener varios agentes: el de siempre (sin --agent) y otros con slug (por ejemplo uno para la dueña del negocio y otro público). Cada uno tiene su plantilla y sus versiones; con slug, apply publica directo. Por API: PUT /api/v2/partner/agent con "slug" en el body, GET …/agent?slug=, y "agent": "<slug>" en POST /api/v2/partner/turns. La memoria y el consumo de un negocio cruzan todos sus agentes (tenants rm borra la de todos).
maxSteps (1-12) es cuántas vueltas modelo↔herramientas da un turno; si en la última el modelo sigue pidiendo herramientas, se le pide una respuesta final sin ellas. Si tu MCP manda instructions en su initialize (p. ej. cómo usar un catálogo con discover_tools/run_tool), entran al prompt (hasta 6,000 caracteres).
maxTokens (256-8000, default 2000) es el tope de cada respuesta del modelo y thinking (true/false, default false) prende el razonamiento de DeepSeek: más lento y caro, útil para un asistente de varios pasos.
Con "engine": "claude" (p. ej. "model": "claude-sonnet-5-5") el agente corre con caja (claude-worker) en vez de solo-MCP: el mismo contrato de turns y el mismo SSE, pero cada conversación va encerrada (un negocio no ve archivos ni historial de otro), no tiene las herramientas de la cuenta del espacio y el token del turno llega a tu MCP por un proxy de Ghosty (nunca entra a la caja). Cada servidor MCP debe llevar ${turn.token} en una cabecera. La llave de Claude es la del espacio (o la de casa si el espacio la tiene encendida).
Robustez del turno. Si tu MCP no responde al arrancar, se reintenta una vez y, si sigue caído, el turno termina con error (no contesta sin herramientas). Un 429/5xx del proveedor se reintenta antes del primer chunk. Si una herramienta contesta 401 a media vuelta (token vencido) y el turno nació en Ghosty (WhatsApp) o trae agent, se canjea un token nuevo en /api/nik/turn-token y la llamada se repite una vez. Las herramientas de lectura (get_, list_, search_, find_) de un mismo paso corren a la vez, y los ids que vio cada turno quedan en su memoria para el siguiente.
Topes. Además del tope por partner hay uno por negocio (8 turnos a la vez) y uno por conversación (20 mensajes por minuto); al pasarse, turns contesta 429.
agent.json lleva sólo lo que quieras cambiar:
Sólo servidores MCP por HTTPS. En los headers no van llaves fijas: sólo los placeholders ${turn.token} y ${tenant.externalId}. Si nada cambió, no se crea una versión nueva.
Tenants
Un tenant es sólo configuración (name, tone, timezone, locale, notes): no tiene máquina propia. El primer turno de un externalId nuevo también lo da de alta.
Consumo por negocio
Por API, firmado con la credencial (GET con cuerpo vacío): GET /api/v2/partner/usage?externalId=&from=&to= → por negocio turns, tokens, costUsd, el desglose por día (días de CDMX) y errors por razón (mcp_down, provider_429, token, timeout, cancelado, reinicio…). from/to en YYYY-MM-DD o ISO; máximo 92 días.
Probar un turno
--agent <slug>prueba otro agente del partner (default: el de siempre).--groupes el usuario final (defaultcli); cada uno tiene su propia conversación.--turn-tokenes el token que Ghosty le pasará a tu MCP como${turn.token}. Aquí--tokentambién funciona y significa lo mismo (no es tu sesión).--verboseenseña los argumentos de cada tool (sin llaves con cara de secreto) y cuánto tardó:↳ denik__buscar_citas {"fecha":"2026-10-01"}·✓ denik__buscar_citas 340 ms.- Con
--jsonsale una línea por evento del turno (chunk,toolconargsyms,error…).
Probar como tu servidor
Con la credencial en el entorno, el CLI firma cada petición igual que lo hará tu backend (HMAC de ${ts}.${keyId}.${body}):
Grupos de WhatsApp de un agente
Un agente tuyo (p. ej. nik-admin) puede tener su propio número de WhatsApp (pareado con ghosty channels wa …). Cada grupo donde está se liga a un negocio y desde ahí contesta todo con las herramientas de ese negocio:
Por API: GET|POST /api/v2/partner/wa/groups y PUT|DELETE /api/v2/partner/wa/groups/{jid}. En cada turno Ghosty pide el token a tu servidor con {"externalId","aud":"<slug del agente>","groupId":"<jid>"}; tu respuesta puede traer además appendSystemPrompt (≤ 4,000) y config (name, timezone…). Si contestas 403/404, ese mensaje no se responde (queda en ghosty turns ls <id> --skipped). El nombre de quien escribe va en el texto (María: …); los adjuntos llegan como URL firmada y las notas de voz transcritas.
WhatsApp Business de un negocio
El número de WhatsApp Business de uno de tus negocios lo puede contestar un agente tuyo (p. ej. nik-public). Si tú das de alta el número en Formmy, pide el link a Ghosty y dáselo a Formmy; no viaja ninguna llave tuya:
Formmy recibe url como externalAgentUrl y secret como externalAgentSecret, y postea cada mensaje a ${url}/message con Authorization: Bearer <secret>. Por API: GET|POST /api/v2/partner/waba/links y PUT|DELETE /api/v2/partner/waba/links/{id}.
Si el número ya está en Formmy y lo mueves tú desde Ghosty:
En cada mensaje Ghosty le pide a tu servidor un token de corta vida (POST /api/nik/turn-token firmado con tu credencial: X-Ghosty-Key, X-Ghosty-Ts, X-Ghosty-Sig = HMAC de ${ts}.${keyId}.${body}; body {"externalId","aud","groupId"}) y corre el turno con él. El groupId es wa:<teléfono>. Si tu servidor no da el token, el mensaje queda registrado sin respuesta (ghosty turns ls <id> --skipped).
GHOSTY_PARTNER_SECRET también funciona (es el nombre de antes), pero no lo confundas: el servidor de Ghosty usa ese mismo nombre para otro secreto suyo. Usa GHOSTY_PARTNER_KEY_SECRET.
Así funcionan agent, tenants y try. ls, enable, credentials y audit siempre usan tu sesión: una credencial no puede fabricar otras.
Un agente con caja para tus usuarios
Si en vez del agente compartido solo-MCP quieres un agente normal (Claude con caja, shell y archivos) que atienda a cada usuario de tu producto con SU llave:
Cada turno: POST /api/v2/fleet-agents/<agente>/message-stream con
{groupId, text, turnToken} (un groupId por usuario final; turnToken de 8 a 4096 caracteres,
sin espacios) y las cabeceras X-Ghosty-Key: gpk_…, X-Ghosty-Ts (epoch en segundos) y
X-Ghosty-Sig = HMAC-SHA256 en hex, con el gps_…, de ${ts}.${keyId}.${cuerpo}. La respuesta
es SSE (chunk, tool, done, error). El token sólo llega a tu MCP: nunca entra a la caja ni
se guarda. Sin turnToken, el turno falla con error. La credencial alcanza sólo los agentes
normales de tu espacio.
Landings para tus negocios
Tu servidor puede pedirle a Ghosty la landing de un negocio con sus datos reales, sección por
sección, para pintarla en tu propio editor. Misma firma que los turnos (X-Ghosty-Key, X-Ghosty-Ts,
X-Ghosty-Sig). Nada se guarda ni se publica en Ghosty: el HTML es tuyo.
POST /api/v2/partner/pages/build (SSE):
Eventos, cada uno data: {…}:
- Datos reales: no se inventan cifras, años, reseñas, testimonios ni servicios. Sin servicios no
hay sección de servicios; testimonios, cifras, equipo y paquetes nunca se piden. Cada servicio con
urllleva su botón «Agendar» a esa liga. - Fotos: las tuyas llenan los huecos primero (en orden); las que falten salen de bancos libres,
elegidas con visión. Las fotos CC llevan
data-credit: pinta el crédito al pie. dialect:gs(default) usa los tokens del constructor de Ghosty;denikdeja clases que horneabakeTailwindHtmly que siguen tu brandkit (sinsurface-deep, fuentes comostyle, fotoshttpssincrossorigin, sindata-id). Las dos conservandata-animate.- Topes: 4 armadas a la vez por partner y 20 por negocio en 24 h (
429). El gasto se registra en el consumo de tu agente, porexternalId.
POST /api/v2/partner/pages/review con {html, viewport?, theme?, externalId?, dialect?} la abre en
un navegador real (escritorio 1280 y celular 390) y devuelve
{problems: [{sectionId, kind, detail, viewport, nodeId}], fixes, sections, skipped, seconds};
kind ∈ broken-image, overflow, clipped, overlap, contrast, contrast-unsure, dead-link.
sections son sólo las que la revisión corrigió. Si no se pudo renderizar contesta 503, nunca una
lista vacía.
Animación: copia
(3 KB, CSS + JS) una vez antes de </body> de tu página publicada. Sin él la página se ve completa,
sólo quieta.