AI Connect/How to create/Automatizaciones y cuentas de servicio
Automatizaciones y cuentas de servicio
Haga que los cron jobs, los webhooks, los pipelines de CI y los procesos por lotes nocturnos posean su propio usuario en el AI Connect SDK, conecten sistemas sin interfaz con credenciales estáticas y actúen con aislamiento y presupuesto por job, sin ningún navegador en ninguna parte. Los procesos se convierten en actores gobernados, no en claves mudas en un archivo de configuración.
La IA más valiosa de tu empresa funciona sin supervisión humana. Un job nocturno que concilia el libro mayor, un webhook que clasifica un nuevo lead en el CRM, un paso de CI que abre una issue cuando un despliegue falla. Hasta hoy, la infraestructura tenía exactamente una forma para ese trabajo: una god-key en un archivo de entorno, sin identidad, sin medición, sin revocación, y una revisión de seguridad que termina en un encogimiento de hombros.
El AI Connect SDK reemplaza esa forma con lo que ninguna plataforma de conectividad ha ofrecido antes: un proceso es un usuario, con su propio external_id, sus propios conectores, sus propias credenciales estáticas, respaldado por miles de conexiones de IA desde el primer día. Dale a cada automatización su propia identidad, conéctala una vez y deja que invoque capacidades de forma indefinida, aislada, medida y auditable. Cada cron job se convierte en un empleado responsable, con credencial, presupuesto y despedida propias. Esa es una frase que no podías escribir sobre ninguna plataforma de integración del mercado hasta esta.
vinkius.user('alice_123').capabilities({ include: ['github'] })GET /apps/vk_app_xxx/users/alice_123/tools?connector=githubconst capabilities = await vinkius
.user('alice_123')
.capabilities({ include: ['github'] });CapabilitySet (6)
github__list_issues read-only
github__create_issue POST /repos/{owner}/{repo}/issues
github__list_pull_requests read-only
github__search_code read-only
...Lo que un proceso-como-usuario aporta
| Propiedad | Por qué importa para jobs sin supervisión |
|---|---|
| Headless por diseño | Los conectores de token estático (api_key, token) no requieren consentimiento interactivo, credentials.set() es todo el flujo. |
| Sin navegador | Un contenedor de cron con solo fetch y tu Application Key ejecuta el SDK completo. |
| Radio de impacto por job | La conexión propia de cada automatización significa que una clave filtrada compromete un job, no todo el conjunto. |
| Medición independiente | Los tokens por conexión muestran exactamente cuánto cuesta el job nocturno de conciliación. |
| Reintentos deterministas | Una clave de idempotencia garantiza que una llamada repetida se aplique exactamente una vez. |
Estos jobs guardan credenciales reales sin un humano en el loop. Mantén cada fragmento en el lado del servidor, obtén los tokens estáticos de tu secret manager (nunca de un repositorio ni de un prompt del modelo) y dale a cada automatización el conjunto de conectores más pequeño con el que pueda funcionar.
1. Una automatización, un id, un conjunto de conectores
Nombra el job por lo que hace y trata ese nombre como el actor dueño de las conexiones.
const JOB = 'svc-nightly-reconcile'; // estable, apto para URL, menos de 255 caracteres, sin / \ ni espacios
const JOB_CONNECTORS = ['netsuite', 'stripe', 'sheets'];2. Aprovisiona una cuenta de servicio (una vez, en la configuración)
Esto se ejecuta una sola vez durante el onboarding, un operador o un script de bootstrap suministra los tokens estáticos. Después de eso, el job solo usa la conexión.
// scripts/bootstrap-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!, // del secret manager
});
async function provision(jobId: string, tokens: Record<string, Record<string, string>>) {
const user = vinkius.user(jobId);
await user.ensure({ kind: 'service-account', job: 'nightly-reconcile' });
for (const [slug, values] of Object.entries(tokens)) {
const connector = user.connector(slug);
await connector.connect(); // obtener-o-crear, idempotente
await connector.credentials.set(values); // solo escritura; p. ej. { API_KEY: … }
}
return Promise.all(
Object.keys(tokens).map(async (slug) => ({
slug,
status: await user.connector(slug).status(), // espera "ready"
})),
);
}
await provision('svc-nightly-reconcile', {
netsuite: await secrets.read('netsuite.reconcile'),
stripe: await secrets.read('stripe.reconcile'),
sheets: await secrets.read('sheets.reconcile'),
});credentials.set() nunca devuelve los valores almacenados, y status() solo informa qué claves están configuradas. Un proceso puede verificar que sus conectores están listos sin poder jamás exfiltrar lo que recibió, la credencial es utilizable, no legible.
3. El job sin supervisión en sí
El proceso programado no necesita navegador, ni consentimiento, ni un usuario presente. Carga sus capacidades y actúa.
// jobs/nightly-reconcile.ts
import { Vinkius } from '@vinkius/connect';
const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
async function run() {
const job = vinkius.user('svc-nightly-reconcile');
const capabilities = await job.capabilities({ include: ['netsuite', 'stripe'] });
const fetchOpen = capabilities.findCapability('stripe__list_invoices');
const postEntry = capabilities.findCapability('netsuite__create_journal_entry');
if (!fetchOpen || !postEntry) {
await alertOps('reconcile: una capacidad no está disponible, ¿caducó una credencial?');
return;
}
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
if (invoices.isError) throw new Error('falló la lista de stripe: ' + invoices.content[0]?.text);
const entry = await postEntry.execute(
{ lines: toJournalLines(invoices) },
{ idempotencyKey: `reconcile:${runDate()}` }, // una ejecución lógica = un asiento
);
if (entry.isError) await alertOps('asiento de reconcile rechazado: ' + entry.content[0]?.text);
}
run().catch(async (error) => {
await alertOps(`reconcile falló: ${error.message}`);
});idempotencyKey es la función de automatización. Un reintento, un disparo duplicado del cron, un redeploy a mitad de ejecución, ninguno de ellos publica dos veces el asiento contable, porque el servidor deduplica las repeticiones que llevan la misma clave. Deriva la clave del evento de negocio (la fecha de ejecución, el id del ticket, el id de entrega del webhook), nunca de Date.now().
4. Webhooks: un actor, una entrega por clave
Para automatizaciones dirigidas por eventos, normalmente mantienes un único usuario de cuenta de servicio, pero cada llamada que muta usa el id de entrega como clave para que un webhook reintentado se aplique exactamente una vez.
// POST /webhooks/lead (verificado)
async function handleLeadWebhook(payload: { id: string; email: string }) {
const user = vinkius.user('svc-lead-intake');
if ((await user.connector('hubspot').status()) !== 'ready') {
await alertOps('CRM de lead-intake no está listo');
return;
}
const caps = await user.capabilities({ include: ['hubspot'] });
await caps.findCapability('hubspot__create_contact')?.execute(
{ email: payload.email },
{ idempotencyKey: `lead:${payload.id}` }, // entrega reintentada -> sin duplicado
);
}5. Pipelines de CI y ejecutores de un solo uso
Un job de CI se autentica exactamente igual que un cron: la misma Application Key, su propio external_id, credenciales estáticas aprovisionadas en el entorno. La diferencia es el ciclo de vida, haces disconnect() en los ejecutores efímeros cuando el pipeline se desmonta.
async function openIssueOnFailedDeploy(runId: string, repo: string) {
const caps = await vinkius.user('ci-deploy-bot').capabilities({ include: ['github'] });
await caps.findCapability('github__create_issue')?.execute(
{ owner: 'acme', repo, title: `Deploy ${runId} fallido` },
{ idempotencyKey: `deploy:${runId}` },
);
}6. Rota y retira limpiamente
Como todo depende de un único external_id, descomisionar una automatización es determinista.
async function decommission(jobId: string) {
const user = vinkius.user(jobId);
for (const conn of await user.connectors()) {
await user.connector(conn.slug).disconnect();
}
}Para rotar una credencial, vuelve a invocar credentials.set() con el nuevo valor, la conexión permanece igual y ninguna referencia de capacidad se rompe.
Fiabilidad y salida estructurada para procesos sin supervisión
Un proceso sin supervisión es donde la robustez integrada del SDK importa más. Tres garantías se heredan automáticamente:
Reintento automático con protección contra estampida. Las lecturas idempotentes (y cualquier escritura que lleve un idempotencyKey) se reintentan automáticamente: solo para errores transitorios 429/502/503/504 y de red, con un backoff de jitter completo que respeta un Retry-After del servidor. Tu cron no necesita su propio bucle de reintento.
La idempotencia es el mecanismo de seguridad. En un job por lotes, un fallo y una reejecución son habituales. Un idempotencyKey estable elimina la ambigüedad de si la reejecución duplicó el efecto, porque el servidor deduplica las repeticiones con esa clave. Suministra la clave en una llamada que muta y el SDK reintentará con seguridad incluso un POST no idempotente.
Consume resultados como objetos, no como texto. Cuando un conector devuelve datos estructurados, result.structuredContent los entrega ya analizados, sin una extracción de texto frágil en el pipeline:
const invoices = await fetchOpen.execute({ status: 'open', limit: 200 });
const list = invoices.structuredContent as { invoices: Array<{ id: string; amount: number }> };
const total = list.invoices.reduce((sum, i) => sum + i.amount, 0);Acompaña las ejecuciones de lotes largos con un timeoutMs por llamada adecuado y una signal abortada al apagarse el proceso, y el job termina de forma predecible durante un despliegue progresivo.
Lista de verificación de producción
- [ ] Dale a cada automatización un
external_iddistinto y legible por humanos (sin una única cuenta amplia compartida). - [ ] Obtén cada token estático de tu secret manager; nunca lo versionees en el repositorio ni lo introduzcas en un prompt.
- [ ] Aprovisiona una vez en la configuración; los runtimes solo leen
status(), nunca rellenan secretos. - [ ] Fija un
idempotencyKeyestable derivado del evento de negocio en cada job que muta. - [ ] Alarma cuando
status() !== 'ready'y anteisError, para que un fallo silencioso no sea un fallo invisible. - [ ] Haz
disconnect()en las cuentas de servicio efímeras como parte del desmontaje.
Ahora tienes automatizaciones que poseen su propia identidad, conectan sistemas reales sin interfaz y actúan según una agenda o un evento, sin toda la mecánica de OAuth y sin la deriva de "¿quién es el dueño de esta clave?" de una cuenta amplia compartida. Tu parque desatendido pasó de ser la parte del sistema de la que se quejan las auditorías a ser la parte con la mejor historia de gobernanza, porque cada proceso en él tiene por fin un nombre.
What you just got
Not a pitch: the properties this build inherits automatically.
Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.
Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.
Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.
One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.
idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.
A cron container with only fetch and your key runs it. An idempotencyKey from the business event makes a re-run a non-event.
Give it to your AI agent
An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.
