RohitChandramouli6618 commited on
Commit
6e28a1b
Β·
1 Parent(s): 50e16d4

Update all dashboard tabs and README with actual test run numbers

Browse files
Files changed (2) hide show
  1. README.md +41 -62
  2. server/app.py +20 -13
README.md CHANGED
@@ -72,15 +72,17 @@ export MODEL_NAME="meta-llama/Llama-3.1-8B-Instruct"
72
  export HF_TOKEN="hf_your_token_here"
73
  export ENV_BASE_URL="https://therubberduckdebuggers-cascade-containment.hf.space"
74
 
 
75
  python inference.py
 
 
 
76
  ```
77
 
78
  ---
79
 
80
  ## Action Space
81
 
82
- One decision per step, deliberately minimal to maximise strategic depth:
83
-
84
  | Field | Type | Values |
85
  | --- | --- | --- |
86
  | `action_type` | `string` | `"test"` Β· `"restrict"` Β· `"allocate"` |
@@ -92,7 +94,7 @@ One decision per step, deliberately minimal to maximise strategic depth:
92
  | **restrict** | Free | Imposes movement restrictions; reduces spread rate; penalised if infection < 0.20 |
93
  | **allocate** | 1 resource | Deploys medical resources; reduces existing infection by 5% and slows future spread |
94
 
95
- Movement restrictions lift automatically once a district's infection drops below the safe threshold β€” reflecting real policy: restrictions are lifted when the outbreak is controlled.
96
 
97
  ---
98
 
@@ -111,8 +113,6 @@ CityObservation:
111
  message: str | None
112
  ```
113
 
114
- Each `DistrictObservation` exposes:
115
-
116
  | Field | Description | Observability |
117
  | --- | --- | --- |
118
  | `reported_infection_rate` | Active infection fraction | Real-time (easy/medium); **3-day lagged** (hard) |
@@ -122,14 +122,10 @@ Each `DistrictObservation` exposes:
122
  | `restriction_active` | Whether movement restrictions are in place | Always real-time |
123
  | `tested_recently` | Tested within last 2 days | Always real-time |
124
 
125
- **The agent never sees:** `true_infection_rate`, `true_spread_rate`, or any ground truth used by the grader.
126
-
127
  ---
128
 
129
  ## Epidemiological Model
130
 
131
- The simulation uses a realistic discrete-time SIR-inspired model:
132
-
133
  ```text
134
  new_infection = current + (spread_rate βˆ’ natural_recovery βˆ’ intervention) + geographic_spillover
135
  ```
@@ -149,104 +145,85 @@ new_infection = current + (spread_rate βˆ’ natural_recovery βˆ’ intervention) +
149
 
150
  | Task | Districts | Steps | Resources | Data Lag | Challenge |
151
  | --- | --- | --- | --- | --- | --- |
152
- | **easy** | 2 | 10 | 10 | None | Single outbreak, clear signal, abundant resources |
153
  | **medium** | 4 | 15 | 8 | None | Two simultaneous outbreaks; forced triage between competing threats |
154
  | **hard** | 6 | 15 | 7 | **3 days** | Six growing outbreaks; invisible acceleration; scarce resources |
155
 
156
- ### Task Design Philosophy
157
-
158
- **Easy** establishes the core mechanic: D0 starts at 0.50 infection, D1 is clean. The agent must sustain focused allocation on D0 before D1 grows through spillover. A speed bonus fires if the agent acts decisively early.
159
 
160
- **Medium** introduces genuine triage. With 8 resources across 4 districts over 15 steps, the agent simply cannot contain all districts. D0 and D2 start in the danger zone; D1 and D3 grow into crisis within 4–6 steps through spillover. The agent must choose which outbreaks to prioritise.
161
 
162
- **Hard** adds the most realistic and challenging mechanic: **3-day information lag**. The agent sees infection rates from 3 days ago while true infection has already grown. The `growth_rate_hint` provides a noisy signal to reason about the current true state. A smart agent estimates current infection as `reported + 3 Γ— growth_hint` and allocates accordingly.
163
 
164
  ---
165
 
166
  ## Reward Function
167
 
168
- Five independent dense reward terms fire every step, providing rich learning signal throughout each episode:
169
-
170
  | Term | Value | Fires When |
171
  | --- | --- | --- |
