Skip to content

lf1up/careersim

Repository files navigation

CareerSIM — AI-Powered Career Skills Simulator

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. 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.

                      ┌──────────────────────┐
                      │   Web (Next.js 15)   │
                      │   App Router + RSC   │
                      │   :3000              │
                      └──────────┬───────────┘
                                 │  REST + SSE (JWT Bearer)
                      ┌──────────▼───────────┐
                      │  API (Fastify 5 +    │
                      │  Drizzle + Zod)      │
                      │  :8000               │
                      └───┬──────────────┬───┘
                          │              │
             POST /conv/* │              │ SQL
            (batch + SSE) │              │
                      ┌───▼──────┐   ┌───▼──────────┐
                      │  Agent   │   │ PostgreSQL 17│
                      │ FastAPI +│   │ + Redis 7    │
                      │ LangGraph│   │ :5432 / :6379│
                      │  :8001   │   └──────────────┘
                      │          │
                      │  embedded│
                      │  Chroma  │
                      └──────────┘
Service Stack Description
web Next.js 15 (App Router, React 19), TypeScript, Tailwind 3 Client-rendered SPA over the API: auth, simulation picker, session chat with SSE streaming, nudge auto-polling, follow-up bursts.
api Fastify 5, Drizzle ORM, PostgreSQL, @fastify/jwt, argon2id, Zod 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 either a Gradio dev console or a FastAPI production server (--serve api). Retrieval uses an embedded Chroma store — no separate RAG service.
postgres PostgreSQL 17 Source of truth for users, sessions, messages, and state snapshots.
redis Redis 7 Present in compose for future rate-limiting / pub-sub work.

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/
├── api/                        # Fastify + Drizzle API (active)
│   ├── src/{agent,config,db,modules,plugins}
│   ├── tests/                  # Vitest + pglite + FakeAgent
│   └── drizzle.config.ts
├── web/                        # Next.js 15 App Router frontend (active)
│   └── src/{app,components,contexts,lib}
├── agent/                      # Python LangGraph agent (active)
│   ├── src/careersim_agent/
│   ├── 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 + postgres + redis)
├── PERSONAS.md                 # AI persona definitions
├── LICENSE.md                  # MIT
└── README.md                   # (this file)

Quick Start

Prerequisites

  • Docker + Docker Compose (for the one-shot path)
  • Node.js ≥ 20, pnpm ≥ 10 (for running api/ or web/ outside Docker)
  • Python ≥ 3.11 + 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

# 2. Bring the whole stack up (builds on first run, hot-reloads after)
docker compose -f docker-compose.local.yml up --build

This starts:

URL Service
http://localhost:3000 web — Next.js app
http://localhost:8000 api — Fastify API
http://localhost:8000/docs API Swagger UI (zod schemas → OpenAPI)
http://localhost:8001 agent — FastAPI (stateless)
http://localhost:8001/docs Agent Swagger UI
localhost:5432 PostgreSQL (careersim / careersim_password)
localhost:6379 Redis

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.

# 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

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.

Core Features

Simulation library

Seven 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.

Live chat with SSE streaming

The api exposes POST /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 /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 /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.

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 /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. Each declares a conversationStyle that the runtime surfaces in GET /sessions/:id.session_config and the web UI badges.

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
Michael Reyes Disengaged High-Performer reengaging-disengaged-employee-michael 1
Chloe Davis Eager but Anxious Junior delegating-task-chloe 2
Priya Patel Senior Data Analyst data-analyst-technical-interview-priya 2

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

Tech Stack

Layer Technology
Web Next.js 15 (App Router, Turbopack), React 19, TypeScript, Tailwind CSS 3, eventsource-parser
API Node.js 20+, Fastify 5, TypeScript (strict ESM), Drizzle ORM + drizzle-kit, @fastify/jwt, argon2id, Zod + fastify-type-provider-zod, undici
Agent Python 3.11+, FastAPI, LangGraph, Chroma (embedded), OpenAI SDK, uv
Data PostgreSQL 17, Redis 7
LLM / models OpenAI-compatible chat + embeddings (OpenAI, OpenRouter, …); in-process LLM eval
Testing Vitest + @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
docker compose -f docker-compose.local.yml up --build         # full 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

# 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
cd agent && uv run pytest

Roadmap

  • Port infrastructure to the new stack — Terraform + Kustomize for api / web / agent.
  • Remove the deprecated directories once the migration is considered complete and nothing still references them.
  • Community features — public leaderboards and discussion forums.
  • Certification paths — structured learning programs with shareable certificates.
  • AI persona builder — user-created custom personas for specialized practice.
  • 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

No releases published

Packages

 
 
 

Contributors