Loading...
Loading...
Build and run durable background coding agents with workflow orchestration, isolated sandboxes, and GitHub integration on Vercel.
npx skill4agent add aradotso/ai-agent-skills vercel-open-agentsSkill by ara.so — AI Agent Skills collection.
Web App (Next.js) → Agent Workflow (Vercel Workflow SDK) → Sandbox VM (isolated execution)openssl rand -base64 32 # for BETTER_AUTH_SECRETPOSTGRES_URL=your_neon_postgres_url
BETTER_AUTH_SECRET=your_generated_secrethttps://YOUR_DOMAIN/api/auth/callback/vercelNEXT_PUBLIC_VERCEL_APP_CLIENT_ID=your_client_id
VERCEL_APP_CLIENT_SECRET=your_client_secrethttps://YOUR_DOMAINhttps://YOUR_DOMAIN/api/auth/callback/githubhttps://YOUR_DOMAIN/api/github/app/callbackNEXT_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# 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 webvc env pull// 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;
});3000517343218000// 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);// 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 };
},
};// 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,
};
},
};// packages/agent/src/tools/index.ts
import { myCustomTool } from './my-tool';
export const tools = [
fileReadTool,
fileWriteTool,
shellTool,
myCustomTool, // Add your tool
// ...
];// 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');// 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',
],
};// 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,
});
}
}
}// 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,
});OPEN_AGENTS_RESOURCE_PROFILE=hobbyVERCEL_SANDBOX_BASE_SNAPSHOT_ID=your_snapshot_idbun run sandbox:snapshot-baseELEVENLABS_API_KEY=your_api_keyREDIS_URL=your_redis_url
KV_URL=your_kv_url// 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 };
}// 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();
}// 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,
};
}// 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;
}
}// Check workflow logs
const logs = await getWorkflowLogs(workflowId);
console.log(logs);
// Increase timeout
export const POST = createWorkflow(
async (context, data) => {
// ...
},
{ timeout: 300000 } // 5 minutes
);// Verify sandbox is running
const status = await sandbox.status();
console.log('Sandbox status:', status);
// Recreate sandbox
await sandbox.stop();
const newSandbox = await createSandbox();https://github.com/settings/installations# Check migration status
bun run ci
# Apply migrations manually if needed
cd apps/web
npx drizzle-kit push:pgBETTER_AUTH_SECRET// 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
}
}# 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// 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>;
}