upstream-agents

agent
SUMMARY

Run AI coding agents in isolated sandboxes connected to your GitHub repositories

README.md

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

  1. Authentication - User authenticates via GitHub OAuth (NextAuth.js)
  2. Repository Setup - User adds repositories from GitHub, creates branches
  3. Sandbox Creation - Each branch spins up a Daytona sandbox with selected agent
  4. Chat Interaction - User sends prompts → API streams agent output in real-time
  5. Agent Execution - Agent reads/writes files, runs commands, makes commits
  6. Background Processing - Long-running tasks continue server-side if browser closes
  7. 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)

  1. Go to your Vercel project → Storage tab
  2. Click Create Database → Select Neon Postgres
  3. Vercel auto-adds DATABASE_URL and DATABASE_URL_UNPOOLED env vars

Option B: Direct Setup

  1. Go to neon.tech → Create project
  2. Copy the connection strings
  3. Add to Vercel env vars:
    DATABASE_URL=postgres://...?sslmode=require
    DATABASE_URL_UNPOOLED=postgres://...?sslmode=require
    

2. GitHub OAuth App

  1. Go to GitHub → SettingsDeveloper settingsOAuth AppsNew OAuth App
  2. 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
  3. Click Register application
  4. Copy the Client ID
  5. 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:

  1. The login page detects dev mode and auto-redirects
  2. A session is created for the dev user
  3. A warning is logged: WARNING: Running in dev mode (GITHUB_PAT is set)
  4. 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 repo and read:user scopes
  • The token hasn't expired

Prisma errors about adapters

rm -rf packages/web/.next
npm run dev

Notes

  • GITHUB_PAT mode 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:

  1. Initiation: Frontend calls /api/agent/execute instead of /api/agent/query
  2. Server-side Processing: Agent runs in Daytona sandbox, events saved to AgentExecution
  3. Polling: ChatPanel polls /api/agent/status for the active branch; useSyncData syncs status for background branches
  4. Snapshot Updates: Server saves snapshots every 500ms (throttled)
  5. 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

  1. Enable Loop - Click the loop toggle (🔁) next to the agent selector in the chat input
  2. Send Initial Prompt - Describe your task to the agent
  3. Automatic Continuation - When the agent finishes, it's asked to continue or say "FINISHED"
  4. 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_SECRET environment 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

  1. Browse - Users open repo settings → MCP Servers → Browse Registry to search Smithery's catalog
  2. 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
  3. Agent Access - Connected servers are injected into the agent's MCP config at sandbox startup
  4. Execution - MCP tool calls route through api.smithery.ai/connect/{namespace}/{connectionId}/mcp

Setup

  1. Get a free API key from smithery.ai/account/api-keys
  2. Add SMITHERY_API_KEY to your .env.local or Vercel environment variables
  3. The app auto-detects or creates a Smithery namespace on first connection (or set SMITHERY_NAMESPACE to 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

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes
  4. Run linting: npm run lint
  5. Commit your changes: git commit -m "Add my feature"
  6. Push to the branch: git push origin feature/my-feature
  7. Open a Pull Request

License

MIT

Yorumlar (0)

Sonuc bulunamadi