Put your Vercel agent on more channels
Keep your agent on Vercel. Keiki brings it SMS, iMessage, WhatsApp, Slack, Telegram and more.
Your agent keeps running on Vercel, on your model and your code. Keiki receives every channel message, sends it to your agent as a signed turn, and delivers the reply. Your app never holds channel credentials or a Keiki dashboard token.
Expose your agent to more channels
Set the Keiki agent's harness to external in push mode, with dispatchUrl pointing at your deployment's turn route
(see External harness):
{
"harness": "external",
"harness_config": {
"mode": "push",
"dispatchUrl": "https://your-app.vercel.app/api/keiki"
}
}Generate the agent's signing secret with an API key for that agent:
curl -X POST https://onkeiki.com/api/v1/developer/agents/<agent-id>/harness/signing-secret \
-H "Authorization: Bearer sk_live_…"The whsec_… value is returned once. Store it in the Vercel project as KEIKI_SIGNING_SECRET.
Install @onkeiki/ai-sdk, @vercel/functions, ai and @ai-sdk/mcp, then add the turn route. waitUntil lets
the function acknowledge the dispatch with 202 and keep running the model:
// app/api/keiki/route.ts
import { createKeikiHandler } from '@onkeiki/ai-sdk'
import { waitUntil } from '@vercel/functions'
import { stepCountIs, streamText, type StopCondition, type ToolSet } from 'ai'
export const maxDuration = 300
const deliveryTools = new Set(['reply', 'send_message'])
// Stop once Keiki has delivered the answer. A failed send comes back as an MCP error, so the model can retry.
const delivered: StopCondition<ToolSet> = ({ steps }) =>
steps.at(-1)?.toolResults.some(
(result) => deliveryTools.has(result.toolName) && (result.output as { isError?: boolean } | undefined)?.isError !== true,
) ?? false
export const POST = createKeikiHandler({
waitUntil,
onTurn: async ({ instructions, messages, tools, prepareStep, abortSignal }) =>
streamText({
model: 'openai/gpt-5.4-nano',
instructions,
messages,
tools: await tools(),
prepareStep,
stopWhen: [delivered, stepCountIs(10)],
abortSignal,
}),
})stopWhen lets the model read tool results before it answers. It stops once reply or send_message has delivered,
so the answer is not sent twice; a failed send returns an error and the model can try again.
The handler verifies Keiki-Signature against the raw body, signs its own turn API and MCP calls with that secret,
and reports the reply and completion back to Keiki. Keiki keeps the turn trace, history and delivery. tools()
returns the turn's Keiki tools (channel, capability and skill tools), authorized for that agent and turn only; the
app never gets a workspace token. See AI SDK integration
for arrivals, cancellation and serving several agents.
To develop locally, run the app and point the agent at it through the tunnel:
curl -fsSL https://onkeiki.com/api/v1/harness/tunnel.sh \
| KEIKI_API_KEY=sk_live_… PORT=3000 TURN_PATH=/api/keiki shexamples/vercel-connect-next in the repository is this route as a runnable Next.js app.
Add channels
Attach channels to the Keiki agent in the dashboard under Channels: an iMessage or SMS line, Slack, WhatsApp, Telegram or an email inbox. Messages on any of them reach the same turn route; nothing changes in your app.