AdityaGuntur commited on
Commit
3c4463c
Β·
verified Β·
1 Parent(s): ec0945c

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +233 -175
README.md CHANGED
@@ -1,259 +1,317 @@
 
 
 
 
 
 
1
  ---
2
- title: PM-Ops RL Environment
3
- emoji: πŸ“‹
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: docker
7
- app_port: 8000
8
- pinned: false
9
- tags:
10
- - openenv
11
- - reinforcement-learning
12
  ---
13
 
14
- # PM-Ops: Procedural PM Operations RL Environment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- A simulated software-org environment β€” Ticketing, Chat, Codebase β€” designed to train LLM agents that learn *organizational conventions* rather than memorize fixed answers.
17
 
18
- **Hosted env:** `https://huggingface.co/spaces/TheCrustaceans/Pm-ops`
19
- **Trained checkpoint:** `Saurav1/pm-ops-grpo-Qwen3-1.7B-triage-v4`
20
- **Stack:** Qwen3-1.7B Β· Unsloth Β· TRL 0.22.2 Β· SFT β†’ GRPO
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  ---
23
 
24
- ## The Core Idea
25
 
26
- Every episode samples a fresh `OrgConfig` β€” different label names, priority schemes, team-to-service mappings, and oncall channels. An agent that hardcodes `label="bug"` or `priority="P1"` will fail most episodes. A trained agent learns to **read the runbook first**, then use exactly what it says.
 
 
27
 
28
  ```
29
- Episode A Episode B
30
- ────────────────────────────── ──────────────────────────────
31
- labels: issue, perf, docs labels: defect, vuln, guide
32
- priority: critical/high/low priority: P0/P1/P2/P3
33
- team: payments β†’ data team: payments β†’ backend
34
- channel: #data-alerts channel: #backend-oncall
 
 
 
 
 
 
 
 
 
 
35
  ```
36
 
37
- The same hardcoded action scores 1.0 in Episode A and 0.0 in Episode B. This is the learning signal.
38
 
39
- ---
40
 
41
- ## Tasks
42
 
43
- | Task | What the agent must do | Max Steps |
44
- |---|---|---|
45
- | **triage** | File a bug ticket with the correct org label + priority, assign to the owning team, page the oncall channel | 25 |
46
- | **incident_routing** | Identify the commit that broke a service, page the right oncall channel, optionally ticket it | 25 |
47
- | **release_notes** | Compile resolved tickets into release notes in the org's style (terse / verbose / bulleted) and post them | 40 |
48
- | **dep_update** | Coordinate a dependency update across all teams that own affected services | 40 |
49
 
50
- ---
 
 
 
 
 
 
 
 
 
51
 
52
- ## Scoring
53
 
54
- Reward is **delayed** to episode end via a deterministic Python verifier β€” no LLM-as-judge.
55
 
56
- ### Triage (example)
57
- ```
58
- +0.25 ticket created
59
- +0.20 correct label (must match label_taxonomy)
60
- +0.20 correct priority (must match priority_levels)
61
- +0.20 assigned to correct team (via team_map)
62
- +0.10 message posted to correct oncall channel
63
- +0.05 message body β‰₯ 20 chars
64
- ─0.10 per duplicate ticket
65
- ─0.05 per message posted to a noise channel
 
 
66
  ```
67
 
68
- ### GRPO Training Reward (runbook-compliance)
69
 
70
- During RL training we add a **dense shaped reward** on top of the env score:
71
 
72
- | Signal | Reward |
73
- |---|---|
74
- | Called `meta.read_runbook` | +0.10 |
75
- | Ticket label in `label_taxonomy` | +0.20 / βˆ’0.10 |
76
- | Ticket priority in `priority_levels` | +0.15 / βˆ’0.10 |
77
- | Team in `team_map` | +0.20 / βˆ’0.10 |
78
- | Posted to `oncall_channels` | +0.25 / βˆ’0.10 per wrong channel |
79
- | Env grader score | Γ—0.10 bonus |
80
 
81
- Reward naturally varies per seed because every org has different valid values β€” the model can't memorize answers.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  ---
84
 
85
- ## Action Space
86
 
87
- The agent emits one JSON block per step:
88
 
89
- ```json
90
- {"action_type": "ticketing.create_ticket", "args": {"summary": "...", "label": "issue", "priority": "high"}}
 
 
 
 
 
 
 
 
91
  ```
