Chat interface for the conversational nudge study (HREC 83897). Students complete a short, course-relevant task with an AI assistant; in the treatment arm a one-line nudge is appended below every AI reply. Anonymous by design — no name, email, or student number is collected.
Browser (React) --> FastAPI backend --> Anthropic API
|
+--> SQLite (sessions, turns, survey_responses)
Key design decisions:
- Anonymous: no name, email, or student number is collected. Each session is a random 8-character code; optional equity indicators are stored against that code only.
- API key lives on the server only (never exposed to the browser).
- System prompt instructs the model to answer directly without engagement hooks.
- The nudge is appended server-side after the AI response; the model never sees it (it is not added to the conversation history).
- Full transcript logging: every turn stores the student message, the raw AI response, and what the student saw (raw + nudge).
- Access is gated by a lab-session time window (server clock); a test token bypasses the window for facilitator/co-investigator testing only.
Routes (React Router):
/session— random condition assignment (what students use)/session/a— force nudge (treatment) — facilitator/test override/session/b— force control (silent) — facilitator/test override
Logins are accepted only during a configured lab-session window
(backend/lab_sessions.json, server clock, UTC). The file is hot-reloaded on
change — edits take effect without a restart. GET /api/labs shows the configured
schedule and which windows are active now (read-only, for facilitator self-checks).
Outside a window students see a "no active session" screen. For testing outside a window, append
?token=<TEST_TOKEN> (set on the server). The token is for facilitators /
co-investigators only.
Per-unit links. Give each lecturer a link with ?lab=<unit_code> (e.g.
/session?lab=MKTG1000). The parameter matches a window's unit_code or lab_id,
narrows the login gate to that unit's windows, and stamps the session with the
matched lab_id — so overlapping windows from different units attribute correctly.
Without the parameter, the session is attributed only if exactly one window is
active; if several units overlap, lab_id is left empty (recover the unit from
transcript context).
Campus attribution. A window entry may carry an optional "campus" field
(e.g. "BENTLEY", "MIRI"); the matched window's campus is stamped on the
session. The browser's IANA timezone (e.g. Australia/Perth) is also recorded
at login for real consenting sessions, as a protocol-compliance cross-check —
it is coarse location only, no IP address is collected or stored.
Teaching-only windows. A window with "research": false (e.g. a campus not
yet covered by the ethics approval) runs the identical tutorial, but every
session in it is forced to test status: nothing is recorded, regardless of the
student's consent choice. Remove the flag once approval covers that site.
Sessions started via the token or in demo mode, and any non-consenting sessions,
are flagged is_test and excluded from analysis.
Demo mode (optional). With DEMO_ENABLED=true, append ?demo=1 to share a
token-free, throwaway session (e.g. /session/a?demo=1 for nudge, /session/b?demo=1
for control). Demo sessions write nothing to the database, are pinned to
DEMO_MODEL (a cheap model), and are capped at DEMO_TURN_CAP turns. Leave it
off during data collection — it shares the API key/quota — and set a spend limit
on the API account while it is on.
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
export ANTHROPIC_API_KEY="sk-ant-..." # or set in backend/.env
uvicorn main:app --reload --port 8000Config (env vars; see .env.example): ANTHROPIC_MODEL (default Haiku for
prototyping), TEST_TOKEN, CONFIG_DIR (where lab_sessions.json / nudge_config.json
/ prompts.json live in Docker), DATA_DIR (SQLite location).
cd frontend
npm install
npm run devOpen http://localhost:5173/session in your browser.
- The student opens an anonymous session link (optionally
/session/aor/session/b). During an active lab window the consent screen appears, with the Participant Information Sheet (HREC 83897) and optional equity indicators embedded. - On a consent choice, the backend creates a session with a random code and a condition (random, or forced by the route).
- The student chats with the AI; all messages go through the backend.
- Nudge arm: the backend appends the nudge below every AI reply (a visually distinct box). Control arm: the AI reply is passed through unchanged.
- On Finish Task the chat locks and (for consenting students) the in-app exit survey is shown.
- Every turn is logged to SQLite with timestamps.
The system prompt suppresses the model's default behaviour of appending follow-up suggestions:
"Do NOT end your response with follow-up questions. Do NOT offer to elaborate. Simply answer what was asked, then stop."
This matters because frontier models are trained to keep users engaged, which would
contaminate both conditions (control students would get pseudo-nudges from the model
itself). System prompt and nudge pool are configurable via prompts.json and
nudge_config.json.
| Endpoint | Method | Description |
|---|---|---|
/api/version |
GET | Build stamp (short commit SHA + build time) of the running image |
/api/session-status |
GET | Is a lab session active? (a valid token bypasses the time check) |
/api/labs |
GET | Read-only lab schedule + which windows are active now |
/api/login |
POST | Start a session; returns a random session code + condition |
/api/chat |
POST | Send a message, get the AI reply (with nudge for the nudge arm) |
/api/finish |
POST | Lock the chat (student clicked Finish Task) |
/api/survey |
POST | Submit the in-app exit survey |
/api/sessions |
GET | List sessions (admin) |
/api/history/{code} |
GET | Display history for a session (restore chat) |
/api/export/{code} |
GET | Export the full transcript for a session |
sessions— one row per login:session_code,condition,is_test,lab_id,campus(from the matched schedule entry),timezone(browser IANA timezone, consenting sessions only), optional equity indicators, timestamps, andchat_locked/survey_completedflags.turns— every message, three rows per turn:user(what the student said),assistant_raw(what the model returned),assistant_display(what the student saw, including the nudge for the nudge arm).survey_responses— one row per completed exit survey.
Sessions are keyed only by a random code; no name, email, or student number is collected or written.
The GitHub Actions build injects the short commit SHA and build time into the image
(build.yml → Dockerfile build args). They surface two ways, so you can confirm
which build the VPS is running:
- a small version badge in the bottom-right of the UI (
v<sha>), and GET /api/version→{"version": "<sha>", "built": "<UTC time>"}.
Local/dev builds with no build arg show dev build.