AI Connect/How to create/A fleet of AI agents

A fleet of AI agents

Ask AI about Vinkius

Run many autonomous AI agents from one application, modeling each agent as its own AI Connect SDK user so it carries only the connectors its job needs, with per-agent credentials, spend and instant revocation. No other platform gives every agent in a swarm its own isolated identity.

The next platform you will build is not one agent, it is a swarm: a triage agent that reads tickets, a research agent that retrieves context, an ops agent that executes runbooks, a compliance agent that blocks unsafe actions. Modern AI systems are already there. What has never existed is the infrastructure to run that swarm safely: until now, every agent either shared your master credentials or you built a permissions system by hand.

The AI Connect SDK gives each agent what no platform has ever given an agent: its own identity as a user, with its own connectors, its own credentials, its own spend and its own kill switch, backed by thousands of AI connections from day one. Giving every agent the union of all tools is a security and cost risk: the model behaves unpredictably, one uncontrolled loop can exhaust the budget, and you cannot attribute actions to the agent that took them. The industry's usual answer is a permissions matrix bolted onto a shared account. The SDK solves it the way nothing else does, because it solves it the same way it solves humans: each agent is a user. One external_id per agent, and capabilities() returns only what that agent is entitled to access. An autonomous program becomes a governed citizen of your platform. That sentence has never been true of an integration product before. It is the structural difference between agents that share your access and agents that own theirs.

Governor agentone key, full viewtriagescoped toolsresearchscoped toolsopsscoped tools+ the swarmsame guaranteesvk_live_*vk_live_*vk_live_*disconnect()one callleast privilege
Each agent is a user: scoped connectors, its own metered token, and one-call revocation, so a misbehaving agent never touches the rest of the swarm.

The agent-as-user benefits

ConcernHow "agent = user" fixes it
ScopeEach agent's external_id connects only its own connectors, the triage agent cannot execute pagerduty__page.
CostEvery connection is metered and revocable through its own token, so spending and termination are per agent.
AuditYou always know which agent's action you are looking at, by external_id.
Blast radiusA misbehaving agent is a single disconnect() call from being revoked, with no impact on the other agents.
LifecycleCreate a new worker by issuing a new id; decommission it by disconnecting. No configuration deploys.

These are AI agents, and they themselves act as users of the SDK. There is no human in the loop when ops-agent connects to your incident tooling at 03:00. The opaque external_id is exactly what lets a non-human actor own isolated credentials.

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.

1. Give every agent a stable identity

Derive the id from the agent's role and, if you scale workers, its instance. Prefix it so an agent id can never collide with a human or a department.

typescript
type AgentRole = 'triage' | 'research' | 'ops' | 'compliance';

const agentUserId = (role: AgentRole, instance = 'primary') =>
  `agent-${role}-${instance}`;
// "agent-triage-primary", "agent-ops-worker-07"

2. Define what each agent is allowed to connect

This registry is your least-privilege policy. Keep it declarative so you can reason about the whole fleet at once.

typescript
// server/fleet.ts
export const AGENTS: Record<AgentRole, { model: string; connectors: string[] }> = {
  triage:     { model: '[MODEL_ID]', connectors: ['zendesk', 'linear'] },
  research:   { model: '[MODEL_ID]', connectors: ['drive', 'confluence', 'search'] },
  ops:        { model: '[MODEL_ID]', connectors: ['pagerduty', 'kubernetes', 'github'] },
  compliance: { model: '[MODEL_ID]', connectors: ['audit-log', 's3'] },
};

3. Provision an agent

On deploy, connect the agent's accounts. ensure() tags the actor as an agent so your governance dashboard can filter the fleet from humans.

typescript
import { vinkius } from './vinkius';
import { AGENTS, type AgentRole, agentUserId } from './fleet';

async function provisionAgent(role: AgentRole, instance = 'primary') {
  const user = vinkius.user(agentUserId(role, instance));
  await user.ensure({ kind: 'agent', role });

  for (const slug of AGENTS[role].connectors) {
    const connector = user.connector(slug);
    await connector.connect();
    // headless agents have no browser consent: supply api_key/token
    // credentials through your secret store via connector.credentials.set()
  }
  return user.connectors(); // [{ slug, status }, …]
}

Agents run headless, so prefer connectors that accept a static token or key via credentials.set(). OAuth connectors need a human consent at least once, do that during onboarding, then the agent reuses the resulting connection.

4. Run one agent's loop

Each agent loads its own scoped capabilities. It cannot see another agent's tools because they reside under a different external_id.

typescript
// server/run-agent.ts
import OpenAI from 'openai';
import { vinkius } from './vinkius';
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';
import { AGENTS, type AgentRole, agentUserId } from './fleet';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });

export async function runAgent(role: AgentRole, task: string, instance = 'primary') {
  const user = vinkius.user(agentUserId(role, instance));
  const capabilities = await user.capabilities({ include: AGENTS[role].connectors });

  const messages: object[] = [
    { role: 'system', content: `You are the ${role} agent. Complete the task using only your tools.` },
    { role: 'user', content: task },
  ];

  for (let step = 0; step < 12; step++) {
    const completion = await openai.chat.completions.create({
      model: AGENTS[role].model,
      messages: messages as never,
      tools: toOpenAITools(capabilities),
    });
    const msg = completion.choices[0].message;
    messages.push(msg as object);
    if (!msg.tool_calls?.length) return msg.content;

    for (const call of msg.tool_calls) {
      const result = await runOpenAIToolCall(capabilities, call);
      messages.push({
        role: 'tool',
        tool_call_id: call.id,
        content: JSON.stringify({ content: result.content, isError: result.isError }),
      });
    }
  }
  return 'Task stopped: step budget reached.';
}

5. Hand off between agents, keeping scope

A triage agent escalates to the ops agent by calling it with a new task. The ops agent runs under its own id and connects its own tools, hand-off never widens what either agent can do.

typescript
async function orchestrate(ticket: string) {
  const triage = await runAgent('triage', `Classify and route: ${ticket}`);

  if (triage?.includes('INFRA')) {
    // ops agent uses pagerduty/kubernetes/github — tools triage never sees
    return runAgent('ops', `Mitigate: ${ticket}`);
  }
  return runAgent('research', `Gather context on: ${ticket}`);
}

Because external_id is opaque, you can scale out: run 20 ops workers as agent-ops-worker-01agent-ops-worker-20. Each has its own connection and meter, so an uncontrolled loop in one worker affects only that worker.

6. The compliance / governor agent

Model a policy agent too. It connects read-only tools, inspects another agent's actions, and can disconnect a connector to revoke an agent, governance expressed as ordinary SDK calls.

typescript
async function quarantine(role: AgentRole, instance: string, slug: string) {
  await vinkius.user(agentUserId(role, instance)).connector(slug).disconnect();
}

7. Python or CrewAI? Bridge with neutral JSON Schema

The SDK is TypeScript-only today, but capabilities are portable. In a Python stack, expose the tool set through the neutral adapter as data, or call the same connection's runtime directly.

typescript
import { toJSONSchemaTools } from '@vinkius/connect/json-schema';

const definitions = toJSONSchemaTools(await vinkius.user('agent-ops-primary').capabilities());
// hand `definitions` to your Python agent framework; it calls back into one route

Internal mechanics: why one misbehaving agent cannot compromise the fleet

The isolation you inherit is not only organizational, it is enforced by the data plane, and it is the real advantage behind an autonomous fleet.

*Every connection owns its own `vk_live_` token.* Listing and executing tools are routed directly to that connection's runtime, where every call is metered and revocable through its own token*, never through the application API. So each agent's spend and each agent's termination are independent of every other agent, by construction.

Revocation fails closed. The SDK issues exactly one token per connection at connect() and never re-issues one implicitly during execution. That means disconnect() stops access immediately: an agent holding a revoked token cannot obtain a replacement on its own. Use it as an emergency cutoff:

typescript
// the governor agent halts an anomalous worker in a single call — immediate and final
await vinkius.user('agent-ops-worker-07').connector('pagerduty').disconnect();

Bound each step. An agent can give a slow tool more time than a human would, but you cap every call with timeoutMs (composed with an AbortSignal). An uncontrolled execution receives a strict deadline instead of an unbounded budget:

typescript
const result = await capabilities
  .findCapability('kubernetes__scale_deployment')!
  .execute(
    { deployment: 'ingress', replicas: 6 },
    { idempotencyKey: `ops:${task.id}:scale`, timeoutMs: 10_000 },
  );

Trace each agent separately. hooks fire per HTTP attempt with a redacted requestId, so you attribute every call, retry and failure to a single external_id without building your own tracing store. Credential fields and the vk_live_* path segment are masked before your hook receives them.

Pass types, not free text. When one agent passes output to another, prefer result.structuredContent (the connector's structured object) over serializing content: more precise, more token-efficient and unambiguous.

Production checklist

  • [ ] Prefix every agent id (agent-…) so agents never collide with humans or departments.
  • [ ] Encode least privilege as the AGENTS registry, one agent, one narrow connector set.
  • [ ] Give mutating agents a per-task idempotencyKey so a retried loop cannot double-execute.
  • [ ] Set a hard step budget per agent loop; return isError to the model instead of throwing.
  • [ ] Tag actors with user.ensure({ kind: 'agent', role }) so governance can filter the fleet.
  • [ ] Build disconnect() into your governor path, emergency revocation is one call per agent.

You now run a fleet where every agent owns exactly the tools its job needs, every agent's spend is independent, and a misbehaving agent is neutralized without affecting the others. Most teams govern their agents with policy documents and crossed fingers. You govern yours with an identity system, and that gap is why your fleet can grow past ten agents while theirs cannot safely grow past two.

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.

Fleet control, no shared super-agent

Give each agent only its connectors, cap each loop with timeoutMs, revoke one misbehaving agent in one call. The others keep running.

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 an agent fleet inherits, plus the SKILL.md, in your language, for your coding agent.

Next steps