92
 
93
- | Namespace | Actions |
94
- |---|---|
95
- | `meta` | `read_runbook` Β· `finish` Β· `noop` |
96
- | `ticketing` | `create_ticket` Β· `update_ticket` Β· `get_ticket` Β· `list_tickets` Β· `assign_ticket` Β· `comment_ticket` Β· `transition_ticket` |
97
- | `chat` | `post_message` Β· `read_channel` Β· `list_channels` Β· `search` |
98
- | `codebase` | `list_commits` Β· `get_commit` Β· `list_prs` |
99
 
100
- The optimal triage sequence takes **5 steps**:
101
  ```
102
- meta.read_runbook β†’ ticketing.create_ticket β†’ ticketing.assign_ticket β†’ chat.post_message β†’ meta.finish
 
 
 
103
  ```
104
 
105
- ---
106
-
107
- ## Procedural World Generation
108
 
109
- Each episode is fully seeded β€” same seed always produces the same org + scenario.
110
 
111
  ```python
112
- # org_generator.py
113
- rng = random.Random(seed)
114
- difficulty = rng.choice(["easy", "medium", "medium", "hard"])
115
- label_taxonomy = {k: rng.choice(variants[k]) for k in sampled_keys}
116
- priority_levels = rng.choice([["P0","P1","P2","P3"], ["critical","high","medium","low"], ...])
117
- team_map = {service: team for service, team in zip(services, teams)}
118
- oncall_channels = {svc: f"#{team_map[svc]}{rng.choice(suffixes)}" for svc in services}
 
 
 
 
 
119
  ```
120
 
121
- **Difficulty** controls the number of services (2 / 3 / 5), labels (3 / 4 / 5), noise channels (1 / 2 / 3), and required ticket fields.
122
-
123
  ---
124
 
125
  ## Training Pipeline
126
 
127
- ### Phase 1 β€” SFT Warmup (~15 min on A100)
128
 
129
- Run the deterministic `baseline_agent` for N episodes and collect every `(observation, action)` pair as a supervised example. Without this, the model outputs freeform text β†’ all GRPO advantages are zero.
130
 
131
- ```
132
- baseline_agent: read_runbook β†’ create_ticket β†’ assign_ticket β†’ list_channels β†’ post_message β†’ finish
133
- SFT target: prompt + ```json\n{"action_type": ..., "args": {...}}\n``` + EOS
134
- ```
135
 
136
- After 2 epochs, the model reliably emits `\`\`\`json ... \`\`\`` blocks.
137
 
138
- ### Phase 2 β€” GRPO (~90 min on A100)
 
 
 
139
 
140
- GRPO learns by comparing rewards across N generations of the same prompt. Each generation gets a different env seed (`gen_slot` offset), so they explore distinct org configs and produce varied rewards β€” giving GRPO a real gradient signal.
141
 
142
- ```
143
- prompt (same seed) β†’ gen_slot=0 β†’ org_A β†’ reward=0.45
144
- β†’ gen_slot=1 β†’ org_B β†’ reward=0.80
145
- β†’ gen_slot=2 β†’ org_C β†’ reward=0.10
146
- advantage = reward - mean(group) β†’ non-zero gradient
147
- ```
 
 
 
148
 
149
- **Critical: the dataset is pre-filtered.** `dataset.py` simulates the env's RNG to only include seeds where `env.reset(seed)` will run a *triage* task β€” eliminating the 75% of seeds that would produce release_notes or dep_update episodes and always score 0.
150
 
151
  ---
152
 
153
- ## Repository Layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  ```
 
156
  β”œβ”€β”€ server/
