Prolific Integration¶
HANI has built-in support for running paid studies on
Prolific. When a participant lands on the
guest app (hani-guest) with a PROLIFIC_PID query parameter, HANI
switches into Prolific mode: it identifies the participant by their PID,
locks in a scenario type, enforces a per-session negotiation quota, can
follow a per-participant schedule, and ends with a completion link.
HANI provides the in-app behavior only. Study creation on Prolific,
consent/pre/post-session questionnaires, and bonus payments are the job
of whatever external service you build around HANI; that service writes
schedule.json (optional) and hosts the completion page that
SCMLWEB_BASE_URL points at.
Entry point¶
The Prolific link must target the guest app:
https://your-host.example/hanplay?PROLIFIC_PID={{%PROLIFIC_PID%}}
Run the guest app with hani-guest (sets HANI_GUEST_MODE=true):
In guest mode, HANI hides the "User Results" pane and disables the login/consent flow — the PID is the identity.
Session flow¶
A participant's session goes:
- First visit only — HANI creates
~/negmas/hani/db/prolific_<PID>/and writesprolific_session.jsonwithstarted_at,scenario_type,max_minutes,max_negs. - Practice round — the first negotiation of a participant's first
session uses a random partner, is flagged
practice=Trueinresults.csv, and does not count. - Counted rounds — the next
PROLIFIC_N_REQUIRED(default 5) negotiations count. Each round pulls a (partner, scenario) cell fromschedule.jsonif present, otherwise falls back to a random partner and a hash-based scenario. - Zero-action rounds don't count — if the participant lets the agent time out without sending anything, accepting, or ending, the counted-slot counter does not advance and the same opponent is shown again next round.
- Returning participants (same PID, later session) skip the practice and start at counted slot 0.
- Completion — when all required counted rounds are done, HANI
shows a panel with a single link to
{SCMLWEB_BASE_URL}/prolific/done?PROLIFIC_PID=<pid>.
If the participant doesn't press Start / Load within
PROLIFIC_AUTO_START_SECONDS (default 120), HANI auto-starts the next
round so a participant can't stall the session.
Environment variables¶
All Prolific behavior is controlled by env vars. Set them in whatever
launches hani-guest.
| Variable | Default | Purpose |
|---|---|---|
PROLIFIC_N_REQUIRED |
5 |
Number of counted negotiations required. Practice is on top, so a first session has N + 1 total rounds. |
PROLIFIC_MAX_MINUTES |
45 |
Recommended duration shown to the participant. Not enforced. |
PROLIFIC_AUTO_START_SECONDS |
120 |
Idle timeout before HANI auto-starts the next round. |
PROLIFIC_FINALISTS |
(empty) | Comma-separated dotted Python class names of the agents available for counted rounds. When non-empty, schedule.json picks from this list. Empty falls back to the configured partner_types. |
PROLIFIC_PER_NEG_YAML |
(unset) | Absolute path to a per-negotiation questionnaire YAML. If unset or the file is missing, the per-round form is silently skipped. |
SCMLWEB_BASE_URL |
https://anac.cs.brown.edu |
Base URL for the completion link. Override with e.g. http://localhost:8000 for local dev. |
Per-participant schedule (schedule.json)¶
If present at ~/negmas/hani/db/prolific_<PID>/schedule.json, HANI uses
it to dispatch counted rounds. The file is a JSON list (or an object
with a top-level negotiations list) of entries:
{
"negotiations": [
{"slot": 0, "agent_class_name": "negmas.sao.AspirationNegotiator",
"scenario_type": "Trade", "scenario_index": 0},
{"slot": 1, "agent_class_name": "negmas.sao.RandomNegotiator",
"scenario_type": "Island", "scenario_index": 3}
]
}
Each entry can carry slot (int), agent_class_name (dotted Python
class, resolved against partner_types; falls back to random if not
found), scenario_type ("Trade" / "Island" / "Grocery"), and
scenario_index (int). Missing fields fall back to HANI's defaults.
When schedule.json is absent or has no scenario_type, HANI picks the
type deterministically from the PID hash:
scenario_type = SCENARIO_LIST[ sha1(PID) mod len(SCENARIO_LIST) ]
so revisits stay on the same domain.
Per-negotiation questionnaire¶
If PROLIFIC_PER_NEG_YAML points at a readable YAML file, HANI renders
a short form after every counted round and writes the answers next to
results.csv. Missing file → form is skipped. The YAML format:
version: "2026-05-19"
title: "After this negotiation"
intro: "Quick check before we continue."
questions:
- id: satisfaction_with_outcome
text: "How satisfied are you with the outcome of this negotiation?"
type: likert5 # likert5 | likert7 | yes_no | select | text
required: true
labels:
1: "Not at all"
5: "Extremely"
- id: partner_cooperativeness
text: "How cooperative did your partner feel?"
type: select
required: true
options: ["Very competitive", "Neutral", "Very cooperative"]
Question text is rendered as Markdown above each widget. likert and
yes_no widgets default to blank, so required-field validation
fires instead of silently submitting "1".
Files HANI writes per Prolific user¶
Inside ~/negmas/hani/db/prolific_<PID>/:
prolific_session.json— written on first visit.schedule.json— (optional, written externally) per-PID schedule.results.csv— one row per negotiation. Columns includepractice,status(success/broken/ …),ended_by(human/agent/ empty), human/agent utilities, agreement, and per-round timing.last_scenario.txt— index of the last scenario shown.
Counting rules¶
- Accept (agreement) counts.
- End by the participant counts.
- Zero-action round (agent timed out, no human action) does not count and the same opponent is shown again.
The end-of-round toast tells the participant which case applied ("timed out", "was ended by you", "was ended by the AI agent", or "reached an agreement").
Local test¶
HANI_GUEST_PORT=12000 \
PROLIFIC_N_REQUIRED=2 \
PROLIFIC_AUTO_START_SECONDS=600 \
SCMLWEB_BASE_URL=http://localhost:8000 \
./runguest.sh --port 12000
open "http://localhost:12000/hanplay?PROLIFIC_PID=test123"
You should see one practice round followed by two counted rounds, then
the completion link. Delete
~/negmas/hani/db/prolific_test123/ between runs to start fresh.