File size: 13,874 Bytes
aa8d9a1
 
 
 
 
 
 
 
 
 
 
 
 
36068f1
aa8d9a1
36068f1
aa8d9a1
36068f1
aa8d9a1
36068f1
 
 
 
 
 
 
9c90904
f2699c7
 
36068f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa8d9a1
36068f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa8d9a1
36068f1
aa8d9a1
36068f1
 
 
 
 
aa8d9a1
36068f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa8d9a1
36068f1
aa8d9a1
36068f1
aa8d9a1
36068f1
 
 
 
 
aa8d9a1
36068f1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa8d9a1
36068f1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
---
title: PM-Ops RL Environment
emoji: πŸ“‹
colorFrom: blue
colorTo: purple
sdk: docker
app_port: 8000
pinned: false
tags:
  - openenv
  - reinforcement-learning
---

# PM-Ops πŸ› οΈ

> **Can a small model learn to operate inside a company it has never seen before?**

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.

---

## Links

| Resource | Link |
|---|---|
| πŸ€— **Live Environment** | [TheCrustaceans/Pm-ops β€” HuggingFace Space](https://huggingface.co/spaces/TheCrustaceans/Pm-ops) |
| πŸ“ **Blog Post** | [BlogPost.mdx](https://huggingface.co/spaces/TheCrustaceans/Pm-ops/blob/main/BlogPost.md) |
| πŸŽ₯ **inference image** | [Google Drive](https://drive.google.com/file/d/1JdwYukKrEaMTaOwc1W4Q8bBRjxwGZ2be/view?usp=drive_link) |
| πŸ“Š **train image** | [Google Drive](https://drive.google.com/file/d/1Wb8G0WEPPvAFppBNSVMZ7EMFBPFjsP8n/view?usp=sharing) |

---

## Table of Contents

- [Motivation](#motivation)
- [Environment](#environment)
  - [The Three Apps](#the-three-apps)
  - [Action Space](#action-space)
  - [Observation Schema](#observation-schema)
  - [Org Generator](#org-generator)
  - [Task Types](#task-types)
  - [Difficulty Tiers](#difficulty-tiers)
- [Reward Design](#reward-design)
- [Training Pipeline](#training-pipeline)
  - [SFT Warmup](#sft-warmup)
  - [GRPO](#grpo)
  - [Reward Shaping](#reward-shaping)
- [Results](#results)
- [Project Structure](#project-structure)
- [Setup & Usage](#setup--usage)

---

## Motivation

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.

The reason: **organizational context is not on the internet.**

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.

**Why system prompts are not the answer:**

| Problem | Detail |
|---|---|
| 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. |
| Static snapshots | Channels get renamed. Teams get reorganized. A system prompt written last quarter is already wrong. |
| No gradient | The model is told what to do, not trained on the consequences of ignoring it. There is no learning signal. |

PM-Ops addresses this by making the agent *experience* the consequences of skipping the runbook across thousands of varied organizational configurations during training.

---

## Environment

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.

### The Three Apps

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    PM-Ops Environment                    β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Ticketing   β”‚  β”‚   Codebase   β”‚  β”‚     Chat     β”‚  β”‚
β”‚  β”‚  (Jira-like) β”‚  β”‚ (GitHub-like)β”‚  β”‚ (Slack-like) β”‚  β”‚
β”‚  β”‚              β”‚  β”‚              β”‚  β”‚              β”‚  β”‚
β”‚  β”‚ tickets      β”‚  β”‚ repositories β”‚  β”‚ channels     β”‚  β”‚
β”‚  β”‚ projects     β”‚  β”‚ commits      β”‚  β”‚ threads      β”‚  β”‚
β”‚  β”‚ teams        β”‚  β”‚ pull requestsβ”‚  β”‚ DMs          β”‚  β”‚
β”‚  β”‚ labels       β”‚  β”‚ file authors β”‚  β”‚ user profilesβ”‚  β”‚
β”‚  β”‚ priorities   β”‚  β”‚ changed filesβ”‚  β”‚              β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                         β”‚
β”‚  Ground truth verified against DB β€” not LLM-judged      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

**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.

**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.

**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.

### Action Space

```json
{ "action_type": "meta.read_runbook",       "args": {} }
{ "action_type": "meta.finish",             "args": {} }
{ "action_type": "meta.noop",               "args": {} }
{ "action_type": "ticketing.create_ticket", "args": { "summary": "...", "label": "...", "priority": "...", "assignee": "..." } }
{ "action_type": "ticketing.assign_ticket", "args": { "ticket_id": "...", "team": "..." } }
{ "action_type": "chat.list_channels",      "args": {} }
{ "action_type": "chat.post_message",       "args": { "channel": "...", "text": "..." } }
{ "action_type": "codebase.get_commits",    "args": { "repo": "..." } }
```

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.

### Observation Schema

Each step returns:

```python
{
    "task_brief":             str,   # the incident/task description
    "last_action_result":     dict,  # {"ok": bool, "data": ..., "error": ...}
    "step":                   int,
    "steps_remaining":        int,
    "reward":                 float, # always 0.0 until meta.finish
    "done":                   bool,
    "token_budget_remaining": int,
}
```

**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.

### Org Generator

LLM-generated scenarios sound plausible but fail formal verification. PM-Ops uses a **deterministic, parameterized, seedable org generator** instead.

Each org is generated from a seed and produces:

- `label_taxonomy` β€” org-specific bug/feature labels
- `priority_levels` β€” org-specific severity scale
- `team_map` β€” service β†’ owning team
- `oncall_channels` β€” team β†’ notification channel
- `required_ticket_fields` β€” what fields must be set for a valid ticket

The same seed always produces the same org. Evaluation is reproducible. Training sees a different org every episode.

### Task Types

| Task | Description | Key Challenge |
|---|---|---|
| **Triage** | Bug report arrives. Create ticket with correct label, priority, assignee. Notify correct channel. | Using org taxonomy, not generic labels. |
| **Incident Routing** | Production incident. Identify affected services, find owning team via codebase + team map, escalate. | Cross-system reasoning: chat β†’ codebase β†’ ticketing. |
| **Release Notes** | Compile and post release notes for resolved tickets in the org's specific format. | Format compliance, not just content. |
| **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. |

### Difficulty Tiers

| Tier | Services | Labels | Priorities | Notes |
|---|---|---|---|---|
| **Easy** | 2 | 3 | 2 | No ambiguity. Runbook is complete. |
| **Medium** | 3 | 4 | 4 | Partial runbook. Agent must infer across systems. Multi-owner configs. |
| **Hard** | β‰₯5 | 5+ | 5+ | Noise channels designed to distract. Outdated/missing docs. Simulated panicking users in general chat β€” correct behavior is to ignore them. |

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.

---

## Reward Design

Rewards are **delayed** β€” revealed only at `meta.finish`.

### Scoring Breakdown

```
Ticket created correctly     +0.25
Correct label                +0.20
Correct priority             +0.20
Correct team assignment      +0.20
Correct channel notification +0.15
─────────────────────────────────
Maximum per episode           1.00
```

### Penalties

```
Wrong channel post           -0.05 each
                             (>2 wrong posts β†’ net negative; defeats channel-spray)
Duplicate ticket             -0.10 each
Zero valid actions (inaction)-1.00
```

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.

### Combined Training Reward

```python
if valid_action_count == 0:
    combined = -1.0                              # never output valid JSON
elif final_score == 0.0:
    combined = valid_json_ratio * 0.15           # tried but failed
            + read_runbook_reward * 0.10
            - 0.30                               # hard penalty for zero completion
else:
    combined = final_score         * 0.45        # task correctness
            + no_wrong_channels    * 0.15        # anti channel-spray
            + valid_json_ratio     * 0.15        # format discipline
            + read_runbook_reward  * 0.15        # process compliance
            + efficiency           * 0.10        # steps saved
```

---

## Training Pipeline

### SFT Warmup

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.

### GRPO

We use **Group Relative Policy Optimization** to train a 7B parameter model directly against PM-Ops episode rewards.

Each training step is a full PM-Ops episode:
1. Model plays through up to 15 turns
2. Environment scores the final state
3. Gradient updates weights based on relative performance across the generation group

No intermediate reward signal is given. The model must learn to plan across multiple steps.

### Reward Shaping

| Component | Weight | Purpose |
|---|---|---|
| `final_score` | 0.45 | Primary correctness from env grader |
| `no_wrong_channels` | 0.15 | Anti-hack: penalise channel spray |
| `valid_json_ratio` | 0.15 | Format discipline |
| `read_runbook` | 0.15 | Process compliance |
| `efficiency` | 0.10 | Steps saved (only when task succeeds) |

`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.

---

## Results

Evaluated on 13 matched episodes, comparing the heuristic baseline agent against the GRPO-trained model:

| Model | Avg Score (13 eps) |
|---|---|
| Heuristic Baseline | 0.269 |
| GRPO Trained (early) | 0.362 |
| **Improvement** | **+34.6%** |

Training reward trend across 14 steps shows a positive slope of **+0.0024/step** with KL divergence remaining stable and controlled throughout.

---

## Project Structure

```
pm_ops/
β”œβ”€β”€ server/
β”‚   β”œβ”€β”€ pm_ops_environment.py   # OpenEnv Environment subclass
β”‚   β”œβ”€β”€ org_generator.py        # Deterministic, seedable org factory
β”‚   β”œβ”€β”€ grader.py               # Ground-truth reward computation
β”‚   └── app.py                  # FastAPI server entry point
β”œβ”€β”€ inference.py                # Heuristic baseline agent + eval runner
└── pyproject.toml

training/
β”œβ”€β”€ rollout.py                  # Multi-turn rollout: build_messages, extract_json_action
β”œβ”€β”€ rewards.py                  # Reward functions + weight constants
β”œβ”€β”€ pm_ops_trainer.py           # PMOpsGRPOTrainer (_calculate_rewards override)
β”œβ”€β”€ prompts.py                  # SYSTEM_PROMPT, format_observation
β”œβ”€β”€ dataset.py                  # Dataset loading + seed parsing
└── train_v3.ipynb              # SFT warmup + GRPO training notebook
```

---

## Setup & Usage

### Run the environment server

```bash
cd pm_ops
pip install -e .
server  # starts FastAPI on :7860
```

### Run the baseline agent

```bash
export API_BASE_URL=https://thecrustaceans-pm-ops.hf.space
python pm_ops/inference.py
```

### Connect via OpenEnv client

```python
from openenv.core import GenericEnvClient

env = GenericEnvClient(base_url="https://thecrustaceans-pm-ops.hf.space").sync()

with env:
    result = env.reset()
    obs = result.observation

    result = env.step({
        "action_type": "meta.read_runbook",
        "args": {}
    })
```

### Training

Open `training/train_v3.ipynb` in a Colab instance with a GPU. The notebook handles SFT warmup, GRPO setup, and checkpointing.

---

**Live Environment:** [https://huggingface.co/spaces/TheCrustaceans/Pm-ops](https://huggingface.co/spaces/TheCrustaceans/Pm-ops)