157
- β”‚ β”œβ”€β”€ pm_ops_environment.py # OpenEnv Environment β€” reset/step/grade
158
- β”‚ β”œβ”€β”€ apps/
159
- β”‚ β”‚ β”œβ”€β”€ ticketing.py # Jira-like app (create, assign, transition tickets)
160
- β”‚ β”‚ β”œβ”€β”€ chat.py # Slack-like app (post, read, search channels)
161
- β”‚ β”‚ └── codebase.py # GitHub-like app (commits, PRs)
162
- β”‚ β”œβ”€β”€ tasks/
163
- β”‚ β”‚ β”œβ”€β”€ triage_task.py # Grader: label + priority + team + channel
164
- β”‚ β”‚ β”œβ”€β”€ incident_routing_task.py
165
- β”‚ β”‚ β”œβ”€β”€ release_notes_task.py
166
- β”‚ β”‚ └── dep_update_task.py
167
- β”‚ └── world/
168
- β”‚ β”œβ”€β”€ org_generator.py # Procedural OrgConfig from seed
169
- β”‚ └── scenario_gen.py # Task brief + expected solution from seed
170
- β”‚
171
- β”œβ”€β”€ training/
172
- β”‚ β”œβ”€β”€ train_v4.ipynb # ← run this (current clean notebook)
173
- β”‚ β”œβ”€β”€ dataset.py # Pre-filtered triage dataset (seed-matched to env)
174
- β”‚ β”œβ”€β”€ rollout.py # Multi-turn episode runner + JSON extractor
175
- β”‚ β”œβ”€β”€ rewards.py # compute_rollout_reward (runbook-compliance)
176
- β”‚ β”œβ”€β”€ prompts.py # System prompt + observation formatter
177
- β”‚ └── pm_ops_trainer.py # PMOpsGRPOTrainer β€” reward cache injection
178
- β”‚
179
- β”œβ”€β”€ inference.py # Deterministic baseline_agent (heuristic)
180
- β”œβ”€β”€ models.py # Pydantic types: PMOpsAction, PMOpsObservation
181
- β”œβ”€β”€ client.py # OpenEnv client helpers
182
- └── Dockerfile # HF Space deployment
183
  ```
184
 
185
  ---
186
 
187
- ## Quick Start
188
 
189
- ### Run the env locally
190
 
191
  ```bash
 
192
  pip install -e .
193
- uvicorn server.app:app --host 0.0.0.0 --port 8000
194
  ```
195
 
196
  ### Run the baseline agent
197
 
198
  ```bash
199
- python inference.py
200
- # [END] success=True steps=6 score=0.75 ...
201
  ```
202
 
203
- ### Connect a custom agent
204
 
205
  ```python
206
  from openenv.core import GenericEnvClient
207
 
