AI-employee platform for creators

Virtual Buddy

An unattended job that silently stops firing looks exactly like a healthy one. Alerting cannot cover that, because an alert only fires from a job that ran.

Role
Sole engineer. Product, agent, multi-tenant data model, billing, observability, native app.
Shape
Next.js 16 on Vercel with Neon Postgres, plus an Expo app on the same API. Live with paid tiers.
Links
Live site
Virtual Buddy marketing site for the AI-employee platform.
Virtual Buddy marketing site for the AI-employee platform.

Architecture

streamDrizzleWeb appNext.js 16Mobile appExpo / RNVercel cronbriefingsAPI routesClerk authClaude agenthaiku → opus tiersToolstrends / dealsNeon PostgresRLS · 14 tables
AGENT LOOP

The problem

This is the one that had to survive contact with real users and real money, which changes which problems matter. Tenant isolation stops being a design intention and becomes a thing that must be provably enforced. Model cost stops being a line in a budget and becomes a per-request routing decision. Scheduled work stops being a cron entry and becomes something that has to prove it ran.

The approach

The agent is a streaming Claude loop over tool-calling, with model tier routed per creator plan across Haiku, Sonnet, and Opus. Tier and persona are derived server-side from the subscription record. The client cannot ask for a more expensive model by editing a request, because the client never names one.

Isolation is enforced at the database rather than in application code. The app connects as a role that cannot bypass row-level security, so a missing WHERE clause in a query is a query that returns nothing rather than a cross-tenant leak. That was verified end to end rather than assumed.

The whole product also runs on deterministic mocks with no keys at all. Each real integration activates when its credentials are present, which keeps local development and CI fast and free while leaving one code path to production.

Decisions

A health endpoint that reports staleness, not liveness

Every scheduled run records status, counts, and duration even when the body throws, and partial success is a distinct status from success, because nine of ten channels refreshing is not fine. Failures fire a webhook. But the important piece is an endpoint that returns 503 when a job has gone stale, because that is the half alerting cannot cover: an alert only fires from a job that actually ran, so a cron that silently stops firing is invisible to alerting and obvious to a freshness check.

A dead OAuth grant is its own case

A revoked or expired token is not a transient failure and retrying never fixes it. The account is flagged as needing reconnection, the creator is told inside the product, and ops is alerted. Treating it as a generic retryable error would produce a silently broken integration and a clean-looking error rate.

Encrypt third-party tokens at rest

OAuth tokens are encrypted with AES-256-GCM before storage. A database read alone does not yield working credentials to someone else's account.

One backend, two clients

The native app consumes the same API and the same database as the web app, with bearer tokens held in the device secure enclave. Web and mobile cannot drift, because there is nothing to drift between.

Evidence

14
table multi-tenant schema with RLS
3
model tiers routed server-side
503
returned when a job goes stale
2
clients on one API and database

What it does not do

Billing is built and signature-verified but stays dormant until live keys are set.

Auth runs on a test instance until a production instance is provisioned against a custom domain.

Several integrations depend on platform approvals that are outside the codebase, so they ship behind feature flags with honest Live, Beta, and Roadmap labels in the product itself.

Other systems