Vercel Open Agents
Skill by ara.so — AI Agent Skills collection.
Open Agents is an open-source template for building cloud-based coding agents that run as durable workflows on Vercel. It provides web UI, agent runtime, sandbox orchestration, and GitHub integration to execute multi-step coding tasks without local execution.
Architecture
Open Agents uses a three-layer architecture:
Web App (Next.js) → Agent Workflow (Vercel Workflow SDK) → Sandbox VM (isolated execution)
Key principle: The agent runs outside the sandbox and interacts with it through tools. This separation enables:
- Durable execution beyond single request lifecycles
- Independent sandbox hibernation and resume
- Flexible model/provider swapping
- Clean separation of concerns
Installation
Deploy to Vercel
Fork and import the repository to Vercel
Generate secrets:
openssl rand -base64 32 # for BETTER_AUTH_SECRETSet required environment variables:
POSTGRES_URL=your_neon_postgres_url BETTER_AUTH_SECRET=your_generated_secretDeploy to get production URL
Create Vercel OAuth app at https://vercel.com/account/settings/oauth-apps
- Callback:
https://YOUR_DOMAIN/api/auth/callback/vercel
- Callback:
Add OAuth credentials:
NEXT_PUBLIC_VERCEL_APP_CLIENT_ID=your_client_id VERCEL_APP_CLIENT_SECRET=your_client_secretCreate GitHub App (optional, for repo access):
- Homepage:
https://YOUR_DOMAIN - Callback:
https://YOUR_DOMAIN/api/auth/callback/github - Setup URL:
https://YOUR_DOMAIN/api/github/app/callback - Make app public for org installs
- Homepage:
Add GitHub credentials:
NEXT_PUBLIC_GITHUB_CLIENT_ID=your_github_app_client_id GITHUB_CLIENT_SECRET=your_github_app_client_secret GITHUB_APP_ID=your_app_id GITHUB_APP_PRIVATE_KEY=your_pem_private_key NEXT_PUBLIC_GITHUB_APP_SLUG=your_app_slug GITHUB_WEBHOOK_SECRET=your_webhook_secret
Local Development
# Install dependencies
bun install
# Copy environment template
cp apps/web/.env.example apps/web/.env
# Fill in required variables in apps/web/.env
# Start dev server
bun run web
For existing Vercel projects:
vc env pull
Core Concepts
Workflows
Agent runs execute as Vercel Workflow SDK workflows, enabling:
- Durable execution: survives across steps and deployments
- Streaming: real-time updates to the UI
- Cancellation: user-initiated stop
- Resume: reconnect to active runs
Example workflow structure:
// apps/web/app/api/workflows/agent/route.ts
import { createWorkflow } from '@vercel/workflow-sdk';
export const POST = createWorkflow(async (context, data) => {
const { message, sessionId } = data;
// Agent execution happens in workflow steps
const result = await context.run('agent-step', async () => {
return await runAgent(message, sessionId);
});
return result;
});
Sandboxes
Sandboxes provide isolated execution environments with:
- Filesystem access: read, write, search files
- Shell execution: run commands
- Git operations: clone, branch, commit, push
- Port exposure:
3000,5173,4321,8000 - Snapshot-based resume: hibernate and restore state
// packages/sandbox/src/client.ts
import { createSandbox } from '@vercel-labs/open-agents/sandbox';
const sandbox = await createSandbox({
baseSnapshotId: process.env.VERCEL_SANDBOX_BASE_SNAPSHOT_ID,
});
// Execute commands
const result = await sandbox.exec('npm install');
// Read files
const content = await sandbox.readFile('package.json');
// Write files
await sandbox.writeFile('src/config.ts', configContent);
Tools
Agents interact with sandboxes through tools:
// packages/agent/src/tools/file-edit.ts
export const fileEditTool = {
name: 'file_edit',
description: 'Edit a file with search-and-replace',
parameters: z.object({
path: z.string(),
search: z.string(),
replace: z.string(),
}),
execute: async ({ path, search, replace }, context) => {
const content = await context.sandbox.readFile(path);
const updated = content.replace(search, replace);
await context.sandbox.writeFile(path, updated);
return { success: true };
},
};
Available tools:
- file_read: Read file contents
- file_write: Write entire file
- file_edit: Search-and-replace edits
- file_search: Search file contents
- shell: Execute shell commands
- task: Create subtasks for the agent
- web: Fetch web content
- skill: Access domain-specific skills
Creating Custom Tools
- Define the tool schema:
// packages/agent/src/tools/my-tool.ts
import { z } from 'zod';
export const myCustomTool = {
name: 'my_tool',
description: 'Does something specific',
parameters: z.object({
input: z.string().describe('The input parameter'),
}),
execute: async ({ input }, context) => {
// Access sandbox
const result = await context.sandbox.exec(`echo ${input}`);
// Return structured data
return {
output: result.stdout,
exitCode: result.exitCode,
};
},
};
- Register the tool:
// packages/agent/src/tools/index.ts
import { myCustomTool } from './my-tool';
export const tools = [
fileReadTool,
fileWriteTool,
shellTool,
myCustomTool, // Add your tool
// ...
];
Skills System
Skills are domain-specific knowledge modules loaded into agent context:
// packages/agent/src/skills/registry.ts
export interface Skill {
id: string;
name: string;
description: string;
content: string; // Markdown content
triggers: string[];
}
// Skills are cached in Redis/KV or in-memory
const skillsCache = new SkillsCache({
redis: process.env.REDIS_URL,
});
// Retrieve skill by trigger
const skill = await skillsCache.findByTrigger('nextjs app router');
Create a custom skill:
// packages/agent/src/skills/custom-skill.ts
export const customSkill: Skill = {
id: 'custom-framework',
name: 'Custom Framework',
description: 'Expertise in CustomFramework usage',
content: `
# Custom Framework Skill
## Installation
\`\`\`bash
npm install custom-framework
\`\`\`
## Usage
\`\`\`typescript
import { Framework } from 'custom-framework';
const app = new Framework();
app.start();
\`\`\`
`,
triggers: [
'use custom framework',
'setup custom framework',
'configure custom framework',
],
};
GitHub Integration
Auto-commit and PR Creation
Configure agent to automatically commit and create PRs:
// packages/agent/src/workflows/agent-workflow.ts
export async function runAgentWorkflow(
message: string,
options: {
autoCommit?: boolean;
autoPR?: boolean;
repository?: string;
branch?: string;
}
) {
const result = await agent.run(message);
if (options.autoCommit && result.success) {
await sandbox.exec('git add .');
await sandbox.exec(`git commit -m "Agent: ${message}"`);
if (options.autoPR) {
await createPullRequest({
repo: options.repository,
branch: options.branch,
title: `AI Agent: ${message}`,
body: result.summary,
});
}
}
}
GitHub App Installation
// apps/web/lib/github/app.ts
import { App } from '@octokit/app';
const app = new App({
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
});
// Get installation token
const installation = await app.octokit.request(
'GET /users/{username}/installation',
{ username: 'user' }
);
const token = await app.octokit.auth({
type: 'installation',
installationId: installation.data.id,
});
Configuration
Resource Profiles
For Hobby tier deployments:
OPEN_AGENTS_RESOURCE_PROFILE=hobby
This adjusts chat and sandbox resource limits to Hobby-compatible values.
Sandbox Base Snapshot
Use a preconfigured sandbox image:
VERCEL_SANDBOX_BASE_SNAPSHOT_ID=your_snapshot_id
Create base snapshot:
bun run sandbox:snapshot-base
Voice Input
Enable voice transcription with ElevenLabs:
ELEVENLABS_API_KEY=your_api_key
Optional Caching
Enable Redis/KV for skills metadata:
REDIS_URL=your_redis_url
KV_URL=your_kv_url
Common Patterns
Multi-step Agent Task
// packages/agent/src/agent.ts
export async function runMultiStepTask(goal: string) {
const steps = await planSteps(goal);
for (const step of steps) {
const result = await executeStep(step, {
tools: availableTools,
sandbox: currentSandbox,
});
if (!result.success) {
// Handle failure, retry, or replan
return { success: false, error: result.error };
}
}
return { success: true, steps };
}
Streaming Agent Response
// apps/web/app/api/chat/route.ts
import { streamText } from 'ai';
export async function POST(req: Request) {
const { messages, sessionId } = await req.json();
const result = await streamText({
model: openai('gpt-4'),
messages,
tools: {
file_read: fileReadTool,
file_write: fileWriteTool,
shell: shellTool,
},
onFinish: async ({ text, toolCalls }) => {
// Save to session
await saveMessage(sessionId, { text, toolCalls });
},
});
return result.toDataStreamResponse();
}
Session Management
// apps/web/lib/sessions.ts
import { db } from './db';
export async function createSession(userId: string, metadata: object) {
const session = await db.session.create({
data: {
userId,
metadata,
createdAt: new Date(),
},
});
return session;
}
export async function shareSession(sessionId: string) {
const shareToken = await generateShareToken(sessionId);
return {
url: `${process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL}/share/${shareToken}`,
token: shareToken,
};
}
Sandbox Lifecycle
// packages/sandbox/src/lifecycle.ts
export class SandboxLifecycle {
async create() {
this.sandbox = await createSandbox({
baseSnapshotId: this.baseSnapshotId,
});
return this.sandbox;
}
async hibernate() {
const snapshot = await this.sandbox.createSnapshot();
await this.sandbox.stop();
return snapshot.id;
}
async resume(snapshotId: string) {
this.sandbox = await createSandbox({
baseSnapshotId: snapshotId,
});
return this.sandbox;
}
async destroy() {
await this.sandbox.stop();
this.sandbox = null;
}
}
Troubleshooting
Workflow Execution Fails
Problem: Workflow steps timeout or fail
// Check workflow logs
const logs = await getWorkflowLogs(workflowId);
console.log(logs);
// Increase timeout
export const POST = createWorkflow(
async (context, data) => {
// ...
},
{ timeout: 300000 } // 5 minutes
);
Sandbox Connection Issues
Problem: Cannot connect to sandbox
// Verify sandbox is running
const status = await sandbox.status();
console.log('Sandbox status:', status);
// Recreate sandbox
await sandbox.stop();
const newSandbox = await createSandbox();
GitHub App Permissions
Problem: Cannot access repositories
- Verify app installation:
https://github.com/settings/installations - Check app permissions include:
- Repository contents: Read & write
- Pull requests: Read & write
- Metadata: Read-only
Database Migrations
Problem: Schema out of sync
# Check migration status
bun run ci
# Apply migrations manually if needed
cd apps/web
npx drizzle-kit push:pg
Authentication Issues
Problem: OAuth callback fails
- Verify callback URLs match exactly (including protocol)
- Check
BETTER_AUTH_SECRETis set and consistent across deployments - Confirm OAuth app credentials are correct
Port Conflicts in Sandbox
Problem: Preview port already in use
// Use alternative ports
const ports = [3000, 5173, 4321, 8000];
for (const port of ports) {
try {
await sandbox.exec(`lsof -ti:${port} | xargs kill -9`);
} catch {
// Port was free
}
}
Development Commands
# Start development server
bun run web
# Lint and format check
bun run check
# Auto-fix linting and formatting
bun run fix
# Type check all packages
bun run typecheck
# Full CI suite
bun run ci
# Refresh sandbox base snapshot
bun run sandbox:snapshot-base
API Reference
Core Types
// Agent context passed to tools
interface AgentContext {
sandbox: Sandbox;
sessionId: string;
userId: string;
metadata: Record<string, unknown>;
}
// Tool definition
interface Tool<T = unknown> {
name: string;
description: string;
parameters: z.ZodSchema<T>;
execute: (params: T, context: AgentContext) => Promise<unknown>;
}
// Sandbox interface
interface Sandbox {
exec(command: string): Promise<ExecResult>;
readFile(path: string): Promise<string>;
writeFile(path: string, content: string): Promise<void>;
search(pattern: string): Promise<SearchResult[]>;
createSnapshot(): Promise<Snapshot>;
status(): Promise<SandboxStatus>;
stop(): Promise<void>;
}
Resources
- Homepage: https://open-agents.dev
- Repository: https://github.com/vercel-labs/open-agents
- Vercel Workflow SDK: https://vercel.com/docs/workflow
- Better Auth: https://www.better-auth.com/
- GitHub Apps: https://docs.github.com/en/apps