208
- with GenericEnvClient(base_url="http://localhost:8000").sync() as env:
209
- result = env.reset(seed=42)
210
- obs = result.observation
211
- result = env.step({"action_type": "meta.read_runbook", "args": {}})
212
- # ... agent loop ...
213
- result = env.step({"action_type": "meta.finish", "args": {}})
214
- print(f"score = {result.reward:.3f}")
215
- ```
216
-
217
- ### Train (cloud β€” A100 or T4)
218
-
219
- Open `training/train_v4.ipynb` on your cloud runtime, run top to bottom. The notebook is self-contained: installs deps, clones the HF Space, runs SFT then GRPO, saves merged 16-bit weights to HF Hub.
220
-
221
- Key hyperparameters auto-adapt to GPU:
222
-
223
- | | A100 (40 GB) | T4 (16 GB) |
224
- |---|---|---|
225
- | `NUM_GEN` | 6 | 2 |
226
- | `GRAD_ACCUM` | 32 | 8 |
227
- | `N_SFT_EPISODES` | 120 | 50 |
228
- | `N_GRPO_EPISODES` | 150 | 30 |
229
- | `MAX_COMP_LEN` | 192 | 192 |
230
-
231
- ---
232
-
233
- ## Key Design Decisions
234
 
235
- **Why SFT before GRPO?**
236
- GRPO computes `advantage = reward βˆ’ mean(group)`. If the model outputs garbage on every step, all rewards are ~0 and all advantages are 0 β†’ zero gradient. SFT teaches the JSON format first so GRPO has something to improve.
 
237
 
238
- **Why runbook-compliance reward instead of env score?**
239
- The env grader requires a precise 5-step sequence with *exact* field values (label, priority, team, channel). Early in training the model rarely completes the full sequence, so env score is 0 for nearly every episode β€” zero variance, zero GRPO signal. The compliance reward fires on individual correct actions and creates variance even from partial completions.
240
-
241
- **Why filter training seeds to triage-only?**
242
- `PMOpsEnvironment.reset(seed)` draws task type uniformly from 4 options. Without filtering, 75% of training prompts describe a triage task but the env runs release_notes or dep_update β†’ guaranteed env score = 0 regardless of what the model does. The dataset pre-simulates the env's RNG to only include seeds where `task_type == "triage"`.
243
 
244
- **Why `gen_slot` offsets?**
245
- GRPO needs reward *variance across generations of the same prompt*. Without the offset, all N generations of the same prompt reset the env with the same seed β†’ same org config β†’ same correct answers β†’ potentially same reward β†’ zero advantage. `gen_slot=k` adds `k` to the seed so generation k explores a slightly different org.
246
 
247
- **Why `PMOpsGRPOTrainer`?**
248
- TRL's GRPOTrainer builds `reward_kwargs` from dataset columns only. Our multi-turn rollout reward (computed inside `rollout_func`) never reaches `_calculate_rewards` through the normal kwargs path. `PMOpsGRPOTrainer` wraps `rollout_func` to cache rewards and injects them directly as a tensor, bypassing TRL's broken plumbing.
249
 
250
  ---
251
 
252
- ## Sponsor Alignment
253
-
254
- | Sponsor | Relevance |
255
- |---|---|
256
- | **Meta RFC 004** | Delayed trajectory reward is the native reward mode |
257
- | **Patronus** | Schema drift β€” tests whether agents track per-org convention changes |
258
- | **Scale AI** | Enterprise workflow simulation with procedurally generated organizations |
259
- | **AI Labs** | Direct PM-in-the-loop positioning for agentic systems |
 
1
+ # PM-Ops πŸ› οΈ
2
+
3
+ > **Can a small model learn to operate inside a company it has never seen before?**
4
+
5
+ PM-Ops is a reinforcement learning benchmark and training environment where an LLM agent operates as a product manager inside a fully simulated software organization β€” navigating ticketing, codebases, and chat β€” using only the organization's own runbook as its guide.
6
+
7
  ---
8
+
9
+ ## Links
10
+
11
+ | Resource | Link |
12
+ |---|---|
13
+ | πŸ€— **Live Environment** | [TheCrustaceans/Pm-ops β€” HuggingFace Space](https://huggingface.co/spaces/TheCrustaceans/Pm-ops) |
14
+ | πŸ“ **Blog Post** | [BlogPost.mdx](https://huggingface.co/spaces/TheCrustaceans/Pm-ops/blob/main/BlogPost.mdx) |
15
+ | **training** | [Google Drive](https://drive.google.com/file/d/1JdwYukKrEaMTaOwc1W4Q8bBRjxwGZ2be/view?usp=drive_link) |
16
+ | **evaluation** | [Google Drive](https://drive.google.com/file/d/1JdwYukKrEaMTaOwc1W4Q8bBRjxwGZ2be/view?usp=sharing) |
17
+
18
  ---
19
 
20
+ ## Table of Contents
21
+
22
+ - [Motivation](#motivation)
23
+ - [Environment](#environment)
24
+ - [The Three Apps](#the-three-apps)
25
+ - [Action Space](#action-space)
26
+ - [Observation Schema](#observation-schema)
27
+ - [Org Generator](#org-generator)
28
+ - [Task Types](#task-types)
29
+ - [Difficulty Tiers](#difficulty-tiers)
30
+ - [Reward Design](#reward-design)
31
+ - [Training Pipeline](#training-pipeline)
32
+ - [SFT Warmup](#sft-warmup)
33
+ - [GRPO](#grpo)
34
+ - [Reward Shaping](#reward-shaping)
35
+ - [Results](#results)
36
+ - [Project Structure](#project-structure)
37
+ - [Setup & Usage](#setup--usage)
38
+
39
+ ---
40
 
41
+ ## Motivation
42
 
43
+ Frontier models achieve near-perfect scores on standard benchmarks. Yet when deployed inside a real organization, they routinely fail tasks that a junior employee handles on day one.
44
+
45
+ The reason: **organizational context is not on the internet.**
46
+
47
+ A model knows that database failures are serious. It does not know that *your* company outsources DB infrastructure to a vendor and that the correct response is an email, not an internal incident ticket. No benchmark measures this gap. PM-Ops does.
48
+
49
+ **Why system prompts are not the answer:**
50
+
51
+ | Problem | Detail |
52
+ |---|---|
53
+ | Attention decay | As conversation grows, the model attends to early context instructions with diminishing weight. Org conventions buried in a long system prompt get effectively ignored. |
54
+ | Static snapshots | Channels get renamed. Teams get reorganized. A system prompt written last quarter is already wrong. |
55
+ | No gradient | The model is told what to do, not trained on the consequences of ignoring it. There is no learning signal. |
56
+
57
+ PM-Ops addresses this by making the agent *experience* the consequences of skipping the runbook across thousands of varied organizational configurations during training.
58
 
59
  ---
60
 
61
+ ## Environment
62
 
63
+ PM-Ops runs as a WebSocket-based environment server (OpenEnv-compatible) hosted on HuggingFace Spaces. Each episode presents the agent with a freshly generated organization β€” different team names, channel names, label taxonomies, and priority levels every time.
64
+
65
+ ### The Three Apps
66
 
67
  ```
