Skip to content
lf1upPublic

Latest commit

ย 

History

656 Commits

Folders and files

Repository files navigation

๐ŸŽฎ CareerSIM โ€” AI-Powered Career Skills Simulator

CareerSIM โ€” practice career-defining negotiations in a safe, arcade-style simulator

CI codecov

CareerSIM is a direct-to-consumer (B2C) web application that helps individuals master critical career skills through hyper-realistic, AI-powered simulations. Users practice challenging professional situations โ€” from behavioral interviews to difficult conversations โ€” in a safe, repeatable environment and receive immediate, data-driven feedback to accelerate their personal and professional growth.

The platform empowers users to build confidence and competence for career-defining moments. By leveraging a LangGraph-based generative AI engine, CareerSIM provides dynamic, conversational practice with a diverse cast of AI personas, moving beyond rote memorization to foster genuine skill development.

Note

Repository is mid-migration. The active runtime is api/ + web/ + agent/ + postgres + redis + headless ghost, plus livekit + agent-voice for the browser-native voice surface, with a standalone static marketing site in landing/. Four earlier services (backend/, frontend/, rag/, transformers/) are still in the tree for reference only and are flagged as deprecated in both their own READMEs and docker-compose.local.yml. Do not build new features against them.

๐Ÿ—๏ธ Architecture

