yashu2000's picture
Upload 2 files
90969a3 verified
Raw
History Blame Contribute Delete
56.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LotteryElicitationEnv: An OpenEnv Benchmark for Post-training Strategic Question Design via Lottery Elicitation as Proxy</title>
<meta name="description" content="LotteryElicitationEnv: an OpenEnv-native benchmark for post-training strategic question design via Lottery Elicitation as Proxy. With GRPO training pipeline.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<!-- Mermaid for inline diagrams -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
themeVariables: {
primaryColor: '#6366f1',
primaryTextColor: '#e2e8f0',
primaryBorderColor: '#818cf8',
lineColor: '#818cf8',
secondaryColor: '#1e293b',
tertiaryColor: '#172033',
background: '#0f172a',
mainBkg: '#1e293b',
nodeBorder: '#818cf8',
clusterBkg: '#172033',
clusterBorder: '#334155',
titleColor: '#e2e8f0',
edgeLabelBackground: '#1e293b',
nodeTextColor: '#e2e8f0'
},
flowchart: { curve: 'basis', htmlLabels: true },
fontFamily: 'Inter, sans-serif'
});
</script>
<style>
:root {
--bg: #0f172a; --surface: #1e293b; --surface-2: #172033; --border: #334155;
--text: #e2e8f0; --muted: #94a3b8; --accent: #6366f1;
--accent2: #818cf8; --green: #22c55e; --red: #ef4444;
--orange: #f59e0b; --radius: 12px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg); color: var(--text); line-height: 1.7;
-webkit-font-smoothing: antialiased; }
.container { max-width: 820px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
/* Top nav */
.topnav { position: sticky; top: 0; z-index: 10; background: rgba(15,23,42,.85);
backdrop-filter: blur(10px); border-bottom: 1px solid var(--border);
padding: .9rem 1.5rem; display: flex; justify-content: space-between;
align-items: center; font-size: .88rem; }
.topnav .brand { font-weight: 700; color: var(--text); text-decoration: none;
display: flex; align-items: center; gap: .5rem; }
.topnav .brand .dot { width: 8px; height: 8px; border-radius: 50%;
background: var(--green); box-shadow: 0 0 8px rgba(34,197,94,.6); }
.topnav .links { display: flex; gap: 1.25rem; }
.topnav .links a { color: var(--muted); text-decoration: none; transition: color .15s; }
.topnav .links a:hover { color: var(--accent2); }
/* Hero */
.hero { text-align: center; padding: 4rem 0 2.5rem; }
.hero-badge { display: inline-block; background: rgba(99,102,241,.15); color: var(--accent2);
padding: .4rem 1.1rem; border-radius: 20px; font-size: .78rem; font-weight: 600;
letter-spacing: .08em; margin-bottom: 1.25rem;
border: 1px solid rgba(99,102,241,.3); text-transform: uppercase; }
.hero h1 { font-size: clamp(2rem, 4.2vw, 3.2rem); font-weight: 800; letter-spacing: -.025em;
line-height: 1.15;
background: linear-gradient(135deg, #e2e8f0 25%, #6366f1 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text; }
.hero .subtitle { color: var(--muted); font-size: 1.15rem; max-width: 640px;
margin: 1rem auto 0; }
.hero .byline { color: var(--muted); font-size: .85rem; margin-top: 1.5rem;
font-style: italic; }
.banner { width: 100%; border-radius: var(--radius); margin: 2rem 0 3rem;
border: 1px solid var(--border); }
/* Badges row */
.badges { display: flex; justify-content: center; gap: .6rem; flex-wrap: wrap;
margin: 1.5rem 0; }
.badges img { height: 22px; }
/* Button group */
.btn-group { display: flex; gap: .75rem; justify-content: center; margin: 2rem 0;
flex-wrap: wrap; }
.btn { display: inline-flex; align-items: center; gap: .45rem; padding: .6rem 1.35rem;
background: var(--accent); color: white; border-radius: 8px; font-size: .88rem;
font-weight: 600; text-decoration: none; transition: all .2s; }
.btn:hover { background: var(--accent2); transform: translateY(-1px); }
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn-outline:hover { border-color: var(--accent); color: var(--accent2);
background: rgba(99,102,241,.08); }
/* TOC */
.toc { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
padding: 1.25rem 1.5rem; margin: 0 0 2.5rem; }
.toc h3 { font-size: .82rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
color: var(--accent2); margin-bottom: .85rem; }
.toc ol { list-style: none; counter-reset: toc; display: flex; flex-wrap: wrap; gap: .35rem .8rem;
margin: 0; padding: 0; }
.toc ol li { counter-increment: toc; font-size: .88rem; }
.toc ol li::before { content: counter(toc) "."; color: var(--accent); font-weight: 700;
font-size: .8rem; margin-right: .3rem; }
.toc ol li a { color: var(--muted); text-decoration: none; transition: color .15s; }
.toc ol li a:hover { color: var(--accent2); }
/* Sections */
section { margin: 3.5rem 0; }
section h2 { font-size: 1.55rem; font-weight: 800; letter-spacing: -.01em;
margin-bottom: 1rem; color: var(--text);
border-left: 3px solid var(--accent); padding-left: .9rem; }
section h3 { font-size: 1.1rem; font-weight: 700; margin: 2rem 0 .75rem;
color: var(--accent2); }
section p { color: #cbd5e1; margin-bottom: 1rem; font-size: 1.02rem; }
section p strong { color: var(--text); }
section ul, section ol { color: #cbd5e1; margin: 1rem 0 1rem 1.5rem; }
section ul li, section ol li { margin-bottom: .5rem; font-size: 1rem; }
section ul li strong, section ol li strong { color: var(--text); }
/* Pull-quote */
blockquote { border-left: 3px solid var(--accent2);
background: rgba(99,102,241,.06); padding: 1.1rem 1.25rem;
margin: 1.5rem 0; border-radius: 0 8px 8px 0;
color: #e2e8f0; font-size: 1.02rem; }
/* Tables */
.table-wrap { margin: 1.5rem 0; overflow-x: auto;
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; font-size: .92rem; }
th { background: rgba(99,102,241,.1); color: var(--accent2);
font-size: .72rem; font-weight: 700; letter-spacing: .06em;
text-transform: uppercase; padding: .85rem 1rem; text-align: left; }
td { padding: .7rem 1rem; border-top: 1px solid var(--border); color: #cbd5e1; }
td.num { text-align: right; font-variant-numeric: tabular-nums;
font-family: 'JetBrains Mono', monospace; font-size: .88rem; }
tr:hover td { background: rgba(99,102,241,.04); }
td strong, th strong { color: var(--text); }
.task-id { font-family: 'JetBrains Mono', monospace; font-weight: 700;
color: var(--accent2); font-size: .85rem; }
tr.avg-row td { background: rgba(99,102,241,.08); font-weight: 700;
color: var(--text); }
tr.novel td:first-child { color: #fca5a5; }
/* Code */
pre { background: #0b1120; border: 1px solid var(--border);
border-radius: var(--radius); padding: 1.1rem 1.25rem; overflow-x: auto;
margin: 1.25rem 0; font-family: 'JetBrains Mono', monospace;
font-size: .85rem; line-height: 1.6; color: #d1d5db; }
pre .c { color: #64748b; }
code { font-family: 'JetBrains Mono', monospace; font-size: .88em;
background: rgba(99,102,241,.12); color: var(--accent2);
padding: .1em .35em; border-radius: 4px; }
pre code { background: none; color: inherit; padding: 0; font-size: 1em; }
/* Figure */
figure { margin: 2rem 0; }
figure img { width: 100%; border-radius: var(--radius);
border: 1px solid var(--border); }
figcaption { text-align: center; color: var(--muted); font-size: .85rem;
margin-top: .75rem; }
/* Mermaid diagram wrapper */
.mermaid-wrap { margin: 2rem 0; background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 1.5rem 1rem; overflow-x: auto; }
.mermaid-wrap .mermaid { display: flex; justify-content: center; }
.mermaid-caption { text-align: center; color: var(--muted); font-size: .85rem;
margin-top: .75rem; }
/* Episode trace */
.episode-trace { background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 1.25rem 1.5rem; margin: 1.5rem 0;
position: relative; }
.episode-trace::before { content: ''; position: absolute; left: 1.5rem; top: 2.5rem;
bottom: 1.25rem; width: 2px; background: var(--border); }
.trace-step { position: relative; padding-left: 2rem; margin-bottom: 1.25rem; }
.trace-step:last-child { margin-bottom: 0; }
.trace-step .step-marker { position: absolute; left: -.45rem; top: .2rem; width: 12px;
height: 12px; border-radius: 50%; border: 2px solid var(--accent);
background: var(--bg); z-index: 1; }
.trace-step .step-marker.terminal { background: var(--red); border-color: var(--red); }
.trace-step .step-marker.good { background: var(--green); border-color: var(--green); }
.trace-step .step-label { font-family: 'JetBrains Mono', monospace; font-size: .78rem;
color: var(--accent2); font-weight: 700; margin-bottom: .25rem; }
.trace-step .step-content { font-size: .9rem; color: #cbd5e1; }
.trace-step .step-content code { font-size: .82em; }
.trace-verdict { margin-top: 1rem; padding: .75rem 1rem; border-radius: 8px;
font-size: .9rem; font-weight: 600; }
.trace-verdict.bad { background: rgba(239,68,68,.1); border: 1px solid rgba(239,68,68,.3);
color: #fca5a5; }
.trace-verdict.good { background: rgba(34,197,94,.1); border: 1px solid rgba(34,197,94,.3);
color: #86efac; }
/* Callout for the closing question */
.callout { text-align: center; padding: 2rem 1.5rem; margin: 3rem 0;
background: linear-gradient(135deg, rgba(99,102,241,.08), rgba(129,140,248,.04));
border: 1px solid rgba(99,102,241,.25); border-radius: var(--radius); }
.callout .q { font-size: 1.25rem; font-weight: 700; color: var(--text);
font-style: italic; margin-bottom: .5rem; }
.callout .sub { color: var(--muted); font-size: .95rem; }
/* Footer */
.footer { text-align: center; padding: 3rem 0 1rem; color: var(--muted);
font-size: .85rem; border-top: 1px solid var(--border); margin-top: 3rem; }
.footer a { color: var(--accent2); text-decoration: none; margin: 0 .5rem; }
.footer a:hover { text-decoration: underline; }
@media (max-width: 640px) {
.container { padding: 1rem 1rem 3rem; }
.hero { padding: 2.5rem 0 1.5rem; }
.topnav .links { display: none; }
section h2 { font-size: 1.3rem; }
table { font-size: .82rem; }
th, td { padding: .55rem .6rem; }
.toc ol { flex-direction: column; }
.episode-trace { padding: 1rem; }
.episode-trace::before { left: 1rem; }
}
/* MathJax display blocks (Scoring + Environment design) */
.math-display {
margin: 1.25rem 0;
padding: 1rem 1.25rem 1.15rem;
overflow-x: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
text-align: center;
}
.math-display mjx-container[jax="CHTML"][display="true"] { margin: 0.65em 0 !important; }
.math-display mjx-container { color: #e2e8f0 !important; }
.math-note { font-size: .9rem; color: var(--muted); margin-top: .35rem; margin-bottom: 0; }
</style>
<!-- MathJax 3: client-side TeX like static HF Spaces (sdk: static) serve plain HTML; external scripts from cdn.jsdelivr.net load in the browser the same way as Mermaid below. -->
<script>
window.MathJax = {
tex: {
inlineMath: [['\\(', '\\)']],
displayMath: [['\\[', '\\]']]
},
options: {
renderActions: {
addMenu: [0, '', ''] /* disable context menu for cleaner Space embed */
}
}
};
</script>
<script defer src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script>
</head>
<body>
<nav class="topnav">
<a href="#top" class="brand"><span class="dot"></span> LotteryElicitationEnv Blog</a>
<div class="links">
<a href="#why">Why</a>
<a href="#design">Design</a>
<a href="#scoring">Scoring</a>
<a href="#traces">Traces</a>
<a href="#results">Results</a>
<a href="#engineering">Engineering</a>
<a href="https://huggingface.co/spaces/yashu2000/LotteryElicitationEnv" target="_blank">Live Space ↗</a>
</div>
</nav>
<div class="container" id="top">
<div class="hero">
<div class="hero-badge">OpenEnv · AgentBeats Phase 2</div>
<h1>LotteryElicitationEnv</h1>
<p class="subtitle">An OpenEnv Benchmark Where LLMs Learn to Design Strategic Questions for Preference Elicitation.</p>
<div class="badges">
<a href="https://github.com/sharma-yash01/LotteryElicitationEnv" target="_blank"><img src="https://img.shields.io/badge/GitHub-Repository-181717?logo=github" alt="GitHub"/></a>
<a href="https://huggingface.co/spaces/yashu2000/LotteryElicitationEnv" target="_blank"><img src="https://img.shields.io/badge/HF%20Space-Live%20Demo-FFD21E?logo=huggingface&logoColor=black" alt="HF Space"/></a>
<img src="https://img.shields.io/badge/OpenEnv-Native-4B8BBE" alt="OpenEnv"/>
<img src="https://img.shields.io/badge/Tasks-10-brightgreen" alt="Variable | Up to 10 Tasks"/>
<img src="https://img.shields.io/badge/Training-GRPO-orange" alt="GRPO"/>
</div>
<div class="byline">AgentBeats Phase 2 &middot; OpenEnv Challenge Submission &nbsp;|&nbsp; Yashaswi Sharma (University of Southern California) &nbsp;|&nbsp; Defu Cao (USC) &nbsp;|&nbsp; Muyan Weng (USC)</div>
</div>
<img src="banner.png" alt="LotteryElicitationEnv: adaptive lottery elicitation cycle — propose pairs, observe choices, recover hidden parameters" class="banner"/>
<div class="btn-group">
<a class="btn" href="https://huggingface.co/spaces/yashu2000/LotteryElicitationEnv" target="_blank">Live Environment Space →</a>
<a class="btn btn-outline" href="https://github.com/sharma-yash01/LotteryElicitationEnv" target="_blank">GitHub Repo</a>
</div>
<!-- Table of Contents -->
<nav class="toc" id="toc">
<h3>Contents</h3>
<ol>
<li><a href="#why">Lottery Elicitation as Proxy for Strategic Question Design</a></li>
<li><a href="#matters">Why This Benchmark Matters</a></li>
<li><a href="#prior-work">Prior Work &amp; Novelty</a></li>
<li><a href="#design">What LotteryElicitationEnv Is</a></li>
<li><a href="#env-design">Environment Design</a></li>
<li><a href="#openenv">Why OpenEnv</a></li>
<li><a href="#scoring">Scoring: Terminal Reward + Format Shaping</a></li>
<li><a href="#architecture">Architecture &amp; Training Pipeline</a></li>
<li><a href="#traces">Episode Traces &amp; Reward Hacking</a></li>
<li><a href="#results">Results: What We Found</a></li>
<li><a href="#engineering">Engineering Lessons</a></li>
<li><a href="#foundations">Foundations &amp; Citations</a></li>
<li><a href="#quickstart">Quick Start</a></li>
<li><a href="#future">Future Work</a></li>
</ol>
</nav>
<!-- 1. WHY -->
<section id="why">
<h2>Lottery Elicitation as Proxy for Strategic Question Design</h2>
<p>Most LLM benchmarks test what a model <strong>can say</strong>. They rarely test whether it can <strong>uncover hidden structure through action</strong>. <strong>LotteryElicitationEnv</strong> is an OpenEnv-native environment where an agent designs sequences of lottery pairs to recover a simulated respondent's prospect-theory parameters: risk aversion (&gamma;) and loss aversion (&lambda;).</p>
<p>Each episode is adaptive. The agent proposes a lottery pair, observes a binary choice from a closed-form respondent, updates its belief in-context, and on the final turn commits a parameter estimate. Reward arrives only at termination, grounded entirely in arithmetic: mean-squared error against the ground-truth &theta;*, Holt&ndash;Laury consistency of the implied preferences, and an efficiency bonus for stopping early when confident.</p>
<p>The challenge is not language. It is <strong>verifiable experimental design</strong>: which lottery pair, right now, is most informative about (&gamma;, &lambda;) given the history so far?</p>
<p>The falsifiable claim: <em>a GRPO-trained LLM can recover (&gamma;, &lambda;) more efficiently than the fixed 10-pair Holt&ndash;Laury (2002) battery that economists still use as the status-quo elicitation protocol.</em></p>
</section>
<!-- 2. WHY IT MATTERS -->
<section id="matters">
<h2>Why this benchmark matters</h2>
<p>Preference elicitation is a foundational problem in behavioral economics, marketing science, medical triage, and alignment. The dominant practice is still a <strong>fixed menu</strong>: every subject answers the same pre-specified list. An adaptive protocol that picks the next question given past answers should be strictly more sample-efficient, but hand-designing a Bayesian optimal experimental design (BOED) over the prospect-theory likelihood is expensive. We ask whether an LLM can <em>learn</em> that policy via RL.</p>
<p>The methodology is <strong>transferable</strong>. Any latent-parameter elicitation with an auditable forward model &mdash; medical triage (which symptoms to ask about next), educational diagnostics (which question reveals a student's misconception), alignment preference learning (which comparison is most informative about a human's utility function) &mdash; fits the same MDP template. Lotteries are the proxy; the capability is <strong>adaptive experimental design</strong>.</p>
<p>Every reward signal here is <strong>ground-truth arithmetic</strong>, not a judge. The environment samples &theta;*, runs a closed-form prospect-theory respondent, and scores the agent against the stored parameters. There is no circularity and no LLM judge in the loop.</p>
</section>
<!-- 3. PRIOR WORK & NOVELTY -->
<section id="prior-work">
<h2>Prior work &amp; novelty</h2>
<p>Most &quot;LLMs + economics&quot; work lands in one of three buckets. None occupies the cell we target:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Prior work bucket</th><th>What it does</th><th>What it does not</th></tr></thead>
<tbody>
<tr><td><strong>Static economic QA</strong><br><span style="font-size:.85em;color:var(--muted)">Recon (Zhou et al., <a href="https://arxiv.org/abs/2506.00577" target="_blank" style="color:var(--accent2)">arXiv:2506.00577</a>, 2025)</span></td><td>SFT + GRPO on curated economic reasoning items, graded by rules on text</td><td>No sequential active choice, no continuous latent recovery</td></tr>
<tr><td><strong>LLMs as agents in games</strong><br><span style="font-size:.85em;color:var(--muted)">RLVR Negotiation (Liu et al., <a href="https://arxiv.org/abs/2604.09855" target="_blank" style="color:var(--accent2)">arXiv:2604.09855</a>, 2026); EconAgent (Li et al., ACL 2024)</span></td><td>RL in strategic or macro simulations, verifiable reward on surplus or budget</td><td>No parameter inference from a known simulator</td></tr>
<tr><td><strong>LLMs as subjects</strong><br><span style="font-size:.85em;color:var(--muted)">Horton's <em>Homo silicus</em> (<a href="https://arxiv.org/abs/2301.07543" target="_blank" style="color:var(--accent2)">arXiv:2301.07543</a>); &quot;PT Fails for LLMs&quot; (<a href="https://arxiv.org/abs/2508.08992" target="_blank" style="color:var(--accent2)">arXiv:2508.08992</a>)</span></td><td>Measures whether LLMs <em>are</em> PT-rational</td><td>Does not train them to <em>query</em> a PT-rational counterpart</td></tr>
<tr class="novel"><td><strong>LotteryElicitationEnv (ours)</strong></td><td>Sequential MDP, structured JSON lottery actions, terminal reward from ground-truth &theta;*, non-learned respondent</td><td>Not a human study (yet)</td></tr>
</tbody>
</table>
</div>
<blockquote>To our knowledge, no prior work trains an LLM to <strong>adaptively design lottery pairs</strong> against a non-learned prospect-theory respondent with terminal ground-truth rewards under the GRPO + OpenEnv contract. The task, action space, and reward semantics are new; the method (RLVR / GRPO on verifiable signals) is shared with the Recon and negotiation-RLVR lineages.</blockquote>
<p><strong>Important caveat</strong> from the &quot;PT Fails for LLMs&quot; literature: we do <em>not</em> claim the policy LLM is PT-rational. We train it to <strong>design experiments</strong> against a PT-rational simulator with known &theta;*. The LLM is the experimenter, not the subject.</p>
</section>
<!-- 4. WHAT IT IS -->
<section id="design">
<h2>What LotteryElicitationEnv is</h2>
<blockquote>An OpenEnv-native sequential MDP in which an LLM agent designs lottery pairs, a closed-form prospect-theory respondent answers, and the agent is rewarded on the final turn for how accurately it recovers the respondent's hidden (&gamma;, &lambda;).</blockquote>
<p>Each episode proceeds like this:</p>
<ul>
<li>The environment samples &theta;* = (&gamma;*, &lambda;*) from configured priors.</li>
<li>On turn <em>t</em>, the agent emits a structured JSON action containing two lotteries, and optionally a current estimate of &theta; and an early-termination flag.</li>
<li>The respondent computes expected utility under prospect theory, picks A or B deterministically (optional <strong>Fechner logistic noise</strong> via <code>noise_std</code>), and returns the choice plus the running history.</li>
<li>Mid-episode reward is always zero. On termination the environment computes a decomposed reward from the final &theta; estimate.</li>
<li>Termination happens when the agent sets <code>terminate_early=true</code>, runs out of turns, or submits on the final allowed turn.</li>
</ul>
<p>The agent's interface is deliberately minimal: <strong>raw JSON output</strong>, no tool-call protocol, no markdown parsing. The LLM emits text, the training client parses and steps the environment over WebSocket.</p>
</section>
<!-- 5. ENVIRONMENT DESIGN -->
<section id="env-design">
<h2>Environment design</h2>
<p>The core contract is three Pydantic types exchanged over the OpenEnv WebSocket:</p>
<pre><code><span class="c"># Action (agent → env)</span>
class LotteryElicitationAction(Action):
lottery_a: Lottery <span class="c"># 2–3 outcomes, probs sum to 1.0</span>
lottery_b: Lottery
theta_estimate: Optional[dict] <span class="c"># {"gamma": float, "lambda": float}</span>
terminate_early: bool = False
<span class="c"># Observation (env → agent)</span>
class LotteryElicitationObservation(Observation):
step_idx, steps_remaining, max_steps: int
history: list[dict] <span class="c"># [{lottery_a, lottery_b, choice}, ...]</span>
last_choice: Optional[str] <span class="c"># "A" | "B"</span>
gamma_range, lambda_range: tuple[float, float]
min_outcome_value, max_outcome_value: float
done: bool; reward: Optional[float]; metadata: dict
<span class="c"># State (hidden from agent)</span>
true_gamma, true_lambda, gamma_mse, lambda_mse, hl_accuracy</code></pre>
<p>A <strong>Lottery</strong> is 2 or 3 outcomes with probabilities that must sum to 1.0. Values lie in <code>[min_outcome_value, max_outcome_value]</code>, both surfaced on every observation so the agent cannot drift off-spec.</p>
<p>The <strong>respondent</strong> is pure arithmetic. No LLM, no heuristic:</p>
<div class="math-display" aria-label="Prospect-theory value function, expected utility, and choice rule">
\[
v(x) = x^{\gamma}\;\text{if } x \geq 0,
\qquad
v(x) = -\lambda\,(-x)^{\gamma}\;\text{if } x \lt 0
\]
\[
\mathrm{EU}(L) = \sum_{i} p_i\, v(x_i)
\]
\[
\text{choice} =
\mathrm{A}\;\text{if }\mathrm{EU}(A) \geq \mathrm{EU}(B),
\quad\text{else }\mathrm{B}
\]
</div>
<p>A <strong>two-stage curriculum</strong> shapes the training distribution:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Stage</th><th>&gamma; sampled</th><th>&lambda; sampled</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><strong>Stage 1</strong></td><td>Uniform[&gamma;<sub>lo</sub>, &gamma;<sub>hi</sub>]</td><td>Fixed at 2.25</td><td>Shorten credit assignment, learn risk curvature first</td></tr>
<tr><td><strong>Stage 2</strong></td><td>Uniform[&gamma;<sub>lo</sub>, &gamma;<sub>hi</sub>]</td><td>Uniform[&lambda;<sub>lo</sub>, &lambda;<sub>hi</sub>]</td><td>Full two-parameter elicitation</td></tr>
</tbody>
</table>
</div>
<p>Curriculum is honored both in <code>EnvConfig</code> and at <code>env.reset(curriculum_stage=...)</code>, so a single server can serve both stages to different sessions concurrently.</p>
</section>
<!-- 6. WHY OPENENV -->
<section id="openenv">
<h2>Why OpenEnv</h2>
<p>OpenEnv gives us three things that matter for this submission: (1) a standard WebSocket environment contract consumable by TRL's <code>rollout_func</code>, (2) per-session state with <code>SUPPORTS_CONCURRENT_SESSIONS=True</code> and <code>max_concurrent_envs=64</code>, so DDP ranks can hammer the same Space without cross-talk, and (3) a uniform deployment path. The same env code runs in-process for tests, as a Docker container for development, and as a Hugging Face Space during training and evaluation.</p>
<p><strong>No in-process environment imports from PT</strong> &mdash; everything crosses the wire, exactly like OpenEnv intends. No new abstractions were invented. Base types only: <code>EnvClient</code>, <code>Environment</code>, Pydantic <code>Action</code> / <code>Observation</code>. All extensions (curriculum stage, reward breakdown, history) ride on <code>metadata</code>. No new method signatures, no fork. The env ships with <code>openenv.yaml</code>, a <code>Dockerfile</code>, and a live Hugging Face Space.</p>
</section>
<!-- 7. SCORING -->
<section id="scoring">
<h2>Scoring: a three-part terminal reward, plus format shaping</h2>
<p>The environment reward is terminal only. No mid-episode credit. On the final step the env computes:</p>
<div class="math-display" aria-label="MSE and Holt-Laury accuracy terms">
\[
\mathrm{mse\_component}
= -\Biggl[
\frac{(\hat{\gamma}-\gamma^*)^2}{(\gamma_{\mathrm{hi}}-\gamma_{\mathrm{lo}})^2}
+
\frac{(\hat{\lambda}-\lambda^*)^2}{(\lambda_{\mathrm{hi}}-\lambda_{\mathrm{lo}})^2}
\Biggr]
\]
\[
\mathrm{hl\_accuracy}
= \frac{1}{10}\sum_{k=1}^{10}
\mathbf{1}\bigl[s_k(\hat{\theta}) = s_k(\theta^*)\bigr]
\]
</div>
<p class="math-note">Here \(s_k(\cdot)\) is the predicted Holt&ndash;Laury choice on menu pair \(k\) under the implied parameters.</p>
<div class="math-display" aria-label="Efficiency bonus and total terminal reward">
\[
\mathrm{efficiency\_bonus}
= \frac{\max\bigl(0,\; S_{\max} - S_{\mathrm{taken}}\bigr)}{S_{\max}}
\]
\[
R
= w_{\mathrm{mse}}\cdot \mathrm{mse\_component}
+ w_{\mathrm{HL}}\cdot \mathrm{hl\_accuracy}
+ w_{\mathrm{eff}}\cdot \mathrm{efficiency\_bonus}
\]
</div>
<p class="math-note">Mapping to code: \(S_{\max}\) is <code>max_steps</code>, \(S_{\mathrm{taken}}\) is <code>steps_taken</code>, and \((w_{\mathrm{mse}}, w_{\mathrm{HL}}, w_{\mathrm{eff}})\) are <code>mse_weight</code>, <code>holt_laury_weight</code>, <code>efficiency_weight</code> in <code>EnvConfig</code>.</p>
<p>Defaults live in <code>EnvConfig</code>:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Component</th><th>Weight</th><th>What it rewards</th></tr></thead>
<tbody>
<tr><td><strong>MSE</strong></td><td class="num">1.0</td><td>Closeness of &theta;&#x0302; to ground truth in normalized range</td></tr>
<tr><td><strong>Holt&ndash;Laury accuracy</strong></td><td class="num">0.5</td><td>Behavioral consistency: &theta;&#x0302; should predict the same HL choices as &theta;*</td></tr>
<tr><td><strong>Efficiency bonus</strong></td><td class="num">0.1</td><td>Stopping early when confident (not just guessing and quitting)</td></tr>
<tr><td><strong>Missing &theta; penalty</strong></td><td class="num">&minus;2.0</td><td>Final-turn action with no valid <code>theta_estimate</code></td></tr>
</tbody>
</table>
</div>
<p>The training package adds one optional auxiliary reward: a <strong>format score</strong> (fraction of turns that produced valid JSON, weighted at 0.1&ndash;0.75) into the GRPO advantage. It is intended as training wheels, removable once the model reliably emits structured output (&gt;90% validity).</p>
<blockquote><strong>Why three components:</strong> MSE alone rewards a lucky guess. Holt&ndash;Laury accuracy alone lets the agent memorize the HL menu without recovering &theta;. Efficiency alone rewards guess-and-quit. The product of incentives forces the agent to <em>actually identify</em> the parameters, not just match a proxy. As we show in the <a href="#traces">Episode Traces</a> section, the efficiency bonus interacts with curriculum design in a way that can <em>create</em> reward-hacking fixed points.</blockquote>
</section>
<!-- 8. ARCHITECTURE & TRAINING PIPELINE -->
<section id="architecture">
<h2>Architecture &amp; training pipeline</h2>
<p>The project is two strictly separated packages: <strong>LotteryElicitationEnv</strong> (the OpenEnv environment) and <strong>LotteryElicitationPT</strong> (the GRPO training client). They communicate exclusively over WebSocket &mdash; no in-process imports.</p>
<div class="mermaid-wrap">
<pre class="mermaid">
flowchart LR
subgraph PT ["LotteryElicitationPT (Training)"]
GRPO["GRPOTrainer<br/>TRL 1.0.0"]
RF["rollout_func"]
VLLM["vLLM<br/>colocate/server"]
PARSE["action_parser<br/>JSON + guardrails"]
end
subgraph ENV ["LotteryElicitationEnv (OpenEnv)"]
WS["FastAPI<br/>WebSocket"]
RESP["PT Respondent<br/>v(x) = x^γ ..."]
REW["Reward<br/>MSE + HL + eff."]
end
GRPO --> RF
RF --> VLLM
VLLM -->|"generate"| PARSE
PARSE -->|"JSON action"| WS
WS --> RESP
RESP -->|"choice A/B"| WS
WS -->|"observation"| RF
REW -->|"terminal reward"| WS
</pre>
<p class="mermaid-caption">Figure 1. System architecture. PT never imports env-side types &mdash; everything crosses the WebSocket.</p>
</div>
<p>Training uses <strong>GRPO</strong> (Group Relative Policy Optimization), a critic-free RL algorithm ideal for terminal-only rewards. We use TRL 1.0.0's <code>rollout_func</code> contract for explicit control over the generate &rarr; parse &rarr; step loop, avoiding TRL's Qwen3-only <code>add_response_schema</code> allowlist.</p>
<p>The rollout function manages: chat-template tokenization with <code>enable_thinking=False</code>, vLLM generation (colocate or server mode), think-block stripping, null-safe JSON parsing with 18 regression tests, probability normalization, and episode logging to <code>reward_logs.jsonl</code>.</p>
</section>
<!-- 9. EPISODE TRACES & REWARD HACKING -->
<section id="traces">
<h2>Episode traces &amp; reward hacking</h2>
<p>The central research-grade finding of this submission is not a converged checkpoint &mdash; it is a <strong>structural diagnosis of how GRPO collapses on multi-turn verifiable-reward environments</strong>. We show the failure, explain the mechanism, and prescribe the fix stack.</p>
<h3>The reward-hacking episode: guess-and-quit</h3>
<p>Under Stage 1 training (&lambda; fixed at 2.25), the policy collapsed to the following single-turn episode:</p>
<div class="episode-trace">
<div class="trace-step">
<div class="step-marker terminal"></div>
<div class="step-label">Turn 1 (and only turn)</div>
<div class="step-content">
<strong>Agent action:</strong><br>
<code>{"lottery_a": {"outcomes": [{"value": 50, "probability": 0.5}, {"value": 10, "probability": 0.5}]}, "lottery_b": {"outcomes": [{"value": 30, "probability": 1.0}]}, "theta_estimate": {"gamma": 1.0, "lambda": 2.25}, "terminate_early": true}</code>
<br><br>
<strong>What happens:</strong> The agent ignores the respondent entirely. It emits a fixed guess (&gamma;=1.0, &lambda;=2.25) and terminates immediately. The lottery pair is irrelevant &mdash; the respondent's choice is never used.
</div>
</div>
<div class="trace-verdict bad">
Reward: efficiency bonus (0.9 &times; 0.1 = 0.09) + partial MSE credit (because &lambda;=2.25 is literally correct under Stage 1) + some HL accuracy. Every rollout in the GRPO group gets the <strong>same reward</strong> &rarr; zero within-group variance &rarr; zero advantage &rarr; zero gradient &rarr; no learning.
</div>
</div>
<h3>The telemetry that reveals collapse</h3>
<p>From 322 logged episodes on Qwen3-1.7B and Qwen2.5-7B-Instruct runs:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Signal</th><th>Value</th><th>What it means</th></tr></thead>
<tbody>
<tr><td><code>frac_reward_zero_std</code></td><td class="num">&asymp; 1.0</td><td>Every completion in the GRPO group gets identical reward</td></tr>
<tr><td><code>loss</code>, <code>grad_norm</code></td><td class="num">&asymp; 0</td><td>No gradient signal &mdash; policy is frozen at a fixed point</td></tr>
<tr><td><code>entropy</code></td><td class="num">&asymp; 1e-5</td><td>Policy has collapsed to a single deterministic output</td></tr>
<tr><td><code>clipped_ratio</code></td><td class="num">&asymp; 1.0</td><td>No policy update being applied</td></tr>
<tr><td>Episodes hitting <code>max_steps</code></td><td class="num">98.4%</td><td>Before collapse, most episodes used all 10 turns</td></tr>
<tr><td>Tokens/step (early &rarr; late)</td><td class="num">~450 &rarr; ~55</td><td>Cold-start verbosity converges; OOM is a cold-start problem</td></tr>
</tbody>
</table>
</div>
<h3>Why this happens: the mechanism</h3>
<p>Stage 1 fixes &lambda;=2.25 in the data-generating process to simplify credit assignment. But this creates a <strong>partially-correct shortcut</strong>: guessing &lambda;=2.25 is always exactly right for that parameter. Combined with the efficiency bonus rewarding early termination, the model discovers that <em>guess-and-quit on turn 1</em> yields a stable reward. Since every rollout in the GRPO group finds the same shortcut, within-group reward variance drops to zero, GRPO's group-relative advantage becomes zero, and the gradient vanishes. The policy is stuck at a fixed point that is <strong>partially correct by construction</strong>.</p>
<blockquote>This failure mode is general, not specific to our environment. <strong>Any GRPO run on a multi-turn verifiable-reward env with a partially-right-but-cheap shortcut has this bug latent.</strong></blockquote>
<h3>The prescribed fix stack</h3>
<ol>
<li><strong>Phase A &mdash; Bug fixes (done):</strong> Null-safe parsing (<code>_safe_float</code>, <code>_safe_int</code>), think-block stripping, hard-cap on <code>completion_ids</code>, probability normalization. 18 regression tests.</li>
<li><strong>Phase B &mdash; Training mechanics:</strong> Raise <code>max_completion_length</code>, shorter episodes (5 turns for Stage 1), stronger <code>format_weight</code> (0.1 &rarr; 0.75), tune learning rate.</li>
<li><strong>Phase C &mdash; Kill reward hacking:</strong> Move to Stage 2 (both &gamma; and &lambda; sampled), lower <code>efficiency_weight</code>, raise <code>mse_weight</code>, force minimum-turn exploration before <code>terminate_early</code> can fire, randomize Stage 1 &lambda; to a narrow band around 2.25.</li>
<li><strong>Phase D &mdash; Advanced shaping:</strong> Information-gain reward term using <code>history</code> to directly incentivize informative lottery pairs.</li>
</ol>
<h3>What a good episode should look like</h3>
<p>For contrast, here is what the agent <em>should</em> learn to do &mdash; an adaptive 5-turn elicitation for a respondent with &gamma;*=0.6, &lambda;*=3.0:</p>
<div class="episode-trace">
<div class="trace-step">
<div class="step-marker good"></div>
<div class="step-label">Turn 1 &mdash; Probe risk aversion (&gamma;)</div>
<div class="step-content">
Lottery A: (100, 0.5; 0, 0.5) vs Lottery B: (40, 1.0)<br>
<strong>Choice: B</strong> &mdash; respondent is risk-averse (&gamma; &lt; 1), prefers the safe option.
</div>
</div>
<div class="trace-step">
<div class="step-marker good"></div>
<div class="step-label">Turn 2 &mdash; Narrow &gamma; range</div>
<div class="step-content">
Lottery A: (60, 0.7; 10, 0.3) vs Lottery B: (35, 1.0)<br>
<strong>Choice: A</strong> &mdash; the expected-value gap is large enough that moderate risk aversion still picks A.
</div>
</div>
<div class="trace-step">
<div class="step-marker good"></div>
<div class="step-label">Turn 3 &mdash; Introduce losses to probe &lambda;</div>
<div class="step-content">
Lottery A: (80, 0.5; &minus;20, 0.5) vs Lottery B: (20, 1.0)<br>
<strong>Choice: B</strong> &mdash; loss aversion makes the mixed gamble unattractive. Constrains &lambda; &gt; 2.
</div>
</div>
<div class="trace-step">
<div class="step-marker good"></div>
<div class="step-label">Turn 4 &mdash; Refine &lambda; estimate</div>
<div class="step-content">
Lottery A: (90, 0.6; &minus;10, 0.4) vs Lottery B: (30, 1.0)<br>
<strong>Choice: B</strong> &mdash; still avoids losses. &lambda; likely &gt; 2.5.
</div>
</div>
<div class="trace-step">
<div class="step-marker good"></div>
<div class="step-label">Turn 5 &mdash; Submit estimate + terminate early</div>
<div class="step-content">
<code>{"theta_estimate": {"gamma": 0.65, "lambda": 2.9}, "terminate_early": true}</code><br>
Efficiency bonus: 5 unused turns &rarr; 0.5 &times; 0.1 = 0.05. MSE is low. HL accuracy is high.
</div>
</div>
<div class="trace-verdict good">
The agent used losses to probe &lambda; (turns 3&ndash;4), safe-vs-risky comparisons to probe &gamma; (turns 1&ndash;2), and terminated early with a close estimate. This is what adaptive experimental design looks like.
</div>
</div>
</section>
<!-- 10. RESULTS -->
<section id="results">
<h2>Results: what we found</h2>
<h3>Baselines</h3>
<p>The environment bundles two deterministic baselines. Both run in-process without a GPU:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Baseline</th><th>Policy</th><th>What it isolates</th></tr></thead>
<tbody>
<tr><td><strong>Random lottery</strong></td><td>Sample valid lottery pairs uniformly; return prior midpoint as &theta;&#x0302;</td><td>Lower bound: beating it proves the model learned something</td></tr>
<tr><td><strong>Holt&ndash;Laury fixed battery</strong></td><td>Replay the canonical 10-pair menu; grid-search fit (&gamma;, &lambda;) at 0.01 resolution</td><td>Status-quo comparison from experimental economics</td></tr>
</tbody>
</table>
</div>
<p>The eval harness adds: zero-shot API LLMs, local vLLM-served LLMs, and a trained HF policy loaded from disk.</p>
<h3>Baseline performance targets</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Metric</th><th>Holt&ndash;Laury fixed</th><th>Random lottery</th><th>Target for trained policy</th></tr></thead>
<tbody>
<tr><td>&gamma; MSE (normalized)</td><td class="num">&asymp; 0.02</td><td class="num">high</td><td class="num">lower than HL, at fewer steps</td></tr>
<tr><td>&lambda; MSE (normalized)</td><td class="num">&asymp; 0.3</td><td class="num">high</td><td class="num">lower than HL</td></tr>
<tr><td>HL accuracy</td><td class="num">&asymp; 0.9</td><td class="num">&asymp; 0.5</td><td class="num">&ge; HL</td></tr>
<tr><td>Steps used</td><td class="num">10 / 10</td><td class="num">10 / 10</td><td class="num">&lt; 10 via <code>terminate_early</code></td></tr>
</tbody>
</table>
</div>
<h3>Current training status</h3>
<p>The pipeline is validated end-to-end. Convergence to a baseline-beating checkpoint is blocked by two factors: the <strong>reward-hacking fixed point</strong> diagnosed above (now understood, fix stack prescribed), and <strong>Unsloth's multi-GPU incompatibility</strong> with our FSDP + vLLM topology (see <a href="#unsloth">Unsloth section</a>), which prevented scaling beyond single-GPU training within the submission window.</p>
<p>What we have demonstrated:</p>
<ul>
<li><strong>Full multi-turn rollouts</strong> against the live OpenEnv WebSocket, with structured JSON actions parsed through null-safe guardrails and episode logs persisting to <code>reward_logs.jsonl</code>.</li>
<li><strong>Stable 1&times;H100 colocate runs on Qwen3-1.7B</strong> &mdash; hundreds of optimizer steps with non-zero reward variance before vLLM memory-sync spikes required lowering <code>LEPT_VLLM_GPU_UTIL</code> from 0.90 to 0.75&ndash;0.80.</li>
<li><strong>Qwen2.5-7B-Instruct colocate run</strong> reaching step 82/1400 (~1 hour wall time) &mdash; the first run to generate parseable JSON lotteries intermittently, before crashing on a <code>float(None)</code> edge case (fixed in Session 12).</li>
<li><strong>322-episode telemetry</strong> from run-1: 98.4% of episodes hit max_steps=10; token length converged from ~4,000&ndash;5,000 to ~55/step by episode 200+. The cold-start OOM problem is solved.</li>
<li><strong>Infrastructure for FSDP v1/v2</strong>, vLLM server/colocate, NCCL padding, and three deployment targets (Lambda, AWS EC2, CARC Discovery).</li>
</ul>
<p>The research story &mdash; can a GRPO-trained LLM beat Holt&ndash;Laury's 24-year-old fixed battery? &mdash; is the experiment this submission sets up. The reward-hacking diagnosis is the finding we contribute now.</p>
</section>
<!-- 11. ENGINEERING LESSONS -->
<section id="engineering">
<h2>Engineering lessons</h2>
<p>Building a real GRPO + OpenEnv + vLLM training pipeline on a multi-turn, verifiable-reward environment surfaced five categories of structural issues. We document them because the next OpenEnv submission will hit every one.</p>
<h3 id="nccl">NCCL desync under variable-length episodes</h3>
<p>In <code>vllm_mode=server</code>, every <code>trainer.vllm_generation.generate()</code> call performs <code>gather_object &rarr; all_gather_object &rarr; broadcast_object_list</code>. Our rollout is <code>while not session.done</code>, so different DDP ranks make different numbers of <code>generate()</code> calls per episode. NCCL collectives are sequence-numbered: different call counts per rank = permanent desync.</p>
<p><strong>Symptoms:</strong> tqdm stuck at <code>0/14</code>, GPUs 0&ndash;6 pinned at ~32 GiB, GPU 7 idle (vLLM). After ~30 minutes, the NCCL watchdog fires with <code>last enqueued: 529 vs last completed: 527</code> on rank 1. Then <code>UnpicklingError</code> as ranks deserialize off-by-one collective buffers.</p>
<p><strong>Fix:</strong> fixed-count padding &mdash; every rank performs exactly 8 <code>generate()</code> calls per episode:</p>
<pre><code>DIST_SERVER_GENERATES_PER_EPISODE = 8
per_episode_generate_cap = min(max_episode_turns, 8)
<span class="c"># After the real loop terminates, issue (8 - num_real_generates)</span>
<span class="c"># dummy generates under _temporary_vllm_max_tokens(..., 1).</span>
<span class="c"># Outputs are DISCARDED. Guarded with try/finally.</span></code></pre>
<p>Active only when <code>vllm_mode == "server"</code> and <code>world_size &gt; 1</code>. Reward, logprobs, and credit assignment are byte-identical to the unpadded case. <strong>Any TRL <code>rollout_func</code> user running variable-length rollouts in server mode has this bug latent.</strong></p>
<h3 id="unsloth">Unsloth &amp; multi-GPU scaling</h3>
<p>We invested significant time integrating <strong>Unsloth</strong> for efficient fine-tuning. Unsloth's multi-GPU support did not work with our distributed FSDP + vLLM server topology &mdash; specifically, the weight-sharding and vLLM weight-sync paths are incompatible with Unsloth's kernel replacements. This prevented us from scaling beyond single-GPU colocate training within the submission window, which in turn limited the training budget available to push past the reward-hacking fixed point.</p>
<p>We document this so the next OpenEnv submitter can avoid the same dead end: if your pipeline requires FSDP + vLLM server mode, Unsloth is not currently a compatible acceleration path.</p>
<h3 id="hygiene">Memory, parsing &amp; model hygiene</h3>
<p>Four issues that each crashed training runs, consolidated with their fixes:</p>
<div class="table-wrap">
<table>
<thead><tr><th>Issue</th><th>Root cause</th><th>Fix</th></tr></thead>
<tbody>
<tr>
<td><strong>CUDA OOM</strong> despite low <code>max_completion_length</code></td>
<td><code>_rollout_one_episode</code> concatenated generated tokens <em>and</em> observation suffixes across 10 turns &rarr; 4,000&ndash;5,000 token sequences per episode</td>
<td>Hard-cap <code>completion_ids</code> to <code>max_completion_length</code>; strip think blocks from training tensor; <code>PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True</code></td>
</tr>
<tr>
<td><strong>Qwen3 <code>&lt;think&gt;</code> blocks</strong> despite <code>reasoning_mode=off</code></td>
<td><code>enable_thinking=False</code> is prompt-side only; Qwen3 hybrid models still emit verbose think traces that truncate before JSON</td>
<td><code>_strip_think_blocks</code> regex (closed + unclosed); re-encode only stripped JSON for <code>completion_ids</code></td>
</tr>
<tr>
<td><strong><code>float(None)</code></strong> crash killing all DDP ranks</td>
<td>LLM emitted <code>{"value": null, "probability": 0.5}</code>; parser checked key presence but not None-ness. One rank died &rarr; gloo cascade killed all others</td>
<td><code>_safe_float</code> / <code>_safe_int</code> guardrails; 18 regression tests; fallback action instead of crash</td>
</tr>
<tr>
<td><strong>FSDP1 <code>_is_root</code> assertion</strong> in server mode</td>
<td>TRL 1.0.0's <code>_sync_fsdp1_params_to_vllm</code> calls <code>summon_full_params</code> per child module, corrupting the FSDP root flag (TRL PR #3582, unmerged)</td>
<td>Default to <code>vllm_mode=colocate</code>; opt-in FSDP2 behind <code>LEPT_FSDP2_SHARDING=1</code> flag</td>
</tr>
</tbody>
</table>
</div>
<h3>Key takeaways</h3>
<ul>
<li><strong>Terminal-only rewards + GRPO is a sweet spot</strong> &mdash; but only after you kill the reward-hacking fixed points. Curriculum and reward weighting must be staged carefully.</li>
<li><strong><code>max_completion_length</code> &ne; what TRL trains on.</strong> The rollout function keeps appending per-turn generations + observation suffixes until you explicitly hard-cap.</li>
<li><strong>Qwen2.5-Instruct &gt; Qwen3-thinking-off</strong> on structured JSON output for this task, despite Qwen3's newer release date.</li>
<li><strong>Verifiable reward &gt;&gt;&gt; LLM judge.</strong> A closed-form simulator means training reduces to "can the model hit the right arithmetic target" &mdash; no second-order "what does the judge like" pathology.</li>
</ul>
</section>
<!-- 12. PRIOR WORK POSITIONING (visual) -->
<section id="positioning">
<h2>Where this submission sits</h2>
<div class="mermaid-wrap">
<pre class="mermaid">
quadrantChart
title LLMs + Economics: Task Structure vs Agent Role
x-axis "Static / Fixed Tasks" --> "Sequential / Adaptive Tasks"
y-axis "LLM as Subject" --> "LLM as Experimenter"
quadrant-1 "Our target"
quadrant-2 "Unexplored"
quadrant-3 "Most prior work"
quadrant-4 "Emerging"
"Recon (GRPO on econ QA)": [0.2, 0.35]
"Homo silicus (Horton)": [0.15, 0.2]
"PT Fails for LLMs": [0.25, 0.15]
"RLVR Negotiation": [0.7, 0.45]
"EconAgent (macro sim)": [0.6, 0.35]
"LotteryElicitationEnv": [0.85, 0.85]
</pre>
<p class="mermaid-caption">Figure 2. Positioning of LotteryElicitationEnv relative to prior work. We occupy the high-sequential, high-experimenter quadrant that no prior work targets.</p>
</div>
</section>
<!-- 13. FOUNDATIONS & CITATIONS -->
<section id="foundations">
<h2>Foundations &amp; citations</h2>
<div class="table-wrap">
<table>
<thead><tr><th>Foundation</th><th>Role in this project</th><th>Citation</th></tr></thead>
<tbody>
<tr><td><strong>Cumulative prospect theory</strong></td><td>Closed-form respondent: piecewise value function, expected utility, binary choice</td><td>Tversky &amp; Kahneman, <em>J. Risk &amp; Uncertainty</em> 5(4), 1992</td></tr>
<tr><td><strong>Holt&ndash;Laury risk elicitation</strong></td><td>Fixed 10-pair battery, HL accuracy reward term, grid-search baseline</td><td>Holt &amp; Laury, <em>American Economic Review</em> 92(5), 2002</td></tr>
<tr><td><strong>Bayesian OED</strong></td><td>Motivation for adaptive &gt; fixed; hand-derived BOED called "expensive"</td><td>Chaloner &amp; Verdinelli, <em>Statistical Science</em> 10(3), 1995</td></tr>
<tr><td><strong>OpenEnv</strong></td><td>Gym-style reset/step, WebSocket transport, HF Space deployment</td><td><a href="https://huggingface.co/blog/openenv" target="_blank" style="color:var(--accent2)">HF Blog: Introducing OpenEnv</a></td></tr>
<tr><td><strong>TRL + GRPO</strong></td><td>GRPOTrainer, custom rollout_func, remote env rollouts</td><td>Shao et al., <a href="https://arxiv.org/abs/2402.03300" target="_blank" style="color:var(--accent2)">arXiv:2402.03300</a> (DeepSeekMath)</td></tr>
<tr><td><strong>ReasoningEconomicsEnv/PT</strong></td><td>Sibling project &mdash; structural template for two-repo split, rollout_func, DDP padding</td><td>Same monorepo</td></tr>
</tbody>
</table>
</div>
</section>
<!-- 14. QUICK START (unchanged position) -->
<section id="quickstart">
<h2>Quick start</h2>
<pre><code><span class="c"># 1. Run the env locally (Python in-process)</span>
pip install -e LotteryElicitationEnv
python -m lottery_elicitation_env.server.app
<span class="c"># 2. Or pull the HF Space</span>
export ENV_BASE_URL=&quot;https://yashu2000-lotteryelicitationenv.hf.space&quot;
<span class="c"># 3. Train with GRPO (1xH100 colocate)</span>
cd LotteryElicitationPT
bash scripts/bootstrap_lambda.sh
bash scripts/preflight_lambda.sh
bash scripts/run_grpo_lambda.sh
<span class="c"># 4. Evaluate a checkpoint against baselines</span>
python -m lottery_elicitation_pt.eval.evaluate \
--policy hf --model ./outputs/ckpt-last \
--episodes 200 --baselines random,holt_laury</code></pre>
<p>All episodes are seeded and reproducible from <code>(env_seed, curriculum_stage, &theta;_prior)</code>. No external fixtures, no live API, no human labels.</p>
</section>
<div class="callout">
<div class="q">Can an LLM learn to ask better questions than the economist's 24-year-old playbook?</div>
<div class="sub">Adaptive elicitation under a verifiable reward is the test. The pipeline is built. The reward-hacking diagnosis is the first finding.</div>
</div>
<!-- 15. FUTURE WORK -->
<section id="future">
<h2>Future work</h2>
<ul>
<li><strong>Run Stage 2 to convergence</strong> (&gamma;+&lambda; sampled, <code>max_steps=10</code>) and publish the comparison table: Random vs Holt&ndash;Laury-Fixed vs Trained-HF across &gamma;-MSE, &lambda;-MSE, HL accuracy, total reward, average steps.</li>
<li><strong>Information-gain reward term</strong> using <code>history</code> &mdash; directly incentivize informative lottery pairs to kill guess-and-quit at the source.</li>
<li><strong>Bayesian OED baseline</strong> for a strong intellectual benchmark that upper-bounds what adaptive elicitation can achieve.</li>
<li><strong>Logistic-noise respondent</strong> (<code>noise_std &gt; 0</code>) for a realism ablation &mdash; how much does the sim-to-real gap depend on clean deterministic choices?</li>
<li><strong>SFT warm-up</strong> on valid-JSON lottery examples before GRPO, to skip the cold-start OOM entirely.</li>
<li><strong>Port the NCCL padding pattern upstream into TRL</strong> &mdash; the bug is general, the fix is simple.</li>
<li><strong>Human-subjects pilot</strong> to measure sim-to-real transfer: real people &ne; prospect theory, but the adaptive-elicitation capability might still beat Holt&ndash;Laury on genuine risk preferences.</li>
</ul>
</section>
<!-- 16. CONCLUSION -->
<section>
<h2>Conclusion</h2>
<p><strong>LotteryElicitationEnv</strong> reframes an economics problem as a verifiable RL task. A non-learned prospect-theory respondent, structured JSON lottery actions, and a terminal reward grounded in MSE against ground-truth &theta;* give us a sequential MDP where every component is auditable.</p>
<p>The infrastructure contributions &mdash; NCCL desync padding for variable-length rollouts, reward-hacking diagnosis under GRPO with partially-correct shortcuts, think-block hygiene for training tensors, null-safe JSON parsing that prevents DDP cascade failures &mdash; are the lessons the next OpenEnv + TRL 1.0 + multi-turn submission will need.</p>
<p>The research question remains open: <em>can a GRPO-trained LLM beat Holt&ndash;Laury's fixed battery?</em> The pipeline to answer it is built, validated, and documented. The reward-hacking diagnosis is the first finding we contribute.</p>
</section>
<div class="footer">
<p>LotteryElicitationEnv &middot; AgentX OpenEnv Track &middot; UC Berkeley RDI</p>
<p style="margin-top:.5rem;">
<a href="https://github.com/sharma-yash01/LotteryElicitationEnv" target="_blank">GitHub</a> &middot;
<a href="https://huggingface.co/spaces/yashu2000/LotteryElicitationEnv" target="_blank">HF Space</a> &middot;
<a href="https://github.com/meta-pytorch/OpenEnv" target="_blank">OpenEnv Framework</a> &middot;
<a href="https://huggingface.co/docs/trl/en/openenv" target="_blank">TRL x OpenEnv</a>
</p>
</div>
</div>
</body>
</html>