"""Unified DriftCall Space — every artefact at a slash-path, served locally. URL surface: / static project site (Vite-built React + Pretext) /assets/* site bundle /healthz OpenEnv health probe /reset POST OpenEnv canonical reset /step POST OpenEnv step /state GET OpenEnv read-only state /close POST OpenEnv close /openenv.yaml OpenEnv v1.0 manifest /docs FastAPI / Swagger UI for the env routes /demo voice demo Gradio app, MOUNTED LOCALLY (no iframe) /env landing page documenting env routes (curl recipes) /lora landing page with LoRA metadata + download link /source landing page with the project file tree + repo link Nothing under this Space depends on a 302 redirect to another origin — the demo Gradio Blocks are mounted via gr.mount_gradio_app, the LoRA and source pages are rendered server-side from local data. """ from __future__ import annotations import os from pathlib import Path from typing import Any from fastapi import FastAPI from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse from fastapi.staticfiles import StaticFiles # `app.py` refuses to start without DRIFTCALL_ENV_TOKEN. For the hackathon # demo Space we publish a known public token so anyone can hit the OpenEnv # API end-to-end. To restrict access, override via Space Settings → Secrets. os.environ.setdefault("DRIFTCALL_ENV_TOKEN", "driftcall-demo") # `app.py` eager-loads Kokoro TTS + faster-whisper ASR at lifespan startup. # On this unified Space those engines aren't needed for the OpenEnv API # surface — they only matter inside the /demo Gradio app, which constructs # its own engines lazily on first audio I/O. We monkey-patch the eager-load # step BEFORE importing app.py so the lifespan handler skips it. This also # dodges the misaki/kokoro version-drift error (`module 'misaki.en' has no # attribute 'MutableToken'`) that was crashing startup. import importlib _audio = importlib.import_module("cells.step_09_audio") def _noop_engine() -> None: return None _audio.get_tts_engine = _noop_engine # type: ignore[assignment] _audio.get_asr_engine = _noop_engine # type: ignore[assignment] from app import app as openenv_app # noqa: E402 # type: ignore[import-not-found] LORA_HUB_URL = "https://huggingface.co/DGXAI/gemma-3n-e2b-driftcall-lora" SOURCE_URL = "https://github.com/saumilyagupta/openenv-DGXAI" SITE_DIR = Path(__file__).parent / "site" MANIFEST_PATH = Path(__file__).parent / "openenv.yaml" # --------------------------------------------------------------------------- # Shared chrome — same dark editorial brutalism as the React site. # --------------------------------------------------------------------------- _HEAD = """ __TITLE__
DriftCall · __SLUG__
__BODY__
""" _NAV_LINKS = [ ("/", "site"), ("/demo", "demo"), ("/env", "env"), ("/openenv.yaml", "manifest"), ("/docs", "docs"), ("/lora", "lora"), ("/source", "source"), ] def _page(title: str, slug: str, body: str, active: str) -> str: nav_items: list[str] = [] for href, label in _NAV_LINKS: cls = ' class="is-active"' if href == active else "" nav_items.append(f'{label}') nav = "\n ".join(nav_items) return ( _HEAD .replace("__TITLE__", title) .replace("__SLUG__", slug) .replace("__NAV__", nav) .replace("__BODY__", body) ) # --------------------------------------------------------------------------- # /env — landing page with curl recipes. # --------------------------------------------------------------------------- _ENV_BODY = """

OpenEnv API at canonical paths

Every endpoint sits at the bare path the OpenEnv v1.0 spec expects — no /api prefix, no rewriting. Auth is bearer (DRIFTCALL_ENV_TOKEN) plus X-Session-Id on every mutating call.

methodpathdescription
GET /healthz health probe (unauthenticated, returns "ok")
POST /reset create or recycle a session (seed / curriculum_stage / language_weights / audio_boundary_enabled)
POST /step advance one turn — body {"action": <DriftCallAction>}
GET /state read-only DriftCallState snapshot
POST /close evict the server-side session
GET /openenv.yaml OpenEnv v1.0 manifest
GET /docs auto-generated FastAPI / Swagger UI

Try it