172
- | Infection penalty | `βˆ’0.50` per district | District infection > 0.40 |
173
- | Hospital breach | `βˆ’1.00` per district | Hospital capacity ≀ 10% |
174
  | Early containment | `+0.50 Γ— (1 βˆ’ step/max_steps)` | District infection < 0.20 |
175
  | Correct prioritisation | `+0.30` | Allocate to highest-infected district |
176
  | Unnecessary restriction | `βˆ’0.20` | Restrict district below 0.20 |
177
 
178
- Key design choices:
179
-
180
- - The **early containment bonus decays over time** β€” containing an outbreak on day 3 is worth more than on day 8. This teaches proactive behaviour rather than reactive scrambling.
181
- - The **infection penalty scales with population density** β€” dense districts contribute more to the penalty, reflecting realistic triage priorities.
182
- - The **hospital breach penalty** fires at 10% capacity, not 0% β€” reflecting real operational collapse thresholds where triage and diversion begin.
183
- - **Restrictions lift automatically** when infection drops below the safe threshold, giving the agent a natural feedback loop on intervention effectiveness.
184
 
185
  ---
186
 
187
  ## Grader
188
 
189
- The grader is **fully deterministic** β€” no randomness, no LLM calls. Identical trajectories always produce identical scores in `[0.0, 1.0]`.
190
-
191
- ### Score Components
192
 
193
  | Component | Weight | Measures |
194
  | --- | --- | --- |
195
  | **Hospital score** | 45% | Average capacity preserved; Γ—0.6 multiplier if any district collapsed |
196
- | **Containment score** | 30% | Fraction of district-days below infection threshold (grace period: first 2 steps excluded) |
197
- | **Efficiency score** | 15% | Fraction of resource actions targeting the highest-infected district (uses pre-action state) |
198
- | **Speed score** | 10% | `1 βˆ’ (steps / max_steps)` if episode ends before max steps; else 0 |
199
-
200
- Weight rationale: In real epidemic response, preserving healthcare system function (45%) is the primary operational constraint β€” a functional hospital system is the prerequisite for everything else. WHO and CDC outbreak protocols define success primarily by healthcare capacity preservation, with infection containment as the secondary signal. Efficiency (15%) rewards triage intelligence. Speed (10%) rewards proactive early intervention.
201
 
202
- The efficiency score uses the **previous step's infection rates** to evaluate targeting decisions, ensuring that a successful treatment that drives infection below threshold is not retroactively penalised for being "unnecessary."
203
 
204
  ---
205
 
206
  ## Baseline Agent β€” GRPO-Style Episodic Memory
207
 
208
- The baseline implements **simulated GRPO with episodic memory** β€” no weight updates, no gradient computation. The prompt is the policy; memory updates are the policy improvement.
209
 
210
  ### Learning Loop
211
 
212
  ```text
213
  Rollout 1: Base prompt, no prior knowledge
214
  compute advantage = R1 - mean([])
215
- store positive-reward steps into EpisodicMemory
216
 
217
  Rollout 2: Memory-augmented prompt
218
- retrieve top-5 similar past decisions by L1 distance
219
  inject as concrete examples into prompt
220
  compute advantage = R2 - mean([R1])
221
- reinforce if above average
222
 
223
- ... repeat for N rollouts
224
  Report best grader score across all rollouts
225
  ```
226
 
227
- ### Memory Retrieval
228
 
229
- Past decisions are stored as `(infection_profile, resources, phase, action, reward)` tuples. At each step, the top-5 most similar past situations are retrieved by L1 distance on infection profiles, weighted by episode phase (early/mid/late). This provides the agent with concrete examples of what worked in similar situations without any gradient update.
230
 
231
- ### Baseline Scores
232
-
233
- | Task | Agent | Containment | Hospital | Efficiency | Final Score |
234
- | --- | --- | --- | --- | --- | --- |
235
- | Easy | Dumb greedy (always D0) | 0.50 | 0.92 | 0.45 | ~0.50 |
236
- | Easy | LLM + GRPO memory | 1.00 | 1.00 | 1.00 | **0.88–0.93** |
237
- | Medium | Dumb greedy | 0.18 | 0.21 | 0.40 | ~0.23 |
238
- | Medium | LLM + GRPO memory | 0.44–0.73 | 0.97–1.00 | 0.87–1.00 | **0.70–0.85** |
239
- | Hard | Dumb greedy | 0.12 | 0.18 | 0.25 | ~0.21 |
240
- | Hard | LLM + GRPO memory | 0.28–0.51 | 0.86–0.97 | 0.47–0.73 | **0.58–0.65** |
241
 
