The Autonomous Professional Network — an AI-powered job application pipeline that finds, tailors, applies, schedules, and preps interviews for you.
TAPN pairs a React dashboard with a 6-agent OpenClaw ecosystem. You fill out a quick profile, the agents go to work, and your dashboard lights up with live progress.
┌─────────────────────────────┐
│ TAPN WEBAPP (Vercel) │
│ React + Vite + Framer │
│ │
│ Landing → Login → Onboard │
│ → Dashboard │
└──────────────┬───────────────┘
│
Supabase Realtime + REST
│
┌──────────────────────────────────▼──────────────────────────────────┐
│ SUPABASE (shared, hosted) │
│ │
│ profiles agent_logs applications │
│ (per user) (per user) (per user) │
│ │
│ mock_interview_sessions interview_library │
│ (per user) (SHARED — all users browse) │
│ │
│ Row Level Security scopes every table to auth.uid() │
│ interview_library is readable by all authenticated users │
└──────────────┬──────────────────────────────┬───────────────────────┘
│ │
│ service_role key │ ElevenLabs API
│ │
┌──────────────▼──────────────┐ ┌──────────▼──────────────┐
│ OPENCLAW GATEWAY (local) │ │ ElevenLabs Conv. AI │
│ Each user runs their own │ │ Voice mock interviews │
│ │ │ Shared agent, signed │
│ 6 agents: │ │ URLs from Vercel API │
│ TAPN · Scout · Taylor │ └─────────────────────────┘
│ Echo · Hermes · Aria │
│ │
│ supabase-logger hook │
│ → writes to shared Supabase │
│ tagged with user_id │
└──────────────────────────────┘
| Resource | Scope | Details |
|---|---|---|
| Webapp (Vercel) | Shared | Hosted at one URL — everyone uses the same deployment |
| Supabase database | Shared | One instance, managed by the admin |
| ElevenLabs agent | Shared | One voice AI agent, accessed via signed URLs |
interview_library |
Shared | All users can browse and practice |
profiles |
Per user | RLS: auth.uid() = id |
agent_logs |
Per user | RLS: auth.uid() = user_id |
applications |
Per user | RLS: auth.uid() = user_id |
mock_interview_sessions |
Per user | RLS: auth.uid() = user_id |
| OpenClaw gateway | Per user | Each user installs and runs their own locally |
| Agent | Role | What It Does |
|---|---|---|
| Tapn | Conductor / Orchestrator | Coordinates the full pipeline, delegates tasks, reports progress via Telegram |
| Scout | Job Discovery | Scrapes career pages and workforce data to find companies actively hiring for your target roles |
| Taylor | Resume Tailoring | Analyzes each job description and produces an ATS-optimized, keyword-matched resume |
| Echo | Application Submission | Navigates application portals, fills fields, uploads resumes, hits submit |
| Hermes | Interview Scheduling | Monitors your inbox for interview invitations, checks availability, books time slots |
| Aria | Interview Prep | Generates prep materials and conducts AI voice mock interviews via ElevenLabs |
1. LANDING PAGE
Galaxy shader background — click "get tapped in"
2. LOGIN / SIGN UP
Email + password auth via Supabase
3. ONBOARDING (5 steps)
01 — Identity: name, email, phone, location, links
02 — Career: current title, experience, target roles/industries, bio
03 — Skills: technical skills, strengths, education
04 — Preferences: work type, salary, company size, must-haves, deal-breakers
05 — Schedule: available hours, timezone, communication style
→ Generates USER.md for the agent ecosystem
→ Saves profile to Supabase
4. DASHBOARD
Overview · Applications · Agents · Pipeline · Resumes · Interviewer
All data scoped to your account via RLS
Mock Interview Library is shared across all users
- Overview — Welcome panel, gateway status, agent cards with live status, recent activity, recent applications
- Applications — Table of all your tracked job applications with status, follow-up alerts
- Agents — Visual profile cards for each agent with avatar, role, last action, status indicator
- Pipeline — Pipeline stages visualization, trigger runs via OpenClaw
- Resumes — Your generated resume variants and their target companies
- Interviewer — Two tabs:
- My Interviews — your prepped + completed mock interviews
- Interview Library — shared templates from all users. Click "practice" to start any interview.
| Layer | Technology |
|---|---|
| Framework | React 19 + Vite 7 |
| Hosting | Vercel (SPA + serverless API routes) |
| Routing | react-router-dom v7 |
| State | Zustand (scene state), React hooks (everything else) |
| Animation | Framer Motion, GSAP |
| 3D / Shaders | React Three Fiber + Drei, OGL (faulty terminal), Three.js |
| Auth + DB | Supabase (PostgreSQL, Auth, Realtime, Row Level Security) |
| Agent Backend | OpenClaw (WebSocket gateway, multi-agent orchestration) |
| Voice AI | ElevenLabs Conversational AI (@elevenlabs/react SDK) |
| Styling | Custom CSS with design tokens, backdrop-filter translucency |
You don't need to clone anything. The webapp is hosted on Vercel — just visit the URL, sign up, complete onboarding, and you're in. Your dashboard, mock interviews, and interview library all work immediately.
If you want the AI agent pipeline (auto-discover jobs, tailor resumes, submit applications, prep interviews), you'll need to set up OpenClaw locally. See User Setup: Connect Your Agents below.
This section is for whoever hosts and maintains the TAPN instance. You only do this once.
git clone https://github.com/MatthewKim323/tapn.git
cd tapn
npm installGo to supabase.com, create a new project, then run the schema SQL in the SQL Editor.
The full schema is in supabase/migration_multi_tenant.sql, or expand below:
Click to expand full SQL schema
-- profiles (stores onboarding data)
CREATE TABLE IF NOT EXISTS profiles (
id UUID REFERENCES auth.users ON DELETE CASCADE PRIMARY KEY,
email TEXT,
full_name TEXT,
phone TEXT,
location TEXT,
linkedin_url TEXT,
portfolio_url TEXT,
current_title TEXT,
years_experience TEXT,
target_roles TEXT[],
target_industries TEXT[],
bio TEXT,
technical_skills TEXT[],
top_strengths TEXT[],
education_degree TEXT,
education_field TEXT,
education_school TEXT,
education_year TEXT,
work_authorization TEXT,
work_type_preference TEXT,
min_salary TEXT,
company_size TEXT,
must_have_criteria TEXT[],
deal_breaker_keywords TEXT[],
blacklisted_companies TEXT[],
available_hours TEXT,
timezone TEXT,
communication_style TEXT,
user_md_content TEXT,
onboarding_complete BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own profile" ON profiles FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Users can insert own profile" ON profiles FOR INSERT WITH CHECK (auth.uid() = id);
CREATE POLICY "Users can update own profile" ON profiles FOR UPDATE USING (auth.uid() = id);
-- agent_logs (OpenClaw hook writes here, scoped to user)
CREATE TABLE IF NOT EXISTS agent_logs (
id BIGSERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
agent_name TEXT NOT NULL,
action TEXT,
status TEXT DEFAULT 'success',
details JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_agent_logs_agent ON agent_logs (agent_name, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_agent_logs_user ON agent_logs (user_id, created_at DESC);
ALTER TABLE agent_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users read own logs" ON agent_logs FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Service inserts logs" ON agent_logs FOR INSERT WITH CHECK (true);
-- applications (per-user)
CREATE TABLE IF NOT EXISTS applications (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
company_name TEXT,
job_title TEXT,
status TEXT DEFAULT 'applied',
tailored_resume_path TEXT,
mock_interview_ready BOOLEAN DEFAULT FALSE,
mock_interview_score NUMERIC,
follow_up_sent_at TIMESTAMPTZ,
applied_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users read own applications" ON applications FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users insert own applications" ON applications FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users update own applications" ON applications FOR UPDATE USING (auth.uid() = user_id);
-- mock_interview_sessions (per-user)
CREATE TABLE IF NOT EXISTS mock_interview_sessions (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
library_id UUID,
agent_id TEXT,
company TEXT,
job_title TEXT,
interviewer_name TEXT,
interview_date TIMESTAMPTZ,
estimated_duration_minutes INT,
questions_summary JSONB,
status TEXT DEFAULT 'ready',
overall_score NUMERIC,
strongest_area TEXT,
weakest_area TEXT,
transcript JSONB,
debrief TEXT,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE mock_interview_sessions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users read own sessions" ON mock_interview_sessions FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users insert own sessions" ON mock_interview_sessions FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users update own sessions" ON mock_interview_sessions FOR UPDATE USING (auth.uid() = user_id);
CREATE POLICY "Users delete own sessions" ON mock_interview_sessions FOR DELETE USING (auth.uid() = user_id);
-- interview_library (SHARED — all authenticated users can browse)
CREATE TABLE IF NOT EXISTS interview_library (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
created_by UUID REFERENCES auth.users(id),
company TEXT NOT NULL,
job_title TEXT NOT NULL,
interviewer_name TEXT DEFAULT 'Alex',
interviewer_persona TEXT,
estimated_duration_minutes INT DEFAULT 25,
questions_summary JSONB,
question_categories TEXT[],
difficulty TEXT DEFAULT 'medium',
times_practiced INT DEFAULT 0,
avg_score NUMERIC,
agent_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE interview_library ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Authenticated users browse library" ON interview_library FOR SELECT USING (auth.uid() IS NOT NULL);
CREATE POLICY "Authenticated users publish to library" ON interview_library FOR INSERT WITH CHECK (auth.uid() = created_by);
CREATE POLICY "Creator can update own template" ON interview_library FOR UPDATE USING (auth.uid() = created_by);
-- Helper: increment times_practiced (SECURITY DEFINER bypasses RLS)
CREATE OR REPLACE FUNCTION increment_library_practiced(lib_id UUID)
RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
UPDATE interview_library
SET times_practiced = COALESCE(times_practiced, 0) + 1
WHERE id = lib_id;
END;
$$;Also disable email confirmation in Supabase → Auth → Settings (for development).
Import the repo at vercel.com/new or use the CLI:
npm i -g vercel
vercelGo to Vercel → Project Settings → Environment Variables and add these four:
| Variable | Value | Where it's used |
|---|---|---|
VITE_SUPABASE_URL |
https://your-project.supabase.co |
Client — baked into the build. Find it in Supabase → Settings → API → Project URL |
VITE_SUPABASE_ANON_KEY |
eyJ... (the long JWT) |
Client — baked into the build. Find it in Supabase → Settings → API → anon public key |
ELEVENLABS_API_KEY |
sk_... |
Server-side only — used by api/interview/signed-url.js to generate signed URLs. Never reaches the browser. Find it in ElevenLabs → API Keys |
VITE_ELEVENLABS_AGENT_ID |
agent_... |
Client — baked into the build. Find it in ElevenLabs → Conversational AI → your agent |
That's it. After setting these four variables and deploying, the webapp is fully functional. Users visit the Vercel URL, sign up, and everything works — dashboard, mock interviews, interview library.
For local dev, create a .env file in the project root with the same variables:
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
ELEVENLABS_API_KEY=sk_your_api_key
VITE_ELEVENLABS_AGENT_ID=agent_your_agent_idThen run:
npm run devThe app starts at http://localhost:5173. The Vite dev server handles the /api/interview/* endpoints locally via middleware in vite.config.js.
This is optional. The webapp works without OpenClaw — you can sign up, browse the dashboard, practice mock interviews from the library, and start quick interviews. OpenClaw is only needed if you want the full AI agent pipeline: automated job discovery, resume tailoring, application submission, scheduling, and interview prep.
pip install openclawSee openclaw.dev for full docs.
After signing up on the TAPN webapp:
- Log in to the TAPN dashboard
- Open browser DevTools → Console
- Run:
(await (await import('./lib/supabase')).supabase.auth.getUser()).data.user.id - Copy the UUID — this is your
TAPN_USER_ID
Or ask the admin to look it up in Supabase → Authentication → Users.
git clone https://github.com/MatthewKim323/tapn.gitYou only need the openclaw-hooks/ directory. Everything else is the hosted webapp.
cd tapn
openclaw hook install ./openclaw-hooks/supabase-loggerThe admin will provide the Supabase URL and service key. Set these in the terminal where you'll run OpenClaw:
export TAPN_SUPABASE_URL=https://your-tapn-instance.supabase.co
export TAPN_SUPABASE_SERVICE_KEY=ask-the-admin-for-this
export TAPN_USER_ID=your-uuid-from-step-2Note: The
service_rolekey is sensitive — the admin should provide it to you securely. It allows the OpenClaw hook to write to Supabase on your behalf.
After completing onboarding, download your USER.md from the dashboard sidebar. Place it in your OpenClaw workspace:
cp ~/Downloads/USER.md ~/.openclaw/USER.mdopenclaw gatewayYour agents will now run and their activity shows up on your personal TAPN dashboard. All stats, logs, and applications are scoped to your account — no one else can see them.
The interview library is a shared resource across all TAPN users.
- When you complete a mock interview, the template (company, role, questions) is automatically published to the library
- Any user can browse the library and practice any interview
- Each practice session creates a personal record — your transcript and score are private
- The library tracks how many times each template has been practiced
tapn/
├── api/ # Vercel serverless functions
│ └── interview/
│ ├── signed-url.js # ElevenLabs signed URL (keeps API key server-side)
│ └── conversations.js # List past ElevenLabs conversations
├── public/
│ ├── assets/ # 3D models (galaxy.glb) + textures
│ └── profiles/ # Agent profile images
├── src/
│ ├── components/ # Reusable UI + visual effects
│ │ ├── Galaxy.jsx # OGL star field shader
│ │ ├── Galaxy3D.jsx # R3F galaxy model
│ │ ├── FaultyTerminal.jsx # OGL faulty terminal shader
│ │ ├── StarNest.jsx # Star nest shader effect
│ │ ├── Dither.jsx # Dither background effect
│ │ └── ...
│ ├── hooks/ # Custom React hooks (all user-scoped)
│ │ ├── useAgentActivity.js # Realtime agent log feed
│ │ ├── useAgentStatus.js # Agent status + gateway health
│ │ ├── useApplications.js # Realtime applications
│ │ └── useDashboardStats.js # Aggregated metrics
│ ├── lib/
│ │ ├── supabase.js # Supabase client
│ │ └── generateUserMd.js # USER.md generator
│ ├── pages/
│ │ ├── LandingPage.jsx
│ │ ├── LoginPage.jsx
│ │ ├── OnboardingPage.jsx
│ │ ├── DashboardPage.jsx
│ │ └── dashboard/
│ │ ├── OverviewView.jsx
│ │ ├── ApplicationsView.jsx
│ │ ├── AgentsView.jsx
│ │ ├── PipelineView.jsx
│ │ ├── ResumesView.jsx
│ │ └── InterviewerView.jsx # Mock interviews + library
│ └── store/
│ └── sceneStore.js
├── openclaw-hooks/
│ └── supabase-logger/ # OpenClaw hook → logs agent events to Supabase
├── supabase/
│ └── migration_multi_tenant.sql # Full DB schema + RLS policies
├── vercel.json # Vercel routing config
├── vite.config.js # Vite config + local dev API middleware
└── .env # Local env vars (gitignored)
- The ElevenLabs API key (
sk_...) is server-side only — served by Vercel serverless functions (production) or Vite middleware (local dev). Never touches the client bundle. - The Supabase anon key is safe to expose client-side — RLS handles all authorization.
.envis gitignored. Never commit secrets.- Row Level Security ensures every user only sees their own data. The
interview_libraryis the only shared table. - The OpenClaw
service_rolekey bypasses RLS — only share it with trusted users. Each user's gateway tags logs with theirTAPN_USER_ID.
Matthew Kim · Brendan Chung · Sou Hamura · Sabrina Nguyen · Allison Gu
Private project.