This Space publishes a known public bearer token so anyone can exercise the API end-to-end: driftcall-demo.

# 1) probe (no auth)
curl https://saumilyajj-driftcall.hf.space/healthz

# 2) reset
curl -X POST https://saumilyajj-driftcall.hf.space/reset \\
  -H "Authorization: Bearer driftcall-demo" \\
  -H "X-Session-Id: smoke-001" \\
  -H "Content-Type: application/json" \\
  -d '{"seed": 42, "curriculum_stage": 2}'

# 3) step
curl -X POST https://saumilyajj-driftcall.hf.space/step \\
  -H "Authorization: Bearer driftcall-demo" \\
  -H "X-Session-Id: smoke-001" \\
  -H "Content-Type: application/json" \\
  -d '{"action": {"type": "end_episode"}}'
""" # --------------------------------------------------------------------------- # /lora — local landing page (no 302) with model card details. # --------------------------------------------------------------------------- _LORA_BODY = f"""

Trained LoRA adapter

GRPO-tuned LoRA over Gemma-3n-E2B-it. Five reward components, three-stage curriculum (no drift → single drift → compound drift), 240 steps total on H100 80GB. Adapter-only — never the merged 16-bit weights, per DESIGN.md §10.5.

repoDGXAI/gemma-3n-e2b-driftcall-lora
base modelunsloth/gemma-3n-E2B-it
adapter typepeft / lora
r16
alpha32
dropout0.0 (Unsloth fast path)
trainerscripts/train_driftcall_grpo.py · native PyTorch GRPO
curriculumstage 1 (70 steps, no drift) → stage 2 (100 steps, single drift) → stage 3 (70 steps, compound)
num_generations2
size on diskadapter_model.safetensors · 84.6 MB · plus tokenizer (33.4 MB)
precision16-bit LoRA on bf16 base (H100 native)
licenseapache-2.0

Load it

from unsloth import FastModel
from peft import PeftModel

model, tokenizer = FastModel.from_pretrained(
    "unsloth/gemma-3n-E2B-it",
    max_seq_length=4096,
    load_in_4bit=False,
    full_finetuning=False,
)
model = PeftModel.from_pretrained(model, "DGXAI/gemma-3n-e2b-driftcall-lora")
model.eval()

The five reward components

idnameweightdescription
R1task_completion 0.40did the agent actually book the cab / settle the payment / hold the reservation
R2drift_detection 0.20noticed the schema mutated mid-episode and retried with the new shape
R3constraint_adherence 0.20budget, time window, dietary, language — all checked deterministically
R4format_compliance 0.10tool args parse against the (possibly drifted) JSON schema
R5anti_hack_penalty 0.10200-episode reward-hacking probe set; pure deterministic checks, no LLM judge

open on hf hub project source

""" # --------------------------------------------------------------------------- # /source — local landing page (no 302) with the project file tree. # --------------------------------------------------------------------------- _SOURCE_BODY = f"""

Project source

This Space bundles the entire DriftCall project — runtime modules, training scripts, the Colab-runnable notebook, design docs, the test suite, and the spec corpus. Browse the full file tree under the Files tab of this Space, or open the canonical mirror on GitHub.

