SwapnilPatil28 commited on
Commit
5175eec
·
verified ·
1 Parent(s): e293a74

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -777
app.py DELETED
@@ -1,777 +0,0 @@
1
- """FastAPI entry-point for the Incident Command Center environment.
2
-
3
- Besides the OpenEnv contract endpoints (`/reset`, `/step`, `/state`, `/close`)
4
- registered by `create_fastapi_app`, this module exposes:
5
-
6
- - `GET /` and `GET /web` — interactive HTML dashboard.
7
- - `GET /healthz` — liveness / readiness probe for orchestrators.
8
- - `GET /version` — build metadata.
9
- - `GET /metadata` — static environment metadata (action space, reward model).
10
- - `GET /metrics` — lightweight in-process counters (best-effort).
11
-
12
- The dashboard is written inline so the environment ships as a single
13
- directory and can be embedded in Hugging Face Spaces without extra assets.
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import json
19
- import logging
20
- from pathlib import Path
21
- from typing import Any, Dict
22
-
23
- import uvicorn
24
- from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse
25
- from fastapi.staticfiles import StaticFiles
26
- from openenv.core.env_server import create_fastapi_app
27
-
28
- from models import IncidentAction, IncidentObservation
29
- from server.config import EnvConfig
30
- from server.domain import ALL_ACTIONS, ALL_ROLES, build_incident_library
31
- from server.domain.reward import (
32
- CLOSURE_CORRECT_BASE,
33
- CLOSURE_WRONG_PENALTY,
34
- CLUE_REWARD,
35
- HANDOFF_CORRECT_REWARD,
36
- MITIGATION_CORRECT_REWARD,
37
- STEP_COST_INVESTIGATION,
38
- TIER_MULTIPLIER,
39
- )
40
- from server.environment import IncidentCommandCenterEnvironment
41
- from server.logging_utils import configure_logging
42
-
43
- _LOG = logging.getLogger("icc.app")
44
- _CONFIG = EnvConfig.from_env()
45
- configure_logging(level=_CONFIG.log_level, structured=_CONFIG.structured_logging)
46
-
47
- # External URLs surfaced on the dashboard so judges can jump straight from
48
- # the HF Space to the GitHub / Colab / docs / training artifacts.
49
- GITHUB_URL = "https://github.com/SwapnilPatil28/Multi-Agent-Incident-Command-Center"
50
- SPACE_PAGE_URL = "https://huggingface.co/spaces/SwapnilPatil28/Multi-Agent-Incident-Command-Center"
51
- SPACE_APP_URL = "https://swapnilpatil28-multi-agent-incident-command-center.hf.space"
52
- COLAB_URL = "https://colab.research.google.com/drive/1vx9E5FrZZrHoRwXs2cvtom3DaI6kZ3LP?usp=sharing"
53
- # Dashboard doc links point at the Hugging Face Space copies of the docs (not
54
- # GitHub) so a judge who opens the Space stays inside the HF ecosystem. The
55
- # README on the Space page is rendered directly, so we point at the Space
56
- # root for it; the other three open the HF file browser.
57
- README_URL = f"{SPACE_PAGE_URL}/blob/main/README.md"
58
- BLOG_POST_URL = f"{SPACE_PAGE_URL}/blob/main/docs/BLOG_POST.md"
59
- VIDEO_SCRIPT_URL = f"{SPACE_PAGE_URL}/blob/main/docs/VIDEO_SCRIPT.md"
60
- SUBMISSION_CHECKLIST_URL = f"{SPACE_PAGE_URL}/blob/main/docs/SUBMISSION_CHECKLIST.md"
61
-
62
- app = create_fastapi_app(
63
- IncidentCommandCenterEnvironment,
64
- IncidentAction,
65
- IncidentObservation,
66
- )
67
-
68
- # Serve the committed training-evidence artifacts (reward_curve.png,
69
- # training_curve.png, reward_components.png, summary_metrics.json, ...)
70
- # so the dashboard can embed them without depending on external hosts.
71
- _ARTIFACTS_DIR = Path(__file__).resolve().parent.parent / "artifacts"
72
- if _ARTIFACTS_DIR.exists():
73
- app.mount(
74
- "/artifacts",
75
- StaticFiles(directory=str(_ARTIFACTS_DIR)),
76
- name="artifacts",
77
- )
78
-
79
-
80
- def _load_summary_metrics() -> Dict[str, Any]:
81
- """Best-effort load of the committed training results for the dashboard."""
82
- path = _ARTIFACTS_DIR / "summary_metrics.json"
83
- if not path.exists():
84
- return {}
85
- try:
86
- with path.open("r", encoding="utf-8") as fh:
87
- return json.load(fh)
88
- except (OSError, json.JSONDecodeError):
89
- return {}
90
-
91
-
92
- # ---------------------------------------------------------------------------
93
- # Introspection helpers
94
- # ---------------------------------------------------------------------------
95
-
96
-
97
- def _resolve_environment() -> IncidentCommandCenterEnvironment | None:
98
- """Best-effort retrieval of the running environment instance.
99
-
100
- OpenEnv versions differ in where they stash the environment, so we try a
101
- few well-known attribute names before giving up.
102
- """
103
- for attr in ("environment", "env", "_environment"):
104
- env = getattr(app.state, attr, None)
105
- if env is not None:
106
- return env # type: ignore[return-value]
107
- return None
108
-
109
-
110
- def _metadata_payload() -> Dict[str, Any]:
111
- library = build_incident_library()
112
- return {
113
- "name": _CONFIG.name,
114
- "version": _CONFIG.version,
115
- "tasks": library.tasks(),
116
- "incidents_per_task": {
117
- task: len(library.templates_for(task)) for task in library.tasks()
118
- },
119
- "actions": list(ALL_ACTIONS),
120
- "roles": list(ALL_ROLES),
121
- "reward_model": {
122
- "step_cost_investigation": STEP_COST_INVESTIGATION,
123
- "clue_reward": CLUE_REWARD,
124
- "handoff_correct": HANDOFF_CORRECT_REWARD,
125
- "mitigation_correct": MITIGATION_CORRECT_REWARD,
126
- "closure_correct_base": CLOSURE_CORRECT_BASE,
127
- "closure_wrong": CLOSURE_WRONG_PENALTY,
128
- "tier_multiplier": TIER_MULTIPLIER,
129
- },
130
- "budgets": {
131
- "easy": _CONFIG.easy_budget,
132
- "medium": _CONFIG.medium_budget,
133
- "hard": _CONFIG.hard_budget,
134
- },
135
- "sla_minutes": {
136
- "easy": _CONFIG.easy_sla_minutes,
137
- "medium": _CONFIG.medium_sla_minutes,
138
- "hard": _CONFIG.hard_sla_minutes,
139
- },
140
- }
141
-
142
-
143
- # ---------------------------------------------------------------------------
144
- # Routes
145
- # ---------------------------------------------------------------------------
146
-
147
-
148
- @app.get("/healthz", response_class=JSONResponse)
149
- async def healthz() -> JSONResponse:
150
- return JSONResponse(
151
- {
152
- "status": "ok",
153
- "name": _CONFIG.name,
154
- "version": _CONFIG.version,
155
- }
156
- )
157
-
158
-
159
- @app.get("/version", response_class=JSONResponse)
160
- async def version() -> JSONResponse:
161
- return JSONResponse(
162
- {
163
- "name": _CONFIG.name,
164
- "version": _CONFIG.version,
165
- "default_seed": _CONFIG.default_seed,
166
- }
167
- )
168
-
169
-
170
- @app.get("/env-info", response_class=JSONResponse)
171
- async def env_info() -> JSONResponse:
172
- """Rich metadata about the environment (rubric, budgets, taxonomy)."""
173
- return JSONResponse(_metadata_payload())
174
-
175
-
176
- @app.get("/metrics", response_class=PlainTextResponse)
177
- async def metrics() -> PlainTextResponse:
178
- env = _resolve_environment()
179
- lines = [
180
- f'icc_info{{name="{_CONFIG.name}",version="{_CONFIG.version}"}} 1',
181
- ]
182
- if env is not None and env.state is not None:
183
- s = env.state
184
- lines += [
185
- f'icc_episode_step_total {s.step_count}',
186
- f'icc_cumulative_reward {s.cumulative_reward}',
187
- f'icc_incidents_resolved_total {s.incidents_resolved}',
188
- f'icc_incidents_failed_total {s.incidents_failed}',
189
- f'icc_budget_remaining {s.budget_remaining}',
190
- f'icc_sla_minutes_remaining {s.sla_minutes_remaining}',
191
- f'icc_current_incident_index {s.current_incident_index}',
192
- ]
193
- return PlainTextResponse("\n".join(lines) + "\n")
194
-
195
-
196
- @app.get("/", response_class=HTMLResponse)
197
- @app.get("/web", response_class=HTMLResponse)
198
- async def root() -> HTMLResponse:
199
- return HTMLResponse(_dashboard_html())
200
-
201
-
202
- def _dashboard_html() -> str:
203
- metadata_json = json.dumps(_metadata_payload(), indent=2)
204
- metrics = _load_summary_metrics()
205
- artifacts_available = _ARTIFACTS_DIR.exists() and (
206
- _ARTIFACTS_DIR / "reward_curve.png"
207
- ).exists()
208
-
209
- # --- Headline training numbers (1.5B SFT vs base, hard task) -------------
210
- base_rewards = metrics.get("base_model_rewards") or [0.0, 0.0, 0.0]
211
- sft_rewards = metrics.get("sft_model_rewards") or [0.0, 0.0, 0.0]
212
- improvement = metrics.get("improvement_sft_over_base") or [0.0, 0.0, 0.0]
213
- headline_delta = improvement[2] if len(improvement) >= 3 else 0.0
214
-
215
- def _fmt(val: Any) -> str:
216
- try:
217
- return f"{float(val):+.2f}"
218
- except (TypeError, ValueError):
219
- return "—"
220
-
221
- training_rows = "".join(
222
- f"<tr><td>{tier}</td><td>{_fmt(base_rewards[idx])}</td>"
223
- f"<td>{_fmt(sft_rewards[idx])}</td>"
224
- f"<td class='delta'>{_fmt(improvement[idx])}</td></tr>"
225
- for idx, tier in enumerate(("easy", "medium", "hard"))
226
- if idx < len(base_rewards)
227
- )
228
-
229
- # --- Training-evidence block (plots + caption) ---------------------------
230
- if artifacts_available:
231
- plots_html = """
232
- <h2>Training evidence</h2>
233
- <p class='sub'>
234
- Committed artifacts from the reference training run
235
- (Qwen2.5-1.5B-Instruct, 8 episodes/task, 3 epochs) plus the
236
- Qwen2.5-0.5B-Instruct ablation. Click any plot to open it full-size.
237
- </p>
238
- <div class='plots'>
239
- <figure>
240
- <a href='/artifacts/reward_curve.png' target='_blank' rel='noopener'>
241
- <img src='/artifacts/reward_curve.png' alt='Reward curve by policy (1.5B)' loading='lazy' />
242
- </a>
243
- <figcaption><strong>1.5B reward curve.</strong> Mean episodic reward per task tier
244
- across Random / Heuristic / Base-LLM / SFT-LLM. SFT matches the heuristic
245
- demonstrator across every tier and outperforms the untuned base by
246
- <strong>+{hard}</strong> on hard incidents.</figcaption>
247
- </figure>
248
- <figure>
249
- <a href='/artifacts/training_curve.png' target='_blank' rel='noopener'>
250
- <img src='/artifacts/training_curve.png' alt='SFT training loss and token accuracy (1.5B)' loading='lazy' />
251
- </a>
252
- <figcaption><strong>1.5B training curve.</strong> Supervised loss collapses from
253
- <code>~2.84 → ~0.02</code> and next-token accuracy climbs from
254
- <code>~0.49 → ~0.99</code> over three epochs on 680 rollout tokens.</figcaption>
255
- </figure>
256
- <figure>
257
- <a href='/artifacts/reward_components.png' target='_blank' rel='noopener'>
258
- <img src='/artifacts/reward_components.png' alt='Reward component decomposition (1.5B)' loading='lazy' />
259
- </a>
260
- <figcaption><strong>1.5B reward-component breakdown.</strong> SFT reproduces the
261
- heuristic's positive components (<code>clue_bonus</code>,
262
- <code>mitigation_correct</code>, <code>closure_correct</code>,
263
- <code>speed_bonus</code>) while the base model stalls on
264
- <code>step_cost</code> and SLA penalties.</figcaption>
265
- </figure>
266
- <figure>
267
- <a href='/artifacts/reward_curve_qwen0p5b.png' target='_blank' rel='noopener'>
268
- <img src='/artifacts/reward_curve_qwen0p5b.png' alt='Reward curve by policy (0.5B ablation)' loading='lazy' />
269
- </a>
270
- <figcaption><strong>0.5B ablation reward curve.</strong> Same pipeline, smaller
271
- backbone. SFT improves by only <strong>+0.43 / +0.14 / +0.00</strong> over base —
272
- the 0.5B model is too small to absorb the multi-step, role-gated policy.
273
- Scale is the story.</figcaption>
274
- </figure>
275
- </div>
276
- <p class='sub' style='margin-top:0.75rem'>
277
- Raw files:
278
- <a href='/artifacts/summary_metrics.json'>summary_metrics.json</a>
279
- ·
280
- <a href='/artifacts/training_log.json'>training_log.json</a>
281
- ·
282
- <a href='/artifacts/summary_metrics_qwen0p5b.json'>summary_metrics_qwen0p5b.json</a>
283
- </p>
284
- """.format(hard=_fmt(headline_delta))
285
- else:
286
- plots_html = (
287
- "<h2>Training evidence</h2>"
288
- "<div class='card'><p class='sub'>Plots not bundled in this image. "
289
- "See the <a href='" + GITHUB_URL + "/tree/main/artifacts'>GitHub artifacts folder</a>.</p></div>"
290
- )
291
-
292
- # --- 0.5B ablation summary ----------------------------------------------
293
- ablation_html = """
294
- <h2>Ablation: model scale matters for imitation learning</h2>
295
- <div class='card'>
296
- <p class='sub'>
297
- Same pipeline, same data schema — only the base-model size differs. The 0.5B
298
- model cannot absorb the expert policy; 1.5B matches it exactly.
299
- </p>
300
- <div class='table-wrap'>
301
- <table>
302
- <thead>
303
- <tr>
304
- <th>Model</th><th>Easy Δ</th><th>Medium Δ</th><th>Hard Δ</th>
305
- <th>Heuristic match?</th>
306
- </tr>
307
- </thead>
308
- <tbody>
309
- <tr>
310
- <td>Qwen2.5-0.5B-Instruct</td>
311
- <td>+0.43</td><td>+0.14</td><td class='delta'>+0.00</td>
312
- <td>No (stuck on step-cost)</td>
313
- </tr>
314
- <tr>
315
- <td><strong>Qwen2.5-1.5B-Instruct</strong></td>
316
- <td>-1.80</td><td>+3.13</td><td class='delta good'>+10.17</td>
317
- <td><strong>Yes (exact match)</strong></td>
318
- </tr>
319
- </tbody>
320
- </table>
321
- </div>
322
- </div>
323
- """
324
-
325
- # Theme mapping now lives in the top story block — keep this var empty
326
- # so the existing `{themes_html}` slot renders to nothing (no duplication).
327
- themes_html = ""
328
-
329
- # --- Reward-rubric details ----------------------------------------------
330
- reward_rubric_rows = "".join(
331
- f"<tr><td><code>{name}</code></td><td>{value}</td></tr>"
332
- for name, value in (
333
- ("step_cost", f"{STEP_COST_INVESTIGATION} per investigation step"),
334
- ("clue_reward", f"+{CLUE_REWARD} per new fact"),
335
- ("handoff_correct", f"+{HANDOFF_CORRECT_REWARD}"),
336
- ("mitigation_correct", f"+{MITIGATION_CORRECT_REWARD}"),
337
- ("closure_correct_base", f"+{CLOSURE_CORRECT_BASE} × tier multiplier"),
338
- ("closure_wrong", f"{CLOSURE_WRONG_PENALTY} × tier multiplier"),
339
- )
340
- )
341
-
342
- return f"""
343
- <!DOCTYPE html>
344
- <html lang='en'>
345
- <head>
346
- <meta charset='UTF-8'>
347
- <meta name='viewport' content='width=device-width, initial-scale=1.0'>
348
- <title>Incident Command Center | OpenEnv Dashboard</title>
349
- <style>
350
- :root {{
351
- --primary:#3b82f6; --accent:#22d3ee; --bg:#0f172a;
352
- --card:#111c31; --card-2:#152238; --text:#e2e8f0; --muted:#94a3b8;
353
- --good:#22c55e; --bad:#ef4444; --warn:#f59e0b;
354
- }}
355
- * {{ box-sizing: border-box; }}
356
- body {{
357
- font-family: -apple-system, 'Segoe UI', sans-serif;
358
- background: radial-gradient(1000px 600px at 10% -10%, #1e293b, var(--bg));
359
- color: var(--text); padding: 2rem; margin: 0; min-height: 100vh;
360
- }}
361
- header {{ display:flex; align-items:center; justify-content:space-between; max-width:1200px; margin:0 auto 1.5rem; flex-wrap:wrap; gap:1rem; }}
362
- .brand {{ display:flex; align-items:center; gap:0.75rem; }}
363
- .logo {{ width:44px; height:44px; border-radius:10px; background:linear-gradient(135deg,var(--primary),var(--accent)); }}
364
- h1 {{ font-size:1.6rem; margin:0; }}
365
- h2 {{ font-size:1.25rem; margin:1.8rem 0 0.6rem; color:#cbd5e1; }}
366
- .sub {{ color: var(--muted); }}
367
- .grid {{ display:grid; grid-template-columns: repeat(auto-fit,minmax(240px,1fr)); gap:1rem; max-width:1200px; margin:0 auto; }}
368
- .grid-3 {{ grid-template-columns: repeat(auto-fit,minmax(280px,1fr)); }}
369
- .card {{ background: var(--card); border: 1px solid #1f2a44; padding: 1.25rem; border-radius: 14px; }}
370
- .card h3 {{ margin:0 0 0.5rem; font-size:1rem; color:#f1f5f9; }}
371
- .pill {{ display:inline-block; padding:2px 8px; margin:2px; border-radius:999px; background:#1e293b; border:1px solid #334155; color:#cbd5e1; font-size:0.78rem; }}
372
- .pill.cta {{ background:linear-gradient(135deg,var(--primary),var(--accent)); color:#0b1225; border-color:transparent; font-weight:600; }}
373
- .container {{ max-width: 1200px; margin: 0 auto; }}
374
- code {{ background:#0b1225; border:1px solid #1f2a44; padding:2px 6px; border-radius:6px; color:#67e8f9; font-family:'JetBrains Mono', monospace; }}
375
- pre {{ background:#0b1225; border:1px solid #1f2a44; padding: 1rem; border-radius: 10px; color:#cbd5e1; overflow-x:auto; font-size:0.85rem; }}
376
- a {{ color: var(--accent); text-decoration: none; }}
377
- a:hover {{ text-decoration: underline; }}
378
- .kpi {{ display:flex; flex-direction:column; gap:0.25rem; }}
379
- .kpi .num {{ font-size:1.6rem; font-weight:700; color:#f8fafc; }}
380
- .kpi .lbl {{ color: var(--muted); font-size:0.8rem; }}
381
- .kpi .num.good {{ color: var(--good); }}
382
- footer {{ max-width:1200px; margin:2rem auto 0; color:var(--muted); font-size:0.85rem; }}
383
- /* Training-evidence plots: one plot per row, centred, with a tighter
384
- max-width so the charts read as compact figures rather than banners.
385
- Click the image to open the full-resolution PNG in a new tab. */
386
- .plots {{ display:flex; flex-direction:column; gap:1.25rem; max-width:1200px; margin:0 auto; }}
387
- .plots figure {{ background: var(--card); border:1px solid #1f2a44; border-radius: 14px; padding: 1rem 1.25rem; margin:0; }}
388
- .plots figure a {{ display:block; }}
389
- .plots img {{
390
- width:100%; height:auto; display:block;
391
- max-width:720px; margin:0 auto;
392
- border-radius:10px; background:#0b1225;
393
- transition: transform 0.2s ease;
394
- }}
395
- .plots img:hover {{ transform: scale(1.01); }}
396
- .plots figcaption {{ color: var(--muted); font-size:0.9rem; margin-top:0.6rem; line-height:1.55; text-align:center; max-width:720px; margin-left:auto; margin-right:auto; }}
397
- .table-wrap {{ overflow-x:auto; }}
398
- table {{ width:100%; border-collapse: collapse; margin-top:0.5rem; font-size:0.9rem; }}
399
- th, td {{ padding:0.5rem 0.75rem; text-align:left; border-bottom:1px solid #1f2a44; }}
400
- th {{ color:#cbd5e1; font-weight:600; }}
401
- td.delta {{ font-weight:600; color:#f8fafc; }}
402
- td.delta.good {{ color: var(--good); }}
403
- .links {{ display:flex; flex-wrap:wrap; gap:0.5rem; }}
404
-
405
- /* "Story in 2 minutes" hero panel — plain-English summary for judges. */
406
- .hero-card {{
407
- background: linear-gradient(135deg, #0f2647 0%, #172a4a 60%, #1f2a44 100%);
408
- border: 1px solid #1f2a44; border-radius: 16px;
409
- padding: 1.75rem 1.75rem 1.5rem; margin: 0 auto 1.5rem;
410
- max-width: 1200px; box-shadow: 0 6px 30px rgba(34,211,238,0.08);
411
- }}
412
- .hero-card h2 {{ font-size:1.35rem; margin:0 0 0.4rem; color:#f1f5f9; }}
413
- .hero-card h3 {{ font-size:1rem; color:#e2e8f0; margin:0 0 0.3rem; }}
414
- .hero-card .lede {{
415
- font-size:1.02rem; line-height:1.6; color:#e2e8f0;
416
- background:#0b1225; border-left: 3px solid var(--accent);
417
- padding: 0.9rem 1.1rem; border-radius: 6px; margin: 0.3rem 0 0;
418
- }}
419
- .hero-card .lede strong {{ color:#f8fafc; }}
420
- .hero-card table {{ font-size:0.92rem; }}
421
- .hero-card .card {{ background: #0e1a30; }}
422
-
423
- /* "Resources & documentation" click-through cards. */
424
- .res-card {{
425
- display:block; color: var(--text); text-decoration:none;
426
- background: var(--card); border:1px solid #1f2a44; border-radius:12px;
427
- padding: 1rem 1.1rem;
428
- transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
429
- }}
430
- .res-card:hover {{
431
- border-color: var(--accent); transform: translateY(-2px);
432
- box-shadow: 0 8px 24px rgba(34,211,238,0.12);
433
- text-decoration:none;
434
- }}
435
- .res-icon {{ font-size:1.6rem; line-height:1; margin-bottom:0.5rem; }}
436
- .res-title {{ font-weight:600; color:#f1f5f9; margin-bottom:0.2rem; }}
437
- </style>
438
- </head>
439
- <body>
440
- <header>
441
- <div class='brand'>
442
- <div class='logo'></div>
443
- <div>
444
- <h1>Incident Command Center</h1>
445
- <div class='sub'>OpenEnv · Multi-Agent · Long-Horizon · Professional-Task Simulation</div>
446
- </div>
447
- </div>
448
- <div class='links'>
449
- <a class='pill cta' href='{GITHUB_URL}' target='_blank' rel='noopener'>GitHub</a>
450
- <a class='pill cta' href='{COLAB_URL}' target='_blank' rel='noopener'>Open in Colab</a>
451
- <a class='pill cta' href='{README_URL}' target='_blank' rel='noopener'>README</a>
452
- <a class='pill cta' href='{BLOG_POST_URL}' target='_blank' rel='noopener'>Blog post</a>
453
- <a class='pill' href='{SPACE_PAGE_URL}' target='_blank' rel='noopener'>HF Space page</a>
454
- <span class='pill'>v{_CONFIG.version}</span>
455
- <span class='pill'>task: easy / medium / hard</span>
456
- </div>
457
- </header>
458
-
459
- <div class='container'>
460
-
461
- <!-- ============================================================ -->
462
- <!-- PART 1 — Plain-English story for non-technical judges -->
463
- <!-- ============================================================ -->
464
- <div class='hero-card'>
465
- <h2 style='margin-top:0'>🚨 The story in 2 minutes</h2>
466
- <p class='lede'>
467
- When a real tech company has an outage, <strong>three people's phones
468
- buzz at once</strong> — a Triage engineer, an Investigator, and an Ops
469
- Manager. They have to cooperate under a ticking <strong>SLA clock</strong>,
470
- every action costs <strong>budget</strong>, and every wrong call costs
471
- <strong>real money</strong> (enterprise outages hurt ~3× more than free-tier).
472
- <br /><br />
473
- We built a simulator of that war room — and we fine-tuned an LLM to run it
474
- <strong>as well as the human expert</strong>.
475
- </p>
476
-
477
- <h3 style='margin-top:1.25rem'>What is the environment?</h3>
478
- <p class='sub' style='margin:0 0 0.75rem'>
479
- Three specialist agents with <strong>different permissions</strong> resolve
480
- a live queue of 13 realistic tech incidents across 3 difficulty tiers.
481
- </p>
482
- <div class='table-wrap'>
483
- <table>
484
- <thead>
485
- <tr><th>Role</th><th>Can do</th><th>Cannot do</th></tr>
486
- </thead>
487
- <tbody>
488
- <tr>
489
- <td>🔍 <strong>Triage</strong></td>
490
- <td>Pull logs · check metrics · consult KB</td>
491
- <td>Close a ticket</td>
492
- </tr>
493
- <tr>
494
- <td>🧪 <strong>Investigator</strong></td>
495
- <td>Apply a fix · roll back a deploy</td>
496
- <td>Escalate or file a post-mortem</td>
497
- </tr>
498
- <tr>
499
- <td>👷 <strong>Ops Manager</strong></td>
500
- <td>Escalate · file post-mortem · <strong>close the ticket</strong></td>
501
- <td>Apply a code fix</td>
502
- </tr>
503
- </tbody>
504
- </table>
505
- </div>
506
-
507
- <h3 style='margin-top:1.25rem'>What did the agent learn?</h3>
508
- <p class='sub' style='margin:0'>
509
- Not "pick the right label." It learned a whole workflow — dig up clues,
510
- hand off to the right specialist, apply the correct fix, respect the SLA,
511
- file the post-mortem, close the ticket. The rubric makes every piece of
512
- that workflow <em>visible</em> as a named reward component, so you can
513
- see <em>why</em> the agent earned (or lost) points at every step.
514
- </p>
515
-
516
- <h3 style='margin-top:1.25rem'>Why it matters for the 3 hackathon themes</h3>
517
- <div class='grid grid-3'>
518
- <div class='card'>
519
- <h3>🤝 Theme #1 — Multi-Agent</h3>
520
- <p class='sub'>
521
- Three distinct roles with <strong>non-overlapping permissions</strong>.
522
- Wrong-actor calls → <code>-0.08</code>. Correct handoff → <code>+0.15</code>.
523
- Cooperation is <em>trained</em>, not hard-coded.
524
- </p>
525
- </div>
526
- <div class='card'>
527
- <h3>⏱️ Theme #2 — Long-Horizon</h3>
528
- <p class='sub'>
529
- Each episode runs <strong>3–5 sequential incidents</strong> over 20–60
530
- steps with a single ticking SLA clock. Big rewards (+0.80 × tier) only
531
- fire after clues → fix → post-mortem. Sparse and delayed by design.
532
- </p>
533
- </div>
534
- <div class='card'>
535
- <h3>🏢 Theme #3 — Professional World-Model</h3>
536
- <p class='sub'>
537
- Real logs, metrics, KB articles, red-herring signals, customer tiers,
538
- SLA timers, revenue impact. Close an enterprise ticket wrong and it
539
- hurts ~3× what a free-tier one does.
540
- </p>
541
- </div>
542
- </div>
543
-
544
- <p class='sub' style='margin-top:1rem;font-style:italic'>
545
- ↓ Keep scrolling for the headline numbers, training plots, ablation, and
546
- the full rubric. Or jump straight to the
547
- <a href='{README_URL}' target='_blank' rel='noopener'>README</a> or the
548
- <a href='{BLOG_POST_URL}' target='_blank' rel='noopener'>blog post</a>.
549
- </p>
550
- </div>
551
-
552
- <!-- ============================================================ -->
553
- <!-- Resources & documentation — every link the judges need -->
554
- <!-- ============================================================ -->
555
- <h2>Resources &amp; documentation</h2>
556
- <div class='grid grid-3'>
557
- <a class='res-card' href='{GITHUB_URL}' target='_blank' rel='noopener'>
558
- <div class='res-icon'>💻</div>
559
- <div class='res-title'>GitHub repository</div>
560
- <div class='sub'>Full source, tests, Dockerfile, CI-ready</div>
561
- </a>
562
- <a class='res-card' href='{SPACE_PAGE_URL}' target='_blank' rel='noopener'>
563
- <div class='res-icon'>🤗</div>
564
- <div class='res-title'>Hugging Face Space page</div>
565
- <div class='sub'>Repo view, build logs, discussions</div>
566
- </a>
567
- <a class='res-card' href='{SPACE_APP_URL}' target='_blank' rel='noopener'>
568
- <div class='res-icon'>🟢</div>
569
- <div class='res-title'>Live environment</div>
570
- <div class='sub'>You are here — OpenEnv endpoints live</div>
571
- </a>
572
- <a class='res-card' href='{COLAB_URL}' target='_blank' rel='noopener'>
573
- <div class='res-icon'>🎓</div>
574
- <div class='res-title'>Reproduce training (Colab T4)</div>
575
- <div class='sub'>One-click notebook, ~1 h wall clock</div>
576
- </a>
577
- <a class='res-card' href='{README_URL}' target='_blank' rel='noopener'>
578
- <div class='res-icon'>📖</div>
579
- <div class='res-title'>README (Part 1 + Part 2)</div>
580
- <div class='sub'>Story overview + full technical deep-dive</div>
581
- </a>
582
- <a class='res-card' href='{BLOG_POST_URL}' target='_blank' rel='noopener'>
583
- <div class='res-icon'>📝</div>
584
- <div class='res-title'>Mini blog post</div>
585
- <div class='sub'>The short writeup — MD file on the HF Space + GitHub</div>
586
- </a>
587
- <a class='res-card' href='{VIDEO_SCRIPT_URL}' target='_blank' rel='noopener'>
588
- <div class='res-icon'>🎬</div>
589
- <div class='res-title'>2-minute video script</div>
590
- <div class='sub'>Optional bonus — shot list + narration</div>
591
- </a>
592
- <a class='res-card' href='{SUBMISSION_CHECKLIST_URL}' target='_blank' rel='noopener'>
593
- <div class='res-icon'>✅</div>
594
- <div class='res-title'>Submission checklist</div>
595
- <div class='sub'>Every judging rule → where to find the evidence</div>
596
- </a>
597
- </div>
598
-
599
- <h2>Headline results</h2>
600
- <div class='grid'>
601
- <div class='card'>
602
- <div class='kpi'>
603
- <span class='lbl'>SFT reward lift on hard tasks</span>
604
- <span class='num good'>{_fmt(headline_delta)}</span>
605
- <span class='sub'>vs Qwen2.5-1.5B-Instruct base</span>
606
- </div>
607
- </div>
608
- <div class='card'>
609
- <div class='kpi'>
610
- <span class='lbl'>Heuristic-policy match</span>
611
- <span class='num'>Exact</span>
612
- <span class='sub'>SFT clones the demonstrator across every tier</span>
613
- </div>
614
- </div>
615
- <div class='card'>
616
- <div class='kpi'>
617
- <span class='lbl'>Scale ablation (hard Δ)</span>
618
- <span class='num'>0.5B → 1.5B</span>
619
- <span class='sub'>+0.00 → +10.17: capacity matters</span>
620
- </div>
621
- </div>
622
- <div class='card'>
623
- <div class='kpi'>
624
- <span class='lbl'>Training data</span>
625
- <span class='num'>680 rows</span>
626
- <span class='sub'>24 heuristic rollouts · 3 epochs</span>
627
- </div>
628
- </div>
629
- </div>
630
-
631
- <h2>Environment at a glance</h2>
632
- <div class='grid'>
633
- <div class='card'>
634
- <div class='kpi'>
635
- <span class='lbl'>Incidents in library</span>
636
- <span class='num' id='kpi-inc'>—</span>
637
- </div>
638
- </div>
639
- <div class='card'>
640
- <div class='kpi'>
641
- <span class='lbl'>Specialist roles</span>
642
- <span class='num'>3</span>
643
- <span class='sub'>triage · investigator · ops manager</span>
644
- </div>
645
- </div>
646
- <div class='card'>
647
- <div class='kpi'>
648
- <span class='lbl'>Reward components</span>
649
- <span class='num'>14+</span>
650
- <span class='sub'>rubric-based, transparent</span>
651
- </div>
652
- </div>
653
- <div class='card'>
654
- <div class='kpi'>
655
- <span class='lbl'>Seeded reproducibility</span>
656
- <span class='num'>Yes</span>
657
- <span class='sub'>default seed {_CONFIG.default_seed}</span>
658
- </div>
659
- </div>
660
- </div>
661
-
662
- <h2>1.5B SFT vs base (reference run)</h2>
663
- <div class='card'>
664
- <div class='table-wrap'>
665
- <table>
666
- <thead>
667
- <tr>
668
- <th>Task tier</th><th>Base reward</th><th>SFT reward</th><th>Δ</th>
669
- </tr>
670
- </thead>
671
- <tbody>
672
- {training_rows}
673
- </tbody>
674
- </table>
675
- </div>
676
- <p class='sub' style='margin-top:0.75rem'>
677
- Numbers loaded live from
678
- <a href='/artifacts/summary_metrics.json'>summary_metrics.json</a>
679
- committed alongside this Space.
680
- </p>
681
- </div>
682
-
683
- {plots_html}
684
-
685
- {ablation_html}
686
-
687
- {themes_html}
688
-
689
- <h2>Endpoints</h2>
690
- <div class='card'>
691
- <p class='sub'>Standard OpenEnv contract plus operational endpoints.</p>
692
- <ul>
693
- <li><code>POST /reset</code> — start a new episode (task_name, seed).</li>
694
- <li><code>POST /step</code> — submit an IncidentAction.</li>
695
- <li><code>GET /state</code> — full environment state.</li>
696
- <li><code>GET /healthz</code> — liveness probe.</li>
697
- <li><code>GET /version</code> — build information.</li>
698
- <li><code>GET /env-info</code> — action space, reward model, budgets.</li>
699
- <li><code>GET /metrics</code> — Prometheus-style counters.</li>
700
- <li><code>GET /docs</code> — interactive OpenAPI documentation.</li>
701
- <li><code>GET /artifacts/…</code> — committed training plots &amp; metrics.</li>
702
- </ul>
703
- </div>
704
-
705
- <h2>Action space</h2>
706
- <div class='card'>
707
- {"".join(f"<span class='pill'>{a}</span>" for a in ALL_ACTIONS)}
708
- <p class='sub' style='margin-top:0.5rem'>
709
- Each action is gated by the acting role; wrong-actor calls are penalised.
710
- </p>
711
- </div>
712
-
713
- <h2>Reward model</h2>
714
- <div class='card'>
715
- <p>
716
- Composable rubric with anti-gaming safeguards. Every step returns a
717
- <code>reward_components</code> dictionary so training curves are
718
- interpretable. Closure rewards and SLA penalties are scaled by
719
- customer-tier multipliers:
720
- </p>
721
- <p>
722
- {"".join(f"<span class='pill'>{tier}: x{mult}</span>" for tier, mult in TIER_MULTIPLIER.items())}
723
- </p>
724
- <div class='table-wrap'>
725
- <table>
726
- <thead><tr><th>Component</th><th>Signal</th></tr></thead>
727
- <tbody>{reward_rubric_rows}</tbody>
728
- </table>
729
- </div>
730
- <p class='sub' style='margin-top:0.75rem'>
731
- Full rubric (invalid-action, repeated-lookup, rollback-effective,
732
- post-mortem-logged, etc.) is documented in the
733
- <a href='{GITHUB_URL}#reward-model' target='_blank' rel='noopener'>README</a>.
734
- </p>
735
- </div>
736
-
737
- <h2>Metadata</h2>
738
- <div class='card'>
739
- <pre id='metadata-json'>{metadata_json}</pre>
740
- </div>
741
- </div>
742
-
743
- <footer>
744
- <div>
745
- <strong>Incident Command Center v{_CONFIG.version}</strong> · Built on
746
- <a href='https://github.com/meta-pytorch/openenv' target='_blank' rel='noopener'>OpenEnv</a>
747
- for the OpenEnv India 2026 Round 2 hackathon.
748
- </div>
749
- <div style='margin-top:0.4rem'>
750
- <a href='{GITHUB_URL}' target='_blank' rel='noopener'>GitHub</a> ·
751
- <a href='{SPACE_PAGE_URL}' target='_blank' rel='noopener'>HF Space page</a> ·
752
- <a href='{COLAB_URL}' target='_blank' rel='noopener'>Colab</a> ·
753
- <a href='{README_URL}' target='_blank' rel='noopener'>README</a> ·
754
- <a href='{BLOG_POST_URL}' target='_blank' rel='noopener'>Blog post</a> ·
755
- <a href='{VIDEO_SCRIPT_URL}' target='_blank' rel='noopener'>Video script</a> ·
756
- <a href='{SUBMISSION_CHECKLIST_URL}' target='_blank' rel='noopener'>Submission checklist</a>
757
- </div>
758
- </footer>
759
-
760
- <script>
761
- try {{
762
- const data = {metadata_json};
763
- const total = Object.values(data.incidents_per_task || {{}}).reduce((a,b)=>a+b,0);
764
- document.getElementById('kpi-inc').textContent = total;
765
- }} catch (e) {{}}
766
- </script>
767
- </body>
768
- </html>
769
- """
770
-
771
-
772
- def main() -> None:
773
- uvicorn.run(app, host="0.0.0.0", port=8000)
774
-
775
-
776
- if __name__ == "__main__":
777
- main()