AI Connect/How to create/Department copilots (logistics)
Department copilots (logistics)
Model every department of a logistics company as its own AI Connect SDK user, Finance, Dispatch, Warehouse and Fleet Ops, each with its own connectors, credentials and capabilities, on one application key.
Here is what an internal AI platform was never allowed to look like until now: a logistics company where Finance reconciles invoices, Dispatch answers "where is load 4412?", Warehouse counts the dock, Fleet Ops tracks maintenance, and every team gets its own copilot with its own connectors and credentials, on one Application key, backed by thousands of AI connections from day one. NetSuite, the ERP, the WMS, a telematics API, Slack channels, Google Sheets: no integration built by you, and each team's credentials belong to the team, not to whichever user is currently logged in.
Every integration platform you have ever evaluated stops exactly there. They connect an application to a service. None of them has ever offered a team, a department, a shared role as a first-class user with its own isolated credentials, because none of them has a user model that can name anything but a human login. The AI Connect SDK can, and that reframe is this build: the department is the user. One external_id per team. The humans are just who operates the copilot; the isolation boundary is the department. The same architecture that serves one person with one Gmail serves the whole org chart, with no new infrastructure, no new platform, no new vendor conversation. You scale an organization by adding ids, not by buying software.
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
...The same loop you saw for a human now runs against dept-dispatch instead of alice_123. The copilot acts on Dispatch's own TMS and Slack, never on Finance's NetSuite, because a connection belongs to whoever owns the external_id.
Why a department is a perfect "user"
- Shared, durable state. Nobody "owns" the connection;
dept-warehouseowns it. Staff turnover never breaks the integration. - Blast-radius isolation. Each connection carries its own data-plane token, so Dispatch's spend and instant revocation are independent from Warehouse's.
- Least privilege by construction. A copilot literally cannot see a connector another department connected, the capability query is scoped to one
external_id. - One application key. All departments live under a single Vinkius Application. You add a team by defining a new id, not by provisioning infrastructure.
1. Name the departments
Use a stable, readable, URL-safe id. Prefix them so they never collide with a human id from another part of your system.
type Department = 'finance' | 'dispatch' | 'warehouse' | 'fleet';
const departmentUserId = (dept: Department) => `dept-${dept}`;
// "dept-finance", "dept-dispatch", "dept-warehouse", "dept-fleet"2. Declare each department's connectors
Different teams need different tools. Keep that as configuration, the rest of the code never changes per team.
// server/departments.ts
interface DeptSpec {
label: string;
connectors: string[]; // catalog slugs
}
export const DEPARTMENTS: Record<Department, DeptSpec> = {
finance: { label: 'Finance', connectors: ['netsuite', 'stripe', 'gmail'] },
dispatch: { label: 'Dispatch', connectors: ['sap', 'slack', 'google-sheets'] },
warehouse: { label: 'Warehouse', connectors: ['wms', 'google-sheets', 'jira'] },
fleet: { label: 'Fleet Ops', connectors: ['telematics', 'servicemax', 'slack'] },
};Connector slugs come from the live catalog. Let your admin UI discover them instead of hardcoding: await vinkius.catalog.search('telematics') or for await (const c of vinkius.catalog.iterate()). See Connectors & credentials.
3. Provision a department once (an admin action)
When a team is onboarded, connect its accounts and store the credentials. The department id is the externalId everywhere, there is no human in this path.
import { vinkius } from './vinkius';
import { DEPARTMENTS, type Department } from './departments';
async function bootstrapDepartment(dept: Department) {
const user = vinkius.user(`dept-${dept}`);
// attach non-secret metadata so you can filter/audit later
await user.ensure({ kind: 'department', label: DEPARTMENTS[dept].label });
for (const slug of DEPARTMENTS[dept].connectors) {
const connector = user.connector(slug);
await connector.connect();
// api_key connectors: connector.credentials.set({ API_KEY: ... })
// oauth connectors: connect() returns after provider consent
}
// report readiness per connector so admins see what still needs credentials
return Promise.all(
DEPARTMENTS[dept].connectors.map(async (slug) => ({
slug,
status: await user.connector(slug).status(),
})),
);
}4. Load the department's capabilities at request time
A request arrives with the department the copilot serves. Resolve its capabilities, scoped and ready to pass to the model.
async function departmentCapabilities(dept: Department) {
const spec = DEPARTMENTS[dept];
return vinkius.user(`dept-${dept}`).capabilities({
include: spec.connectors,
onConnectorError: (slug, error) => {
// surface to admins; do not interrupt the response
console.warn(`${dept}/${slug}`, (error as Error).message);
},
});
}5. Answer as the department
Route the message to the right copilot, give the model that department's tools only, execute on that department's connection.
// server/copilot.ts
import OpenAI from 'openai';
import { toOpenAITools, runOpenAIToolCall } from '@vinkius/connect/openai';
import { type Department } from './departments';
import { departmentCapabilities } from './capabilities';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
const SYSTEM_PROMPT: Record<Department, string> = {
finance: 'You are the Finance copilot. Reconcile invoices and answer billing questions using the connected accounting tools.',
dispatch: 'You are the Dispatch copilot. Answer where loads are and update ETAs using the TMS and Slack.',
warehouse: 'You are the Warehouse copilot. Report dock counts and open tasks using the WMS and Sheets.',
fleet: 'You are the Fleet Ops copilot. Report vehicle health and maintenance using telematics and ServiceMax.',
};
export async function ask(dept: Department, question: string) {
const capabilities = await departmentCapabilities(dept);
const completion = await openai.chat.completions.create({
model: '[MODEL_ID]',
messages: [
{ role: 'system', content: SYSTEM_PROMPT[dept] },
{ role: 'user', content: question },
],
tools: toOpenAITools(capabilities),
});
const call = completion.choices[0]?.message.tool_calls?.[0];
if (!call) return { answer: completion.choices[0].message.content };
const result = await runOpenAIToolCall(capabilities, call); // runs as this dept
return { answer: result.content, tool: call.function.name };
}// Dispatch asks about its own TMS; it cannot reach Finance's NetSuite.
await ask('dispatch', 'What is the ETA for load 4412 and which driver is on it?');
await ask('finance', 'Which customer invoices over 30 days are still open this month?');6. Let humans act as a department, with an audit trail
The people operating a copilot are not the "user" in the SDK sense, but you should still know who asked. Log the operator alongside the department id, the capability boundary stays with the department.
async function askAs(dept: Department, operator: string, question: string) {
const answer = await ask(dept, question);
// your own audit store — the SDK never sees the operator
await audit.record({
actor: operator,
on_behalf_of: `dept-${dept}`,
question,
});
return answer;
}Keep the operator identity entirely in your system. To Vinkius, the actor is always dept-finance. That is what gives you department-level isolation and lets the same copilot behave identically no matter which employee is typing.
7. Grant and revoke a team in a single operation
Because a department is one external_id, decommissioning is trivial. Disconnecting a connector removes only that team's access.
async function retireConnector(dept: Department, slug: string) {
await vinkius.user(`dept-${dept}`).connector(slug).disconnect();
}Power features worth knowing here
One department, one connector, without re-listing everything
A focused copilot that only ever accesses one system should not incur the cost of a fan-out over the whole team's connections. forConnector slices an already-loaded set; and to skip fetching the others entirely, list from a single connector handle:
// from an aggregated set:
const sheetsOnly = capabilities.forConnector('google-sheets');
// or avoid listing every connection at all:
const tmsOnly = await vinkius.user(`dept-${dept}`).connector('sap').capabilities();The fan-out is concurrent and self-healing
user.capabilities() lists the connection summaries once, then fans out to the ready connectors with a concurrency cap of 8 and reuses each resolved connection id (no per-connector re-list). A runtime timeout on one connector does not interrupt the response, the rest still resolve, and onConnectorError reports which team tool was unavailable so you can warn that department's administrator.
Read a department's own metadata back
The non-secret kind: 'department' you attached with ensure() is available for you to render. user.get() returns the stored metadata and status, enough to build a "which teams are onboarded?" dashboard without any extra database.
const profile = await vinkius.user('dept-finance').get();
console.log(profile.metadata); // { kind: 'department', label: 'Finance' }
console.log(profile.status); // active | ...Production checklist
- [ ] Prefix department ids so they can never collide with human ids (
dept-…). - [ ] Store the per-department connector list as configuration, not code branches.
- [ ] Onboard with
user.ensure({ kind: 'department' })so metadata-driven dashboards work. - [ ] Render
connector.status()in the admin console so gaps (needs_credentials) are visible. - [ ] Route human operators to their department copilot; keep who-asked logging on your side.
- [ ] Give every mutating copilot action a stable
idempotencyKey(e.g. ticket + dept).
You now run an internal AI platform where Finance, Dispatch, Warehouse and Fleet each have their own connected tools and their own copilot, all from one application key and one external_id per team. You did not buy a platform for departments; the platform simply has no ceiling on what a user can be. Adding a fifth team tomorrow is one line of configuration. That is what it means to own the connectivity layer: the org chart becomes your user table.
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 copilot literally cannot see another department’s connectors. Onboarding a team is a new external_id, not new infrastructure.
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.
