Skip to main content

Overview

Digcrate is a Next.js application with a Convex real-time backend. The browser communicates with serverless API routes on Vercel, which in turn talk to Convex (database), Anthropic (AI), and MCP tool servers (music data).
Browser (Next.js App Router)

  ├── Clerk (auth middleware)

  ├── POST /api/chat ─────────────────────── Vercel serverless
  │     │                                         │
  │     │                               agentic-loop.ts
  │     │                                    │         │
  │     │                             Anthropic SDK   OpenRouter
  │     │                                    │
  │     │                             tool_use calls
  │     │                                    │
  │     │                             crate-cli MCP servers
  │     │                          (Discogs, MusicBrainz, Last.fm, …)
  │     │
  │     └── SSE stream ──────────────────── Browser
  │           (thinking, tool_start, tool_end, answer_token, done)

  └── Convex subscriptions (real-time)
        (sessions, messages, artifacts, playlists, usage)

Tech stack

LayerTechnologyPurpose
FrameworkNext.js 16 (App Router)SSR, API routes, Turbopack dev
DeploymentVercelServerless functions
AuthClerkUser sign-in, OAuth
Connected servicesAuth0 Token VaultOAuth connections for Spotify, Slack, Google
DatabaseConvexReal-time sessions, messages, playlists, collections, influence graph, subscriptions, skills, shares
Dynamic UIOpenUI (@openuidev/react-lang)Agent-generated interactive components
AgentAnthropic SDK + agentic loopTool-use loop with crate-cli MCP servers
BillingStripeCheckout, billing portal, webhooks
StylingTailwind CSS v4Dark theme, responsive mobile
AudioYouTube IFrame APIPersistent player bar
AnalyticsPostHogProduct analytics, LLM observability

Project structure

crate-web/
├── convex/                          # Convex backend (schema + query/mutation functions)
│   ├── schema.ts                    # Database schema (26 tables)
│   ├── sessions.ts                  # Chat session CRUD
│   ├── messages.ts                  # Message persistence
│   ├── playlists.ts                 # Playlist management
│   ├── subscriptions.ts             # Stripe subscription state
│   ├── usage.ts                     # Usage tracking and quotas
│   ├── userSkills.ts                # Custom skill CRUD
│   ├── shares.ts                    # Published Deep Cut shares
│   └── users.ts                     # User sync (Clerk → Convex)
├── src/
│   ├── app/
│   │   └── api/
│   │       ├── chat/route.ts        # SSE streaming — agentic loop entry point
│   │       ├── auth0/               # OAuth connect, callback, status
│   │       ├── stripe/              # Checkout, billing portal, webhooks
│   │       ├── skills/              # Custom skill management
│   │       └── keys/route.ts        # User API key management
│   ├── components/
│   │   ├── workspace/               # Chat panel, Deep Cuts panel, workspace shell
│   │   ├── sidebar/                 # Desktop and mobile sidebar
│   │   ├── settings/                # Connected services, plan, skills, keys
│   │   └── player/                  # YouTube audio player
│   └── lib/
│       ├── agentic-loop.ts          # Core agent loop (Anthropic + OpenRouter)
│       ├── openui/                  # 27+ OpenUI component definitions and registry
│       ├── web-tools/               # Connected service tools (Spotify, Slack, Google)
│       ├── auth0-token-vault.ts     # OAuth token exchange via Auth0 Management API
│       └── plans.ts                 # Subscription tiers and rate limiting

The agentic loop

Every user message goes through a multi-turn tool-use loop implemented in src/lib/agentic-loop.ts.
  1. The browser sends a POST to /api/chat with the message, session history, model choice, and API key.
  2. The route handler builds the tool list from crate-cli MCP servers and calls agenticLoop().
  3. The loop calls the Anthropic (or OpenRouter) API with the message and available tools.
  4. If the model returns tool_use blocks, the loop executes each tool call against the appropriate MCP server.
  5. Tool results are fed back to the model as tool_result blocks. Steps 3–5 repeat up to 25 turns.
  6. Each turn emits Server-Sent Events to the browser: thinking, tool_start, tool_end, answer_token, and finally done.
  7. The browser’s chat panel renders tokens as they arrive and parses OpenUI components from the final answer.
The loop supports both Anthropic (direct SDK) and OpenRouter (OpenAI-compatible API) with the same event format. The useOpenRouter flag on AgenticLoopOptions selects the path.

Key data flows

Authentication

Request arrives
  → Clerk middleware checks session token
  → Authenticated: request proceeds with userId
  → Unauthenticated: redirect to /sign-in
User records are synced from Clerk to Convex via a webhook at /api/webhooks/clerk. The Convex users table stores the Clerk user ID, email, encrypted API keys, and Stripe customer ID.

Agent query

POST /api/chat
  → Validate Clerk session, load subscription + usage
  → Check quota (free: 10/month, pro: 50/month, team: 200/month pooled)
  → Build tool groups from crate-cli MCP servers
  → agenticLoop() → Anthropic SDK → tool calls → MCP servers
  → Stream CrateEvents as SSE
  → On done: write message + artifacts to Convex

Real-time state (Convex)

The browser subscribes to Convex queries that push updates in real time:
SubscriptionWhat updates
sessions.listByUserSidebar session list
messages.listBySessionChat message history
artifacts.listBySessionDeep Cuts panel
playlists.listByUserPlaylist library
usage.getForCurrentPeriodUsage meter in settings

Connected services (Auth0 Token Vault)

User clicks "Connect Spotify" in Settings
  → GET /api/auth0/connect?service=spotify
  → Generates CSRF nonce, redirects to Auth0 /authorize
  → User grants permissions on Auth0 / Spotify consent screen
  → Auth0 redirects to /api/auth0/callback
  → Callback extracts Auth0 user ID, sets secure cookie

Agent calls read_spotify_library tool
  → getTokenVaultToken("spotify", auth0UserId)
  → Fetches Management API token (cached 23 hours)
  → GET /api/v2/users/{userId}?fields=identities
  → Returns access_token from the Spotify identity
  → Tool calls Spotify Web API with the token

Database schema

The Convex database has 24 tables. Key tables:
TablePurpose
usersUser accounts with Clerk ID, encrypted keys, Stripe customer ID
cratesNamed session folders for organizing research
sessionsChat sessions (title, crate membership, starred, archived)
messagesChat messages (user and assistant roles)
artifactsSaved research components (Deep Cuts)
toolCallsTool execution log per session
playerQueueAudio player queue tracks per session
playlists / playlistTracksUser playlists with track metadata
collectionVinyl collection records
subscriptionsStripe subscription state
usageEventsPer-user, per-period query count
userSkillsCustom slash commands
sharesPublished Deep Cut share records
influenceArtists / influenceEdgesCached influence graph nodes and edges
telegraphAuth / telegraphEntriesTelegraph publishing credentials and posts
tumblrAuth / tumblrPostsTumblr publishing credentials and posts
orgKeysTeam-level shared API keys by domain