68
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
69
+ β”‚ PM-Ops Environment β”‚
70
+ β”‚ β”‚
71
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
72
+ β”‚ β”‚ Ticketing β”‚ β”‚ Codebase β”‚ β”‚ Chat β”‚ β”‚
73
+ β”‚ β”‚ (Jira-like) β”‚ β”‚ (GitHub-like)β”‚ β”‚ (Slack-like) β”‚ β”‚
74
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
75
+ β”‚ β”‚ tickets β”‚ β”‚ repositories β”‚ β”‚ channels β”‚ β”‚
76
+ β”‚ β”‚ projects β”‚ β”‚ commits β”‚ β”‚ threads β”‚ β”‚
77
+ β”‚ β”‚ teams β”‚ β”‚ pull requestsβ”‚ β”‚ DMs β”‚ β”‚
78
+ β”‚ β”‚ labels β”‚ β”‚ file authors β”‚ β”‚ user profilesβ”‚ β”‚
79
+ β”‚ β”‚ priorities β”‚ β”‚ changed filesβ”‚ β”‚ β”‚ β”‚
80
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
81
+ β”‚ β”‚
82
+ β”‚ Ground truth verified against DB β€” not LLM-judged β”‚
83
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
84
  ```
85
 
86
+ **Ticketing App:** The source of truth for org state. Agent creates tickets, assigns teams, sets labels and priorities. Correctness is verified by checking the database, not asking another LLM.
87
 
88
+ **Codebase App:** Pure detective work. No code is written. When a bug report arrives, the agent traces commit history, identifies the responsible change, and finds the author.
89
 
90
+ **Chat App:** Episode-specific channels. The channel `#oncall-payments` in episode 1 may be `#urgent-billing` in episode 2. The agent must read the runbook to know which one is live β€” not guess from training data.
91
 
92
+ ### Action Space
 
 
 
 
 
93
 
94
+ ```json
95
+ { "action_type": "meta.read_runbook", "args": {} }
96
+ { "action_type": "meta.finish", "args": {} }
97
+ { "action_type": "meta.noop", "args": {} }
98
+ { "action_type": "ticketing.create_ticket", "args": { "summary": "...", "label": "...", "priority": "...", "assignee": "..." } }
99
+ { "action_type": "ticketing.assign_ticket", "args": { "ticket_id": "...", "team": "..." } }
100
+ { "action_type": "chat.list_channels", "args": {} }
101
+ { "action_type": "chat.post_message", "args": { "channel": "...", "text": "..." } }
102
+ { "action_type": "codebase.get_commits", "args": { "repo": "..." } }
103
+ ```
104
 
