ThinkHigher is a work simulation platform for practicing and assessing higher-order thinking. Students work through realistic, multi-stage workplace scenarios (e.g. a junior software engineer handling a bug report, or an analyst briefing an executive), talking to AI-driven stakeholders in text or voice. Each session is scored against a rubric and can produce a cognitive profile of the participant.
- Framework: Next.js 16 (App Router, TypeScript, Tailwind CSS 4)
- LLM: Google Gemini (
gemini-2.5-flashby default) — used for both stakeholder chat and speech-to-text transcription - Text-to-speech: Amazon Polly
- Auth: NextAuth v5 (Google OAuth)
- Database: Vercel Postgres, via
drizzle-orm— falls back to an in-memory store when unconfigured - Email: Resend +
@react-email/components - State machine: XState (
@xstate/react) - Testing: Vitest + Testing Library + MSW
- Hosting: Vercel
npm install
cp .env.example .env.local # fill in the values, see below
npm run dev # http://localhost:3000Use
http://localhost:3000, not your machine's network IP — the microphone requires a secure context, and onlylocalhostqualifies without HTTPS.
Other scripts:
npm run build # production build
npm run start # run the production build
npm run lint # eslint
npm run test # vitest, watch mode
npm run test:ci # vitest, single run (used in CI)
npm run test:coverage # vitest with coverageCI (.github/workflows/test.yml) runs npm run test:ci on every push/PR to main and dev.
Copy .env.example to .env.local and fill in:
| Variable | Service | Required for |
|---|---|---|
GEMINI_API_KEY |
Google AI Studio | Stakeholder chat (/api/chat) and speech-to-text (/api/stt, via Gemini multimodal transcription — no separate STT key) |
GEMINI_MODEL |
— | Optional override, defaults to gemini-2.5-flash |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION |
Amazon Polly | Text-to-speech (/api/tts). IAM user needs AmazonPollyReadOnlyAccess. us-east-1 recommended. |
AUTH_SECRET |
NextAuth v5 | Session auth. Generate with openssl rand -base64 32. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google OAuth | Sign-in (/api/auth/[...nextauth]) |
POSTGRES_URL |
Vercel Postgres | Persisting sessions/transcripts/assessments. If unset, the app uses an in-memory store (src/lib/db.ts) — fine for local dev, but data doesn't survive a restart. |
RESEND_API_KEY |
Resend | Transactional email (/api/email) |
Without GEMINI_API_KEY the chat and STT routes return a clear 500 rather than failing silently. Everything else degrades gracefully (persistence failures never block the UX — see Conventions below).
Full endpoint list: src/app/api/{chat,stt,tts,auth,sessions,evaluate,profile,compare,email}/route.ts.
A scenario is a JSON file in src/data/scenarios/ describing a sequence of stakeholder conversations ("stages"), each with an AI persona, turn limits, and an assessment rubric. There are two schema versions in use — pick based on complexity:
- V1 ("Vela" style) — one agent per stage, no shared agents across stages. See
src/data/scenarios/001-i-thought-it-worked.json/002-vela-sde-sim-001.jsonand types insrc/lib/types.ts(ScenarioDefinition,StageDefinition). - V2 ("Ridgeline" style) — agents defined once in a top-level
agentsdict and referenced by ID from stages (needed when the same stakeholder appears in multiple stages), plus support for data-table/document artifacts and scriptedstructuredProbes. Seesrc/data/scenarios/003-ridgeline-analyst-sim-001.json, typesSimulationV2/StageV2, and the full schema rationale indocs/design/simulation-schema-proposal.md.
Steps to add a new scenario:
- Write the JSON under
src/data/scenarios/NNN-your-scenario-id.json, following one of the two schemas above. Key fields:assessmentConfig.skillDimensions— the rubric dimensions scored at the end (originality, communication, etc.)- each stage's
agentProfile.personaPrompt(V1) oragents.<id>.personaPrompt(V2) — the system prompt driving that stakeholder's behavior, written in-character with instructions on tone, what to probe for, and when to wrap up turnConfig(minTurns/maxTurns/wrapUpSignalTurn) — controls how long the conversation runs before the agent naturally wraps uppollyVoiceper agent — must be a valid Amazon Polly voice ID (falls back to"Joanna"if omitted)
- Load it — add a small loader module in
src/lib/(mirrorsrc/lib/scenarios.tsfor V1 orsrc/lib/scenarios-ridgeline.tsfor V2) that imports the JSON and exports the typed scenario plus any derived arrays (e.g. resolvingagentIdsto full agent objects for V2). - Add a route — create
src/app/simulations/<your-scenario-slug>/page.tsxrendering<Simulation />(V1) or<SimulationV2 />(V2), pointed at your new loader. - List it in the catalog — add an entry to the
SIMULATIONSarray insrc/app/simulations/page.tsx(id, href, title, description, duration, role, icon). - If the stage has a video intro or an artifact (HTML prototype, data table, document), add the video component under
src/components/or the static artifact file underpublic/mockups/, and reference it viavideoComponent/artifactSrc(V1) orvideo.component/artifact.src(V2). - Test it end-to-end — run
npm run dev, walk through every stage, and confirm the assessment (/api/evaluate) produces sensible scores for the new rubric.
If you're touching the schema itself (not just authoring a new scenario), read docs/design/simulation-schema-proposal.md first — it documents the differences between V1/V2 and the reasoning behind each field before you add a third variant.
src/
app/
api/ # chat, stt, tts, auth, sessions, evaluate, profile, compare, email
simulations/ # one route per scenario, catalog at simulations/page.tsx
tasks/ # standalone cognitive tasks (bandit, ARC grid, etc.)
components/ # Simulation.tsx (V1), SimulationV2.tsx, speech/voice UI, catalog cards
data/scenarios/ # scenario JSON definitions
hooks/ # useSpeechInput / useSpeechOutput
lib/
types.ts # all TypeScript types (scenario, chat, persistence, survey, profile)
scenarios.ts / scenarios-ridgeline.ts # scenario loaders
db.ts / db-postgres.ts # persistence layer (in-memory or Postgres)
rt-metrics.ts # response-time metrics / cognitive signals
centaur.ts # Centaur behavioral prediction integration
gecco.ts # GeCCo cognitive model generation pipeline
participant.ts # Prolific ID passthrough + device metadata
docs/
dev.md # environment setup + changelog of major features
design/ # schema proposals, architecture notes
research/ # verified references for the cognitive-modeling roadmap
- Persistence failures never block the user experience — session/transcript writes are fire-and-forget.
- Scenario JSON is written to be Deliberate Lab-compatible (
turnConfig,channelType,agentProfile/agents). - Response time is tracked per message (
TranscriptEntry.responseTimeMs) for cognitive profiling. - Hierarchical Bayesian models (planned cognitive-modeling work) use non-centered parameterization.
- The brand name is "ThinkHigher" — not "ThinkWith" or other variants.
- New scenarios are the easiest way to contribute — see How to add a new scenario above.
If you use ThinkHigher in academic or research work, please cite it as:
@software{thinkhigher,
title = {ThinkHigher: A Simulation Platform for Higher-Order Thinking Assessment},
author = {Gong, Jiachen and Hugh, Morgan and Ching, Joseph},
year = {2026},
url = {https://github.com/m9h/Thinking-Higher}
}