CareerSIM runs as three first-party services plus shared infrastructure. The API owns all persistence; the agent is a pure function of its inputs (full state snapshot in, new messages + updated state out) โ€” this makes replay, testing, and horizontal scaling straightforward. Voice mode adds two more processes โ€” a self-hosted LiveKit SFU and a dedicated agent-voice worker โ€” that reuse the same LangGraph engine over a WebRTC audio path.

                      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                      โ”‚   Web (Next.js 16)   โ”‚ โ”€โ”€โ”€โ”€โ”€ WebRTC (mic) โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚  LiveKit SFU โ”‚
                      โ”‚   App Router + RSC   โ”‚ โ—„โ”€โ”€โ”€โ”€ agent audio โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚  :7880/:7882 โ”‚
                      โ”‚   :3000              โ”‚                           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                  โ”‚ PCM
                                 โ”‚  REST + SSE (JWT Bearer)                     โ”‚
                                 โ”‚  text chat ยท sessions ยท auth ยท nudges        โ”‚
                      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                      โ”‚  API (Fastify 5 +    โ”‚ โ—„โ”€โ”€ internal + voice โ”€โ”€โ”€โ”€ โ”‚  agent-voice โ”‚
                      โ”‚  Drizzle + Zod)      โ”‚     turns over REST       โ”‚  worker      โ”‚
                      โ”‚  :8000               โ”‚                           โ”‚  STTโ†’LGโ†’TTS  โ”‚
                      โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜                           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚              โ”‚
             POST /conv/* โ”‚              โ”‚ SQL
            (batch + SSE) โ”‚              โ”‚
                      โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                      โ”‚  Agent    โ”‚  โ”‚ PostgreSQL 17 โ”‚
                      โ”‚ FastAPI + โ”‚  โ”‚ + Redis 7     โ”‚
                      โ”‚ LangGraph โ”‚  โ”‚ :5432 / :6379 โ”‚
                      โ”‚  :8001    โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                      โ”‚           โ”‚
                      โ”‚  embedded โ”‚
                      โ”‚  Chroma   โ”‚
                      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The text-chat path (Web โ†’ API โ†’ Agent โ†’ Postgres) is unchanged from the non-voice runtime. Voice adds a parallel audio lane: the browser streams mic audio to the LiveKit SFU, the agent-voice worker runs STT โ†’ the same LangGraph turn โ†’ TTS, and it loops every spoken turn back through the API's existing message/internal routes โ€” so persistence, goal eval, and quota all run through one engine, not a second copy.

Voice mode has its own operator runbook โ€” see VOICE_MODE.md for the end-to-end architecture, kill switches, provider selection, daily-budget enforcement, and the pre-merge smoke checklist.

Service Stack Description
landing Astro 6 static site, TypeScript, plain CSS Public careersim.local marketing page generated from the Figma landing design. Includes a manual Figma sync script for reference screenshots and node metadata.
web Next.js 16 (App Router, React 19), TypeScript 6, Tailwind 3 Client-rendered SPA over the API: auth, simulation picker, session chat with SSE streaming, nudge auto-polling, follow-up bursts.
api Node.js 22, Fastify 5, Drizzle ORM, PostgreSQL, @fastify/jwt, argon2id, Zod 4 Owns auth, persistence, and all session state. Proxies agent calls (including SSE) and enforces per-session ownership, nudge cadence, and proactive-trigger policy.
agent Python 3.11+, FastAPI, LangGraph, Chroma (embedded), OpenAI / OpenRouter Stateless conversation engine. One binary serves a Gradio dev console, a FastAPI production server (--serve api), or the voice worker (--serve voice). Retrieval uses an embedded Chroma store โ€” no separate RAG service.
agent-voice Python 3.11+, LiveKit Agents SDK, faster-whisper / Piper (self-hosted defaults) Dedicated voice worker. Joins LiveKit rooms minted by the API, runs STT โ†’ the same LangGraph turn โ†’ TTS, streams captions, and posts transcripts back through the public API on the user's bearer.
livekit LiveKit Server (WebRTC SFU) Self-hosted SFU routing audio between the browser and agent-voice. Runs in --dev mode locally; needs a real config + TLS in production.
postgres PostgreSQL 17 Source of truth for users, sessions, messages, state snapshots, and daily voice-minute usage.
redis Redis 7 Shared rate-limit buckets (@fastify/rate-limit); falls back to per-process LRU when REDIS_URL is unset.
ghost Ghost 6 (headless) + MySQL 8 Blog CMS. Content API only โ€” Next.js renders posts at /blog when NEXT_PUBLIC_BLOG_ENABLED=true (off by default if unset). Put Ghost in Private Site Mode so its own frontend stays noindex. See web/README.md.

๐Ÿชฆ Deprecated services (kept for reference)

Legacy directory Replaced by Notes
backend/ (Express + TypeORM + Socket.IO + Stripe) api/ REST surface, JWT, and SSE streaming fully rewritten on Fastify.
frontend/ (Vite + React 18 SPA) web/ Retro theme ported 1:1; admin panel / analytics dashboards dropped โ€” the new API doesn't back them yet.
transformers/ (FastAPI + HuggingFace sentiment / emotion / toxicity) agent/ Evaluation now happens in-process via an LLM eval model (OPENAI_EVAL_MODEL).
rag/ (FastAPI + ChromaDB service) agent/ Chroma is embedded directly in the agent; no HTTP boundary.

Each of those directories carries a > [!WARNING] DEPRECATED banner at the top of its README. They are also commented out in docker-compose.local.yml under a deprecation block and will be removed in a future clean-up pass.

๐Ÿ“ Project Structure

careersim/
โ”œโ”€โ”€ landing/                    # Astro static landing page + Figma design sync
โ”‚   โ”œโ”€โ”€ src/{pages,styles}
โ”‚   โ”œโ”€โ”€ figma/                  # source design metadata and sync summary
โ”‚   โ””โ”€โ”€ scripts/sync-figma.mjs
โ”œโ”€โ”€ api/                        # Fastify + Drizzle API (active)
โ”‚   โ”œโ”€โ”€ src/{agent,config,db,modules,plugins}
โ”‚   โ”œโ”€โ”€ tests/                  # Vitest + pglite + FakeAgent
โ”‚   โ””โ”€โ”€ drizzle.config.ts
โ”œโ”€โ”€ web/                        # Next.js 16 App Router frontend (active)
โ”‚   โ””โ”€โ”€ src/{app,components,contexts,lib}
โ”œโ”€โ”€ agent/                      # Python LangGraph agent (active)
โ”‚   โ”œโ”€โ”€ src/careersim_agent/
โ”‚   โ”‚   โ””โ”€โ”€ voice/              # voice worker, STT/TTS providers, persona voice
โ”‚   โ”œโ”€โ”€ scripts/                # voice_smoke.py, voice_perf.py, prefetch_voice_models.py
โ”‚   โ”œโ”€โ”€ data/{personas.json,simulations.json,documents/}
โ”‚   โ””โ”€โ”€ tests/
โ”œโ”€โ”€ backend/                    # DEPRECATED โ€” superseded by api/
โ”œโ”€โ”€ frontend/                   # DEPRECATED โ€” superseded by web/
โ”œโ”€โ”€ rag/                        # DEPRECATED โ€” absorbed into agent/
โ”œโ”€โ”€ transformers/               # DEPRECATED โ€” absorbed into agent/
โ”œโ”€โ”€ infrastructure/             # Targets legacy stack; needs a pass for api/web/agent
โ”‚   โ”œโ”€โ”€ aws/                    # Terraform: ECS/Fargate, RDS, ElastiCache, ALB
โ”‚   โ”œโ”€โ”€ aws-transformers/       # Standalone Transformers deployment
โ”‚   โ””โ”€โ”€ k8s/                    # Kustomize (dev + prod overlays)
โ”œโ”€โ”€ docker-compose.local.yml    # Local dev stack (api + web + agent + agent-voice + livekit + postgres + redis + ghost)
โ”œโ”€โ”€ PERSONAS.md                 # AI persona definitions
โ”œโ”€โ”€ VOICE_MODE.md               # Voice-mode operator guide + pre-merge smoke checklist
โ”œโ”€โ”€ LICENSE.md                  # MIT
โ””โ”€โ”€ README.md                   # (this file)

๐Ÿš€ Quick Start

๐Ÿ“‹ Prerequisites

  • Docker + Docker Compose (for the one-shot path)
  • Node.js โ‰ฅ 22.12 for api/; Node.js โ‰ฅ 20 for landing/ and web/; pnpm โ‰ฅ 10
  • Python โ‰ฅ 3.11 + [uv](https://docs.astral.sh/uv/) (for running agent/ outside Docker)
  • An OpenAI-compatible API key (OpenAI, OpenRouter, โ€ฆ) for the agent

๐Ÿณ Local development with Docker Compose

# 1. Configure each service's .env (all three are required)
cp agent/.env.example agent/.env   # set OPENAI_API_KEY + model names
cp api/.env.example   api/.env     # set JWT_SECRET (min 16 chars)
cp web/.env.example   web/.env     # usually OK as-is; NEXT_PUBLIC_API_URL=http://localhost:8000/v1

# 2. Bring the stock stack up without optional plugins
# Builds on first run, hot-reloads after
CAREERSIM_PLUGINS= docker compose -f docker-compose.local.yml up --build

The Compose file defaults to the optional creator plugin, which requires a separate checkout. The empty CAREERSIM_PLUGINS= override above lets this repository run on its own. See Plugin support to enable plugins.

This starts:

URL Service
http://localhost:3000 web โ€” Next.js app
http://localhost:3000/blog Blog (rendered by web from Ghost; off unless NEXT_PUBLIC_BLOG_ENABLED=true)
http://localhost:8000 api โ€” Fastify API
http://localhost:8000/v1/docs API Swagger UI (zod schemas โ†’ OpenAPI)
http://localhost:8001 agent โ€” FastAPI (stateless)
http://localhost:8001/docs Agent Swagger UI
http://localhost:2368/ghost ghost โ€” headless CMS admin UI
ws://localhost:7880 livekit โ€” WebRTC SFU signalling (voice mode)
localhost:5432 PostgreSQL (careersim / careersim_password)
localhost:6379 Redis

The compose stack also starts the agent-voice worker (no published port โ€” it joins LiveKit rooms outbound). The first --build is slower than the others because the voice image prefetches the default faster-whisper + Piper models into the image / a named volume. Set VOICE_ENABLED=false in agent/.env + api/.env to skip voice entirely (the worker exits cleanly and the Call button is hidden) โ€” see VOICE_MODE.md.

The api container runs Drizzle migrations on start; no manual seeding needed. Register a new user from the web UI (there is no default admin account โ€” that concept belonged to the legacy backend/).

๐Ÿ’ป Running a service outside Docker

Useful when you want a faster hot-reload loop for a single service. Each subdirectory has its own README with detailed flags.

# Landing
cd landing && pnpm install && pnpm dev                       # :4321

# API
cd api && pnpm install && pnpm db:migrate && pnpm dev        # :8000

# Web
cd web && pnpm install && pnpm dev                           # :3000

# Agent (FastAPI production server)
cd agent && uv sync && uv run python -m careersim_agent.main --serve api --port 8001

# Agent (Gradio dev console โ€” stateful, good for prompt iteration)
cd agent && uv run python -m careersim_agent.main            # :7860

# Agent voice worker (joins LiveKit rooms; needs a running livekit + api)
cd agent && uv run python -m careersim_agent.main --serve voice

When mixing Docker + host, point each service at the others via host.docker.internal / localhost as appropriate โ€” see docker-compose.local.yml for the canonical wiring.

๐Ÿ”Œ Plugin support

CareerSIM supports optional plugins in the API and web app. Plugins can add API routes, database migrations in their own schema, simulation providers, web pages, and navigation items. The host SDKs live in api/src/extensions/sdk.ts and web/src/extensions/sdk.ts.

The creator plugin is not part of this repository. Its source lives in the separate, private careersim-studio repository, under plugins/creator, and requires access to that repository. It adds persona and simulation authoring, reusable personas, multi-persona casts, and a live conversation playground. This repository contains the plugin support code; the stock application works without the creator plugin.

Plugins are copied into generated, Git-ignored directories before the API or web app is built. Plugin source and generated routes are not committed to this repository. Configure the same plugin selection for both services, using compatible host and plugin revisions.

Local development with plugins

With careersim-studio checked out beside careersim, the Compose file mounts ../careersim-studio/plugins and enables creator by default:

# From the careersim repository root; requires the separate plugin checkout
docker compose -f docker-compose.local.yml up --build

Set CAREERSIM_PLUGIN_HOST_DIR in the shell or a repo-root .env to use a different plugin directory. To disable plugins, set CAREERSIM_PLUGINS= explicitly; leaving it unset uses Compose's creator default.

When switching an existing checkout back to the stock application, stop the API and web development servers and clear the generated overlays first:

# From the careersim repository root
CAREERSIM_PLUGINS= node api/scripts/plugins.mjs fetch
CAREERSIM_PLUGINS= node web/scripts/plugins.mjs fetch

For services running outside Docker, pass the plugin settings to each development command:

# From api/ or web/; the path points to the directory containing creator/
CAREERSIM_PLUGINS=creator CAREERSIM_PLUGIN_DIR=../../careersim-studio/plugins pnpm dev

Both development servers watch the local plugin source for changes. The API restarts and applies migrations; the web app recopies plugin pages for hot reload.

Build configuration

Variable Purpose
CAREERSIM_PLUGINS Comma-separated plugin IDs. Empty or unset builds the stock application and removes previously generated plugin overlays during fetch/sync.
CAREERSIM_PLUGIN_DIR Local directory containing <id>/plugin.json and the plugin source. When set, plugins are copied from disk.
CAREERSIM_PLUGIN_REF Revision of careersim-studio to download when no local plugin directory is supplied. Pin a commit for repeatable builds.
CAREERSIM_PLUGIN_TOKEN GitHub token with read-only Contents access to careersim-studio, used for the build-time download. Keep it server-side; never prefix it with NEXT_PUBLIC_.

Set these in the build environment for both API and web builds. With no plugins selected, no plugin checkout or token is required. For the API Docker build, pass the token as the BuildKit secret careersim_plugin_token.

See api/.env.example and web/.env.example for the configuration reference. The API's pnpm db:migrate command applies plugin migrations after the core migrations; Docker startup and plugin-enabled API development run this command automatically.

โœจ Core Features

๐Ÿ“š Simulation library

Nine first-party simulations shipped in agent/data/simulations.json, each bound to a persona with its own hidden goals, difficulty, and success criteria. The web app lists them at /simulations.

๐Ÿ“ฐ Headless Ghost blog

Self-hosted Ghost (Docker + MySQL) is used as a CMS only. Next.js fetches posts via the Content API and renders them at /blog on the main domain (subdirectory SEO), with BlogPosting JSON-LD, sitemap entries, and a publish webhook for on-demand ISR.

Feature flag: the blog is off by default. Omitting NEXT_PUBLIC_BLOG_ENABLED entirely (or setting it to anything other than true/1) keeps it off. It turns on only when NEXT_PUBLIC_BLOG_ENABLED=true and GHOST_API_URL + GHOST_CONTENT_API_KEY are set in web/.env. Otherwise /blog routes 404, sitemap / robots / llms.txt omit blog URLs, and the revalidate webhook is rejected. Restart web after changing the flag. Landing Blog links appear only when LANDING_BLOG_URL is set (leave it unset by default).

See web/README.md for the one-time Admin setup (Private Mode, Content API key, webhook).

๐Ÿ’ฌ Live chat with SSE streaming

The api exposes POST /v1/sessions/:id/messages/stream, which proxies the agent's SSE stream end-to-end. The web client shows an optimistic echo of the user's message, renders a typing indicator until the first AI chunk lands, then streams the reply token-by-token. Persistence happens exactly once when the upstream emits done.

๐Ÿค– LangGraph conversation engine

Stateful graph inside agent/:

  • Processes user input, fetches embedded-Chroma context, generates a persona response, and runs an LLM-based eval pass (sentiment / emotion / per-goal progress).
  • Proactive messages are explicit graph branches: start (conversation opener, fired during session init), followup (multi-message burst, capped by burstiness.max), and inactivity (guardrailed nudge).
  • Fully stateless at the API boundary โ€” the caller-owned state_snapshot is sent on every turn, mirroring the TestStatelessness suite in agent/tests/test_api.py.

๐Ÿ‘‹ Inactivity nudges (pull model)

The api exposes POST /v1/sessions/:id/nudge; the server decides idempotently whether to fire based on the persona's inactivityNudgeDelaySec window and inactivityNudges.max budget. The web client polls every 5 s while idle and stops automatically when the server returns nudges_disabled or budget_exhausted, re-arming on the next human reply. See api/README.md for the exact contract.

๐Ÿ” Follow-up bursts

POST /v1/sessions/:id/proactive/stream drives persona-initiated follow-ups capped by burstiness.max - 1 additional messages. The web UI exposes this behind a "Follow up" button and surfaces the cap as a {N} followups max badge alongside typing speed and nudge count.

๐ŸŽ™๏ธ Voice mode (browser-native)

Real-time spoken practice with the same personas, no install required. The web client mints a short-lived LiveKit token via POST /v1/sessions/:id/voice/start, joins a self-hosted WebRTC SFU, and the agent-voice worker runs STT โ†’ the existing LangGraph turn โ†’ TTS in the room โ€” captions stream over a data channel and transcripts persist back through the public API on the user's bearer (audio itself is never stored). Defaults are fully self-hosted (faster-whisper + Piper); OpenAI, Deepgram, and ElevenLabs are opt-in per provider. Each persona declares a voice block (speaking rate, barge-in tolerance, filler-word density, per-provider voice IDs) so characters sound distinct. A daily per-user minute budget (VOICE_DAILY_MINUTES_PER_USER) is enforced authoritatively by the worker, and a single VOICE_ENABLED=false flag is a full kill switch. See VOICE_MODE.md for the operator runbook.

๐Ÿ”Ž Retrieval-Augmented Generation (embedded)

Per-simulation and per-persona Markdown under agent/data/documents/ is indexed into a persisted Chroma store (volume agent_chroma_db). No separate HTTP hop.

๐Ÿ“Š Per-turn evaluation

Sentiment and emotion for both sides of the conversation, plus per-goal progress with confidence scoring, computed by a cheaper eval model (OPENAI_EVAL_MODEL) on every turn. Results are persisted to the session's analysis + goal_progress columns and returned in GET /v1/sessions/:id.

๐Ÿ” Authentication

Email + password โ†’ JWT bearer (stored in localStorage on the web side; Authorization: Bearer on every request). Passwords hashed with argon2id. No refresh tokens, email verification, or Stripe billing โ€” all of that lived in the deprecated backend/ and has not been re-implemented.

๐ŸŽญ AI Personas

Shipped in agent/data/personas.json (plus simulations.json, avatars/, and documents/). Each persona declares a conversationStyle that the runtime surfaces in GET /v1/sessions/:id.session_config and the web UI badges.

For local development the git-backed agent/data/ tree is the source of truth. Deployments can instead set PERSONAS_S3_ENABLED=true on the agent so it downloads that same layout from an S3 bucket at startup and rewrites the local files before any session loads โ€” useful when future cast updates live in a personas repo that publishes into the bucket. See agent/README.md for the flag, prefix layout, and credential notes.

Persona Role Simulation slug Typing (wpm) Nudges max Burst max
Brenda Vance By-the-Book HR Manager behavioral-interview-brenda 110 2 3
Alex Chen Passionate Tech Lead tech-cultural-interview-alex 140 3 3
David Miller Senior Analyst / Skeptical Veteran pitching-idea-david 120 2 1
Sarah Jenkins Overwhelmed Project Manager saying-no-to-extra-work-sarah 130 2 2
Michael Reyes Disengaged High-Performer reengaging-disengaged-employee-michael 100 1 1
Chloe Davis Eager but Anxious Junior delegating-task-chloe 135 2 2
Priya Patel Senior Data Analyst data-analyst-technical-interview-priya 125 3 2
Vikram Shah Pipeline-Pressured Recruiter recruiter-coldreach-vikram 150 3 3
Marcus Whitfield Time-Boxed VP of Engineering informational-chat-marcus 95 1 1

Each persona also declares a voice block (speakingRateWpm, bargeInToleranceMs, fillerWordFrequency, silenceThresholdMs, and per-provider voice settings for piper_local / openai_tts / elevenlabs) that drives how it sounds in voice mode โ€” so Vikram is rapid-fire and barges in quickly while Marcus is slow, dry, and tolerant of silence.

See PERSONAS.md for the full persona definitions, hidden goals, and success criteria.

๐Ÿงช End-to-end simulation testing

agent/test_simulation.py is a CLI harness for running a complete simulation end-to-end against the agent's Gradio dev console โ€” useful for sanity-checking a new persona, a tweaked goal, or an evaluation-threshold change without clicking through the UI turn-by-turn. Two modes: interactive (you type the user's side) or **--auto** (an OpenAI-driven candidate plays the user side using a per-simulation strategy prompt baked into the script). The full transcript and per-turn goal-progress snapshots can be written to agent/logs/ with --log and exported as JSON with --json.

# Requires the Gradio dev console running on :7860 and OPENAI_API_KEY for --auto.
cd agent && uv run python -m careersim_agent.main &
cd agent && uv run python test_simulation.py --list
cd agent && uv run python test_simulation.py \
  --sim recruiter-coldreach-vikram --auto --log --json

See agent/README.md for the full flag table and the convention for adding SIMULATION_PROMPTS entries when introducing a new simulation.

๐Ÿ› ๏ธ Tech Stack

Layer Technology
Landing Astro 6 static output, TypeScript 5.9, plain CSS, Figma reference sync
Web Next.js 16 (App Router, Turbopack), React 19, TypeScript 6, Tailwind CSS 3, eventsource-parser, livekit-client (lazy)
API Node.js 22.12+, Fastify 5, TypeScript 6 (strict ESM), Drizzle ORM + drizzle-kit, @fastify/jwt, argon2id, Zod 4 + fastify-type-provider-zod, undici, livekit-server-sdk
Agent Python 3.11+, FastAPI, LangGraph, Chroma (embedded), OpenAI SDK, Gradio 5, uv
Voice LiveKit Server (SFU) + LiveKit Agents SDK; faster-whisper + Piper self-hosted defaults; OpenAI Whisper/TTS, Deepgram, ElevenLabs opt-in
Data PostgreSQL 17, Redis 7
LLM / models OpenAI-compatible chat + embeddings (OpenAI, OpenRouter, โ€ฆ); in-process LLM eval
Testing Vitest 4 + @electric-sql/pglite + FakeAgent (api), pytest + _FakeGraph (agent)
Infrastructure Docker, Docker Compose; Terraform (AWS) and Kustomize (K8s) checked in but targeting the legacy layout

โ˜๏ธ Infrastructure

๐ŸŸง AWS (Terraform) โ€” infrastructure/aws/

Production-ready ECS Fargate topology with VPC, ALB, RDS PostgreSQL, ElastiCache Redis, EFS, Cloud Map service discovery, and optional GPU instances for the deprecated transformers service. Currently wired for the legacy backend + frontend + transformers + rag stack and has not been updated for api + web + agent.

โš“ Kubernetes (Kustomize) โ€” infrastructure/k8s/

Self-hosted deployment with dev and prod overlays, StatefulSets for databases, and GPU scheduling. Same caveat as above โ€” it targets the legacy layout.

Important

Both infrastructure trees need a pass before a production deploy of the new stack. The local docker-compose.local.yml is the canonical topology in the meantime.

๐Ÿ”„ Development Loops

# From the repo root
CAREERSIM_PLUGINS= docker compose -f docker-compose.local.yml up --build # stock stack
docker compose -f docker-compose.local.yml logs -f api web    # tail two services
docker compose -f docker-compose.local.yml restart api        # after a .env change

# Landing
cd landing && pnpm check && pnpm build
cd landing && pnpm sync:figma  # requires FIGMA_TOKEN in the shell

# API
cd api && pnpm test          # vitest (pglite + FakeAgent โ€” no OpenAI, no network)
cd api && pnpm e2e           # interactive CLI against a live stack
cd api && pnpm typecheck

# Web
cd web && pnpm lint && pnpm typecheck

# Agent โ€” unit / contract tests (mocked LangGraph, no OpenAI key needed)
cd agent && uv run pytest

# Agent โ€” end-to-end simulation runs against a live Gradio dev console.
# Useful for sanity-checking new personas, goals, or eval thresholds.
# Requires the dev console running (`uv run python -m careersim_agent.main`)
# and OPENAI_API_KEY for --auto. See agent/README.md for the full flag list.
cd agent && uv run python test_simulation.py --list
cd agent && uv run python test_simulation.py \
  --sim recruiter-coldreach-vikram --auto --log --json

๐Ÿ—บ๏ธ Roadmap

  • Port infrastructure to the new stack โ€” Terraform + Kustomize for api / web / agent / agent-voice / livekit.
  • Remove the deprecated directories once the migration is considered complete and nothing still references them.
  • Real phone-call practice with AI personas โ€” building on the shipped browser-native voice mode, extend to outbound (the persona calls the user at a scheduled time) and inbound (the user dials a number) calls via Twilio Voice / LiveKit SIP, with the same LangGraph engine driving the conversation.
  • Community features โ€” public leaderboards and discussion forums.
  • Certification paths โ€” structured learning programs with shareable certificates.
  • AI persona builder โ€” available through the optional creator plugin in the separate careersim-studio repository; see Plugin support.
  • Team / B2B version โ€” enterprise offering with team analytics and management.

๐Ÿ“œ License

This project is licensed under the MIT License โ€” see the LICENSE.md file for details.

๐Ÿ‘ค Author

Pavel Vdovenko (reactivecake@gmail.com)

Releases

Packages

Used by

Contributors

Languages