upstream-agents
Run AI coding agents in isolated sandboxes connected to your GitHub repositories
Upstream Agents
A sophisticated multi-tenant web application that enables users to run AI coding agents (Claude Code, OpenCode, Codex) in isolated Daytona sandboxes. Features a Slack-like interface for managing AI-powered coding agents across multiple GitHub repositories with real-time streaming output, background execution, and persistent chat history.
Features
Core Capabilities
- Multi-Agent Support - Run Claude Code, OpenCode, or Codex agents with configurable models
- GitHub OAuth Login - Sign in with GitHub, OAuth tokens used for seamless repo access
- Isolated Sandboxes - Each branch gets its own Daytona sandbox environment
- Real-time Streaming - Live agent output via Server-Sent Events (SSE)
- Background Execution - Agent tasks continue even when browser is closed
- Persistent Chat History - Full conversation history with tool calls and content blocks
- Loop Mode - Agents automatically continue working until they respond "FINISHED"
User Experience
- Slack-like Interface - Repository sidebar with branch-based conversations
- Multi-tenant Architecture - User data fully isolated, shared infrastructure
- Quota Enforcement - Configurable concurrent sandbox limits
- Encrypted Credentials - API keys stored AES-encrypted in database
- Drag-and-Drop Reordering - Customize repository order in sidebar
- Dark Mode Support - Theme switching with next-themes
- Mobile Responsive - Full mobile UI with drawer navigation
Developer Features
- Pull Request Integration - Create PRs directly from branches
- Git Diff Viewer - Compare branches and view changes
- Git History - Browse commit history per branch
- Advanced Git Operations - Merge, rebase, reset, tag, rename, and delete remote branches
- MCP Server Registry - Browse and connect 3,000+ MCP servers via Smithery
- Environment Variables - Per-repository encrypted env vars for sandboxes
- Auto-Stop - Configurable sandbox auto-stop intervals (5-20 minutes)
- Safe Push Handling - Branch checks plus retry and graceful "already up-to-date" handling
Automation
- Loop Mode - Toggle per-branch to have agents continue until task completion
- Configurable Iterations - Set max loop iterations (1-25) in Settings → Automation
- Background Loop Checking - Vercel cron job continues loops even when browser is closed
Architecture
┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ Browser (React) │────▶│ Next.js 16 API │────▶│ Neon Postgres │
│ - Shadcn/ui │◀────│ (Vercel/Node) │◀────│ (Serverless) │
│ - SSE Streaming │ │ - 34 API routes │ │ - Prisma ORM │
└─────────────────────┘ └──────────┬──────────┘ └──────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Daytona Cloud │ │ GitHub API │ │ LLM APIs │
│ (Sandboxes) │ │ (OAuth) │ │ - Anthropic │
│ - SDK control │ │ - Repos/PRs │ │ - OpenAI │
└────────┬────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Coding Agents │────────▶│ Smithery │
│ - Claude Code │ │ (MCP Registry │
│ - OpenCode │◀────────│ + Connect) │
│ - Codex │ └─────────────────┘
└─────────────────┘
Data Flow
- Authentication - User authenticates via GitHub OAuth (NextAuth.js)
- Repository Setup - User adds repositories from GitHub, creates branches
- Sandbox Creation - Each branch spins up a Daytona sandbox with selected agent
- Chat Interaction - User sends prompts → API streams agent output in real-time
- Agent Execution - Agent reads/writes files, runs commands, makes commits
- Background Processing - Long-running tasks continue server-side if browser closes
- Pull Requests - User creates PRs from completed branch work
Credential Management
| Credential | Storage | Access |
|---|---|---|
| GitHub OAuth Token | NextAuth Account table | Server-side only, auto-refreshed |
| Daytona API Key | Environment variable | Shared infrastructure, server-side |
| Anthropic API Key | AES encrypted in database | User provides, decrypted at runtime |
| OpenAI API Key | AES encrypted in database | User provides, decrypted at runtime |
| OpenCode API Key | AES encrypted in database | User provides, decrypted at runtime |
| Smithery API Key | Environment variable | Shared infrastructure, server-side |
| MCP Server Tokens | AES encrypted in database | Per-repo per-server, managed by Smithery Connect |
| Repository Env Vars | AES encrypted in database | Per-repo, injected into sandbox |
Tech Stack
Frontend
- Framework: Next.js 16.1.6 (App Router, React 19)
- UI Library: Shadcn/ui (50+ Radix UI components)
- Styling: Tailwind CSS 4.2
- Forms: React Hook Form + Zod validation
- Icons: Lucide React (564 icons)
- Charts: Recharts
- Notifications: Sonner toast notifications
- Markdown: react-markdown for agent output
Backend
- Server: Next.js API Routes (serverless)
- ORM: Prisma 7.4.2 with Neon adapter
- Database: PostgreSQL (Neon serverless)
- Authentication: NextAuth.js 4.24 (GitHub OAuth)
- Encryption: crypto-js (AES encryption)
External Services
- Sandboxes: Daytona SDK (@daytonaio/sdk)
- Agent Runner: background-agents
- LLM Providers: Anthropic SDK, OpenAI SDK
- MCP Registry: Smithery (server discovery + managed connections)
Setup
Prerequisites
- Node.js 18+
- A Vercel account (for deployment + Neon integration)
- A GitHub account (for OAuth app)
- A Daytona API key
1. Neon Database
Option A: Via Vercel (Recommended)
- Go to your Vercel project → Storage tab
- Click Create Database → Select Neon Postgres
- Vercel auto-adds
DATABASE_URLandDATABASE_URL_UNPOOLEDenv vars
Option B: Direct Setup
- Go to neon.tech → Create project
- Copy the connection strings
- Add to Vercel env vars:
DATABASE_URL=postgres://...?sslmode=require DATABASE_URL_UNPOOLED=postgres://...?sslmode=require
2. GitHub OAuth App
- Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
- Fill in:
- Application name:
Upstream Agents - Homepage URL:
https://your-app.vercel.app - Authorization callback URL:
https://your-app.vercel.app/api/auth/callback/github
- Application name:
- Click Register application
- Copy the Client ID
- Generate a Client Secret and copy it
3. Generate Secrets
# NextAuth secret (32-byte base64)
openssl rand -base64 32
# Encryption key for API credentials (32-byte hex)
openssl rand -hex 32
4. Environment Variables
Add these to Vercel (Settings → Environment Variables):
| Variable | Description | Example |
|---|---|---|
DATABASE_URL |
Neon pooled connection | (auto-set by Vercel) |
DATABASE_URL_UNPOOLED |
Neon direct connection (migrations) | (auto-set by Vercel) |
NEXTAUTH_URL |
Your app's URL | https://your-app.vercel.app |
NEXTAUTH_SECRET |
Random secret for NextAuth | (output of openssl rand -base64 32) |
GITHUB_CLIENT_ID |
From GitHub OAuth App | Ov23li... |
GITHUB_CLIENT_SECRET |
From GitHub OAuth App | abc123... |
ENCRYPTION_KEY |
For encrypting API keys | (output of openssl rand -hex 32) |
DAYTONA_API_KEY |
Your shared Daytona API key | dtn_... |
DAYTONA_API_URL |
Daytona API endpoint | https://api.daytona.io |
SMITHERY_API_KEY |
Smithery API key for MCP registry | (from smithery.ai/account/api-keys) |
CRON_SECRET |
Secret for Vercel cron jobs (loop mode) | (output of openssl rand -base64 32) |
5. Deploy
# Install dependencies
npm install
# Generate Prisma client
npx prisma generate
# Run migrations (uses DATABASE_URL_UNPOOLED)
npx prisma migrate deploy
# Build
npm run build
Or push to Vercel - the build script handles migrations automatically.
6. Setup Checklist
[ ] Neon database provisioned
[ ] DATABASE_URL set
[ ] DATABASE_URL_UNPOOLED set
[ ] GitHub OAuth App created
[ ] GITHUB_CLIENT_ID set
[ ] GITHUB_CLIENT_SECRET set
[ ] NEXTAUTH_URL set
[ ] NEXTAUTH_SECRET set
[ ] ENCRYPTION_KEY set
[ ] DAYTONA_API_KEY set
[ ] DAYTONA_API_URL set
[ ] SMITHERY_API_KEY set (optional, for MCP server registry)
[ ] CRON_SECRET set (optional, for loop mode)
Development
This is a monorepo with two packages:
packages/
├── agents/ # @sandboxed-agents/sdk - TypeScript SDK for AI coding agents
└── web/ # @sandboxed-agents/web - Next.js web application
Prerequisites
- Node.js 20+
- PostgreSQL (local or Docker)
- A GitHub account
Required Environment Variables
For local development, you only need two environment variables:
| Variable | Description | How to get it |
|---|---|---|
GITHUB_PAT |
GitHub Personal Access Token | Create one here with scopes: repo, read:user |
DAYTONA_API_KEY |
Daytona API key for sandboxes | Get from Daytona |
When GITHUB_PAT is set:
- The login page auto-creates a session and redirects to the app
- No GitHub OAuth app needed
- A dev user is auto-created in the database
- The PAT is used for all GitHub operations
Quick Start
1. Install PostgreSQL
Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y postgresql postgresql-contrib
sudo service postgresql start
macOS:
brew install postgresql@17
brew services start postgresql@17
2. Create the Database
sudo -u postgres psql -c "CREATE USER sandboxed WITH PASSWORD 'sandboxed123';"
sudo -u postgres psql -c "CREATE DATABASE sandboxed_agents OWNER sandboxed;"
3. Configure Environment
Create packages/web/.env:
# Database (Local PostgreSQL)
DATABASE_URL="postgresql://sandboxed:sandboxed123@localhost:5432/sandboxed_agents"
DATABASE_URL_UNPOOLED="postgresql://sandboxed:sandboxed123@localhost:5432/sandboxed_agents"
# NextAuth - Set to your actual URL
# - Local dev: http://localhost:3000
# - Daytona: https://{port}-{sandbox-id}.daytonaproxy01.net
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="dev-secret-not-used-in-dev-mode"
# GitHub OAuth (placeholder - not used when GITHUB_PAT is set)
GITHUB_CLIENT_ID="placeholder"
GITHUB_CLIENT_SECRET="placeholder"
# Encryption key for credentials
ENCRYPTION_KEY="0000000000000000000000000000000000000000000000000000000000000000"
# === REQUIRED FOR DEVELOPMENT ===
GITHUB_PAT=ghp_your_token_here
DAYTONA_API_KEY=dtn_your_key_here
# Optional
SMITHERY_API_KEY="placeholder"
4. Install and Build
npm install
npm run build:sdk
5. Initialize the Database
cd packages/web && npx prisma db push
6. Start the Development Server
npm run dev
The app will be available at http://localhost:3000
What Happens on First Visit
When you visit the app with GITHUB_PAT set:
- The login page detects dev mode and auto-redirects
- A session is created for the dev user
- A warning is logged:
WARNING: Running in dev mode (GITHUB_PAT is set) - A dev user is auto-created with admin privileges and 100 sandbox quota
You'll be logged in automatically — no OAuth flow needed.
Available Scripts (Root)
| Script | Description |
|---|---|
npm run dev |
Start web development server |
npm run build |
Build SDK + web app |
npm run build:sdk |
Build only the SDK package |
npm run build:web |
Build only the web app |
npm run start |
Start production server |
npm run lint |
ESLint check across all packages |
npm run clean |
Clean build artifacts |
Package-Specific Commands
# SDK package (packages/agents)
npm run build -w @sandboxed-agents/sdk
npm run test -w @sandboxed-agents/sdk
# Web package (packages/web)
npm run dev -w @sandboxed-agents/web
npm run build -w @sandboxed-agents/web
SDK Development
The SDK (@sandboxed-agents/sdk) is in packages/agents/. The web app depends on it via workspace reference.
# Build the SDK after making changes
npm run build:sdk
# Run SDK tests
npm run test -w @sandboxed-agents/sdk
# Run SDK tests with coverage
cd packages/agents && npm run test:coverage
See packages/agents/README.md for full SDK documentation.
Troubleshooting
"Can't reach database server"
sudo service postgresql status
# If not running:
sudo service postgresql start
"GitHub account not linked" or GitHub API errors
Your GITHUB_PAT is missing or invalid. Make sure:
- The token has
repoandread:userscopes - The token hasn't expired
Prisma errors about adapters
rm -rf packages/web/.next
npm run dev
Notes
GITHUB_PATmode is disabled in production (NODE_ENV=production)- The dev user has a fixed ID:
dev-user-00000000-0000-0000-0000-000000000000 - All data is isolated to your local database
Database Schema
Entity Relationship
User
├── id, name, email, image (NextAuth)
├── githubId, githubLogin
├── isAdmin, maxSandboxes (quota override)
├── repoOrder (JSON array for sidebar ordering)
├── credentials (1:1) → UserCredentials
├── repos (1:n) → Repo
└── sandboxes (1:n) → Sandbox
UserCredentials
├── anthropicApiKey (AES encrypted)
├── anthropicAuthType ("api-key" | "claude-max")
├── anthropicAuthToken (encrypted, for Claude Max)
├── openaiApiKey (AES encrypted)
├── opencodeApiKey (AES encrypted)
├── daytonaApiKey (optional custom key)
├── sandboxAutoStopInterval (5-20 minutes)
└── defaultLoopMaxIterations (1-25, default 10)
Repo
├── owner, name, avatar, defaultBranch
├── envVars (encrypted JSON)
└── branches (1:n) → Branch
Branch
├── name, baseBranch, startCommit
├── status ("idle" | "running" | "creating" | "stopped" | "error")
├── agent ("claude-code" | "opencode" | "codex")
├── model (selected model name)
├── draftPrompt (unsent message)
├── prUrl (pull request link)
├── loopEnabled, loopCount, loopMaxIterations (loop mode)
├── sandbox (1:1) → Sandbox
└── messages (1:n) → Message
Sandbox
├── sandboxId (format: upstream-{userId}-{uuid})
├── contextId, sessionId (for SDK resumption)
├── sessionAgent (track agent type)
├── previewUrlPattern (web previews)
├── status, lastActiveAt
└── execution → AgentExecution
Message
├── role ("user" | "assistant")
├── content (full output text)
├── toolCalls (JSON array)
├── contentBlocks (interleaved text/tool order)
├── commitHash, commitMessage
├── timestamp
└── execution (1:1) → AgentExecution
AgentExecution
├── executionId (SDK execution ID)
├── status ("running" | "completed" | "error")
├── isLoopIteration (triggered by loop mode)
├── latestSnapshot (streaming content)
├── accumulatedEvents (full event list)
└── lastSnapshotPolledAt (500ms throttle)
Streaming Protocol
Agent output streams via Server-Sent Events:
// Frontend consumption
const response = await fetch("/api/agent/query", {
method: "POST",
credentials: "include", // Session cookie
body: JSON.stringify({ sandboxId, prompt }),
})
const reader = response.body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
// Parse SSE: "data: {...}\n\n"
}
Event Types
| Type | Description | Payload |
|---|---|---|
token |
Text content token | { content: "..." } |
tool |
Tool call event | { toolCall: { tool: "Read", summary: "..." } } |
stdout |
Standard output | { content: "..." } |
stderr |
Error output | { content: "..." } |
session-id |
Session for resumption | { sessionId: "..." } |
context-updated |
Context ID updated | { contextId: "..." } |
error |
Fatal error | { message: "..." } |
done |
Query complete | {} |
Background Execution
For long-running tasks, the app supports background execution that continues even when the browser is closed:
- Initiation: Frontend calls
/api/agent/executeinstead of/api/agent/query - Server-side Processing: Agent runs in Daytona sandbox, events saved to
AgentExecution - Polling:
ChatPanelpolls/api/agent/statusfor the active branch;useSyncDatasyncs status for background branches - Snapshot Updates: Server saves snapshots every 500ms (throttled)
- Resumption: On page reload, active executions are detected and resumed in UI
Loop Mode
Loop mode allows agents to automatically continue working on multi-step tasks until they explicitly indicate completion.
How It Works
- Enable Loop - Click the loop toggle (🔁) next to the agent selector in the chat input
- Send Initial Prompt - Describe your task to the agent
- Automatic Continuation - When the agent finishes, it's asked to continue or say "FINISHED"
- Termination - Loop stops when:
- Agent responds with "FINISHED" (case-insensitive exact match)
- Agent includes "FINISHED" (all caps) anywhere in response
- Maximum iterations reached (configurable, default 10, max 25)
- User toggles loop off
Configuration
- Default Max Iterations: Settings → Automation tab (1-25, default 10)
- Per-Branch Setting: Each branch tracks its own loop state independently
Background Loop Checking
A Vercel cron job runs every minute to check for completed executions where loop should continue:
- Handles cases where the browser is closed mid-loop
- Waits 15 seconds after completion before triggering (to let frontend handle first)
- Protected with
CRON_SECRETenvironment variable - Automatically configured via
vercel.json
Continuation Message
When loop continues, the agent receives:
"If you have finished all tasks, respond with just the phrase FINISHED. Otherwise, continue working on the remaining tasks."
MCP Server Integration
The platform integrates with Smithery to provide a registry of 3,000+ MCP (Model Context Protocol) servers that extend agent capabilities with external tools and services.
How It Works
- Browse - Users open repo settings → MCP Servers → Browse Registry to search Smithery's catalog
- Connect - Clicking "Connect" creates a managed connection via Smithery Connect
- Authless servers (e.g. Context7) connect instantly, no popup needed
- OAuth servers (e.g. GitHub, Slack) open a popup for user authorization
- Agent Access - Connected servers are injected into the agent's MCP config at sandbox startup
- Execution - MCP tool calls route through
api.smithery.ai/connect/{namespace}/{connectionId}/mcp
Setup
- Get a free API key from smithery.ai/account/api-keys
- Add
SMITHERY_API_KEYto your.env.localor Vercel environment variables - The app auto-detects or creates a Smithery namespace on first connection (or set
SMITHERY_NAMESPACEto use a specific one)
Pricing
Smithery's free tier includes 25,000 RPCs/month. Each MCP tool call from an agent counts as one RPC. Registry browsing and connection setup are separate. See smithery.ai/pricing for details.
Connection Isolation
Each connection is scoped to {repoId}-{serverSlug}, so OAuth credentials are isolated per repo per server. Smithery stores credentials encrypted and write-only — they can execute requests but are never readable. All connections share a single SMITHERY_API_KEY. The namespace is auto-resolved per API key.
Agent & Model Support
Supported Agents
| Agent | Provider | Models |
|---|---|---|
| Claude Code | Anthropic | claude-sonnet-4-20250514, claude-3-7-sonnet-latest, claude-opus-4-20250514 |
| OpenCode | OpenCode | Various |
| Codex | OpenAI | codex-mini, o3, o4-mini, gpt-4.1-mini |
Model Configuration
Models are configured per-branch in the chat header. The system automatically:
- Injects the correct API key based on model provider
- Persists model selection across sessions
- Validates model/agent compatibility
Quotas & Limits
| Resource | Default Limit | Configurable |
|---|---|---|
| Concurrent sandboxes | 10 per user | Per-user override via admin |
| Auto-stop interval | 10 minutes | User settings (5-20 min) |
| Sandbox statuses counted | CREATING, RUNNING, STOPPED | - |
| Message history | Unlimited | - |
When quota is reached, new sandbox creation is blocked until user stops an existing one.
Security
Data Protection
- No credentials in localStorage - All secrets stored server-side
- Encrypted at rest - API keys AES-256 encrypted in database
- Session-based auth - JWT via NextAuth, HTTP-only cookies
- Parameterized queries - Prisma prevents SQL injection
Access Control
- Sandbox isolation - Users can only access their own sandboxes
- Multi-tenant data - All queries filtered by userId
- Admin separation - Admin routes require isAdmin flag
External Services
- Shared Daytona key - Never exposed to frontend
- OAuth token handling - GitHub tokens stored in NextAuth Account table
- Rate limiting - Handled by external services (GitHub, Anthropic, etc.)
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes
- Run linting:
npm run lint - Commit your changes:
git commit -m "Add my feature" - Push to the branch:
git push origin feature/my-feature - Open a Pull Request
License
MIT
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi