AI Connect/How to create/Automatizaciones y cuentas de servicio

Automatizaciones y cuentas de servicio

Pregunta a la IA sobre Vinkius

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.

Agent loop · one turn
One user turn of the quickstart, exactly as the console serves it. Click a step or press Run.
vinkius.user('alice_123').capabilities({ include: ['github'] })
HTTPGET /apps/vk_app_xxx/users/alice_123/tools?connector=github
const 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
  ...
Step 1 of 6
The agent loop, step by step. Press Run and follow one user turn: capabilities load, convert to tools, the model calls github__create_issue, the SDK executes on that user connection and the result feeds back. Every step shows the real SDK call and its HTTP request.
Real systemsledger · CRM · issuescron · nightlyown external_idwebhookown external_idCI pipelineown external_idstatic credentialsfrom your secret managerbrowsernever neededidempotent actions
Processes as users: headless jobs with their own static credentials act on real systems, idempotent and metered, with no browser anywhere.

Lo que un proceso-como-usuario aporta

PropiedadPor qué importa para jobs sin supervisión
Headless por diseñoLos conectores de token estático (api_key, token) no requieren consentimiento interactivo, credentials.set() es todo el flujo.
Sin navegadorUn contenedor de cron con solo fetch y tu Application Key ejecuta el SDK completo.
Radio de impacto por jobLa conexión propia de cada automatización significa que una clave filtrada compromete un job, no todo el conjunto.
Medición independienteLos tokens por conexión muestran exactamente cuánto cuesta el job nocturno de conciliación.
Reintentos deterministasUna 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.

typescript
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.

typescript
// 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.

typescript
// 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.

typescript
// 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.

typescript
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.

typescript
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:

typescript
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_id distinto 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 idempotencyKey estable derivado del evento de negocio en cada job que muta.
  • [ ] Alarma cuando status() !== 'ready' y ante isError, 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.

Isolation by construction

Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.

Write-only credentials

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.

Metered, revocable spend

Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.

Any model runtime

One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.

Production safety built in

idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.

Headless and deterministic

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.

Download SKILL.md6 · Available in your language
What unattended automations inherit, plus the SKILL.md, in your language, for your coding agent.

Próximos pasos