242
- The gap between dumb greedy and LLM+GRPO β€” particularly on medium (0.23 β†’ 0.78) β€” demonstrates that the environment meaningfully discriminates between agent quality. This is the core benchmark property.
243
 
244
  ---
245
 
246
  ## Generalisation
247
 
248
- This environment is not epidemic-specific. The underlying mechanics apply directly to:
249
-
250
  | Domain | Spreading cascade | Delayed data | Resource scarcity |
251
  | --- | --- | --- | --- |
252
  | 🦠 **Epidemic containment** | Infection spreads between districts | Lagged case counts | Medical resources |
@@ -255,8 +232,6 @@ This environment is not epidemic-specific. The underlying mechanics apply direct
255
  | πŸ“’ **Misinformation containment** | Narrative spread through networks | Viral detection lag | Correction budget |
256
  | 🀝 **Poverty intervention** | Deprivation cascades through communities | Census data lag | Aid allocation |
257
 
258
- The same trained policy generalises across domains with minimal prompt adaptation β€” this is the intended use case for the OpenEnv ecosystem.
259
-
260
  ---
261
 
262
  ## Project Structure
@@ -269,26 +244,29 @@ cascade-containment/
269
  β”œβ”€β”€ openenv.yaml # Environment manifest for OpenEnv registry
270
  β”‚
271
  β”œβ”€β”€ server/
272
- β”‚ β”œβ”€β”€ app.py # FastAPI server + judge dashboard + /grade /info /demo endpoints
273
  β”‚ β”œβ”€β”€ environment.py # Core RL loop (reset/step/state OpenEnv interface)
274
  β”‚ β”œβ”€β”€ grader.py # Deterministic trajectory scorer β€” no LLM calls
275
  β”‚ β”œβ”€β”€ constants.py # Single source of truth for all numeric configuration
276
  β”‚ β”œβ”€β”€ utils.py # Spread computation, observation builder, helper functions
277
  β”‚ β”œβ”€β”€ Dockerfile # Container definition
278
  β”‚ └── tasks/
279
- β”‚ β”œβ”€β”€ task_easy.py # 2 districts, 10 steps, real-time data
280
  β”‚ β”œβ”€β”€ task_medium.py # 4 districts, 15 steps, forced triage
281
  β”‚ └── task_hard.py # 6 districts, 15 steps, 3-day data lag
282
  β”‚
283
  β”œβ”€β”€ baseline/
284
  β”‚ β”œβ”€β”€ policy.py # LLM policy with chain-of-thought prompting
285
- β”‚ β”œβ”€β”€ evaluator.py # GRPO episodic memory loop
286
  β”‚ └── run.py # CLI entry point
287
  β”‚
 
 
 
288
  └── core/
289
- β”œβ”€β”€ trajectory.py # EpisodicMemory β€” L1 similarity retrieval
290
  β”œβ”€β”€ reward.py # Score normalisation utilities
291
- └── policy_update.py # Advantage computation, memory gating
292
  ```
293
 
294
  ---
@@ -307,11 +285,12 @@ cascade-containment/
307
  | HF Space deploys | βœ… |
308
  | `inference.py` at root | βœ… |
309
  | `[START][STEP][END]` structured logs | βœ… |
310
- | Runtime < 20 minutes | βœ… ~16 minutes |
311
  | `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN` env vars | βœ… |
312
  | OpenAI client for all LLM calls | βœ… |
313
  | Grader scores in `[0.0, 1.0]` | βœ… |
314
  | 3+ tasks with difficulty progression | βœ… |
 
315
 
316
  ---
317
 
 
72
  export HF_TOKEN="hf_your_token_here"
73
  export ENV_BASE_URL="https://therubberduckdebuggers-cascade-containment.hf.space"
74
 
75
+ # Full LLM+GRPO evaluation (~20 minutes, emits [START][STEP][END] logs)
76
  python inference.py
77
+
78
+ # Local validation + greedy benchmark
79
+ python scripts/test_local.py
80
  ```
81
 
82
  ---
83
 
84
  ## Action Space
85
 
 
 