/ (HF Space root) ├── cells/ # importable runtime modules (used by the running app) │ ├── step_04_models.py # DriftCallAction, DriftCallObservation, ActionType │ ├── step_05_vendors.py # 5 mock vendor APIs │ ├── step_06_drift_injector.py # 20-pattern drift catalogue │ ├── step_07_task_generator.py # deterministic seeded task generation │ ├── step_08_rewards.py # the 5 reward components + Brier + uncertain floor │ ├── step_09_audio.py # Kokoro TTS + faster-whisper ASR │ ├── step_10_env.py # DriftCallEnv (composes 04..09) │ ├── step_12_gemma_boot.py # Unsloth model loader │ ├── step_13_grpo_config.py # GRPOConfig builder │ ├── step_14_custom_trainer.py # trainer + dataset adapter │ ├── step_15..17_train_stage*.py # 3-stage curriculum │ ├── step_18_eval_baseline.py # 50-ep baseline eval │ ├── step_19_eval_final.py # 50-ep final eval (paired) │ ├── step_20_probe.py # 200-ep reward-hacking probe │ ├── step_23_demo_gradio.py # notebook variant of the demo │ └── step_24_deploy_hf.py # push_lora_to_hub + push_*_space ├── data/ # briefs, drift patterns, API schemas (authored fixtures) ├── scripts/ # training scripts (bundled, not run on this Space) │ ├── train_driftcall_grpo.py # native PyTorch GRPO loop (1300 LOC) │ ├── train_full_gemma3n.sh # orchestrator: stage 1 → 2 → 3 │ ├── smoke_gemma3n_boot.py # one-off model boot smoke │ └── run_driftcall_train.py # legacy TRL-based path (deprecated) ├── notebooks/ # Colab-runnable artefact │ ├── train_driftcall.ipynb # the 25-cell training notebook │ └── build_notebook.py # jupytext concatenator ├── docs/ # 14 module specs + 14 test plans + critic transcripts │ ├── modules/ # env.md, rewards.md, training.md, … │ ├── tests/ # test plan docs │ ├── critics/ # reviewer transcripts │ └── api_contracts/ # vendor API schema specs ├── tests/ # pytest suite (35+ files) ├── site/ # Vite-built React frontend (mounted at /) ├── app.py # OpenEnv FastAPI server (786 LOC) ├── demo_app.py # Gradio demo (mounted at /demo) ├── unified_app.py # this Space's entrypoint ├── openenv.yaml # OpenEnv v1.0 manifest ├── Dockerfile · requirements.txt # build configuration ├── DESIGN.md # 54 KB design doc, 20 sections ├── CLAUDE.md # development guide / project rules ├── PROJECT_README.md # top-level project README ├── pyproject.toml # Python project metadata └── README.md # Space card (HF page renders this)

open on github browse files on hf openenv api docs

""" def _build_demo_blocks() -> Any: """Build the Gradio Blocks lazily so a missing dep at import time doesn't kill the whole FastAPI app — instead /demo will return 503 with a clear message until the deps come back online.""" try: from demo_app import build_ui # type: ignore[import-not-found] return build_ui() except Exception as exc: # noqa: BLE001 # We log via FastAPI's normal startup logs; nothing more to do. import logging logging.getLogger("unified").exception("demo blocks build failed: %s", exc) return None def build_unified_app() -> FastAPI: app: FastAPI = openenv_app @app.get("/openenv.yaml", include_in_schema=False) async def serve_manifest() -> Any: if MANIFEST_PATH.exists(): return FileResponse(MANIFEST_PATH, media_type="text/yaml") return {"error": "openenv.yaml not found"} @app.get("/env", include_in_schema=False) async def serve_env_page() -> HTMLResponse: return HTMLResponse(_page("DriftCall — OpenEnv API", "/env", _ENV_BODY, "/env")) @app.get("/lora", include_in_schema=False) async def serve_lora_page() -> HTMLResponse: return HTMLResponse(_page("DriftCall — LoRA", "/lora", _LORA_BODY, "/lora")) @app.get("/source", include_in_schema=False) async def serve_source_page() -> HTMLResponse: return HTMLResponse(_page("DriftCall — source", "/source", _SOURCE_BODY, "/source")) # Mount the Gradio voice demo at /demo — runs locally, no iframe. # Gradio mounts under /demo/ (with trailing slash). Register a # bare /demo handler that redirects to /demo/ so users typing the # short URL don't hit the SPA static catch-all and 404. @app.get("/demo", include_in_schema=False) async def demo_trailing_slash() -> RedirectResponse: return RedirectResponse(url="/demo/", status_code=308) blocks = _build_demo_blocks() if blocks is not None: try: import gradio as gr # type: ignore[import-not-found] gr.mount_gradio_app(app, blocks, path="/demo") except Exception: import logging logging.getLogger("unified").exception("gr.mount_gradio_app failed") # SPA static mount — must come LAST so OpenEnv routes and our # explicit /env, /lora, /source, /demo handlers take precedence. if SITE_DIR.exists(): app.mount( "/", StaticFiles(directory=SITE_DIR, html=True), name="frontend", ) return app app = build_unified_app()