sai1906/opsguard
OpsGuard — Train a 3B Model to Run On-Call for an Open-Source Repo
Can a 3B model triage 60 issues in a row — while a co-evolved adversary tries to slip spam past it?
OpsGuard is an OpenEnv environment that trains LLM maintainers against synthetic spammers across 6 difficulty tiers, scoring against the real maintainer decisions of huggingface/peft. No simulator. Real GitHub data. Real ground truth.
Hero numbers — rule-based baselines on real GH data
375 real issues, 1911 comments. Each scenario mixes real issues + synthetic spam at scenario-defined rate. Mean over 3 seeds:
Spread grows monotonically: 7 → 18 → 36 → 64 reward points as horizon and adversary intensity rise. Greedy strategies fail at long horizons. No shortcut wins.
Trained 3B agent (post-GRPO): numbers populate after training run lands. Live updates pushed to this README + Hub repo sai1906/opsguard-grpo.
The Story — 4 Acts
Act 1 — The Naive Triager. Baseline (label_everything) blindly tags every issue bug. On E2, half the queue is synthetic spam — none caught. Reward: -9.76.
Act 2 — The Keyword Spotter. Hand-coded rules detect spam tokens, route by content keyword, request info on thin reports. Same E2 scenario: +23.32. Spam recall jumps 0% → 77%.
Act 3 — The Adversary Learns. A second 3B model is DPO-trained as a spammer, mining the maintainer's misses. It paraphrases real issue titles, fabricates urgency, mimics trusted-contributor voice. Keyword spotter's spam recall starts dropping back toward 50% on harder tiers.
Act 4 — The Co-evolved Maintainer. The maintainer is GRPO-trained on rollouts against the trained adversary. By round 3, it learns to weigh hidden contributor reputation, query history before deciding, and resist self_contradiction-style appeals. Final hero numbers populate post-training.
What's in the box
opsguard/
├── models.py # 9 ActionTypes + Pydantic Action/Observation
├── server/
│ ├── opsguard_environment.py # OpenEnv Environment subclass; reset/step/state
│ ├── app.py # FastAPI + WebSocket via openenv.core.create_app
│ └── Dockerfile # Multi-stage uv build, ghcr.io/meta-pytorch/openenv-base
├── world/
│ ├── db.py # SQLite query helpers (RepoDB, IssueRow, Contributor)
│ ├── scenarios.py # E0_quiet_day → E5_hostile_fork_war (30→500 step budgets)
│ ├── adversary.py # 5 spam patterns × 5 tiers (scripted)
│ ├── trainable_adversary.py # LoRA spammer (round-2 co-evolution); template fallback
│ ├── grader.py # Composable rubric + multiplicative terminal
│ └── curriculum.py # Mastery-based tier unlock
├── eval/
│ ├── policies.py # 5 baselines: random, label_everything, close_everything, keyword_triager, memory_aware
│ └── harness.py # Rollout + aggregate + markdown summary
├── scripts/
│ ├── pull_gh.py # GH REST API issue/PR/comment puller
│ ├── ingest_to_sqlite.py # JSONL → SQLite, derives truth_action
│ ├── build_sft_traces.py # Roll rule-based policies, emit (prompt, completion) JSONL
│ ├── system_prompt.py # SYSTEM_PROMPT, format_observation, parse_action
│ ├── sft_warmstart.py # Unsloth 4-bit + LoRA SFT (1 epoch)
│ ├── train_grpo.py # TRL OpenEnv GRPO trainer (vLLM colocate optional)
│ ├── train_grpo_colab.ipynb # Self-contained Colab notebook
│ └── make_plots.py # Reward / spam-recall plots
├── tests/ # Smoke tests (env loop + train integration), all green
├── data/repo.db # 375 real issues + 1911 comments from huggingface/peft
└── openenv.yaml # OpenEnv v1 specHackathon themes hit
- T2 long-horizon — episodes 30 → 500 steps; multi-issue queues; agent must process backlog while resisting 60-issue spam waves
- T4 self-improvement — co-evolved adversary (trainable spammer LoRA) + mastery-driven curriculum (E0 → E5 unlocks on rolling reward, kube-sre-gym pattern)
- T3 world modeling touch — hidden contributor reputation drives reward but is never exposed to the agent. Agent infers trust from public stats (PR count, account age) — env penalizes mis-judgement.
Action space (9 actions, single per step)
label · close_spam · request_info · link_duplicate · assign · comment · merge_pr · query_history · wait
JSON contract:
{"action_type":"label","target_issue_id":12345,"label":"bug","reasoning":"..."}Reward (composable rubric, hard to game)
Per the OpenEnv hackathon hint "composable rubrics > monolithic scoring", OpsGuard splits reward into 7 components fired conditionally:
The terminal is multiplicative on purpose — a policy that hits queue completion but ignores spam is gated to 0. Greedy strategies fail.
Try it yourself (3 paths)
1. Run baseline eval (CPU only, ~2 min):
git clone https://huggingface.co/spaces/sai1906/opsguard
cd opsguard
pip install -e .
python scripts/run_baseline_eval.py --out eval_outputs/baseline
cat eval_outputs/baseline/summary.md2. Run env locally:
python -m server.app # → http://localhost:8000
curl http://localhost:8000/state3. Train + GRPO (Colab T4 free OR HF Jobs A100 ~1h):
# Open scripts/train_grpo_colab.ipynb in Colab → Run All
# OR via HF Jobs:
hf jobs uv run --flavor a100-large \
--with "trl,unsloth,openenv-core,peft,bitsandbytes,vllm,datasets" \
--secrets HF_TOKEN \
-- python scripts/train_grpo.py \
--model unsloth/Qwen2.5-7B-Instruct-bnb-4bit \
--hub-repo sai1906/opsguard-grpo \
--num-steps 200Live links
- HF Space (this env): https://huggingface.co/spaces/sai1906/opsguard
- Colab demo: scripts/train_grpo_colab.ipynb
- Trained LoRA: https://huggingface.co/sai1906/opsguard-grpo (populates after training)
- Data source:
huggingface/peft— 375 issues, 1911 comments, 6-month window
Why it matters
Open-source maintainers spend 30–50% of triage time on duplicates, urgency-fabrications, and low-info noise. A 3B model that catches 80% of synthetic spam at 95% precision and routes the rest to the right label is a real productivity win — and it's a problem nobody has trained for in OpenEnv before.
References
- OpenEnv (PyTorch + Meta, 2026) — https://github.com/meta-pytorch/OpenEnv
- TRL OpenEnv integration — https://huggingface.co/docs/trl/en/openenv
- AgentGym-RL ScalingInter-RL (Sep 2025, arXiv 2509.08755) — curriculum design
- kube-sre-gym (SF OpenEnv 1st place, Mar 2026) — mastery curriculum + adversarial designer pattern
- Multi-Agent Evolve (arXiv 2510.23595) — co-evolution recipe (Proposer/Solver/Judge)
- AgentLAB long-horizon attacks (arXiv 2602.16901) — adversarial maintenance pattern catalog
Architecture (textual)
┌────────────────────────┐
│ GitHub REST API │
│ (peft, 6-month pull) │
└──────────┬─────────────┘
│ pull_gh.py
▼
┌────────────────────────┐
│ data/repo.db (SQLite) │
│ 375 issues, 1911 cmts │
└──────────┬─────────────┘
│
┌────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Adversary │ │ Curriculum │ │ Composable │
│ - 5 patterns │ │ - Mastery │ │ Rubric │
│ - 5 tiers │ │ - E0→E5 unlock │ │ - 7 components │
│ - LoRA-able │ │ │ │ - Multipl. term │
└──────┬───────┘ └────────┬────────┘ └────────┬─────────┘
│ │ │
└────────────────────┼─────────────────────┘
▼
┌────────────────────────┐
│ OpsGuardEnvironment │
│ (OpenEnv subclass) │
│ reset / step / state │
└──────────┬─────────────┘
│ FastAPI + WS
▼
┌────────────────────────┐
│ TRL GRPOTrainer │
│ Qwen2.5-7B + LoRA │
│ (vLLM colocate) │
└────────────────────────┘License
BSD-3 (matching OpenEnv).