105
+ All actions are emitted as chain-of-thought reasoning followed by a ` ```json ` block. Three-pass extraction handles malformed outputs: code block β†’ raw JSON β†’ regex fallback.
106
 
107
+ ### Observation Schema
108
 
109
+ Each step returns:
110
+
111
+ ```python
112
+ {
113
+ "task_brief": str, # the incident/task description
114
+ "last_action_result": dict, # {"ok": bool, "data": ..., "error": ...}
115
+ "step": int,
116
+ "steps_remaining": int,
117
+ "reward": float, # always 0.0 until meta.finish
118
+ "done": bool,
119
+ "token_budget_remaining": int,
120
+ }
121
  ```
122
 
123
+ **Delayed reward:** `reward` is always `0.0` until the agent calls `meta.finish`. This forces the agent to commit to a plan and execute it β€” step-by-step reward hacking is not possible.
124
 
125
+ ### Org Generator
126
 
127
+ LLM-generated scenarios sound plausible but fail formal verification. PM-Ops uses a **deterministic, parameterized, seedable org generator** instead.
128
+
129
+ Each org is generated from a seed and produces:
 
 
 
 
 
130
 
131
+ - `label_taxonomy` β€” org-specific bug/feature labels
132
+ - `priority_levels` β€” org-specific severity scale
133
+ - `team_map` β€” service β†’ owning team
134
+ - `oncall_channels` β€” team β†’ notification channel
135
+ - `required_ticket_fields` β€” what fields must be set for a valid ticket
136
+
137
+ The same seed always produces the same org. Evaluation is reproducible. Training sees a different org every episode.
138
+
139
+ ### Task Types
140
+
141
+ | Task | Description | Key Challenge |
142
+ |---|---|---|
143
+ | **Triage** | Bug report arrives. Create ticket with correct label, priority, assignee. Notify correct channel. | Using org taxonomy, not generic labels. |
144
+ | **Incident Routing** | Production incident. Identify affected services, find owning team via codebase + team map, escalate. | Cross-system reasoning: chat β†’ codebase β†’ ticketing. |
145
+ | **Release Notes** | Compile and post release notes for resolved tickets in the org's specific format. | Format compliance, not just content. |
146
+ | **Dependency Update** | Breaking library change hits multiple services. Notify each team through their own oncall channel, create per-team tickets. | Multi-target coordination without collapsing to single-service logic. |
147
+
148
+ ### Difficulty Tiers
149
+
150
+ | Tier | Services | Labels | Priorities | Notes |
151
+ |---|---|---|---|---|
152
+ | **Easy** | 2 | 3 | 2 | No ambiguity. Runbook is complete. |
153
+ | **Medium** | 3 | 4 | 4 | Partial runbook. Agent must infer across systems. Multi-owner configs. |
154
+ | **Hard** | β‰₯5 | 5+ | 5+ | Noise channels designed to distract. Outdated/missing docs. Simulated panicking users in general chat β€” correct behavior is to ignore them. |
155
+
156
+ The hard tier specifically tests resistance to NLP pressure. A model that responds to "THE SITE IS DOWN PLEASE HELP" in `#general` instead of reading the runbook and acting on verified info fails the episode.
157
 
158
  ---
159
 
160
+ ## Reward Design
161
 
162
+ Rewards are **delayed** β€” revealed only at `meta.finish`.
163
 
164
+ ### Scoring Breakdown
165
+
166
+ ```
167
+ Ticket created correctly +0.25
168
+ Correct label +0.20
169
+ Correct priority +0.20
170
+ Correct team assignment +0.20
171
+ Correct channel notification +0.15
172
+ ─────────────────────────────────
173
+ Maximum per episode 1.00
174
  ```
175
 
176
+ ### Penalties
 
 
 
 
 
177
 
 
178
  ```
179
+ Wrong channel post -0.05 each
180
+ (>2 wrong posts β†’ net negative; defeats channel-spray)
181
+ Duplicate ticket -0.10 each
182
+ Zero valid actions (inaction)-1.00
183
  ```
184
 
185
+ The `-0.05` per wrong channel is calibrated: posting to every channel to guarantee hitting the right one becomes net negative after 2 wrong posts. The agent must read the runbook to know the correct channel.
 
 
186
 
187
+ ### Combined Training Reward
188
 
189
  ```python
190
+ if valid_action_count == 0:
191
+ combined = -1.0 # never output valid JSON
192
+ elif final_score == 0.0:
193
+ combined = valid_json_ratio * 0.15 # tried but failed
194
+ + read_runbook_reward * 0.10
195
+ - 0.30 # hard penalty for zero completion
196
+ else:
197
+ combined = final_score * 0.45 # task correctness
198
+ + no_wrong_channels * 0.15 # anti channel-spray
199
+ + valid_json_ratio * 0.15 # format discipline
200
+ + read_runbook_reward * 0.15 # process compliance
201
+ + efficiency * 0.10 # steps saved
202
  ```
203
 
 
 
204
  ---
205
 
206
  ## Training Pipeline
207
 
208
+ ### SFT Warmup
209
 
210
+ Before GRPO, a short supervised fine-tuning phase runs on baseline agent demonstrations. Without this, the model outputs prose instead of structured JSON actions and the GRPO gradient is zero β€” the model needs to learn the output format before it can learn the task.
211
 
212
+ ### GRPO
 
 
 
213
 
214
+ We use **Group Relative Policy Optimization** to train a 7B parameter model directly against PM-Ops episode rewards.
215
 
216
+ Each training step is a full PM-Ops episode:
217
+ 1. Model plays through up to 15 turns
218
+ 2. Environment scores the final state
219
+ 3. Gradient updates weights based on relative performance across the generation group
220
 
