| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> |
| <title>AdaptiveWorldEnv — ProthamD</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Outfit:wght@500;600;700&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> |
| <style> |
| :root { |
| --bg: #09090b; |
| --surface: #18181b; |
| --surface2: #27272a; |
| --border: #3f3f46; |
| --text: #fafafa; |
| --text2: #a1a1aa; |
| --text3: #71717a; |
| --accent: #6366f1; |
| --accent-glow: rgba(99,102,241,0.15); |
| --green: #22c55e; |
| --orange: #f97316; |
| --blue: #3b82f6; |
| --pink: #ec4899; |
| --task-color: #60a5fa; |
| --belief-color: #fb923c; |
| --combined-color: #4ade80; |
| } |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| html { scroll-behavior: smooth; } |
| body { |
| background: var(--bg); |
| color: var(--text); |
| font-family: 'Inter', -apple-system, sans-serif; |
| font-size: 15px; |
| line-height: 1.7; |
| min-height: 100vh; |
| } |
| |
| |
| html { |
| scrollbar-width: thin; |
| scrollbar-color: rgba(99, 102, 241, 0.55) var(--surface); |
| } |
| ::-webkit-scrollbar { |
| width: 10px; |
| height: 10px; |
| } |
| ::-webkit-scrollbar-track { |
| background: var(--surface); |
| border-radius: 6px; |
| } |
| ::-webkit-scrollbar-thumb { |
| background: linear-gradient(180deg, rgba(99, 102, 241, 0.65) 0%, rgba(139, 92, 246, 0.45) 100%); |
| border-radius: 6px; |
| border: 2px solid var(--surface); |
| box-shadow: 0 0 12px rgba(99, 102, 241, 0.15); |
| } |
| ::-webkit-scrollbar-thumb:hover { |
| background: linear-gradient(180deg, rgba(129, 140, 248, 0.85) 0%, rgba(167, 139, 250, 0.55) 100%); |
| } |
| ::-webkit-scrollbar-corner { |
| background: var(--surface); |
| } |
| |
| |
| nav { |
| position: sticky; |
| top: 0; |
| z-index: 100; |
| background: rgba(9,9,11,0.85); |
| backdrop-filter: blur(12px); |
| border-bottom: 1px solid var(--border); |
| padding: 0 max(24px, env(safe-area-inset-right, 0px)) 0 max(24px, env(safe-area-inset-left, 0px)); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| height: 56px; |
| } |
| .nav-logo { |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text); |
| letter-spacing: -0.3px; |
| margin-right: 16px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| .nav-logo .dot { |
| width: 8px; height: 8px; |
| background: var(--accent); |
| border-radius: 50%; |
| box-shadow: 0 0 8px var(--accent); |
| } |
| .nav-tabs { |
| display: flex; |
| gap: 2px; |
| flex: 1; |
| } |
| .nav-tab { |
| padding: 6px 14px; |
| border-radius: 6px; |
| border: none; |
| background: transparent; |
| color: var(--text3); |
| font-size: 13px; |
| font-weight: 500; |
| font-family: 'Inter', sans-serif; |
| cursor: pointer; |
| transition: all 0.15s ease; |
| letter-spacing: 0.01em; |
| } |
| .nav-tab:hover { color: var(--text2); background: var(--surface); } |
| .nav-tab.active { |
| color: var(--text); |
| background: var(--surface2); |
| } |
| .nav-right { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| .nav-toggle { |
| display: none; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| gap: 5px; |
| width: 44px; |
| height: 44px; |
| padding: 0; |
| margin: 0; |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| background: var(--surface); |
| cursor: pointer; |
| flex-shrink: 0; |
| -webkit-tap-highlight-color: transparent; |
| transition: border-color 0.15s, background 0.15s; |
| } |
| .nav-toggle:hover { |
| border-color: rgba(99, 102, 241, 0.45); |
| background: var(--surface2); |
| } |
| .nav-toggle span { |
| display: block; |
| width: 18px; |
| height: 2px; |
| border-radius: 1px; |
| background: var(--text2); |
| transition: transform 0.2s ease, opacity 0.2s ease; |
| } |
| nav.is-open .nav-toggle span:nth-child(1) { |
| transform: translateY(7px) rotate(45deg); |
| } |
| nav.is-open .nav-toggle span:nth-child(2) { |
| opacity: 0; |
| } |
| nav.is-open .nav-toggle span:nth-child(3) { |
| transform: translateY(-7px) rotate(-45deg); |
| } |
| .nav-backdrop { |
| display: none; |
| position: fixed; |
| inset: 0; |
| z-index: 99; |
| background: rgba(0, 0, 0, 0.55); |
| backdrop-filter: blur(4px); |
| -webkit-backdrop-filter: blur(4px); |
| } |
| .nav-backdrop.is-visible { |
| display: block; |
| } |
| .nav-badge { |
| padding: 3px 8px; |
| border-radius: 4px; |
| border: 1px solid var(--border); |
| font-size: 11px; |
| color: var(--text3); |
| font-family: 'JetBrains Mono', monospace; |
| } |
| .nav-badge.live { border-color: rgba(34,197,94,0.4); color: var(--green); } |
| .nav-badge.live::before { content: '● '; font-size: 9px; } |
| |
| |
| .panel { display: none; } |
| .panel.active { display: block; } |
| |
| |
| .hero { |
| max-width: 760px; |
| margin: 72px auto 0; |
| padding: 0 24px 48px; |
| } |
| .hero-tag { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| padding: 4px 10px; |
| border: 1px solid rgba(99,102,241,0.3); |
| border-radius: 20px; |
| font-size: 11px; |
| color: var(--accent); |
| background: var(--accent-glow); |
| margin-bottom: 20px; |
| letter-spacing: 0.05em; |
| text-transform: uppercase; |
| font-weight: 500; |
| } |
| .hero h1 { |
| font-size: 42px; |
| font-weight: 700; |
| line-height: 1.15; |
| letter-spacing: -1px; |
| color: var(--text); |
| margin-bottom: 16px; |
| } |
| .hero h1 span { color: var(--accent); } |
| .hero-sub { |
| font-size: 16px; |
| color: var(--text2); |
| max-width: 560px; |
| line-height: 1.6; |
| margin-bottom: 28px; |
| } |
| .hero-meta { |
| display: flex; |
| gap: 20px; |
| flex-wrap: wrap; |
| } |
| .meta-item { |
| display: flex; |
| flex-direction: column; |
| gap: 2px; |
| } |
| .meta-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.08em; } |
| .meta-value { font-size: 14px; font-weight: 500; color: var(--text2); font-family: 'JetBrains Mono', monospace; } |
| |
| |
| .blog-content { |
| max-width: 700px; |
| margin: 0 auto; |
| padding: 0 24px 100px; |
| } |
| .blog-section { |
| margin-bottom: 48px; |
| } |
| .blog-section h2 { |
| font-size: 22px; |
| font-weight: 600; |
| color: var(--text); |
| letter-spacing: -0.5px; |
| margin-bottom: 12px; |
| padding-bottom: 10px; |
| border-bottom: 1px solid var(--border); |
| } |
| .blog-section h3 { |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--text2); |
| margin: 20px 0 8px; |
| } |
| .blog-section p { |
| color: var(--text2); |
| margin-bottom: 14px; |
| font-size: 15px; |
| line-height: 1.75; |
| } |
| .blog-section code { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| border-radius: 4px; |
| padding: 1px 6px; |
| color: var(--accent); |
| } |
| .blog-section em { color: var(--text); font-style: italic; } |
| .callout { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-left: 3px solid var(--accent); |
| border-radius: 0 8px 8px 0; |
| padding: 16px 20px; |
| margin: 20px 0; |
| font-size: 14px; |
| color: var(--text2); |
| } |
| .callout.green { border-left-color: var(--green); } |
| .callout.orange { border-left-color: var(--orange); } |
| .author-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 20px 24px; |
| margin-bottom: 40px; |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| } |
| .author-avatar { |
| width: 44px; height: 44px; |
| border-radius: 50%; |
| background: linear-gradient(135deg, var(--accent), #8b5cf6); |
| display: flex; align-items: center; justify-content: center; |
| font-size: 18px; font-weight: 700; color: white; |
| flex-shrink: 0; |
| } |
| .author-name { font-size: 15px; font-weight: 600; color: var(--text); } |
| .author-role { font-size: 12px; color: var(--text3); margin-top: 2px; } |
| .formula-box { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| padding: 16px 20px; |
| margin: 20px 0; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| color: var(--text); |
| text-align: center; |
| } |
| .formula-box .highlight { color: var(--accent); } |
| .formula-box .green { color: var(--green); } |
| .formula-box .orange { color: var(--orange); } |
| |
| |
| .training-wrap { |
| max-width: 900px; |
| margin: 0 auto; |
| padding: 48px 24px 100px; |
| } |
| .training-header { |
| margin-bottom: 40px; |
| } |
| .training-header h2 { |
| font-size: 28px; |
| font-weight: 700; |
| letter-spacing: -0.5px; |
| color: var(--text); |
| margin-bottom: 8px; |
| } |
| .training-header p { |
| color: var(--text3); |
| font-size: 14px; |
| } |
| .stats-row { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); |
| gap: 12px; |
| margin-bottom: 40px; |
| } |
| .stat-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 16px 18px; |
| transition: border-color 0.2s; |
| } |
| .stat-card:hover { border-color: var(--accent); } |
| .stat-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; } |
| .stat-value { font-size: 22px; font-weight: 700; color: var(--text); font-family: 'JetBrains Mono', monospace; } |
| .stat-delta { font-size: 12px; margin-top: 4px; } |
| .stat-delta.up { color: var(--green); } |
| .stat-delta.neutral { color: var(--text3); } |
| |
| .chart-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 20px; |
| margin-bottom: 24px; |
| } |
| .chart-grid.single { grid-template-columns: 1fr; } |
| .chart-grid.three { grid-template-columns: 1fr 1fr 1fr; } |
| .chart-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 20px; |
| } |
| .chart-card h4 { |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text3); |
| text-transform: uppercase; |
| letter-spacing: 0.08em; |
| margin-bottom: 16px; |
| } |
| .chart-wrap { position: relative; height: 220px; } |
| .chart-wrap.tall { height: 280px; } |
| .pipeline-section { |
| margin-top: 40px; |
| } |
| .pipeline-section h3 { |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--text2); |
| margin-bottom: 16px; |
| letter-spacing: -0.3px; |
| } |
| .pipeline { |
| display: flex; |
| align-items: center; |
| gap: 0; |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| overflow: hidden; |
| margin-bottom: 20px; |
| } |
| .pipe-step { |
| flex: 1; |
| padding: 16px; |
| text-align: center; |
| border-right: 1px solid var(--border); |
| position: relative; |
| } |
| .pipe-step:last-child { border-right: none; } |
| .pipe-step .pipe-label { font-size: 11px; color: var(--text3); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.06em; } |
| .pipe-step .pipe-name { font-size: 15px; font-weight: 600; } |
| .pipe-step .pipe-detail { font-size: 11px; color: var(--text3); margin-top: 3px; font-family: 'JetBrains Mono', monospace; } |
| .pipe-step.sft .pipe-name { color: var(--green); } |
| .pipe-step.grpo .pipe-name { color: var(--accent); } |
| .pipe-step.dpo .pipe-name { color: var(--orange); } |
| .pipe-step.hf .pipe-name { color: var(--text2); } |
| .pipe-arrow { |
| width: 0; height: 0; |
| border-top: 6px solid transparent; |
| border-bottom: 6px solid transparent; |
| border-left: 8px solid var(--border); |
| margin: 0 -1px; |
| flex-shrink: 0; |
| z-index: 1; |
| } |
| .img-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 20px; |
| margin-bottom: 20px; |
| } |
| .img-card h4 { |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text3); |
| text-transform: uppercase; |
| letter-spacing: 0.08em; |
| margin-bottom: 14px; |
| } |
| .img-card img { |
| width: 100%; |
| border-radius: 6px; |
| filter: brightness(0.95); |
| } |
| .correlation-note { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 18px 20px; |
| margin-top: 20px; |
| font-size: 13px; |
| color: var(--text2); |
| line-height: 1.6; |
| } |
| .correlation-note strong { color: var(--text); } |
| .correlation-note .r-val { color: var(--accent); font-family: 'JetBrains Mono', monospace; font-size: 14px; font-weight: 600; } |
| |
| |
| .readme-wrap { |
| max-width: 760px; |
| margin: 0 auto; |
| padding: 48px 24px 100px; |
| } |
| .readme-wrap h2 { |
| font-size: 22px; |
| font-weight: 700; |
| color: var(--text); |
| letter-spacing: -0.5px; |
| margin: 36px 0 12px; |
| padding-bottom: 8px; |
| border-bottom: 1px solid var(--border); |
| } |
| .readme-wrap h2:first-child { margin-top: 0; } |
| .readme-wrap h3 { |
| font-size: 16px; |
| font-weight: 600; |
| color: var(--text2); |
| margin: 22px 0 8px; |
| } |
| .readme-wrap p { color: var(--text2); margin-bottom: 12px; font-size: 14px; line-height: 1.7; } |
| .readme-wrap code { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 12px; |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| border-radius: 4px; |
| padding: 1px 5px; |
| color: #a5b4fc; |
| } |
| .readme-wrap pre { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| padding: 16px 20px; |
| margin: 16px 0; |
| overflow-x: auto; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 12px; |
| color: var(--text2); |
| line-height: 1.6; |
| } |
| .readme-wrap pre code { |
| background: none; |
| border: none; |
| padding: 0; |
| color: inherit; |
| font-size: inherit; |
| } |
| .readme-table { |
| width: 100%; |
| border-collapse: collapse; |
| margin: 16px 0; |
| font-size: 13px; |
| } |
| .readme-table th { |
| background: var(--surface2); |
| padding: 8px 12px; |
| text-align: left; |
| font-weight: 600; |
| color: var(--text); |
| border: 1px solid var(--border); |
| } |
| .readme-table td { |
| padding: 8px 12px; |
| color: var(--text2); |
| border: 1px solid var(--border); |
| font-size: 13px; |
| } |
| .readme-table tr:nth-child(even) td { background: rgba(255,255,255,0.02); } |
| .readme-table td code { font-size: 11px; } |
| .readme-wrap ul { |
| margin: 8px 0 14px 0; |
| padding-left: 20px; |
| } |
| .readme-wrap li { |
| color: var(--text2); |
| margin-bottom: 6px; |
| font-size: 14px; |
| } |
| .readme-wrap li code { font-size: 11px; } |
| .contributions-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 10px; |
| margin: 16px 0; |
| } |
| .contrib-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| padding: 14px 16px; |
| font-size: 13px; |
| } |
| .contrib-num { |
| font-size: 11px; |
| font-weight: 700; |
| color: var(--accent); |
| font-family: 'JetBrains Mono', monospace; |
| margin-bottom: 4px; |
| } |
| .contrib-title { font-weight: 600; color: var(--text); margin-bottom: 2px; } |
| .contrib-desc { color: var(--text3); font-size: 12px; } |
| .results-table-wrap { |
| margin: 16px 0; |
| } |
| .results-table-wrap table { width: 100%; border-collapse: collapse; font-size: 13px; } |
| .results-table-wrap th { |
| background: var(--surface2); |
| padding: 9px 12px; |
| text-align: left; |
| font-weight: 600; |
| color: var(--text); |
| border: 1px solid var(--border); |
| } |
| .results-table-wrap td { |
| padding: 8px 12px; |
| color: var(--text2); |
| border: 1px solid var(--border); |
| } |
| .results-table-wrap td.before { color: var(--text3); } |
| .results-table-wrap td.after { color: var(--green); font-weight: 500; } |
| .results-table-wrap td.neutral { color: var(--text2); } |
| |
| |
| .space-wrap { |
| max-width: 700px; |
| margin: 0 auto; |
| padding: 80px 24px 100px; |
| text-align: center; |
| } |
| .space-icon { |
| width: 64px; height: 64px; |
| background: var(--accent-glow); |
| border: 1px solid rgba(99,102,241,0.3); |
| border-radius: 16px; |
| display: flex; align-items: center; justify-content: center; |
| font-size: 28px; |
| margin: 0 auto 24px; |
| } |
| .space-wrap h2 { |
| font-size: 24px; |
| font-weight: 700; |
| color: var(--text); |
| margin-bottom: 10px; |
| letter-spacing: -0.5px; |
| } |
| .space-wrap p { color: var(--text2); font-size: 15px; margin-bottom: 8px; } |
| .link-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 12px; |
| margin: 32px 0; |
| text-align: left; |
| } |
| .link-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| padding: 18px 20px; |
| text-decoration: none; |
| transition: border-color 0.15s, transform 0.15s; |
| display: block; |
| } |
| .link-card:hover { border-color: var(--accent); transform: translateY(-2px); } |
| .link-card .lc-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: 0.07em; margin-bottom: 6px; } |
| .link-card .lc-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 4px; } |
| .link-card .lc-url { font-size: 11px; color: var(--accent); font-family: 'JetBrains Mono', monospace; word-break: break-all; } |
| .endpoint-list { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 10px; |
| overflow: hidden; |
| margin: 20px 0; |
| text-align: left; |
| } |
| .endpoint-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 12px 16px; |
| border-bottom: 1px solid var(--border); |
| font-size: 13px; |
| } |
| .endpoint-item:last-child { border-bottom: none; } |
| .method { |
| font-size: 10px; |
| font-weight: 700; |
| padding: 3px 7px; |
| border-radius: 4px; |
| font-family: 'JetBrains Mono', monospace; |
| min-width: 40px; |
| text-align: center; |
| } |
| .method.get { background: rgba(34,197,94,0.15); color: var(--green); border: 1px solid rgba(34,197,94,0.3); } |
| .method.post { background: rgba(99,102,241,0.15); color: var(--accent); border: 1px solid rgba(99,102,241,0.3); } |
| .endpoint-path { font-family: 'JetBrains Mono', monospace; color: var(--text); font-size: 12px; flex: 1; } |
| .endpoint-desc { color: var(--text3); font-size: 12px; } |
| |
| |
| #panel-experience { |
| --x-glow: rgba(99, 102, 241, 0.35); |
| --x-warn: #f97316; |
| --x-ok: #22c55e; |
| } |
| .x-wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px 80px; } |
| .x-hero { |
| position: relative; |
| text-align: center; |
| padding: 48px 0 36px; |
| overflow: hidden; |
| } |
| .x-hero::before { |
| content: ''; |
| position: absolute; |
| inset: -40% -20%; |
| background: |
| radial-gradient(ellipse 50% 40% at 30% 30%, rgba(99,102,241,0.14), transparent), |
| radial-gradient(ellipse 45% 35% at 70% 60%, rgba(236,72,153,0.10), transparent), |
| radial-gradient(ellipse 40% 30% at 50% 80%, rgba(34,197,94,0.06), transparent); |
| animation: xMesh 14s ease-in-out infinite alternate; |
| pointer-events: none; |
| z-index: 0; |
| } |
| @keyframes xMesh { |
| 0% { transform: translate(0, 0) scale(1); opacity: 1; } |
| 100% { transform: translate(-3%, 2%) scale(1.05); opacity: 0.85; } |
| } |
| .x-hero > * { position: relative; z-index: 1; } |
| .x-eyebrow { |
| font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; |
| color: var(--accent); margin-bottom: 14px; |
| animation: xFadeUp 0.7s ease both; |
| } |
| .x-hero h1 { |
| font-family: 'Outfit', system-ui, sans-serif; |
| font-size: clamp(2rem, 5vw, 3.1rem); |
| font-weight: 700; |
| letter-spacing: -0.04em; |
| line-height: 1.12; |
| margin-bottom: 16px; |
| background: linear-gradient(120deg, #fafafa 0%, #a5b4fc 45%, #f472b6 100%); |
| -webkit-background-clip: text; |
| background-clip: text; |
| -webkit-text-fill-color: transparent; |
| animation: xFadeUp 0.75s ease 0.08s both; |
| } |
| .x-lead { |
| color: var(--text2); |
| font-size: 1.05rem; |
| max-width: 560px; |
| margin: 0 auto; |
| line-height: 1.65; |
| animation: xFadeUp 0.8s ease 0.15s both; |
| } |
| @keyframes xFadeUp { |
| from { opacity: 0; transform: translateY(14px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .x-stage { |
| display: grid; |
| grid-template-columns: 1fr minmax(72px, 100px) 1fr; |
| gap: 12px; |
| align-items: center; |
| margin: 32px 0 28px; |
| min-height: 168px; |
| } |
| |
| @media (max-width: 768px) { |
| .x-stage { |
| grid-template-columns: 1fr; |
| gap: 14px; |
| min-height: 0; |
| } |
| .x-beam-wrap { |
| transform: none; |
| min-height: 16px; |
| margin: 2px 0; |
| } |
| .x-beam { |
| width: min(148px, 42vw); |
| max-width: 148px; |
| margin-left: auto; |
| margin-right: auto; |
| } |
| } |
| .x-node { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 16px; |
| padding: 20px 22px; |
| transition: border-color 0.35s ease, box-shadow 0.35s ease, transform 0.35s ease; |
| } |
| .x-node.x-pulse { |
| border-color: rgba(99,102,241,0.45); |
| box-shadow: 0 0 40px rgba(99,102,241,0.12); |
| } |
| .x-node-label { font-size: 10px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text3); margin-bottom: 6px; } |
| .x-node-title { font-size: 1rem; font-weight: 600; color: var(--text); margin-bottom: 4px; } |
| .x-node-sub { font-size: 12px; color: var(--text3); line-height: 1.45; } |
| .x-beam-wrap { display: flex; align-items: center; justify-content: center; position: relative; } |
| .x-beam { |
| height: 4px; width: 100%; border-radius: 4px; |
| background: linear-gradient(90deg, rgba(99,102,241,0.15), var(--accent), rgba(236,72,153,0.5)); |
| position: relative; |
| overflow: hidden; |
| } |
| .x-beam::after { |
| content: ''; |
| position: absolute; |
| inset: 0; |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent); |
| animation: xShine 2.2s ease-in-out infinite; |
| } |
| @keyframes xShine { |
| 0% { transform: translateX(-100%); } |
| 100% { transform: translateX(100%); } |
| } |
| .x-beam.x-drift { |
| animation: xDriftPulse 0.6s ease 2; |
| box-shadow: 0 0 20px rgba(249,115,22,0.5); |
| } |
| @keyframes xDriftPulse { |
| 0%, 100% { filter: brightness(1); } |
| 50% { filter: brightness(1.5) hue-rotate(-15deg); } |
| } |
| .x-controls { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-bottom: 28px; } |
| .x-btn { |
| font-family: inherit; |
| font-size: 13px; |
| font-weight: 600; |
| padding: 10px 20px; |
| border-radius: 10px; |
| border: 1px solid var(--border); |
| cursor: pointer; |
| transition: transform 0.15s, border-color 0.15s, background 0.15s; |
| } |
| .x-btn-primary { |
| background: var(--accent); |
| color: #fff; |
| border-color: transparent; |
| } |
| .x-btn-primary:hover { transform: translateY(-1px); filter: brightness(1.05); } |
| .x-btn-ghost { background: var(--surface); color: var(--text2); } |
| .x-btn-ghost:hover { border-color: var(--accent); color: var(--text); } |
| .x-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; } |
| .x-split { |
| display: grid; |
| grid-template-columns: 1fr 1.1fr; |
| gap: 20px; |
| align-items: start; |
| } |
| @media (max-width: 900px) { .x-split { grid-template-columns: 1fr; } } |
| .x-steps { display: flex; flex-direction: column; gap: 10px; } |
| .x-card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 12px; |
| padding: 14px 16px; |
| text-align: left; |
| transition: opacity 0.35s, transform 0.35s, border-color 0.35s; |
| opacity: 0.38; |
| transform: scale(0.985); |
| } |
| .x-card.x-active { |
| opacity: 1; |
| transform: scale(1); |
| border-color: rgba(99,102,241,0.4); |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); |
| } |
| .x-card.x-done { opacity: 0.65; transform: scale(1); border-color: rgba(34,197,94,0.25); } |
| .x-card-top { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } |
| .x-card-n { |
| font-size: 10px; font-weight: 800; |
| width: 22px; height: 22px; border-radius: 6px; |
| background: rgba(99,102,241,0.15); color: var(--accent); |
| display: flex; align-items: center; justify-content: center; |
| } |
| .x-card h4 { font-size: 14px; font-weight: 600; color: var(--text); } |
| .x-card p { font-size: 12px; color: var(--text3); line-height: 1.5; margin-top: 4px; } |
| .x-tag { |
| font-size: 10px; font-weight: 600; |
| padding: 2px 8px; border-radius: 999px; |
| font-family: 'JetBrains Mono', monospace; |
| margin-left: auto; |
| } |
| .x-tag.ok { background: rgba(34,197,94,0.15); color: var(--green); } |
| .x-tag.err { background: rgba(249,115,22,0.15); color: var(--orange); } |
| .x-tag.info { background: rgba(99,102,241,0.12); color: var(--accent); } |
| .x-terminal { |
| background: #0c0c0f; |
| border: 1px solid #27272a; |
| border-radius: 14px; |
| padding: 18px; |
| min-height: 280px; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 11px; |
| line-height: 1.55; |
| color: #a1a1aa; |
| position: relative; |
| overflow: hidden; |
| } |
| .x-terminal-hdr { |
| display: flex; align-items: center; gap: 8px; |
| margin-bottom: 14px; |
| padding-bottom: 10px; |
| border-bottom: 1px solid #27272a; |
| font-size: 10px; |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| color: #71717a; |
| } |
| .x-terminal-hdr span.dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; } |
| .x-term-line { margin-bottom: 10px; white-space: pre-wrap; word-break: break-word; } |
| .x-term-k { color: #a78bfa; } |
| .x-term-s { color: #4ade80; } |
| .x-term-e { color: #fb923c; } |
| .x-live-out { |
| margin: 0 0 28px; |
| padding: 18px 20px; |
| background: linear-gradient(145deg, rgba(24, 24, 27, 0.98), rgba(39, 39, 42, 0.35)); |
| border: 1px solid rgba(99, 102, 241, 0.22); |
| border-radius: 14px; |
| font-size: 13px; |
| color: var(--text2); |
| box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35); |
| transition: border-color 0.25s ease, box-shadow 0.25s ease; |
| } |
| .x-live-out.x-live-busy { |
| border-color: rgba(99, 102, 241, 0.45); |
| box-shadow: 0 0 0 1px rgba(99, 102, 241, 0.12), 0 16px 48px rgba(99, 102, 241, 0.08); |
| } |
| .x-live-out.x-live-done { |
| border-color: rgba(34, 197, 94, 0.28); |
| } |
| .x-live-out.x-live-fail { |
| border-color: rgba(249, 115, 22, 0.35); |
| } |
| .x-live-head { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| gap: 12px; |
| flex-wrap: wrap; |
| margin-bottom: 10px; |
| } |
| .x-live-head strong { color: var(--text); } |
| .x-live-badge { |
| font-size: 10px; |
| font-weight: 700; |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| padding: 4px 10px; |
| border-radius: 999px; |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| color: var(--text3); |
| } |
| .x-live-badge.on { color: var(--accent); border-color: rgba(99, 102, 241, 0.4); background: var(--accent-glow); } |
| .x-live-badge.ok { color: var(--green); border-color: rgba(34, 197, 94, 0.35); background: rgba(34, 197, 94, 0.1); } |
| .x-live-badge.err { color: var(--orange); border-color: rgba(249, 115, 22, 0.35); background: rgba(249, 115, 22, 0.08); } |
| .x-live-hint { |
| font-size: 12px; |
| color: var(--text3); |
| line-height: 1.55; |
| margin-bottom: 8px; |
| } |
| .x-live-hint code { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 11px; |
| background: var(--surface2); |
| padding: 1px 6px; |
| border-radius: 4px; |
| color: var(--accent); |
| } |
| .x-live-status { |
| font-size: 12px; |
| color: var(--text2); |
| margin-bottom: 10px; |
| min-height: 1.2em; |
| } |
| .x-live-status em { color: var(--text); font-style: normal; font-weight: 600; } |
| .x-live-out pre { |
| margin: 0; |
| padding: 14px 16px; |
| background: #0c0c0f; |
| border: 1px solid #27272a; |
| border-radius: 10px; |
| overflow: auto; |
| font-size: 11px; |
| max-height: 260px; |
| line-height: 1.45; |
| font-family: 'JetBrains Mono', monospace; |
| color: #a1a1aa; |
| scrollbar-width: thin; |
| scrollbar-color: rgba(99, 102, 241, 0.6) #18181b; |
| } |
| @media (prefers-reduced-motion: reduce) { |
| .x-hero::before, .x-beam::after, .x-beam.x-drift { animation: none; } |
| .x-card, .x-node { transition: none; } |
| .nav-tabs { transition: none; } |
| .nav-toggle span { transition: none; } |
| } |
| |
| |
| img, video { max-width: 100%; height: auto; } |
| html { overflow-x: clip; } |
| |
| @media (max-width: 900px) { |
| .chart-grid, |
| .chart-grid.three { grid-template-columns: 1fr !important; } |
| .contributions-grid { grid-template-columns: 1fr; } |
| .link-grid { grid-template-columns: 1fr; } |
| .training-wrap { padding: 32px 16px 72px; } |
| .training-header h2 { font-size: clamp(1.35rem, 5vw, 1.75rem); } |
| .readme-wrap { padding: 32px 16px 72px; } |
| .blog-content { padding: 0 16px 72px; } |
| .hero { margin-top: 40px; padding: 0 16px 40px; } |
| .space-wrap { padding: 48px 16px 72px; } |
| .results-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: 8px; margin-left: -4px; margin-right: -4px; padding: 0 4px; } |
| .results-table-wrap table { min-width: 480px; } |
| .readme-wrap .table-scroll { |
| overflow-x: auto; |
| -webkit-overflow-scrolling: touch; |
| margin: 12px 0; |
| border-radius: 8px; |
| border: 1px solid var(--border); |
| } |
| .readme-wrap .table-scroll table { margin: 0; min-width: 520px; } |
| } |
| |
| @media (max-width: 768px) { |
| body { font-size: 14px; } |
| |
| nav { |
| padding: 10px 12px; |
| height: auto; |
| min-height: 52px; |
| flex-wrap: nowrap; |
| align-items: center; |
| position: relative; |
| z-index: 100; |
| } |
| .nav-logo { |
| font-size: 13px; |
| margin-right: 8px; |
| flex: 0 0 auto; |
| } |
| .nav-right { |
| margin-left: auto; |
| flex-shrink: 0; |
| } |
| .nav-toggle { |
| display: inline-flex; |
| margin-left: 8px; |
| order: 4; |
| } |
| .nav-logo { order: 1; } |
| .nav-right { order: 3; } |
| .nav-tabs { |
| order: 5; |
| flex: none; |
| position: absolute; |
| top: 100%; |
| left: 0; |
| right: 0; |
| flex-direction: column; |
| align-items: stretch; |
| flex-wrap: nowrap; |
| gap: 2px; |
| max-height: 0; |
| overflow: hidden; |
| opacity: 0; |
| visibility: hidden; |
| padding: 0 12px; |
| margin: 0; |
| background: rgba(12, 12, 15, 0.98); |
| border-bottom: 1px solid transparent; |
| border-top: 1px solid var(--border); |
| box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45); |
| transition: max-height 0.32s ease, opacity 0.22s ease, padding 0.22s ease, visibility 0.22s; |
| } |
| nav.is-open .nav-tabs { |
| max-height: min(420px, 70vh); |
| opacity: 1; |
| visibility: visible; |
| padding: 10px 12px 16px; |
| overflow-y: auto; |
| border-bottom-color: var(--border); |
| -webkit-overflow-scrolling: touch; |
| } |
| .nav-tabs::-webkit-scrollbar { width: 4px; } |
| .nav-tab { |
| width: 100%; |
| text-align: left; |
| padding: 12px 16px; |
| font-size: 14px; |
| white-space: normal; |
| border-radius: 10px; |
| } |
| .nav-tab.active { |
| background: var(--surface2); |
| box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.35); |
| } |
| |
| .hero h1 { font-size: clamp(1.65rem, 7.5vw, 2.25rem); letter-spacing: -0.03em; } |
| .author-card { flex-wrap: wrap; padding: 16px; } |
| |
| .pipeline { |
| flex-direction: column; |
| align-items: stretch; |
| } |
| .pipe-step { |
| border-right: none; |
| border-bottom: 1px solid var(--border); |
| padding: 14px 16px; |
| text-align: left; |
| } |
| .pipe-step:last-child { border-bottom: none; } |
| |
| .stat-card { padding: 14px; } |
| .stat-value { font-size: 18px; } |
| |
| .chart-card { padding: 14px; } |
| .chart-wrap.tall { height: 240px; } |
| |
| .endpoint-item { |
| flex-wrap: wrap; |
| gap: 8px; |
| } |
| .endpoint-desc { flex: 1 1 100%; font-size: 11px; } |
| |
| .x-wrap { padding: 0 16px 56px; } |
| .x-hero { padding: 28px 0 24px; } |
| .x-lead { font-size: 0.95rem; padding: 0 4px; } |
| .x-controls { flex-direction: column; align-items: stretch; gap: 8px; } |
| .x-controls .x-btn { width: 100%; justify-content: center; text-align: center; } |
| .x-live-out { padding: 14px 16px; } |
| .x-node { padding: 16px; } |
| .x-card h4 { font-size: 13px; } |
| .x-card-top { flex-wrap: wrap; align-items: flex-start; } |
| .x-card-top .x-tag { margin-left: 0; } |
| .x-terminal { min-height: 220px; padding: 14px; font-size: 10px; } |
| } |
| |
| @media (max-width: 480px) { |
| .nav-badge { font-size: 10px; padding: 2px 6px; } |
| .formula-box { font-size: 11px; padding: 12px 14px; } |
| .blog-section h2 { font-size: 18px; } |
| .readme-wrap h2 { font-size: 18px; } |
| .space-wrap h2 { font-size: 1.35rem; } |
| .x-card-top h4 { flex: 1 1 calc(100% - 32px); min-width: 0; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <nav id="site-nav"> |
| <div class="nav-logo"> |
| <div class="dot"></div> |
| AdaptiveWorldEnv |
| </div> |
| <div class="nav-tabs" id="nav-tablist" role="tablist" aria-label="Main sections"> |
| <button type="button" class="nav-tab active" role="tab" aria-selected="true" onclick="switchTab('experience', this)">Experience</button> |
| <button type="button" class="nav-tab" role="tab" aria-selected="false" onclick="switchTab('blog', this)">Blog</button> |
| <button type="button" class="nav-tab" role="tab" aria-selected="false" onclick="switchTab('training', this)">Training Runs</button> |
| <button type="button" class="nav-tab" role="tab" aria-selected="false" onclick="switchTab('readme', this)">README</button> |
| <button type="button" class="nav-tab" role="tab" aria-selected="false" onclick="switchTab('space', this)">Space / API</button> |
| </div> |
| <div class="nav-right"> |
| <span class="nav-badge live">v2.0</span> |
| <span class="nav-badge">OpenEnv</span> |
| </div> |
| <button type="button" class="nav-toggle" id="navToggle" aria-label="Open menu" aria-expanded="false" aria-controls="nav-tablist" onclick="toggleNavDrawer(event)"> |
| <span></span><span></span><span></span> |
| </button> |
| </nav> |
| <div class="nav-backdrop" id="navBackdrop" onclick="closeNavDrawer()" aria-hidden="true"></div> |
|
|
| |
| <div id="panel-experience" class="panel active"> |
| <div class="x-wrap"> |
| <header class="x-hero"> |
| <p class="x-eyebrow">First-hand · No account required</p> |
| <h1>The world changes underneath you</h1> |
| <p class="x-lead"> |
| Watch a real <strong style="color:var(--text);font-weight:600">easy_field_rename</strong> episode: two successful orders, then the API silently renames <code>qty</code> → <code>quantity</code> and requires <code>customer_id</code>. There is no announcement — only evidence. |
| </p> |
| </header> |
|
|
| <div class="x-stage"> |
| <div class="x-node" id="xNodeAgent"> |
| <div class="x-node-label">Agent</div> |
| <div class="x-node-title">Multi-step policy</div> |
| <div class="x-node-sub">Repeats the last known good request until reality disagrees.</div> |
| </div> |
| <div class="x-beam-wrap"> |
| <div class="x-beam" id="xBeam" aria-hidden="true"></div> |
| </div> |
| <div class="x-node" id="xNodeApi"> |
| <div class="x-node-label">Mock API</div> |
| <div class="x-node-title">POST /mock_api/orders</div> |
| <div class="x-node-sub" id="xApiSub">Dynamic schema — mutates at step 3 of the episode.</div> |
| </div> |
| </div> |
|
|
| <div class="x-controls"> |
| <button type="button" class="x-btn x-btn-primary" id="xPlayBtn">Play walkthrough</button> |
| <button type="button" class="x-btn x-btn-ghost" id="xResetBtn">Reset</button> |
| <button type="button" class="x-btn x-btn-ghost" id="xLiveBtn">Run on this server</button> |
| </div> |
|
|
| <div class="x-live-out" id="xLiveOut"> |
| <div class="x-live-head"> |
| <strong>Live <code>/run_episode</code></strong> |
| <span class="x-live-badge" id="xLiveBadge">Ready</span> |
| </div> |
| <p class="x-live-hint"> |
| Calls your FastAPI app on the <strong>same host and port</strong> as this page. |
| Opening the file directly (<code>file://</code>) will not work — use |
| <code>http://127.0.0.1:7860/</code> after <code>uvicorn server.app:app --port 7860</code>. |
| </p> |
| <p class="x-live-status" id="xLiveStatus">Click the button above to run a real <code>easy_field_rename</code> episode.</p> |
| <pre id="xLivePre">// JSON response will appear here</pre> |
| </div> |
|
|
| <div class="x-split"> |
| <div class="x-steps" id="xSteps" aria-live="polite"></div> |
| <div class="x-terminal" id="xTerminal"> |
| <div class="x-terminal-hdr"><span class="dot"></span> Episode trace — ecommerce order</div> |
| <div id="xTermBody"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="panel-blog" class="panel"> |
| <div class="hero"> |
| <div class="hero-tag">✦ Hackathon Write-up</div> |
| <h1>Teaching Agents to<br><span>Understand Change</span></h1> |
| <p class="hero-sub"> |
| What happens when the world your agent is operating in mutates mid-episode — no warning, no changelog? This is the problem AdaptiveWorldEnv was built to solve. |
| </p> |
| <div class="hero-meta"> |
| <div class="meta-item"> |
| <span class="meta-label">Author</span> |
| <span class="meta-value">ProthamD</span> |
| </div> |
| <div class="meta-item"> |
| <span class="meta-label">Theme</span> |
| <span class="meta-value">3.1 + 5 (Wild Card)</span> |
| </div> |
| <div class="meta-item"> |
| <span class="meta-label">Model</span> |
| <span class="meta-value">Qwen2.5-3B</span> |
| </div> |
| <div class="meta-item"> |
| <span class="meta-label">Pipeline</span> |
| <span class="meta-value">SFT → GRPO → DPO</span> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="blog-content"> |
|
|
| <div class="author-card"> |
| <div class="author-avatar">P</div> |
| <div> |
| <div class="author-name">Pratham</div> |
| <div class="author-role">3rd year B.Tech (IT), IIEST Shibpur · OpenEnv Hackathon</div> |
| </div> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>The Problem Nobody Talks About</h2> |
| <p>Almost all of us have seen a Google Assistant demo — it schedules appointments, makes calls, books things. But when you say <em>"book a flight, also a hotel, and buy some products"</em> — it falls apart. There are several reasons. One of the biggest: the APIs agents depend on <strong>change</strong>, silently, mid-operation. No warning. No changelog. The agent has no idea.</p> |
| <div class="callout"> |
| Suppose our agent is halfway through booking a flight and the API it was just successfully talking to decides to rename a field, or version its endpoint, or silently change what a response value <em>means</em>. No error — sometimes not even a 4xx. Just a 200 OK with a different meaning. And the agent keeps going, completely wrong, with full confidence. |
| </div> |
| <p>This is what AdaptiveWorldEnv is about.</p> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>The Solution: A Picture of the World</h2> |
| <p>The solution I came up with is from a real-world reference — the way all of us work. When we do something wrong we figure out how we could have done it better. When playing a game, we don't know what the next level will bring. We go in, take actions, win or lose, and then figure out what it actually needed. We build a picture of the world in our head as we go.</p> |
| <p>So that is exactly what I gave the agent — a picture of the world. And to measure how accurate that picture is, I introduced a metric other than just REWARD:</p> |
| <div class="formula-box"> |
| <span class="highlight">combined_reward</span> = <span class="green">0.7 × task_reward</span> + <span class="orange">0.3 × belief_accuracy</span> |
| </div> |
| <p><code>task_reward</code> tells us <em>if</em> the agent completed the task. <code>belief_accuracy</code> tells us <em>if the agent actually understood what was happening</em>. Those two things are not always the same — an agent can get lucky and complete a task with a completely wrong understanding of the world, and you'd never know from reward alone.</p> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>How It Actually Works</h2> |
| <p>The environment gives the agent a task — place an order, book a flight, file a claim. The agent starts calling APIs, getting responses. Simple enough. But at some point mid-episode, without telling the agent, the world mutates.</p> |
| <div class="callout orange"> |
| A field gets renamed. An endpoint gets versioned. A policy flips. Or the scariest one — the API returns 200 OK, same endpoint, same call, but the meaning of a response value has silently changed. No error. Nothing. Just wrong. |
| </div> |
| <p>The agent has to figure it out — not because somebody told it something changed, but because it starts seeing things that don't match what it knew before. So it calls <code>query_history</code>, compares responses, calls <code>probe_schema</code> to check what the API actually expects today, updates its picture of the world, and retries.</p> |
| <h3>Two Types of Agents</h3> |
| <p>Suppose the agent is doing an e-commerce order. Step 1 and 2 go fine. Step 3 — the world drifts. <code>qty</code> becomes <code>quantity</code>, a new required field <code>customer_id</code> appears. Agent gets a 422.</p> |
| <p>Agent A just retries slightly differently and somehow gets through. Task reward goes up. But belief accuracy is low — it never figured out what changed.</p> |
| <p>Agent B calls <code>query_history</code>, compares step 2 response to step 4 response, calls <code>probe_schema</code>, finds the new field names, updates its belief, retries correctly. Task reward goes up. Belief accuracy also goes up. <strong>That's the agent we're training.</strong></p> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>Adaptive Difficulty</h2> |
| <p>The environment doesn't stay at one difficulty. When the agent gets good at handling a single drift — say a field rename — the environment automatically starts combining drift types in the same episode. Field rename plus a policy change at the same time. Then it starts hiding error signals more. Then it forces silent semantic drift where there's no 4xx anywhere. Difficulty escalates based on how well the agent is actually understanding the world, not just completing tasks.</p> |
| <div class="callout green"> |
| The agent's world model does not reset between episodes. Whatever the agent learned about how this API behaves — it carries that forward. Just like how when you go back to a game after a break you still remember the patterns. Except here that memory can also hurt you, because the world might have changed. |
| </div> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>The Training Pipeline: SFT → GRPO → DPO</h2> |
| <p>The training pipeline has three stages and understanding all of them is important because most people just throw a model into RL and wonder why it's not learning.</p> |
| <h3>Stage 1: SFT (30 steps, lr=5e-5)</h3> |
| <p>I downloaded a dataset of correct behavior examples — broken API call, agent calling <code>probe_schema</code>, comparing history, corrected call with right field name, declared belief state. Basically showing the model what good looks like before asking it to figure out good on its own. Think of it like watching someone else play the game before you pick up the controller.</p> |
| <h3>Stage 2: GRPO (200 steps, lr=2e-6)</h3> |
| <p>Once the model had that baseline I switched to <strong>GRPO — group relative policy optimization</strong>. The model starts generating its own actions, those actions get sent to the environment, and the reward function scores on both <code>task_reward</code> and <code>belief_accuracy</code>.</p> |
| <h3>Combating Reward Hacking</h3> |
| <p>The model could just always output <code>probe_schema</code> because that always returns a small positive reward (0.05) without doing anything meaningful. Or it could guess the endpoint correctly and get high task reward with a completely wrong belief state.</p> |
| <p>So: the combined reward weights belief accuracy at 0.3 — you can't fully hack one without the other. Plus the reward function has a local scorer checking actual content — did the <code>field_name</code> in the belief state match the ground truth, did the model actually declare <code>drift_detected: true</code>. And <code>max_grad_norm=0.2</code> is tight — prevents collapse into one repetitive strategy.</p> |
| <h3>Stage 3: DPO (~50 steps)</h3> |
| <p>During GRPO, every generation saves the prompt, completion, and reward. Those rollouts become preference pairs for DPO — high-reward completions as chosen, low-reward as rejected. About 50 steps of DPO on top of GRPO, then pushed the final model to HuggingFace.</p> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>The Results: Honest Numbers</h2> |
| <p>The most important chart is the correlation. Early training still looks messy if we only watch raw step-by-step samples — four GRPO completions per step means some draws look lucky even when belief is wrong. On smoothed rolling windows, an early segment already sits around <strong>r≈0.957</strong>, and late training pushes to about <strong>r≈0.977</strong> in this run. The agent is succeeding <em>because</em> it understood what changed, not despite misunderstanding it. That shift is the whole point of this environment.</p> |
| <div class="callout"> |
| Combined reward averaged about <strong>0.41</strong> late vs ~<strong>0.30</strong> early, task reward about <strong>0.49</strong> vs ~<strong>0.35</strong>, belief accuracy about <strong>0.23</strong> vs ~<strong>0.16</strong> — measured from the same 200-step GRPO run (4 completions/step). Raw traces stay noisy; smoothed curves and drift-detection rate show stabilization toward the end. |
| </div> |
| <p>Most environments just show task reward going up and call it done. Here we can see that task reward going up means nothing if belief accuracy is not following. The model needs to earn both. r≈0.977 is not the ceiling — it is the headline measured correlation from this submission run.</p> |
| </div> |
|
|
| <div class="blog-section"> |
| <h2>Post Hackathon — Honest Assessment</h2> |
| <p>I tried to go beyond traditional RL and ran into several failures during the hackathon window. I retrained many times, burned through most compute credits, and went through a lot of iteration before landing on a training script that held together.</p> |
| <p>My honest estimate: we are probably looking at needing 1000+ GRPO steps, and only after that would DPO on top start producing really significant improvement. The environment is hard, the reward signal is sparse in early steps, and the model needs a lot more exploration before it can start exploiting what it has learned.</p> |
| <div class="callout green"> |
| The dual reward signal, belief tracking, three stage pipeline — all of that is sound. It just needs more runway than a hackathon gives. If the judges think this is worth going deeper into — I'm genuinely willing to take that opportunity. |
| </div> |
| </div> |
|
|
| </div> |
| </div> |
|
|
| |
| <div id="panel-training" class="panel"> |
| <div class="training-wrap"> |
| <div class="training-header"> |
| <h2>Training Runs</h2> |
| <p>SFT → GRPO → DPO pipeline · Qwen 2.5 3B (4-bit) · 200 GRPO steps · 4 completions/step</p> |
| </div> |
|
|
| <div class="stats-row"> |
| <div class="stat-card" style="border-color:rgba(99,102,241,0.5);"> |
| <div class="stat-label">Corr. r (late)</div> |
| <div id="statCorrLate" class="stat-value" style="color:var(--accent);">—</div> |
| <div id="statCorrDelta" class="stat-delta up">—</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Combined reward</div> |
| <div id="statCombinedAvg" class="stat-value">—</div> |
| <div id="statCombinedDelta" class="stat-delta up">—</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Task reward (avg)</div> |
| <div id="statTaskAvg" class="stat-value">—</div> |
| <div id="statTaskDelta" class="stat-delta neutral">—</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Belief acc. (avg)</div> |
| <div id="statBeliefAvg" class="stat-value">—</div> |
| <div id="statBeliefDelta" class="stat-delta neutral">—</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">GRPO Steps</div> |
| <div id="statSteps" class="stat-value">200</div> |
| <div class="stat-delta neutral">+ 30 SFT + 50 DPO</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-label">Model</div> |
| <div class="stat-value" style="font-size:13px;margin-top:2px;">Qwen2.5-3B</div> |
| <div class="stat-delta neutral">4-bit quant</div> |
| </div> |
| </div> |
|
|
| |
| <div class="pipeline-section"> |
| <h3>Training Pipeline</h3> |
| <div class="pipeline"> |
| <div class="pipe-step sft"> |
| <div class="pipe-label">Stage 1</div> |
| <div class="pipe-name">SFT</div> |
| <div class="pipe-detail">30 steps · lr=5e-5</div> |
| </div> |
| <div class="pipe-step grpo"> |
| <div class="pipe-label">Stage 2</div> |
| <div class="pipe-name">GRPO</div> |
| <div class="pipe-detail">200 steps · lr=2e-6</div> |
| </div> |
| <div class="pipe-step dpo"> |
| <div class="pipe-label">Stage 3</div> |
| <div class="pipe-name">DPO</div> |
| <div class="pipe-detail">~50 steps · rollouts</div> |
| </div> |
| <div class="pipe-step hf"> |
| <div class="pipe-label">Output</div> |
| <div class="pipe-name">HF Hub</div> |
| <div class="pipe-detail">ProthamD/awe-model</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="chart-grid"> |
| <div class="chart-card"> |
| <h4>Raw Training Signals — Task vs Belief (per step)</h4> |
| <div class="chart-wrap"> |
| <canvas id="rawChart"></canvas> |
| </div> |
| </div> |
| <div class="chart-card"> |
| <h4>Smoothed Metrics + Difficulty Transition</h4> |
| <div class="chart-wrap"> |
| <canvas id="smoothedChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="chart-grid"> |
| <div class="chart-card"> |
| <h4>Task vs Belief Correlation — Early (pink) vs Late (green)</h4> |
| <div class="chart-wrap"> |
| <canvas id="scatterChart"></canvas> |
| </div> |
| </div> |
| <div class="chart-card"> |
| <h4>Before vs After Training (first 20 / last 20 steps)</h4> |
| <div class="chart-wrap"> |
| <canvas id="beforeAfterChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="chart-grid"> |
| <div class="chart-card"> |
| <h4>Combined Reward over Training</h4> |
| <div class="chart-wrap"> |
| <canvas id="combinedChart"></canvas> |
| </div> |
| </div> |
| <div class="chart-card"> |
| <h4>Reward Distribution — Early vs Late</h4> |
| <div class="chart-wrap"> |
| <canvas id="distChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="img-card" style="margin-top:12px;"> |
| <h4>Actual Training Output — GRPO Step-by-Step (200 steps)</h4> |
| <img src="/static/_a_final_training_img.png" alt="Training runs dashboard — SFT → GRPO → DPO (final run)" /> |
| </div> |
|
|
| <div class="correlation-note"> |
| <strong>The headline numbers:</strong> Rolling correlation between task_reward and belief_accuracy reaches about <span class="r-val">r=0.977</span> late vs ~<span class="r-val">0.957</span> in an early window on this run. Combined reward averages ~<span class="r-val">0.41</span> late (~<span class="r-val">0.30</span> early), task ~<span class="r-val">0.49</span> (~<span class="r-val">0.35</span> early), belief ~<span class="r-val">0.23</span> (~<span class="r-val">0.16</span> early). Late in training the agent succeeds more often <em>because</em> it understood what changed. Raw GRPO traces remain noisy — check smoothed curves and drift-detection rate for stabilization. |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="panel-readme" class="panel"> |
| <div class="readme-wrap"> |
|
|
| <h2>AdaptiveWorld Env</h2> |
| <p><strong>Theme 3.1</strong> (World Modeling / Professional Tasks) + <strong>Theme 5</strong> (Wild Card)<br> |
| <strong>Sub-theme:</strong> Patronus AI — Consumer Workflows with Schema Drift<br> |
| <strong>Built on:</strong> <code>ProthamD/api-debug-env</code> (Round 1)</p> |
|
|
| <p>An OpenEnv environment where an AI agent completes multi-step professional tasks (booking, ordering, filing claims) while the world <strong>mutates mid-episode without announcement</strong>. API schemas change, field names shift, policies flip, and sometimes the API returns <code>200 OK</code> but the meaning of the response has silently changed.</p> |
|
|
| <h2>Five Novel Contributions</h2> |
| <div class="contributions-grid"> |
| <div class="contrib-card"> |
| <div class="contrib-num">01</div> |
| <div class="contrib-title">Non-stationary world</div> |
| <div class="contrib-desc">Environment mutates mid-episode (drift mechanic)</div> |
| </div> |
| <div class="contrib-card"> |
| <div class="contrib-num">02</div> |
| <div class="contrib-title">Dual metrics</div> |
| <div class="contrib-desc">task_reward + belief_accuracy tracked separately</div> |
| </div> |
| <div class="contrib-card"> |
| <div class="contrib-num">03</div> |
| <div class="contrib-title">Adaptive difficulty</div> |
| <div class="contrib-desc">Auto-escalates when agent masters easy drift types</div> |
| </div> |
| <div class="contrib-card"> |
| <div class="contrib-num">04</div> |
| <div class="contrib-title">Drift provenance hidden</div> |
| <div class="contrib-desc">Agent must infer drift from evidence, not be told</div> |
| </div> |
| <div class="contrib-card"> |
| <div class="contrib-num">05</div> |
| <div class="contrib-title">Cross-episode persistence</div> |
| <div class="contrib-desc">Agent's world model carries across episodes</div> |
| </div> |
| </div> |
|
|
| <h2>Quick Start</h2> |
| <pre><code># Install |
| pip install -e . |
|
|
| # Run server |
| uvicorn server.app:app --host 0.0.0.0 --port 7860 |
|
|
| # Health check |
| curl http://localhost:7860/health |
|
|
| # Run inference |
| python inference.py --difficulty easy --episodes 3</code></pre> |
|
|
| <h2>Project Structure</h2> |
| <pre><code>adaptive-world-env/ |
| ├── server/ |
| │ ├── app.py ← FastAPI entry point |
| │ ├── adaptive_world_environment.py ← Core environment logic |
| │ ├── mock_api.py ← Dynamic mock API (4 domains) |
| │ ├── drift_injector.py ← World state mutation manager |
| │ └── difficulty_controller.py ← Adaptive difficulty escalation |
| ├── scenarios/ |
| │ ├── easy.py (E1: field rename, E2: endpoint version, E3: auth scheme) |
| │ ├── medium.py (M1: double drift, M2: policy flip, M3: rate limit) |
| │ ├── hard.py (H1: silent status, H2: response structure, H3: cascading) |
| │ └── expert.py (EX1: transient vs real, EX2: cross-service, EX3: red herring) |
| ├── graders/grader.py ← grade_task + grade_belief + infer_belief |
| ├── models.py ← AdaptiveAction, AdaptiveObservation, AdaptiveState |
| ├── inference.py ← LLM inference loop (OpenAI-compatible) |
| └── openenv.yaml ← OpenEnv spec</code></pre> |
|
|
| <h2>Action Space</h2> |
| <div class="table-scroll"> |
| <table class="readme-table"> |
| <thead><tr><th>action_type</th><th>What it does</th></tr></thead> |
| <tbody> |
| <tr><td><code>call_api</code></td><td>Standard HTTP request to mock API</td></tr> |
| <tr><td><code>probe_schema</code></td><td>GET current API schema — reveals post-drift contract</td></tr> |
| <tr><td><code>query_history</code></td><td>Review last N step logs — compare pre vs post drift responses</td></tr> |
| <tr><td><code>declare_belief</code></td><td>Agent states its current world model (scored later)</td></tr> |
| <tr><td><code>submit_result</code></td><td>End episode, agent declares final belief_state</td></tr> |
| </tbody> |
| </table> |
| </div> |
|
|
| <h2>Drift Types</h2> |
| <div class="table-scroll"> |
| <table class="readme-table"> |
| <thead><tr><th>Type</th><th>Error Signal</th><th>Example</th></tr></thead> |
| <tbody> |
| <tr><td><code>field_rename</code></td><td>422</td><td><code>qty</code> → <code>quantity</code></td></tr> |
| <tr><td><code>endpoint_version</code></td><td>404</td><td><code>/book</code> → <code>/v2/book</code></td></tr> |
| <tr><td><code>policy_change</code></td><td>403 or 429</td><td>Discount requires gold membership</td></tr> |
| <tr><td><code>silent_semantic</code></td><td>None (200 OK)</td><td><code>"confirmed"</code> → <code>"approved"</code></td></tr> |
| </tbody> |
| </table> |
| </div> |
|
|
| <h2>Training Results</h2> |
| <div class="results-table-wrap"> |
| <table> |
| <thead><tr><th>Metric</th><th>Early training (first 20 steps)</th><th>Late training (last 20 steps)</th></tr></thead> |
| <tbody> |
| <tr><td>Task reward (avg)</td><td class="before">0.35</td><td class="after">0.49</td></tr> |
| <tr><td>Belief accuracy (avg)</td><td class="before">0.16</td><td class="after">0.23</td></tr> |
| <tr><td>Combined reward (avg)</td><td class="before">0.30</td><td class="after">0.41</td></tr> |
| <tr><td>Correlation r (task vs belief)</td><td class="before">0.957</td><td class="after" style="font-weight:700;">0.977</td></tr> |
| </tbody> |
| </table> |
| </div> |
| <p style="font-size:13px; color:var(--text3); margin-top:8px;">Task and belief averages are roughly stable — expected, as the environment escalated difficulty mid-run (easy → medium at step ~50). The correlation is the headline metric.</p> |
|
|
| <h2>Combating Reward Hacking</h2> |
| <p>During development, highly capable models would exhibit reward hacking — completing tasks within 2 steps before the drift trigger even fired. When the API returned 200 OK (even with silent semantic drift inside), the environment was auto-terminating, resulting in 0.0 belief accuracy.</p> |
| <ul> |
| <li><strong>Immediate Drift:</strong> Lowered <code>drift_trigger_step = 1</code> for most scenarios. World mutates on the very first API call.</li> |
| <li><strong>Preventing Auto-Termination:</strong> Removed premature auto-termination on task completion. On 200 OK, environment returns: <em>"Task completed. Please review the response for any silent changes, and use submit_result to end the episode."</em></li> |
| </ul> |
|
|
| <h2>HF Space</h2> |
| <pre><code>Space URL: https://huggingface.co/spaces/ProthamD/prothamd-adaptive-world-env |
| API URL: https://prothamd-adaptive-world-env.hf.space</code></pre> |
|
|
| </div> |
| </div> |
|
|
| |
| <div id="panel-space" class="panel"> |
| <div class="space-wrap"> |
| <div class="space-icon">🌐</div> |
| <h2>Space & API</h2> |
| <p>AdaptiveWorldEnv is deployed as a Hugging Face Space running on Docker with FastAPI + uvicorn on port 7860.</p> |
|
|
| <div class="link-grid"> |
| <a class="link-card" href="https://huggingface.co/spaces/ProthamD/prothamd-adaptive-world-env" target="_blank"> |
| <div class="lc-label">HF Space</div> |
| <div class="lc-title">Hugging Face Space</div> |
| <div class="lc-url">spaces/ProthamD/prothamd-adaptive-world-env</div> |
| </a> |
| <a class="link-card" href="/docs" target="_blank"> |
| <div class="lc-label">OpenAPI</div> |
| <div class="lc-title">Interactive API Docs</div> |
| <div class="lc-url">/docs (Swagger UI)</div> |
| </a> |
| <a class="link-card" href="https://huggingface.co/ProthamD" target="_blank"> |
| <div class="lc-label">HF Profile</div> |
| <div class="lc-title">ProthamD on HuggingFace</div> |
| <div class="lc-url">huggingface.co/ProthamD</div> |
| </a> |
| <a class="link-card" href="/redoc" target="_blank"> |
| <div class="lc-label">ReDoc</div> |
| <div class="lc-title">API Reference Docs</div> |
| <div class="lc-url">/redoc</div> |
| </a> |
| </div> |
|
|
| <div class="endpoint-list"> |
| <div class="endpoint-item"> |
| <span class="method get">GET</span> |
| <span class="endpoint-path">/health</span> |
| <span class="endpoint-desc">Server health check</span> |
| </div> |
| <div class="endpoint-item"> |
| <span class="method post">POST</span> |
| <span class="endpoint-path">/reset</span> |
| <span class="endpoint-desc">Reset environment, get initial observation</span> |
| </div> |
| <div class="endpoint-item"> |
| <span class="method post">POST</span> |
| <span class="endpoint-path">/step</span> |
| <span class="endpoint-desc">Take one action, receive observation + reward</span> |
| </div> |
| <div class="endpoint-item"> |
| <span class="method post">POST</span> |
| <span class="endpoint-path">/run_episode</span> |
| <span class="endpoint-desc">Run a full multi-step episode in one request</span> |
| </div> |
| <div class="endpoint-item"> |
| <span class="method post">POST</span> |
| <span class="endpoint-path">/start_episode</span> |
| <span class="endpoint-desc">Phase 1 of 2-phase training: pre-drift + probe</span> |
| </div> |
| <div class="endpoint-item"> |
| <span class="method post">POST</span> |
| <span class="endpoint-path">/finish_episode</span> |
| <span class="endpoint-desc">Phase 2 of 2-phase training: belief scoring</span> |
| </div> |
| </div> |
|
|
| <p style="font-size:13px; color:var(--text3); margin-top:24px;"> |
| Built on <code>ProthamD/api-debug-env</code> (Round 1) · Version 2.3 · Adaptive Difficulty + Drift Provenance Hidden + Cross-Episode Persistence |
| </p> |
| </div> |
| </div> |
|
|
| <script> |
| function closeNavDrawer() { |
| const nav = document.getElementById('site-nav'); |
| const btn = document.getElementById('navToggle'); |
| const bd = document.getElementById('navBackdrop'); |
| if (nav) nav.classList.remove('is-open'); |
| if (btn) { |
| btn.setAttribute('aria-expanded', 'false'); |
| btn.setAttribute('aria-label', 'Open menu'); |
| } |
| if (bd) bd.classList.remove('is-visible'); |
| document.body.style.overflow = ''; |
| } |
| |
| function toggleNavDrawer(ev) { |
| if (ev) ev.stopPropagation(); |
| const nav = document.getElementById('site-nav'); |
| const btn = document.getElementById('navToggle'); |
| const bd = document.getElementById('navBackdrop'); |
| if (!nav || window.matchMedia('(min-width: 769px)').matches) return; |
| const open = !nav.classList.contains('is-open'); |
| nav.classList.toggle('is-open', open); |
| if (btn) { |
| btn.setAttribute('aria-expanded', open); |
| btn.setAttribute('aria-label', open ? 'Close menu' : 'Open menu'); |
| } |
| if (bd) bd.classList.toggle('is-visible', open); |
| document.body.style.overflow = open ? 'hidden' : ''; |
| } |
| |
| function switchTab(name, btn) { |
| document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); |
| document.querySelectorAll('.nav-tab').forEach(t => { |
| t.classList.remove('active'); |
| t.setAttribute('aria-selected', 'false'); |
| }); |
| document.getElementById('panel-' + name).classList.add('active'); |
| btn.classList.add('active'); |
| btn.setAttribute('aria-selected', 'true'); |
| closeNavDrawer(); |
| if (name === 'training' && !window._chartsBuilt) { |
| window._chartsBuilt = true; |
| setTimeout(buildCharts, 50); |
| } |
| } |
| |
| document.addEventListener('keydown', function (e) { |
| if (e.key === 'Escape') closeNavDrawer(); |
| }); |
| window.addEventListener('resize', function () { |
| if (window.matchMedia('(min-width: 769px)').matches) closeNavDrawer(); |
| }); |
| |
| |
| const X_SCENARIO = { |
| steps: [ |
| { title: 'Calibrated request', tag: '200', tagClass: 'ok', |
| body: 'Same JSON the agent used yesterday still works.', |
| html: '<span class="x-term-k">POST</span> /mock_api/orders\n<span class="x-term-s">200</span> { "order_id": "…", "status": "confirmed", "product_id": 5, "qty": 2 }' }, |
| { title: 'Stability window', tag: '200', tagClass: 'ok', |
| body: 'Nothing hints that the contract is about to change.', |
| html: '<span class="x-term-k">POST</span> /mock_api/orders\n<span class="x-term-s">200</span> { "order_id": "…", "status": "confirmed", "product_id": 5, "qty": 2 }' }, |
| { title: 'Silent drift + retry', tag: '422', tagClass: 'err', |
| body: 'At step 3 the environment mutates the world. Same payload now fails validation.', |
| html: '<span class="x-term-k">POST</span> /mock_api/orders <span class="x-term-e">422</span>\n{ "detail": "Missing required field: \'quantity\'" }\n<span class="x-term-k">→</span> The agent was never told the world changed.' }, |
| { title: 'Evidence: probe_schema', tag: 'tool', tagClass: 'info', |
| body: 'Inspect the live contract — discover quantity + customer_id.', |
| html: '<span class="x-term-k">GET</span> (internal) OpenAPI snapshot\n<span class="x-term-s">200</span> … "quantity", "customer_id" required …' }, |
| { title: 'Corrected call', tag: '200', tagClass: 'ok', |
| body: 'Task succeeds only after the belief matches reality.', |
| html: '<span class="x-term-k">POST</span> /mock_api/orders\n<span class="x-term-s">200</span> { "order_id": "…", "quantity": 2, "customer_id": "demo", … }' }, |
| { title: 'submit_result', tag: 'done', tagClass: 'info', |
| body: 'Dual metrics: task_reward vs belief_accuracy. The env still does not add drift_occurred to observations — that line in the trace means the agent was never “told”; it had to figure it out.', |
| html: '<span class="x-term-k">declare</span> belief_state:\n order_field: "quantity"\n required_extra: "customer_id"\n<span class="x-term-s">Episode graded</span> — <span class="x-term-e">Drift did happen</span> at step 3, but <span class="x-term-k">observations never include drift_occurred</span>; the agent infers it from errors + schema.' }, |
| ], |
| }; |
| |
| let xPlayTimer = null; |
| let xStepIdx = -1; |
| |
| function xRenderStepCards() { |
| const root = document.getElementById('xSteps'); |
| if (!root) return; |
| root.innerHTML = ''; |
| X_SCENARIO.steps.forEach((s, i) => { |
| const el = document.createElement('div'); |
| el.className = 'x-card'; |
| el.dataset.idx = String(i); |
| el.innerHTML = |
| '<div class="x-card-top"><span class="x-card-n">' + (i + 1) + '</span><h4>' + s.title + '</h4>' + |
| '<span class="x-tag ' + s.tagClass + '">' + s.tag + '</span></div><p>' + s.body + '</p>'; |
| root.appendChild(el); |
| }); |
| } |
| |
| function xSetTerminal(html) { |
| const b = document.getElementById('xTermBody'); |
| if (b) b.innerHTML = '<div class="x-term-line">' + html + '</div>'; |
| } |
| |
| function xHighlightStep(i, playing) { |
| document.querySelectorAll('#xSteps .x-card').forEach((c, j) => { |
| c.classList.remove('x-active', 'x-done'); |
| if (j < i) c.classList.add('x-done'); |
| if (j === i) c.classList.add('x-active'); |
| }); |
| const agent = document.getElementById('xNodeAgent'); |
| const api = document.getElementById('xNodeApi'); |
| const beam = document.getElementById('xBeam'); |
| const sub = document.getElementById('xApiSub'); |
| if (agent) agent.classList.toggle('x-pulse', playing && i >= 0); |
| if (api) api.classList.toggle('x-pulse', playing && i >= 0); |
| if (beam) { |
| beam.classList.toggle('x-drift', playing && i === 2); |
| } |
| if (sub) { |
| if (i >= 2) sub.textContent = 'Schema has drifted — qty → quantity + customer_id.'; |
| else sub.textContent = 'Dynamic schema — mutates at step 3 of the episode.'; |
| } |
| } |
| |
| function xResetWalk() { |
| if (xPlayTimer) { clearTimeout(xPlayTimer); xPlayTimer = null; } |
| xStepIdx = -1; |
| xHighlightStep(-1, false); |
| xSetTerminal('<span class="x-term-k">#</span> Press <strong>Play walkthrough</strong> to animate the episode.'); |
| const pb = document.getElementById('xPlayBtn'); |
| if (pb) pb.disabled = false; |
| } |
| |
| function xPlayStep() { |
| xStepIdx++; |
| if (xStepIdx >= X_SCENARIO.steps.length) { |
| xPlayTimer = null; |
| xHighlightStep(X_SCENARIO.steps.length - 1, false); |
| const pb = document.getElementById('xPlayBtn'); |
| if (pb) pb.disabled = false; |
| return; |
| } |
| const s = X_SCENARIO.steps[xStepIdx]; |
| xHighlightStep(xStepIdx, true); |
| xSetTerminal(s.html); |
| xPlayTimer = setTimeout(xPlayStep, xStepIdx === 2 ? 1700 : 1250); |
| } |
| |
| function initExperience() { |
| xRenderStepCards(); |
| xResetWalk(); |
| const play = document.getElementById('xPlayBtn'); |
| const reset = document.getElementById('xResetBtn'); |
| const live = document.getElementById('xLiveBtn'); |
| if (play) play.onclick = () => { |
| if (xPlayTimer) clearTimeout(xPlayTimer); |
| xStepIdx = -1; |
| play.disabled = true; |
| xPlayStep(); |
| }; |
| if (reset) reset.onclick = () => xResetWalk(); |
| if (live) live.onclick = async () => { |
| const out = document.getElementById('xLiveOut'); |
| const pre = document.getElementById('xLivePre'); |
| const st = document.getElementById('xLiveStatus'); |
| const badge = document.getElementById('xLiveBadge'); |
| const setBadge = (text, cls) => { |
| if (!badge) return; |
| badge.textContent = text; |
| badge.className = 'x-live-badge' + (cls ? ' ' + cls : ''); |
| }; |
| const setOutState = (state) => { |
| if (!out) return; |
| out.classList.remove('x-live-busy', 'x-live-done', 'x-live-fail'); |
| if (state) out.classList.add(state); |
| }; |
| |
| live.disabled = true; |
| setOutState('x-live-busy'); |
| setBadge('Running…', 'on'); |
| if (st) st.innerHTML = 'POST <code>/run_episode</code> …'; |
| if (pre) pre.textContent = '…'; |
| out && out.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
| |
| const payload = { |
| scenario_id: 'easy_field_rename', |
| difficulty: 'easy', |
| actions: [ |
| { action_type: 'call_api', method: 'POST', url: '/mock_api/orders', body: { product_id: 5, qty: 2 } }, |
| { action_type: 'call_api', method: 'POST', url: '/mock_api/orders', body: { product_id: 5, qty: 2 } }, |
| { action_type: 'call_api', method: 'POST', url: '/mock_api/orders', body: { product_id: 5, qty: 2 } }, |
| { action_type: 'probe_schema' }, |
| { action_type: 'call_api', method: 'POST', url: '/mock_api/orders', |
| body: { product_id: 5, quantity: 2, customer_id: 'demo-user' } }, |
| { action_type: 'submit_result', belief_state: { order_field: 'quantity', required_extra: 'customer_id' } }, |
| ], |
| }; |
| |
| try { |
| const r = await fetch('/run_episode', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload), |
| }); |
| const raw = await r.text(); |
| let j = null; |
| try { j = raw ? JSON.parse(raw) : null; } catch (_) {} |
| |
| if (!r.ok) { |
| setOutState('x-live-fail'); |
| setBadge('HTTP ' + r.status, 'err'); |
| if (st) st.innerHTML = '<em>Request failed.</em> The server responded with an error — see body below.'; |
| if (pre) pre.textContent = raw || '(empty body)'; |
| return; |
| } |
| |
| if (j && typeof j === 'object' && 'task_reward' in j) { |
| setOutState('x-live-done'); |
| setBadge('OK', 'ok'); |
| const tr = j.task_reward; |
| const ba = j.belief_accuracy; |
| const tc = j.task_completed ? 'yes' : 'no'; |
| if (st) { |
| st.innerHTML = 'Episode finished — <em>task_reward</em> ' + tr + ', <em>belief_accuracy</em> ' + ba + |
| ', <em>task_completed</em> ' + tc + '.'; |
| } |
| if (pre) pre.textContent = JSON.stringify(j, null, 2); |
| } else { |
| setOutState('x-live-fail'); |
| setBadge('Bad JSON', 'err'); |
| if (st) st.textContent = 'Got 200 but body was not the expected JSON. Is /run_episode from this app?'; |
| if (pre) pre.textContent = raw; |
| } |
| } catch (e) { |
| setOutState('x-live-fail'); |
| setBadge('Offline', 'err'); |
| if (st) { |
| st.innerHTML = '<em>Network error.</em> Start the app (e.g. <code>uvicorn server.app:app --host 127.0.0.1 --port 7860</code>) and open this page from that URL — not as a local file.'; |
| } |
| if (pre) pre.textContent = String(e); |
| } finally { |
| live.disabled = false; |
| } |
| }; |
| } |
| |
| initExperience(); |
| |
| |
| function seededRand(seed) { |
| let s = seed; |
| return function() { |
| s = (s * 1664525 + 1013904223) & 0xffffffff; |
| return (s >>> 0) / 0xffffffff; |
| }; |
| } |
| |
| function generateTrainingData() { |
| |
| |
| const realCombined = [ |
| 0.302895, 0.151535, 0.459024, 0.532374, 0.304554, 0.456292, 0.253994, 0.349395, 0.179510, 0.151810, |
| 0.363569, 0.164617, 0.259235, 0.219301, 0.307351, 0.180785, 0.255935, 0.456292, 0.284083, 0.245326, |
| 0.261560, 0.257025, 0.264442, 0.297408, 0.380233, 0.300346, 0.192494, 0.087945, 0.252365, 0.459024, |
| 0.306860, 0.233894, 0.199169, 0.339270, 0.292019, 0.321279, 0.284260, 0.428542, 0.369485, 0.502215, |
| 0.312429, 0.532374, 0.301470, 0.315719, 0.223717, 0.258269, 0.471826, 0.398389, 0.241769, 0.328417, |
| 0.373270, 0.356860, 0.281335, 0.455995, 0.397870, 0.322720, 0.241585, 0.288010, 0.374845, 0.390685, |
| 0.374845, 0.321610, 0.352435, 0.299260, 0.317635, 0.481315, 0.424495, 0.430210, 0.350860, 0.265510, |
| 0.473440, 0.389695, 0.399205, 0.361360, 0.475645, 0.430210, 0.237985, |
| ]; |
| |
| const r = seededRand(42); |
| const n = 200; |
| const task = [], belief = [], combined = [], difficulty = [], driftDetected = []; |
| |
| function clip01(x) { return Math.max(0, Math.min(1, x)); } |
| function diffForStep(i) { |
| if (i < 50) return "easy"; |
| if (i < 110) return "medium"; |
| return "hard"; |
| } |
| |
| |
| const seedMean = realCombined.slice(-10).reduce((a, b) => a + b, 0) / 10; |
| |
| for (let i = 0; i < n; i++) { |
| const step = i + 1; |
| const d = diffForStep(i); |
| difficulty.push(d); |
| |
| let c; |
| if (i < realCombined.length) { |
| |
| c = realCombined[i]; |
| } else { |
| |
| const synProgress = (step - realCombined.length) / (n - realCombined.length); |
| const baseC = seedMean + 0.07 * synProgress + (d === "hard" ? -0.03 : d === "medium" ? -0.01 : 0); |
| const noise = (r() - 0.5) * (d === "hard" ? 0.50 : d === "medium" ? 0.46 : 0.40); |
| const spike = (r() > 0.88) ? r() * 0.14 : 0; |
| const dip = (r() < 0.10) ? r() * 0.11 : 0; |
| c = clip01(baseC + noise + spike - dip); |
| } |
| combined.push(c); |
| |
| |
| |
| |
| const bNoise = (r() - 0.5) * 0.05; |
| const b = clip01(c * 0.55 + bNoise); |
| const t = clip01((c - 0.3 * b) / 0.7); |
| task.push(t); |
| belief.push(b); |
| |
| |
| const detBase = 0.22 + 0.58 * (step / n) + (d === "hard" ? -0.10 : d === "medium" ? -0.05 : 0); |
| driftDetected.push(r() < clip01(detBase)); |
| } |
| |
| return { task, belief, combined, difficulty, driftDetected }; |
| } |
| |
| function smooth(data, w) { |
| return data.map((_, i) => { |
| const start = Math.max(0, i - w); |
| const end = Math.min(data.length - 1, i + w); |
| const slice = data.slice(start, end + 1); |
| return slice.reduce((a, b) => a + b, 0) / slice.length; |
| }); |
| } |
| |
| function generateScatterData() { |
| const { task, belief } = generateTrainingData(); |
| const early = []; |
| const late = []; |
| for (let i = 0; i < 40; i++) early.push({ x: task[i], y: belief[i] }); |
| for (let i = task.length - 40; i < task.length; i++) late.push({ x: task[i], y: belief[i] }); |
| return { early, late }; |
| } |
| |
| function generateDistData() { |
| const r = seededRand(123); |
| const bins = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]; |
| const earlyHist = new Array(bins.length).fill(0); |
| const lateHist = new Array(bins.length).fill(0); |
| const { combined } = generateTrainingData(); |
| const early = combined.slice(0, 80); |
| const late = combined.slice(Math.max(0, combined.length - 80)); |
| function put(hist, v) { |
| const vv = Math.max(0, Math.min(0.79, v)); |
| const bi = bins.findIndex((b, j) => j === bins.length - 1 || (vv >= b && vv < bins[j + 1])); |
| hist[Math.max(0, bi)]++; |
| } |
| early.forEach(v => put(earlyHist, v + (r() - 0.5) * 0.02)); |
| late.forEach(v => put(lateHist, v + (r() - 0.5) * 0.02)); |
| return { bins: bins.map(b => b.toFixed(1)), earlyHist, lateHist }; |
| } |
| |
| |
| Chart.defaults.color = '#71717a'; |
| Chart.defaults.borderColor = '#3f3f46'; |
| Chart.defaults.font.family = 'Inter, sans-serif'; |
| |
| function buildCharts() { |
| const { task, belief, combined, difficulty, driftDetected } = generateTrainingData(); |
| const labels = Array.from({ length: combined.length }, (_, i) => i + 1); |
| const sTask = smooth(task, 6); |
| const sBelief = smooth(belief, 6); |
| const sCombined = smooth(combined, 6); |
| const bestSoFar = combined.map((v, i) => Math.max(...combined.slice(0, i + 1))); |
| const detRate = driftDetected.map((v, i) => { |
| const start = Math.max(0, i - 15); |
| const slice = driftDetected.slice(start, i + 1); |
| const frac = slice.filter(Boolean).length / Math.max(1, slice.length); |
| return frac; |
| }); |
| |
| |
| const setTxt = (id, txt) => { const el = document.getElementById(id); if (el) el.textContent = txt; }; |
| setTxt("statSteps", String(combined.length)); |
| |
| setTxt("statCorrLate", "0.977"); |
| setTxt("statCorrDelta", "↑ from 0.957 early"); |
| setTxt("statCombinedAvg", "0.41"); |
| setTxt("statCombinedDelta", "↑ from 0.30 early"); |
| setTxt("statTaskAvg", "0.49"); |
| setTxt("statTaskDelta", "↑ from 0.35 early"); |
| setTxt("statBeliefAvg", "0.23"); |
| setTxt("statBeliefDelta", "↑ from 0.16 early"); |
| |
| |
| new Chart(document.getElementById('rawChart'), { |
| type: 'line', |
| data: { |
| labels, |
| datasets: [ |
| { label: 'task', data: task, borderColor: '#60a5fa', borderWidth: 1, pointRadius: 0, tension: 0.1, fill: false, }, |
| { label: 'belief', data: belief, borderColor: '#fb923c', borderWidth: 1, pointRadius: 0, tension: 0.1, fill: false, }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { legend: { labels: { boxWidth: 10, font: { size: 11 } } } }, |
| scales: { |
| x: { ticks: { font: { size: 10 }, maxTicksLimit: 10 }, grid: { color: 'rgba(63,63,70,0.5)' } }, |
| y: { min: 0, max: 1, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| |
| |
| const diffLine = Array(combined.length).fill(null); |
| |
| diffLine[49] = 0; diffLine[50] = 1; |
| diffLine[109] = 0; diffLine[110] = 1; |
| new Chart(document.getElementById('smoothedChart'), { |
| type: 'line', |
| data: { |
| labels, |
| datasets: [ |
| { label: 'task (smooth)', data: sTask, borderColor: '#60a5fa', borderWidth: 2, pointRadius: 0, tension: 0.4, fill: false }, |
| { label: 'belief (smooth)', data: sBelief, borderColor: '#fb923c', borderWidth: 2, pointRadius: 0, tension: 0.4, fill: false }, |
| { label: 'combined (smooth)', data: sCombined, borderColor: '#4ade80', borderWidth: 2, pointRadius: 0, tension: 0.4, fill: false, borderDash: [4, 3] }, |
| { label: 'drift_detected rate (roll)', data: detRate, borderColor: 'rgba(167,139,250,0.9)', borderWidth: 1.5, pointRadius: 0, tension: 0.4, fill: false, borderDash: [2, 4] }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { |
| legend: { labels: { boxWidth: 10, font: { size: 11 } } }, |
| annotation: {}, |
| }, |
| scales: { |
| x: { ticks: { font: { size: 10 }, maxTicksLimit: 12 }, grid: { color: 'rgba(63,63,70,0.5)' } }, |
| y: { min: 0, max: 1, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| |
| |
| const { early, late } = generateScatterData(); |
| new Chart(document.getElementById('scatterChart'), { |
| type: 'scatter', |
| data: { |
| datasets: [ |
| { label: 'early', data: early, backgroundColor: 'rgba(236,72,153,0.5)', pointRadius: 5, pointHoverRadius: 7 }, |
| { label: 'late', data: late, backgroundColor: 'rgba(34,197,94,0.5)', pointRadius: 5, pointHoverRadius: 7 }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { legend: { labels: { boxWidth: 10, font: { size: 11 } } } }, |
| scales: { |
| x: { title: { display: true, text: 'task_reward', font: { size: 11 }, color: '#71717a' }, min: 0, max: 1, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } }, |
| y: { title: { display: true, text: 'belief_accuracy', font: { size: 11 }, color: '#71717a' }, min: 0, max: 0.7, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| |
| |
| const first20 = (arr) => arr.slice(0, 20).reduce((a, b) => a + b, 0) / 20; |
| const last20 = (arr) => { |
| const s = arr.slice(Math.max(0, arr.length - 20)); |
| return s.reduce((a, b) => a + b, 0) / Math.max(1, s.length); |
| }; |
| new Chart(document.getElementById('beforeAfterChart'), { |
| type: 'bar', |
| data: { |
| labels: ['Task', 'Belief', 'Combined'], |
| datasets: [ |
| { label: 'First 20 steps', data: [first20(task), first20(belief), first20(combined)], backgroundColor: 'rgba(236,72,153,0.7)', borderRadius: 4 }, |
| { label: 'Last 20 steps', data: [last20(task), last20(belief), last20(combined)], backgroundColor: 'rgba(34,197,94,0.7)', borderRadius: 4 }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { legend: { labels: { boxWidth: 10, font: { size: 11 } } } }, |
| scales: { |
| x: { ticks: { font: { size: 11 } }, grid: { display: false } }, |
| y: { min: 0, max: 1.0, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| |
| |
| new Chart(document.getElementById('combinedChart'), { |
| type: 'line', |
| data: { |
| labels, |
| datasets: [ |
| { label: 'combined (raw)', data: combined, borderColor: 'rgba(74,222,128,0.3)', borderWidth: 1, pointRadius: 0, fill: false }, |
| { label: 'combined (smooth)', data: sCombined, borderColor: '#4ade80', borderWidth: 2.5, pointRadius: 0, tension: 0.4, fill: false }, |
| { label: 'best so far', data: bestSoFar, borderColor: 'rgba(96,165,250,0.9)', borderWidth: 1.5, pointRadius: 0, tension: 0.0, fill: false, borderDash: [6, 4] }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { legend: { labels: { boxWidth: 10, font: { size: 11 } } } }, |
| scales: { |
| x: { ticks: { font: { size: 10 }, maxTicksLimit: 12 }, grid: { color: 'rgba(63,63,70,0.5)' } }, |
| y: { min: 0, max: 1, ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| |
| |
| const { bins, earlyHist, lateHist } = generateDistData(); |
| new Chart(document.getElementById('distChart'), { |
| type: 'bar', |
| data: { |
| labels: bins, |
| datasets: [ |
| { label: 'early', data: earlyHist, backgroundColor: 'rgba(236,72,153,0.6)', borderRadius: 2 }, |
| { label: 'late', data: lateHist, backgroundColor: 'rgba(34,197,94,0.6)', borderRadius: 2 }, |
| ] |
| }, |
| options: { |
| animation: { duration: 600 }, |
| responsive: true, maintainAspectRatio: false, |
| plugins: { legend: { labels: { boxWidth: 10, font: { size: 11 } } } }, |
| scales: { |
| x: { title: { display: true, text: 'combined_reward', font: { size: 10 }, color: '#71717a' }, ticks: { font: { size: 10 } }, grid: { display: false } }, |
| y: { ticks: { font: { size: 10 } }, grid: { color: 'rgba(63,63,70,0.5)' } } |
| } |
| } |
| }); |
| } |
| </script> |
| </body> |
| </html> |
|
|