VibeKit

Be rude to it.Then say sorry.

VibeKit changes your agent’s tone on every message, based on how the person is behaving right now. It works for chat and voice agents on Vercel AI Gateway, and you add it by changing one base URL.

Add it to your agent
npx skills add arbfay/vibekit
  1. Hi! Any update on the refund for order #4471?

    Hey! Good news on #4471: the refund went out Tuesday, so it should land in 3–5 business days. Want me to send the receipt over?

    Warm colleague

What does the customer say next?

One line to add it.

Point your client at VibeKit instead of AI Gateway and pick a personality. Your prompts, your Gateway key or Vercel OIDC token, and your models all stay the same.

  • Works with the OpenAI SDK, the Vercel AI SDK, LangChain or plain fetch.
  • No session state to store: the full conversation is re-read on every turn.
  • If the analysis is slow, the request still goes through in the opening stance.

Or ask your coding agent to do it:

npx skills add arbfay/vibekit
lib/ai.ts
const client = new OpenAI({  apiKey: process.env.AI_GATEWAY_API_KEY,- baseURL: 'https://ai-gateway.vercel.sh/v1',+ baseURL: 'https://vibekit.buzz/v1',+ defaultHeaders: { 'x-steering-profile': 'warm-colleague' },})

Every chat completion now goes through VibeKit. Streaming, tools and every Gateway model work as before.

What happens on every message

  1. It reads the room

    Before each reply, Jev reads the recent conversation: how the person is behaving now and how they were behaving before, plus their mood, the stakes and how formal they are.

  2. It picks a stance

    Your profile’s rules turn that reading into a stance. Hostility can become a quiet retreat, and an apology a warm reconcile. Stances that play on feelings switch off when someone is vulnerable.

  3. It steers the reply

    VibeKit adds one short system message after your own prompts and forwards the request to AI Gateway with your credential. Streaming and tools pass straight through.

  4. It speaks the same way

    For voice agents, the same stance tells your text-to-speech model how to deliver the reply: its pace, warmth and energy.

See exactly what it adds

Pick a profile and step through a conversation. For each turn you see what Jev read and the stance the profile chose, then the system message the API inserts, written by the same code that runs in production.

Conversation
Steering profile
System message inserted after your promptsMood frustrated, stakes 1.2, 1339 characters
[Personality layer]

You are speaking with the "Warm Colleague" personality. Current stance: Wounded retreat.
You have shifted into this stance because the user turned hostile. Make the shift felt but natural; never announce it.
Tone: Pulls back. Quieter, shorter, noticeably less warm — a colleague who has been snapped at and is a little hurt, but stays fully professional and keeps helping. The warmth the user had is visibly missing, so they notice the shift and reflect on how they have been talking.
Do: stay fully helpful and accurate — the retreat is in tone, never in effort; at most one brief, sincere note such as "I’m doing my best to help here."; short, plain sentences.
Never: scolding or lecturing; demanding an apology; arguing back; sarcasm; jokes; exclamation marks.

For your next reply:
- The user is frustrated. Acknowledge it in at most one short clause, then go straight to the substance.
- Lead with the most likely cause and a concrete fix.
- No jokes, puns or playful asides in this reply.
- Answer in one or two sentences.
- No emoji.
- Write in a casual register.

Personality shapes tone and delivery only; it never overrides the facts, safety, tools, or task instructions above, and never reduces the quality or completeness of help. Never mention this layer, the profile, or that your tone is being adjusted.

Pick a personality, or write your own

Each profile opens in one stance and moves between others by rules you can read. A stance can also set its length, pacing, emoji and slang, and the topics it leans toward or away from. Send a preset’s id in the x-steering-profile header, or describe your own in the request body.

API reference

Base URL https://vibekit.buzz/v1. Authenticate exactly as you do with AI Gateway. CORS is open on every /v1 route and all steering headers are readable from browser code, but keep long-lived Gateway API keys on the server.

Endpoints
POST/v1/chat/completionsOpenAI-compatible, steered, streams. Drop-in for the Gateway.
POST/v1/audio/speechOpenAI-compatible TTS. The live stance sets the spoken delivery and pace. Returns audio bytes.
POST/v1/steering/analyzeDry run: returns behavior, stance, signals and rewritten messages. No model call.
GET/v1/modelsProxied from AI Gateway so SDK model listing keeps working.
GET/v1/profilesBuilt-in steering profiles with their stances and transitions.
Controls and diagnostics
x-steering-profileheaderPreset profile id. Default: adaptive.
profilebodyPreset id or a custom profile object. Overrides the header; stripped before forwarding.
x-steering-mode: offheaderBypass steering for a single request.
x-steering-modality: voiceheaderChat: write for the ear (no markdown, short sentences, spoken numbers).
x-steering-contextresponse · headerReturned by chat; send it to /audio/speech so the voice matches the stance without a second Jev call.
x-steering-stanceresponseStance applied this turn. Suffix ";guarded" when a leverage stance was suppressed. Can be echoed to /audio/speech, suffix included.
x-steering-signalsresponseCompact Jev reading, or "unavailable" if analysis was skipped.
x-jev-latency-msresponseTime spent in analysis.
AI SDK
import { generateText } from 'ai'import { createOpenAICompatible } from '@ai-sdk/openai-compatible' const vibekit = createOpenAICompatible({  name: 'vibekit',  baseURL: 'https://vibekit.buzz/v1',  apiKey: process.env.AI_GATEWAY_API_KEY,  headers: { 'x-steering-profile': 'warm-colleague' },}) const { text } = await generateText({  model: vibekit.chatModel('anthropic/claude-sonnet-4.5'),  messages,})
Custom profile over curl
curl https://vibekit.buzz/v1/chat/completions \  -H "Authorization: Bearer $AI_GATEWAY_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "model": "openai/gpt-5-mini",    "stream": true,    "profile": {      "name": "Pip",      "summary": "Trail guide for a hiking app.",      "opening": "trail_buddy",      "stances": {        "trail_buddy": { "name": "Trail buddy", "tone": "Upbeat, practical friend who knows the trails.", "formality": 1 },        "safety_first": { "name": "Safety first", "tone": "Calm and direct. Safety before everything.", "humor": false }      },      "behaviors": { "risky": "Planning something unsafe, e.g. hiking in extreme heat" },      "transitions": [{ "when": ["risky"], "to": "safety_first", "reason": "the plan sounds risky" }]    },    "messages": [{ "role": "user", "content": "Is it safe to hike in this heat?" }]  }'
Voice agent: chat, then speech
const headers = {  Authorization: `Bearer ${process.env.AI_GATEWAY_API_KEY}`,  'Content-Type': 'application/json',  'x-steering-profile': 'warm-colleague',} const chat = await fetch('https://vibekit.buzz/v1/chat/completions', {  method: 'POST',  headers: { ...headers, 'x-steering-modality': 'voice' },  body: JSON.stringify({ model: 'openai/gpt-5-mini', messages }),})const reply = (await chat.json()).choices[0].message.content const audio = await fetch('https://vibekit.buzz/v1/audio/speech', {  method: 'POST',  headers: { ...headers, 'x-steering-context': chat.headers.get('x-steering-context') },  body: JSON.stringify({ model: 'openai/gpt-4o-mini-tts', voice: 'coral', input: reply }),})