221
+ No intermediate reward signal is given. The model must learn to plan across multiple steps.
222
 
223
+ ### Reward Shaping
224
+
225
+ | Component | Weight | Purpose |
226
+ |---|---|---|
227
+ | `final_score` | 0.45 | Primary correctness from env grader |
228
+ | `no_wrong_channels` | 0.15 | Anti-hack: penalise channel spray |
229
+ | `valid_json_ratio` | 0.15 | Format discipline |
230
+ | `read_runbook` | 0.15 | Process compliance |
231
+ | `efficiency` | 0.10 | Steps saved (only when task succeeds) |
232
 
233
+ `PMOpsGRPOTrainer` subclasses TRL's `GRPOTrainer` and overrides `_calculate_rewards()` to inject pre-computed rewards from the rollout directly, bypassing TRL's broken kwargs flow for multi-turn rollouts.
234
 
235
  ---
236
 
237
+ ## Results
238
+
239
+ Evaluated on 13 matched episodes, comparing the heuristic baseline agent against the GRPO-trained model:
240
+
241
+ | Model | Avg Score (13 eps) |
242
+ |---|---|
243
+ | Heuristic Baseline | 0.269 |
244
+ | GRPO Trained (early) | 0.362 |
245
+ | **Improvement** | **+34.6%** |
246
+
247
+ Training reward trend across 14 steps shows a positive slope of **+0.0024/step** with KL divergence remaining stable and controlled throughout.
248
+
249
+ ![Eval Results](eval_results.png)
250
+ ![Training Metrics](training_metrics.png)
251
+
252
+ ---
253
+
254
+ ## Project Structure
255
 
256
  ```
257
+ pm_ops/
258
  β”œβ”€β”€ server/
259
+ β”‚ β”œβ”€β”€ pm_ops_environment.py # OpenEnv Environment subclass
260
+ β”‚ β”œβ”€β”€ org_generator.py # Deterministic, seedable org factory
261
+ β”‚ β”œβ”€β”€ grader.py # Ground-truth reward computation
262
+ β”‚ └── app.py # FastAPI server entry point
263
+ β”œβ”€β”€ inference.py # Heuristic baseline agent + eval runner
264
+ └── pyproject.toml
265
+
266
+ training/
267
+ β”œβ”€β”€ rollout.py # Multi-turn rollout: build_messages, extract_json_action
268
+ β”œβ”€β”€ rewards.py # Reward functions + weight constants
269
+ β”œβ”€β”€ pm_ops_trainer.py # PMOpsGRPOTrainer (_calculate_rewards override)
270
+ β”œβ”€β”€ prompts.py # SYSTEM_PROMPT, format_observation
271
+ β”œβ”€β”€ dataset.py # Dataset loading + seed parsing
272
+ └── train_v3.ipynb # SFT warmup + GRPO training notebook
 
 
 
 
 
 
 
 
 
 
 
 
273
  ```
274
 
275
  ---
276
 
277
+ ## Setup & Usage
278
 
279
+ ### Run the environment server
280
 
281
  ```bash
282
+ cd pm_ops
283
  pip install -e .
284
+ server # starts FastAPI on :7860
285
  ```
286
 
287
  ### Run the baseline agent
288
 
289
  ```bash
290
+ export API_BASE_URL=https://thecrustaceans-pm-ops.hf.space
291
+ python pm_ops/inference.py
292
  ```
293
 
294
+ ### Connect via OpenEnv client
295
 
296
  ```python
297
  from openenv.core import GenericEnvClient
298
 
299
+ env = GenericEnvClient(base_url="https://thecrustaceans-pm-ops.hf.space").sync()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
+ with env:
302
+ result = env.reset()
303
+ obs = result.observation
304
 
305
+ result = env.step({
306
+ "action_type": "meta.read_runbook",
307
+ "args": {}
308
+ })
309
+ ```
310
 
311
+ ### Training
 
312
 
313
+ Open `training/train_v3.ipynb` in a Colab instance with a GPU. The notebook handles SFT warmup, GRPO setup, and checkpointing.
 
314
 
315
  ---
316
 
317
+ **Live Environment:** [https://huggingface.co/spaces/TheCrustaceans/Pm-ops](https://huggingface.co/spaces/TheCrustaceans/Pm-ops)