86
  | Field | Type | Values |
87
  | --- | --- | --- |
88
  | `action_type` | `string` | `"test"` Β· `"restrict"` Β· `"allocate"` |
 
94
  | **restrict** | Free | Imposes movement restrictions; reduces spread rate; penalised if infection < 0.20 |
95
  | **allocate** | 1 resource | Deploys medical resources; reduces existing infection by 5% and slows future spread |
96
 
97
+ Movement restrictions lift automatically once a district's infection drops below the safe threshold.
98
 
99
  ---
100
 
 
113
  message: str | None
114
  ```
115
 
 
 
116
  | Field | Description | Observability |
117
  | --- | --- | --- |
118
  | `reported_infection_rate` | Active infection fraction | Real-time (easy/medium); **3-day lagged** (hard) |
 
122
  | `restriction_active` | Whether movement restrictions are in place | Always real-time |
123
  | `tested_recently` | Tested within last 2 days | Always real-time |
124
 
 
 
125
  ---
126
 
127
  ## Epidemiological Model
128
 
 
 
129
  ```text
130
  new_infection = current + (spread_rate βˆ’ natural_recovery βˆ’ intervention) + geographic_spillover
131
  ```
 
145
 
146
  | Task | Districts | Steps | Resources | Data Lag | Challenge |
147
  | --- | --- | --- | --- | --- | --- |
148
+ | **easy** | 2 | 10 | 10 | None | Single outbreak; D1 starts infected, D0 is clean |
149
  | **medium** | 4 | 15 | 8 | None | Two simultaneous outbreaks; forced triage between competing threats |
150
  | **hard** | 6 | 15 | 7 | **3 days** | Six growing outbreaks; invisible acceleration; scarce resources |
151
 
152
+ **Easy** β€” D1 starts at 0.50 infection, D0 is clean. The agent must observe and target the correct district. A fixed-target agent ignoring observations scores ~43% and breaches hospitals 60% of the time.
 
 
153
 
154
+ **Medium** β€” D0 and D2 start in the danger zone; D1 and D3 grow into crisis within 4–6 steps. With 8 resources across 4 districts over 15 steps, genuine triage is required.
155
 
156
+ **Hard** β€” 3-day information lag means the agent sees infection rates from 3 days ago. The `growth_rate_hint` provides a noisy signal to estimate current state. Structural uncertainty β€” not testable around.
157
 
158
  ---
159
 
160
  ## Reward Function
161
 
 
 
162
  | Term | Value | Fires When |
163
  | --- | --- | --- |
164
+ | Infection penalty | `βˆ’0.50 Γ— density` | District infection > 0.40 |
165
+ | Hospital breach | `βˆ’1.00` | Hospital capacity ≀ 10% |
166
  | Early containment | `+0.50 Γ— (1 βˆ’ step/max_steps)` | District infection < 0.20 |
167
  | Correct prioritisation | `+0.30` | Allocate to highest-infected district |
168
  | Unnecessary restriction | `βˆ’0.20` | Restrict district below 0.20 |
169
 
170
+ Key design choices: early containment bonus decays over time (proactive action worth more); infection penalty scales with population density (realistic triage); hospital breach fires at 10% not 0% (real ICU thresholds); restrictions auto-lift when infection drops below safe threshold.
 
 
 
 
 
171
 
172
  ---
173
 
174
  ## Grader
175
 
176
+ Fully deterministic β€” no randomness, no LLM calls. Identical trajectories always produce identical scores in `[0.0, 1.0]`.
 
 
177
 
178
  | Component | Weight | Measures |
179
  | --- | --- | --- |
180
  | **Hospital score** | 45% | Average capacity preserved; Γ—0.6 multiplier if any district collapsed |
181
+ | **Containment score** | 30% | Fraction of district-days below infection threshold (first 2 steps excluded) |
182
+ | **Efficiency score** | 15% | Fraction of resource actions targeting highest-infected district (uses pre-action state) |
183
+ | **Speed score** | 10% | `1 βˆ’ (steps / max_steps)` if episode ends early; else 0 |
 
 
184
 
185
+ Hospital is weighted highest because system capacity preservation is the primary operational constraint in real outbreak response β€” a functioning healthcare system is the prerequisite for everything else.
186
 
187
  ---
188
 
189
  ## Baseline Agent β€” GRPO-Style Episodic Memory
190
 
191
+ No weight updates, no gradient computation. The prompt is the policy; memory updates are the policy improvement.
192
 
193
  ### Learning Loop
194
 
195
  ```text
