VibeAgent is the autonomous-workflow agent — a separate controller, its own model registry, and a different mental model from the chat agent. You give it a goal; it plans steps, calls tools to execute them, evaluates results, retries failures, and either delivers an outcome or explains why it gave up. It's the engine behind Vibe Studio's project-build flow and the recommended target for non-conversational AI features (data migrations, code generation, analysis pipelines).
Endpoints
/ai/vibe-agent/chatJWT/ai/vibe-agent/streamJWT/ai/vibe-agent/stream/:streamIdJWT/ai/vibe-agent/modelsJWT/ai/vibe-agent/v1/messagesJWTThe /v1/messages endpoint is Anthropic-compatible — drop in as the base URL for any client SDK that targets messages.anthropic.com.
When to use VibeAgent vs ChatAgent
| Use VibeAgent when | Use ChatAgent when |
|---|---|
| Goal is multi-step | One question, one answer |
| Steps depend on prior results | Independent turns |
| You want automatic retry | Failure is fine — user retries themselves |
| Headless / batch use | Interactive conversation |
| Standalone integration | Embedded in your app's chat UI |
Models
const { models } = await fetch('/ai/vibe-agent/models', {
headers: { orgid: 'my-org', Authorization: `Bearer ${jwt}` },
}).then(r => r.json());
// [
// { id: 'claude-opus-4-20250514', name: 'Claude 4 Opus', context_window: 200000, ... },
// { id: 'claude-sonnet-4-20250514', name: 'Claude 4 Sonnet', context_window: 200000, ... },
// { id: 'gpt-5.4', name: 'GPT-5.4', ... },
// { id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', supports_reasoning: true, ... },
// ...
// ]
VibeAgent supports Anthropic, OpenAI, and DeepSeek. Reasoning models (gpt-03, gpt-04-mini, deepseek-reasoner) are picked automatically when the goal benefits from explicit chain-of-thought.
Single-shot chat
For a non-streaming response (good for short workflows or background jobs):
const result = await fetch('/ai/vibe-agent/chat', {
method: 'POST',
headers: {
orgid: 'my-org',
Authorization: `Bearer ${jwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [
{ role: 'system', content: 'You are a data migration assistant.' },
{ role: 'user', content: 'Convert this CSV to JSON, then upload it to the contacts collection: ...' },
],
model: 'claude-sonnet-4-20250514',
max_tokens: 4096,
temperature: 0,
}),
}).then(r => r.json());
// { content: '...', usage: { prompt_tokens, completion_tokens }, model: '...' }
Streaming
Same two-step pattern as the chat agent, but on the vibe-agent paths:
const { streamId } = await fetch('/ai/vibe-agent/stream', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
messages: [/* ... */],
model: 'claude-opus-4-20250514',
stream: true,
}),
}).then(r => r.json());
// Then GET /ai/vibe-agent/stream/:streamId for SSE
Anthropic-compatible mode
VibeAgent exposes an /v1/messages endpoint that mirrors the Anthropic Messages API shape. Useful for swapping AppEngine in as the upstream of any tool that already supports Anthropic:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
baseURL: 'https://appengine.appmint.io/ai/vibe-agent',
apiKey: jwt, // bearer token, not an Anthropic key
defaultHeaders: { orgid: 'my-org' },
});
const msg = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hi.' }],
});
The endpoint converts Anthropic's request shape into the internal format and converts the response back. Streaming and non-streaming are both supported.
Multi-step planning
Out of the box VibeAgent runs a planning loop:
- 1
Decompose the goal
The first model call breaks the user's task into ordered steps. Each step has a description, expected output, and optional tools.
- 2
Execute each step
Steps run sequentially. For each, VibeAgent makes a sub-call with that step's context, possibly invoking tools.
- 3
Evaluate results
After each step, the agent checks whether the output satisfies the step's success criteria.
- 4
Retry on failure
A failed step retries with adjusted parameters. Default max retries is 3 per step; configurable via
settings.maxRetries. - 5
Synthesise the final answer
The last call combines the step outputs into the user-facing response.
You can override the planner by passing pre-decomposed steps in the request (steps: [...]) — useful when you've authored the workflow yourself and just want VibeAgent to run it.
Tool invocation
VibeAgent uses the same MCP tool registry as the chat agent (see Function calling and tools). The difference is that VibeAgent treats tools as first-class step actions — a planning step often is a tool call.
{
messages: [
{ role: 'user', content: 'Find all VIP contacts and add the "tax-2026" tag.' },
],
availableTools: ['server.repository.find', 'crm.contact.tag'],
model: 'claude-sonnet-4-20250514',
}
The agent decomposes:
- Call
server.repository.findwith{ collection: 'contact', query: { 'data.tags': 'vip' } }. - For each result, call
crm.contact.tagwith{ contactId: result.sk, tag: 'tax-2026' }. - Report total tagged.
Retry and abort
Failures bubble up as tool_result.error chunks. By default the agent retries up to 3 times per step before failing the whole run. Override:
{
messages: [/* ... */],
settings: {
maxRetries: 1,
onError: 'abort', // 'abort' | 'continue' | 'retry'
},
}
A run that aborts mid-flight returns a partial result with status: 'failed' and the step that failed; nothing is rolled back automatically.
Cost considerations
VibeAgent runs are typically 5–20× more expensive than a chat-agent turn because of the planning + per-step model calls. Set explicit budget caps in the request:
{
messages: [/* ... */],
settings: {
maxTokensTotal: 50000, // hard cap across all sub-calls
maxStepDuration: 60000, // ms per step
},
}
When the cap is reached, the run completes whatever step it's on and returns. See Cost and token control.
A VibeAgent run that creates records is not automatically idempotent. If you trigger the same goal twice, you'll get duplicates. For workflows that mutate data, include an idempotency key in the system prompt and have the first step check whether a marker already exists.