196
  Rollout 1: Base prompt, no prior knowledge
197
  compute advantage = R1 - mean([])
198
+ store steps with reward > -0.3 into EpisodicMemory
199
 
200
  Rollout 2: Memory-augmented prompt
201
+ retrieve top-5 similar past decisions by L1 distance on infection profiles
202
  inject as concrete examples into prompt
203
  compute advantage = R2 - mean([R1])
204
+ reinforce if advantage > -0.5
205
 
206
+ ... repeat for N rollouts (easy=3, medium=4, hard=4)
207
  Report best grader score across all rollouts
208
  ```
209
 
210
+ ### Benchmark Scores
211
 
212
+ Results from `baseline/run.py` (Llama 3.3 70B via Groq, runtime ~19.8 minutes):
213
 
214
+ | Task | Greedy (D0) | LLM+GRPO | Lift |
215
+ | --- | --- | --- | --- |
216
+ | Easy | 42.8% (breach 60%) | **90.8%** | +48pp |
217
+ | Medium | 42.7% (breach 80%) | **71.6%** | +29pp |
218
+ | Hard | 33.0% (breach 100%) | **66.1%** | +33pp |
219
+ | **Average** | **39.5%** | **76.2%** | **+37pp** |
 
 
 
 
220
 
221
+ The greedy baseline (always allocates to D0) scores 33–43% with 60–100% hospital breach rates β€” no trivial exploit path. LLM+GRPO reaches 66–91% with zero breaches, demonstrating that genuine triage reasoning is required and rewarded.
222
 
223
  ---
224
 
225
  ## Generalisation
226
 
 
 
227
  | Domain | Spreading cascade | Delayed data | Resource scarcity |
228
  | --- | --- | --- | --- |
229
  | 🦠 **Epidemic containment** | Infection spreads between districts | Lagged case counts | Medical resources |
 
232
  | πŸ“’ **Misinformation containment** | Narrative spread through networks | Viral detection lag | Correction budget |
233
  | 🀝 **Poverty intervention** | Deprivation cascades through communities | Census data lag | Aid allocation |
234
 
 
 
235
  ---
236
 
237
  ## Project Structure
 
244
  β”œβ”€β”€ openenv.yaml # Environment manifest for OpenEnv registry
245
  β”‚
246
  β”œβ”€β”€ server/
247
+ β”‚ β”œβ”€β”€ app.py # FastAPI server + judge dashboard + /grade /info /demo /validate endpoints
248
  β”‚ β”œβ”€β”€ environment.py # Core RL loop (reset/step/state OpenEnv interface)
249
  β”‚ β”œβ”€β”€ grader.py # Deterministic trajectory scorer β€” no LLM calls
250
  β”‚ β”œβ”€β”€ constants.py # Single source of truth for all numeric configuration
251
  β”‚ β”œβ”€β”€ utils.py # Spread computation, observation builder, helper functions
252
  β”‚ β”œβ”€β”€ Dockerfile # Container definition
253
  β”‚ └── tasks/
254
+ β”‚ β”œβ”€β”€ task_easy.py # 2 districts, 10 steps, real-time data (D1 seeded)
255
  β”‚ β”œβ”€β”€ task_medium.py # 4 districts, 15 steps, forced triage
256
  β”‚ └── task_hard.py # 6 districts, 15 steps, 3-day data lag
257
  β”‚
258
  β”œβ”€β”€ baseline/
259
  β”‚ β”œβ”€β”€ policy.py # LLM policy with chain-of-thought prompting
260
+ β”‚ β”œβ”€β”€ evaluator.py # GRPO episodic memory loop (easy=3, medium=4, hard=4 rollouts)
261
  β”‚ └── run.py # CLI entry point
262
  β”‚
263
+ β”œβ”€β”€ scripts/
264
+ β”‚ └── test_local.py # Phase 1 spec checks + greedy benchmark + variance analysis
265
+ β”‚
266
  └── core/
267
+ β”œβ”€β”€ trajectory.py # EpisodicMemory β€” L1 similarity retrieval, phase-weighted
268
  β”œβ”€β”€ reward.py # Score normalisation utilities
269
+ └── policy_update.py # Advantage computation, memory gating (threshold -0.3)
270
  ```
271
 
272
  ---
 
285
  | HF Space deploys | βœ… |
286
  | `inference.py` at root | βœ… |
287
  | `[START][STEP][END]` structured logs | βœ… |
288
+ | Runtime < 20 minutes | βœ… ~19.8 minutes |
289
  | `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN` env vars | βœ… |
290
  | OpenAI client for all LLM calls | βœ… |
291
  | Grader scores in `[0.0, 1.0]` | βœ… |
292
  | 3+ tasks with difficulty progression | βœ… |
293
+ | Phase 1 automated validation | βœ… 10/10 checks pass |
294
 
295
  ---
296
 
server/app.py CHANGED
@@ -593,7 +593,7 @@ body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:1
593
  <div class="brand-icon">🦠</div>
594
  <div>
595
  <div class="brand-name">Cascade Containment</div>
596
- <div class="brand-sub">OpenEnv Benchmark &middot; Meta PyTorch Hackathon Γ— SST 2026</div>
597
  </div>
598
  </div>
599
  <div class="header-right">
@@ -636,8 +636,8 @@ body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:1
636
  </div>
637
  <div class="hero-stats">
638
  <div class="hs-item"><div class="hs-val">3</div><div class="hs-label">Task levels</div></div>
639
- <div class="hs-item"><div class="hs-val">5</div><div class="hs-label">Reward terms</div></div>
640
- <div class="hs-item"><div class="hs-val">4+</div><div class="hs-label">Domains</div></div>
641
  </div>
642
  </div>
643
 
@@ -742,16 +742,23 @@ body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:1
742
  </div>
743
 
744
  <div style="margin-top:1rem;" class="card">
745
- <div class="card-title">What Phase 1 Checks</div>
746
  <table class="table">
747
- <tr><th>Check</th><th>Requirement</th></tr>
748
- <tr><td>HF Space deploys</td><td>Environment responds on port 7860</td></tr>
749
- <tr><td>OpenEnv spec compliance</td><td>reset(), step(), state property all function correctly</td></tr>
750
- <tr><td>3+ tasks with graders</td><td>Easy, Medium, Hard all produce grader scores in [0.0, 1.0]</td></tr>
751
- <tr><td>Invalid actions handled</td><td>Bad input doesn't crash the episode</td></tr>
752
- <tr><td>Difficulty progression</td><td>Tasks scale in complexity (districts, steps, resources)</td></tr>
753
- <tr><td>Grader deterministic</td><td>Same trajectory always returns same score</td></tr>
 
 
 
 
754
  </table>
 
 
 
755
  </div>
756
  </div>
757
 
@@ -1014,7 +1021,7 @@ body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:1
1014
  </tr>
1015
  <tr>
1016
  <td>Trivially containable (easy task too easy)</td>
1017
- <td>Random spread rates [0.03–0.08] create variance; greedy agent scores only ~0.50</td>
1018
  <td><span class="badge badge-green">Addressed</span></td>
1019
  </tr>
1020
  <tr>
@@ -1147,7 +1154,7 @@ body{font-family:var(--mono);background:var(--bg);color:var(--text);min-height:1
1147
  <div class="arch-card"><div class="arch-icon">πŸ“Š</div><div class="arch-name">grader.py</div><div class="arch-desc">Deterministic trajectory scorer. Reads hidden CityState. Four components weighted into final_score ∈ [0.0, 1.0]. Zero LLM calls.</div></div>
1148
  <div class="arch-card"><div class="arch-icon">🌊</div><div class="arch-name">utils.py</div><div class="arch-desc">SIR-inspired spread model with linear spillover (no wrap-around). Observation builder enforcing partial observability by task.</div></div>
1149
  <div class="arch-card"><div class="arch-icon">🧠</div><div class="arch-name">core/trajectory.py</div><div class="arch-desc">EpisodicMemory. Stores (obs, action, reward) tuples. Retrieves top-k by L1 distance on infection profiles, phase-weighted.</div></div>
1150
- <div class="arch-card"><div class="arch-icon">🎯</div><div class="arch-name">baseline/evaluator.py</div><div class="arch-desc">GRPO-style loop. Advantage = Rα΅’ βˆ’ mean(R). Memory gated by advantage threshold. Reports best grader score across rollouts.</div></div>
1151
  </div>
1152
 
1153
  <div class="card">
 
593
  <div class="brand-icon">🦠</div>
594
  <div>
595
  <div class="brand-name">Cascade Containment</div>
596
+ <div class="brand-sub">OpenEnv Benchmark &middot; Meta PyTorch Hackathon Γ— SST 2026 &middot; LLM avg 76.2%</div>
597
  </div>
598
  </div>
599
  <div class="header-right">
 
636
  </div>
637
  <div class="hero-stats">
638
  <div class="hs-item"><div class="hs-val">3</div><div class="hs-label">Task levels</div></div>
639
+ <div class="hs-item"><div class="hs-val">76.2%</div><div class="hs-label">LLM+GRPO avg</div></div>
640
+ <div class="hs-item"><div class="hs-val">+37pp</div><div class="hs-label">vs greedy</div></div>
641
  </div>
642
  </div>
643
 
 
742
  </div>
743
 
744
  <div style="margin-top:1rem;" class="card">
745
+ <div class="card-title">Phase 1 Results β€” Last Run (2026-04-07)</div>
746
  <table class="table">
747
+ <tr><th>Check</th><th>Result</th><th>Detail</th></tr>
748
+ <tr><td>Env instantiates</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>EpidemicContainmentEnv()</td></tr>
749
+ <tr><td>Reset β€” Easy</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>2 districts, 10 steps</td></tr>
750
+ <tr><td>Reset β€” Medium</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>4 districts, 15 steps</td></tr>
751
+ <tr><td>Reset β€” Hard</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>6 districts, 15 steps</td></tr>
752
+ <tr><td>Step works</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>reward=-0.0599, done=False</td></tr>
753
+ <tr><td>State property</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>episode_id present, step_count present</td></tr>
754
+ <tr><td>Grader valid range</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>final_score=0.5573 in [0.0, 1.0]</td></tr>
755
+ <tr><td>Invalid action handled</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>Gracefully defaulted to allocate</td></tr>
756
+ <tr><td>Difficulty progression</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>easy=2d, medium=4d, hard=6d</td></tr>
757
+ <tr><td>Grader deterministic</td><td><span class="badge badge-green">&#10003; Pass</span></td><td>Scoring logic is pure β€” no internal randomness</td></tr>
758
  </table>
759
+ <div style="margin-top:0.75rem;padding:0.6rem 1rem;background:var(--green-dim);border:1px solid rgba(61,214,140,0.2);border-radius:6px;font-size:0.7rem;color:var(--text);">
760
+ &#10003; <strong>10 / 10 checks passed.</strong> Click "Run Validation" above to re-run live against the deployed environment.
761
+ </div>
762
  </div>
763
  </div>
764
 
 
1021
  </tr>
1022
  <tr>
1023
  <td>Trivially containable (easy task too easy)</td>
1024
+ <td>D0-only allocation scores 43% avg; 60–100% hospital breach rate β€” no trivial path to high scores</td>
1025
  <td><span class="badge badge-green">Addressed</span></td>
1026
  </tr>
1027
  <tr>
 
1154
  <div class="arch-card"><div class="arch-icon">πŸ“Š</div><div class="arch-name">grader.py</div><div class="arch-desc">Deterministic trajectory scorer. Reads hidden CityState. Four components weighted into final_score ∈ [0.0, 1.0]. Zero LLM calls.</div></div>
1155
  <div class="arch-card"><div class="arch-icon">🌊</div><div class="arch-name">utils.py</div><div class="arch-desc">SIR-inspired spread model with linear spillover (no wrap-around). Observation builder enforcing partial observability by task.</div></div>
1156
  <div class="arch-card"><div class="arch-icon">🧠</div><div class="arch-name">core/trajectory.py</div><div class="arch-desc">EpisodicMemory. Stores (obs, action, reward) tuples. Retrieves top-k by L1 distance on infection profiles, phase-weighted.</div></div>
1157
+ <div class="arch-card"><div class="arch-icon">🎯</div><div class="arch-name">baseline/evaluator.py</div><div class="arch-desc">GRPO-style loop. Per-task rollouts: easy=3, medium=4, hard=4. Advantage = Rα΅’ βˆ’ mean(R). Memory threshold -0.3. Best score reported.</div></div>
1158
  </div>
1159
 
1160
  <div class="card">