Angshuman28 commited on
Commit
d2cb56e
Β·
verified Β·
1 Parent(s): 8b1452d

Upload folder using huggingface_hub

Browse files
Blog.md CHANGED
@@ -338,3 +338,6 @@ governance policy for its own cognition.
338
 
339
  And in a crisis world, that may be the difference between fast agreement and
340
  good judgment.
 
 
 
 
338
 
339
  And in a crisis world, that may be the difference between fast agreement and
340
  good judgment.
341
+
342
+ # Results
343
+ ![Results](https://drive.google.com/uc?export=view&id=1n9uF3NZFy3FszypLIl3cGuzeORwy7NOL)
__init__.py CHANGED
@@ -1,16 +1,16 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- # All rights reserved.
3
- #
4
- # This source code is licensed under the BSD-style license found in the
5
- # LICENSE file in the root directory of this source tree.
6
-
7
- """Crisisworldcortex Environment."""
8
-
9
- from .client import CrisisworldcortexEnv
10
- from .models import CrisisworldcortexAction, CrisisworldcortexObservation
11
-
12
- __all__ = [
13
- "CrisisworldcortexAction",
14
- "CrisisworldcortexObservation",
15
- "CrisisworldcortexEnv",
16
- ]
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Crisisworldcortex Environment."""
8
+
9
+ from .client import CrisisworldcortexEnv
10
+ from .models import CrisisworldcortexAction, CrisisworldcortexObservation
11
+
12
+ __all__ = [
13
+ "CrisisworldcortexAction",
14
+ "CrisisworldcortexObservation",
15
+ "CrisisworldcortexEnv",
16
+ ]
demo/CLAUDE.md CHANGED
@@ -1,45 +1,45 @@
1
- # demo/CLAUDE.md
2
-
3
- Replay-only visualization. Live demos fail under judging pressure; ship canned scenarios.
4
-
5
- ## Belongs here
6
-
7
- - `visualizer/trace_renderer.py` β€” renders a JSON trace as a "council in action" view.
8
- - `visualizer/reward_curve_plot.py` β€” plots reward curves from training logs.
9
- - `demo_scenarios/*.json` β€” pre-recorded trajectories for the pitch (e.g. `scenario_flat_fails.json`, `scenario_cortex_holds_dissent.json`).
10
-
11
- ## Does not belong here
12
-
13
- Live agent execution (record offline, replay here). Training logic. Graders.
14
-
15
- ## Allowed imports
16
-
17
- - `cortex.schemas` β€” typed parse of trace JSON. Types only, no logic.
18
- - stdlib + plotting libs (matplotlib / plotly).
19
-
20
- ## Forbidden imports
21
-
22
- - `server/*`, `training/*`, `baselines/*`.
23
- - `cortex.council`, `cortex.routing_policy` β€” if you need to re-run the agent, do it offline and ship a new JSON.
24
-
25
- ## Binding contracts
26
-
27
- - Every JSON scenario conforms to `cortex.schemas.Trajectory`.
28
- - Rendering is deterministic: same JSON β†’ same output, modulo timestamps.
29
- - The pitch-demo scenario must showcase B2 overcommit/misallocate vs Cortex dissent-preservation (design Β§27).
30
- - A pre-recorded demo video (MP4) lives alongside the JSON scenarios as the live-demo fallback.
31
-
32
- ## Public APIs (owned here)
33
-
34
- - `render_trace(json_path: str, out_path: str) -> None`
35
- - `plot_reward_curves(log_paths: list[str], out_path: str) -> None`
36
-
37
- ## Testing requirements
38
-
39
- - Each committed JSON scenario parses into a `Trajectory` without error.
40
- - `render_trace` produces a non-empty output file for each scenario.
41
-
42
- ## Common failure modes
43
-
44
- - Live re-run during the demo β€” network/Colab flakiness kills the pitch. Replay only.
45
- - Renderer depending on a `cortex.council` instance β€” import breaks when Cortex API shifts. Keep read-only on types.
 
1
+ # demo/CLAUDE.md
2
+
3
+ Replay-only visualization. Live demos fail under judging pressure; ship canned scenarios.
4
+
5
+ ## Belongs here
6
+
7
+ - `visualizer/trace_renderer.py` β€” renders a JSON trace as a "council in action" view.
8
+ - `visualizer/reward_curve_plot.py` β€” plots reward curves from training logs.
9
+ - `demo_scenarios/*.json` β€” pre-recorded trajectories for the pitch (e.g. `scenario_flat_fails.json`, `scenario_cortex_holds_dissent.json`).
10
+
11
+ ## Does not belong here
12
+
13
+ Live agent execution (record offline, replay here). Training logic. Graders.
14
+
15
+ ## Allowed imports
16
+
17
+ - `cortex.schemas` β€” typed parse of trace JSON. Types only, no logic.
18
+ - stdlib + plotting libs (matplotlib / plotly).
19
+
20
+ ## Forbidden imports
21
+
22
+ - `server/*`, `training/*`, `baselines/*`.
23
+ - `cortex.council`, `cortex.routing_policy` β€” if you need to re-run the agent, do it offline and ship a new JSON.
24
+
25
+ ## Binding contracts
26
+
27
+ - Every JSON scenario conforms to `cortex.schemas.Trajectory`.
28
+ - Rendering is deterministic: same JSON β†’ same output, modulo timestamps.
29
+ - The pitch-demo scenario must showcase B2 overcommit/misallocate vs Cortex dissent-preservation (design Β§27).
30
+ - A pre-recorded demo video (MP4) lives alongside the JSON scenarios as the live-demo fallback.
31
+
32
+ ## Public APIs (owned here)
33
+
34
+ - `render_trace(json_path: str, out_path: str) -> None`
35
+ - `plot_reward_curves(log_paths: list[str], out_path: str) -> None`
36
+
37
+ ## Testing requirements
38
+
39
+ - Each committed JSON scenario parses into a `Trajectory` without error.
40
+ - `render_trace` produces a non-empty output file for each scenario.
41
+
42
+ ## Common failure modes
43
+
44
+ - Live re-run during the demo β€” network/Colab flakiness kills the pitch. Replay only.
45
+ - Renderer depending on a `cortex.council` instance β€” import breaks when Cortex API shifts. Keep read-only on types.
inference.py CHANGED
@@ -42,7 +42,11 @@ import os
42
  import sys
43
  from dataclasses import dataclass
44
  from typing import Any, Dict, List, Literal, Optional
45
-
 
 
 
 
46
  from baselines.flat_agent import B1FlatAgent, B1StepEvent
47
  from cortex.llm_client import LLMClient
48
  from CrisisWorldCortex.models import OuterActionPayload
 
42
  import sys
43
  from dataclasses import dataclass
44
  from typing import Any, Dict, List, Literal, Optional
45
+ try:
46
+ from dotenv import load_dotenv
47
+ load_dotenv()
48
+ except ImportError:
49
+ pass
50
  from baselines.flat_agent import B1FlatAgent, B1StepEvent
51
  from cortex.llm_client import LLMClient
52
  from CrisisWorldCortex.models import OuterActionPayload
manual_testing.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .models import NoOp, DeployResource, RestrictMovement, CrisisworldcortexAction, CrisisworldcortexObservation, PublicCommunication
2
+ from .server import CrisisWorld as CrisisWorldEnvironment
3
+
4
+ def logger(count, obs: CrisisworldcortexObservation, action: CrisisworldcortexAction | None) -> None:
5
+ if action:
6
+ print(f"[Action Taken]: {action.model_dump(exclude_none=True)}")
7
+ else:
8
+ print("Initial Observation:")
9
+ print(f"turn={count}, done={obs.done}, reward={obs.reward}")
10
+ for reg in obs.regions:
11
+ print(
12
+ f"{reg.region_id} pop={reg.population} "
13
+ f"inf={reg.infected} rec={reg.recovered} "
14
+ f"dec={reg.deceased}"
15
+ )
16
+ print("-" * 40)
17
+ print()
18
+
19
+ env = CrisisWorldEnvironment(config=EnvConfig(max_turns=6, num_regions=4))
20
+ obs = env.reset(seed=42, episode_id="manual-test")
21
+ logger(0, obs, None)
22
+
23
+ # Turn 1 (NoOp)
24
+ action = DeployResource(resource="medical", region_id="r0", amount=10)
25
+ # action = NoOp()
26
+ obs = env.step(action)
27
+ logger(1, obs, action)
28
+
29
+ # Turn 2 (NoOp)
30
+ action = NoOp()
31
+ obs = env.step(action)
32
+ logger(2, obs, action)
33
+
34
+ # Turn 3 (DeployResource to r0)
35
+ action = RestrictMovement(region_id="r0", level=2)
36
+ obs = env.step(action)
37
+ logger(3, obs, action)
38
+
39
+ # Turn 4 (PublicCommunication)
40
+ action = PublicCommunication(audience="public", message="Stay safe! Wear masks and maintain social distancing. Don't move between regions, stay home if you can, and seek medical care if you have symptoms.")
41
+ obs = env.step(action)
42
+ logger(4, obs, action)
43
+
44
+ # Turn 5 (NoOp)
45
+ action = DeployResource(resource="medical", region_id="r0", amount=40)
46
+ # action = NoOp()
47
+ obs = env.step(action)
48
+ logger(5, obs, action)
notebooks/colab_demo_unsloth_smollm.ipynb ADDED
@@ -0,0 +1,538 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# CrisisWorldCortex β€” Colab Mini-Demo (Unsloth + SmolLM2-360M)\n",
8
+ "\n",
9
+ "**Purpose.** Stand-alone Colab notebook that demonstrates the GRPO training pipeline end-to-end on free Colab resources. Mirrors the production pipeline in `training/scripts/train_b1_grpo.py` but with:\n",
10
+ "\n",
11
+ "- **Model:** `unsloth/SmolLM2-360M-Instruct` (~360M params, well under the 500M ceiling for free Colab).\n",
12
+ "- **Steps:** `MAX_TRAIN_STEPS = 20` (vs. 300 in production). Just enough to show learning signal flowing.\n",
13
+ "- **Dataset:** 2 tasks Γ— 5 seeds = 10 prompts (vs. 3 tasks Γ— 50 seeds = 150 in production).\n",
14
+ "- **Group size:** 2 (vs. 4 in production).\n",
15
+ "\n",
16
+ "Wall-clock: ~5–10 minutes on a free Colab T4. CPU-only fallback also works (slower).\n",
17
+ "\n",
18
+ "**Compatibility note.** The user's production HF Jobs runs use `train_b1_grpo.py` with Qwen3-7B / Llama-3.1-8B. This notebook does **not** replace those; it is a demo-grade smoke run for showing the loop turning. The training contract β€” single-step GRPO against the Phase-1-fixed `outer_reward`, parse-failure β†’ marker action β€” is identical.\n",
19
+ "\n",
20
+ "**Prereqs.**\n",
21
+ "1. Colab Secrets has `HF_TOKEN` set (Tools β†’ Secrets, name = `HF_TOKEN`, value = `hf_xxx` with write access). Skip if you don't want to push to Hub.\n",
22
+ "2. The HF Space `Angshuman28/CrisisWorldCortex` is running and reachable. (You can override `ENV_URL` below if you've forked it.)\n",
23
+ "\n",
24
+ "**One-shot run:** `Runtime β†’ Run all`. The notebook prints a clear status line at the end indicating whether the loop completed, and prints reward statistics so you can see signal."
25
+ ]
26
+ },
27
+ {
28
+ "cell_type": "markdown",
29
+ "metadata": {},
30
+ "source": [
31
+ "## 1. Install dependencies\n",
32
+ "\n",
33
+ "Mirrors the install cell in `notebooks/train_b1_grpo.ipynb`. Unsloth ships its own torch / vLLM / xformers stack tuned for Colab T4."
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": null,
39
+ "metadata": {},
40
+ "outputs": [],
41
+ "source": [
42
+ "%%capture\n",
43
+ "!pip install --upgrade pip\n",
44
+ "!pip install unsloth vllm\n",
45
+ "!pip install --upgrade --no-deps \"trl>=0.14\" peft accelerate bitsandbytes\n",
46
+ "!pip install pydantic openenv huggingface_hub matplotlib datasets"
47
+ ]
48
+ },
49
+ {
50
+ "cell_type": "markdown",
51
+ "metadata": {},
52
+ "source": [
53
+ "## 2. Authenticate with Hugging Face (optional)\n",
54
+ "\n",
55
+ "Reads `HF_TOKEN` from Colab Secrets. If unavailable, the notebook still runs end-to-end β€” the final Hub push step will be skipped."
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "execution_count": null,
61
+ "metadata": {},
62
+ "outputs": [],
63
+ "source": [
64
+ "import os\n",
65
+ "\n",
66
+ "HF_TOKEN = \"\"\n",
67
+ "try:\n",
68
+ " from google.colab import userdata\n",
69
+ " HF_TOKEN = userdata.get(\"HF_TOKEN\") or \"\"\n",
70
+ "except Exception:\n",
71
+ " HF_TOKEN = os.environ.get(\"HF_TOKEN\", \"\")\n",
72
+ "\n",
73
+ "if HF_TOKEN:\n",
74
+ " os.environ[\"HF_TOKEN\"] = HF_TOKEN\n",
75
+ " print(\"HF auth OK β€” Hub push enabled at the end.\")\n",
76
+ "else:\n",
77
+ " print(\"No HF_TOKEN found. Notebook will still run; Hub push step will be skipped.\")"
78
+ ]
79
+ },
80
+ {
81
+ "cell_type": "markdown",
82
+ "metadata": {},
83
+ "source": [
84
+ "## 3. Clone CrisisWorldCortex and install\n",
85
+ "\n",
86
+ "Pulls the deployed HF Space repo and installs it locally. This gives the notebook the same `CrisisworldcortexEnv` HTTP client, `baselines.flat_agent` system prompt, and parser used by the production pipeline. **Nothing in the repo is modified β€” this is a read-only consumer.**"
87
+ ]
88
+ },
89
+ {
90
+ "cell_type": "code",
91
+ "execution_count": null,
92
+ "metadata": {},
93
+ "outputs": [],
94
+ "source": [
95
+ "%%capture\n",
96
+ "!rm -rf /content/CrisisWorldCortex\n",
97
+ "!git clone https://huggingface.co/spaces/Angshuman28/CrisisWorldCortex /content/CrisisWorldCortex\n",
98
+ "%cd /content/CrisisWorldCortex\n",
99
+ "!pip install -e ."
100
+ ]
101
+ },
102
+ {
103
+ "cell_type": "code",
104
+ "execution_count": null,
105
+ "metadata": {},
106
+ "outputs": [],
107
+ "source": [
108
+ "import sys\n",
109
+ "sys.path.insert(0, \"/content/CrisisWorldCortex\")\n",
110
+ "\n",
111
+ "from baselines.flat_agent import (\n",
112
+ " build_system_prompt,\n",
113
+ " parse_action,\n",
114
+ " parse_failure_marker,\n",
115
+ " serialize_observation,\n",
116
+ ")\n",
117
+ "from CrisisWorldCortex import CrisisworldcortexAction, CrisisworldcortexObservation\n",
118
+ "from CrisisWorldCortex.client import CrisisworldcortexEnv\n",
119
+ "\n",
120
+ "print(\"CrisisWorld imports OK\")"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "markdown",
125
+ "metadata": {},
126
+ "source": [
127
+ "## 4. Load SmolLM2-360M with Unsloth + LoRA\n",
128
+ "\n",
129
+ "SmolLM2-360M is ~360M parameters β€” well under the user-requested 500M ceiling and comfortable on free Colab T4 (or CPU as fallback). LoRA rank 16 is plenty for a smoke demo.\n",
130
+ "\n",
131
+ "Alternative tiny models, all Unsloth-supported, all under 500M:\n",
132
+ "- `unsloth/SmolLM2-135M-Instruct` (135M β€” fastest)\n",
133
+ "- `unsloth/SmolLM2-360M-Instruct` (360M β€” current default, best signal-to-cost)\n",
134
+ "- `unsloth/Qwen2.5-0.5B-Instruct` (~500M β€” closest in family to the production Qwen3 baseline)"
135
+ ]
136
+ },
137
+ {
138
+ "cell_type": "code",
139
+ "execution_count": null,
140
+ "metadata": {},
141
+ "outputs": [],
142
+ "source": [
143
+ "import torch\n",
144
+ "from unsloth import FastLanguageModel\n",
145
+ "\n",
146
+ "MODEL_NAME = \"unsloth/SmolLM2-360M-Instruct\"\n",
147
+ "MAX_SEQ_LEN = 2048\n",
148
+ "LORA_RANK = 16\n",
149
+ "\n",
150
+ "model, tokenizer = FastLanguageModel.from_pretrained(\n",
151
+ " model_name=MODEL_NAME,\n",
152
+ " max_seq_length=MAX_SEQ_LEN,\n",
153
+ " load_in_4bit=True,\n",
154
+ " fast_inference=True, # vLLM-backed generate, required by GRPOTrainer\n",
155
+ " max_lora_rank=LORA_RANK,\n",
156
+ " gpu_memory_utilization=0.5,\n",
157
+ ")\n",
158
+ "\n",
159
+ "model = FastLanguageModel.get_peft_model(\n",
160
+ " model,\n",
161
+ " r=LORA_RANK,\n",
162
+ " target_modules=[\n",
163
+ " \"q_proj\",\n",
164
+ " \"k_proj\",\n",
165
+ " \"v_proj\",\n",
166
+ " \"o_proj\",\n",
167
+ " \"gate_proj\",\n",
168
+ " \"up_proj\",\n",
169
+ " \"down_proj\",\n",
170
+ " ],\n",
171
+ " lora_alpha=LORA_RANK * 2,\n",
172
+ " use_gradient_checkpointing=\"unsloth\",\n",
173
+ " random_state=42,\n",
174
+ ")\n",
175
+ "print(f\"Model + LoRA loaded β€” {MODEL_NAME}\")"
176
+ ]
177
+ },
178
+ {
179
+ "cell_type": "markdown",
180
+ "metadata": {},
181
+ "source": [
182
+ "## 5. Connect to the deployed CrisisWorld env\n",
183
+ "\n",
184
+ "Same HTTP client + URL as the production notebook. Each rollout = one `env.reset()` + one `env.step()` (single-step GRPO; matches the binding contract in `training/CLAUDE.md`)."
185
+ ]
186
+ },
187
+ {
188
+ "cell_type": "code",
189
+ "execution_count": null,
190
+ "metadata": {},
191
+ "outputs": [],
192
+ "source": [
193
+ "ENV_URL = \"https://angshuman28-crisisworldcortex.hf.space\"\n",
194
+ "TASKS = (\"outbreak_easy\", \"outbreak_medium\") # 2 tasks for the mini-demo\n",
195
+ "EPISODE_TICKS = 12\n",
196
+ "SEEDS_PER_TASK = 5\n",
197
+ "\n",
198
+ "\n",
199
+ "def make_env() -> CrisisworldcortexEnv:\n",
200
+ " return CrisisworldcortexEnv(base_url=ENV_URL)\n",
201
+ "\n",
202
+ "\n",
203
+ "_test_env = make_env()\n",
204
+ "_obs = _test_env.reset(task_name=\"outbreak_easy\", seed=0, max_ticks=EPISODE_TICKS)\n",
205
+ "print(f\"Env OK. Initial tick={_obs.tick}, regions={[r.region for r in _obs.regions]}\")"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "markdown",
210
+ "metadata": {},
211
+ "source": [
212
+ "## 6. Build the prompt dataset\n",
213
+ "\n",
214
+ "10 prompts total (2 tasks Γ— 5 seeds). Production uses 150."
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": null,
220
+ "metadata": {},
221
+ "outputs": [],
222
+ "source": [
223
+ "import random\n",
224
+ "from datasets import Dataset\n",
225
+ "\n",
226
+ "SYSTEM_PROMPT = build_system_prompt()\n",
227
+ "\n",
228
+ "\n",
229
+ "def make_chat_prompt(obs: CrisisworldcortexObservation) -> str:\n",
230
+ " return tokenizer.apply_chat_template(\n",
231
+ " [\n",
232
+ " {\"role\": \"system\", \"content\": SYSTEM_PROMPT},\n",
233
+ " {\"role\": \"user\", \"content\": serialize_observation(obs)},\n",
234
+ " ],\n",
235
+ " tokenize=False,\n",
236
+ " add_generation_prompt=True,\n",
237
+ " )\n",
238
+ "\n",
239
+ "\n",
240
+ "rng = random.Random(0)\n",
241
+ "_seed_pool = [\n",
242
+ " {\"task\": task, \"seed\": seed}\n",
243
+ " for task in TASKS\n",
244
+ " for seed in range(SEEDS_PER_TASK)\n",
245
+ "]\n",
246
+ "rng.shuffle(_seed_pool)\n",
247
+ "\n",
248
+ "_prompts: list[str] = []\n",
249
+ "_meta: list[dict] = []\n",
250
+ "for entry in _seed_pool:\n",
251
+ " env = make_env()\n",
252
+ " obs = env.reset(\n",
253
+ " task_name=entry[\"task\"], seed=entry[\"seed\"], max_ticks=EPISODE_TICKS\n",
254
+ " )\n",
255
+ " _prompts.append(make_chat_prompt(obs))\n",
256
+ " _meta.append(entry)\n",
257
+ "\n",
258
+ "train_dataset = Dataset.from_dict(\n",
259
+ " {\n",
260
+ " \"prompt\": _prompts,\n",
261
+ " \"task\": [m[\"task\"] for m in _meta],\n",
262
+ " \"seed\": [m[\"seed\"] for m in _meta],\n",
263
+ " }\n",
264
+ ")\n",
265
+ "print(f\"Dataset built: {len(train_dataset)} examples\")"
266
+ ]
267
+ },
268
+ {
269
+ "cell_type": "markdown",
270
+ "metadata": {},
271
+ "source": [
272
+ "## 7. Reward function\n",
273
+ "\n",
274
+ "Identical contract to the production trainer: parse the completion β†’ submit one env step β†’ return `obs.reward` ∈ `[-1.0, 1.0]`. Parse failures hit the Β§19 marker-action path (`-1.0` + terminate)."
275
+ ]
276
+ },
277
+ {
278
+ "cell_type": "code",
279
+ "execution_count": null,
280
+ "metadata": {},
281
+ "outputs": [],
282
+ "source": [
283
+ "def crisisworld_reward(\n",
284
+ " prompts: list[str],\n",
285
+ " completions: list[str],\n",
286
+ " task: list[str],\n",
287
+ " seed: list[int],\n",
288
+ " **_kwargs: object,\n",
289
+ ") -> list[float]:\n",
290
+ " rewards: list[float] = []\n",
291
+ " for completion, t, s in zip(completions, task, seed):\n",
292
+ " env = make_env()\n",
293
+ " env.reset(task_name=t, seed=int(s), max_ticks=EPISODE_TICKS)\n",
294
+ " action_payload = parse_action(completion) or parse_failure_marker()\n",
295
+ " try:\n",
296
+ " result = env.step(CrisisworldcortexAction(action=action_payload))\n",
297
+ " reward = (\n",
298
+ " result.observation.reward\n",
299
+ " if hasattr(result, \"observation\")\n",
300
+ " else result.reward\n",
301
+ " )\n",
302
+ " rewards.append(float(reward) if reward is not None else 0.0)\n",
303
+ " except Exception as exc:\n",
304
+ " print(f\"[WARN] env.step failed task={t} seed={s}: {exc}\")\n",
305
+ " rewards.append(-1.0)\n",
306
+ " return rewards"
307
+ ]
308
+ },
309
+ {
310
+ "cell_type": "markdown",
311
+ "metadata": {},
312
+ "source": [
313
+ "## 8. GRPO training (mini)\n",
314
+ "\n",
315
+ "20 steps Γ— group size 2 = 40 rollouts. Wall-clock ~5–10 minutes on T4. The point of this notebook is to **show the loop running and producing rewards**, not to converge."
316
+ ]
317
+ },
318
+ {
319
+ "cell_type": "code",
320
+ "execution_count": null,
321
+ "metadata": {},
322
+ "outputs": [],
323
+ "source": [
324
+ "from trl import GRPOConfig, GRPOTrainer\n",
325
+ "\n",
326
+ "MAX_TRAIN_STEPS = 20\n",
327
+ "GROUP_SIZE = 2\n",
328
+ "MAX_PROMPT_LEN = 1536\n",
329
+ "MAX_COMPLETION_LEN = 256\n",
330
+ "\n",
331
+ "training_args = GRPOConfig(\n",
332
+ " output_dir=\"/content/colab_demo_lora\",\n",
333
+ " learning_rate=5e-6,\n",
334
+ " per_device_train_batch_size=GROUP_SIZE,\n",
335
+ " gradient_accumulation_steps=1,\n",
336
+ " num_generations=GROUP_SIZE,\n",
337
+ " max_prompt_length=MAX_PROMPT_LEN,\n",
338
+ " max_completion_length=MAX_COMPLETION_LEN,\n",
339
+ " max_steps=MAX_TRAIN_STEPS,\n",
340
+ " save_steps=MAX_TRAIN_STEPS, # save once at the end\n",
341
+ " logging_steps=1,\n",
342
+ " report_to=\"none\",\n",
343
+ " bf16=True,\n",
344
+ " optim=\"adamw_8bit\",\n",
345
+ " temperature=0.8,\n",
346
+ " use_vllm=True,\n",
347
+ " vllm_mode=\"colocate\",\n",
348
+ " seed=42,\n",
349
+ ")\n",
350
+ "\n",
351
+ "trainer = GRPOTrainer(\n",
352
+ " model=model,\n",
353
+ " processing_class=tokenizer,\n",
354
+ " reward_funcs=[crisisworld_reward],\n",
355
+ " args=training_args,\n",
356
+ " train_dataset=train_dataset,\n",
357
+ ")\n",
358
+ "print(\"GRPOTrainer constructed; starting train()...\")"
359
+ ]
360
+ },
361
+ {
362
+ "cell_type": "code",
363
+ "execution_count": null,
364
+ "metadata": {},
365
+ "outputs": [],
366
+ "source": [
367
+ "trainer.train()\n",
368
+ "print(\"\\n=== Training finished ===\")"
369
+ ]
370
+ },
371
+ {
372
+ "cell_type": "markdown",
373
+ "metadata": {},
374
+ "source": [
375
+ "## 9. Reward summary\n",
376
+ "\n",
377
+ "Pulls the per-step rewards from the trainer log and plots them. With only 20 steps you should not expect convergence β€” the goal is to confirm rewards are non-trivially distributed and the gradient pipeline is alive."
378
+ ]
379
+ },
380
+ {
381
+ "cell_type": "code",
382
+ "execution_count": null,
383
+ "metadata": {},
384
+ "outputs": [],
385
+ "source": [
386
+ "import matplotlib.pyplot as plt\n",
387
+ "\n",
388
+ "log_history = trainer.state.log_history\n",
389
+ "reward_steps = [\n",
390
+ " (entry.get(\"step\", i), entry[\"reward\"])\n",
391
+ " for i, entry in enumerate(log_history)\n",
392
+ " if \"reward\" in entry\n",
393
+ "]\n",
394
+ "\n",
395
+ "if reward_steps:\n",
396
+ " steps, rewards = zip(*reward_steps)\n",
397
+ " print(f\"Logged {len(rewards)} reward points.\")\n",
398
+ " print(f\" mean={sum(rewards)/len(rewards):.3f} min={min(rewards):.3f} max={max(rewards):.3f}\")\n",
399
+ "\n",
400
+ " plt.figure(figsize=(8, 4))\n",
401
+ " plt.plot(steps, rewards, marker=\"o\", linewidth=1)\n",
402
+ " plt.xlabel(\"GRPO step\")\n",
403
+ " plt.ylabel(\"Mean group reward\")\n",
404
+ " plt.title(\"CrisisWorldCortex mini-demo (SmolLM2-360M)\")\n",
405
+ " plt.axhline(0.0, linestyle=\":\", color=\"grey\")\n",
406
+ " plt.grid(alpha=0.3)\n",
407
+ " plt.tight_layout()\n",
408
+ " plt.show()\n",
409
+ "else:\n",
410
+ " print(\"No 'reward' entries in log_history β€” check trainer logging_steps.\")"
411
+ ]
412
+ },
413
+ {
414
+ "cell_type": "markdown",
415
+ "metadata": {},
416
+ "source": [
417
+ "## 10. (Optional) Push LoRA adapter to HF Hub\n",
418
+ "\n",
419
+ "Skipped if no `HF_TOKEN` was found in step 2. Change `HUB_REPO` to your namespace if you want to push."
420
+ ]
421
+ },
422
+ {
423
+ "cell_type": "code",
424
+ "execution_count": null,
425
+ "metadata": {},
426
+ "outputs": [],
427
+ "source": [
428
+ "OUTPUT_DIR = \"/content/colab_demo_lora\"\n",
429
+ "HUB_REPO = \"Angshuman28/crisisworld-colab-demo-smollm2-360m\"\n",
430
+ "\n",
431
+ "model.save_pretrained(OUTPUT_DIR)\n",
432
+ "tokenizer.save_pretrained(OUTPUT_DIR)\n",
433
+ "print(f\"LoRA adapter saved locally to {OUTPUT_DIR}\")\n",
434
+ "\n",
435
+ "if HF_TOKEN:\n",
436
+ " from huggingface_hub import HfApi\n",
437
+ "\n",
438
+ " api = HfApi()\n",
439
+ " api.create_repo(\n",
440
+ " HUB_REPO, exist_ok=True, repo_type=\"model\", private=False, token=HF_TOKEN\n",
441
+ " )\n",
442
+ " api.upload_folder(\n",
443
+ " folder_path=OUTPUT_DIR,\n",
444
+ " repo_id=HUB_REPO,\n",
445
+ " repo_type=\"model\",\n",
446
+ " token=HF_TOKEN,\n",
447
+ " )\n",
448
+ " print(f\"Pushed to https://huggingface.co/{HUB_REPO}\")\n",
449
+ "else:\n",
450
+ " print(\"HF_TOKEN unset β€” skipping Hub push. Adapter is still saved locally.\")"
451
+ ]
452
+ },
453
+ {
454
+ "cell_type": "markdown",
455
+ "metadata": {},
456
+ "source": [
457
+ "## 11. Quick eval β€” one episode per task\n",
458
+ "\n",
459
+ "Runs a single 12-tick episode with the trained adapter on each task at `seed=0` and reports cumulative reward. With only 20 training steps this is a sanity check that the trained model still emits parseable actions, not an evaluation of policy quality."
460
+ ]
461
+ },
462
+ {
463
+ "cell_type": "code",
464
+ "execution_count": null,
465
+ "metadata": {},
466
+ "outputs": [],
467
+ "source": [
468
+ "FastLanguageModel.for_inference(model)\n",
469
+ "\n",
470
+ "\n",
471
+ "def _hf_chat(system: str, user: str, max_new_tokens: int = 192) -> str:\n",
472
+ " prompt = tokenizer.apply_chat_template(\n",
473
+ " [\n",
474
+ " {\"role\": \"system\", \"content\": system},\n",
475
+ " {\"role\": \"user\", \"content\": user},\n",
476
+ " ],\n",
477
+ " tokenize=False,\n",
478
+ " add_generation_prompt=True,\n",
479
+ " )\n",
480
+ " inputs = tokenizer(prompt, return_tensors=\"pt\").to(model.device)\n",
481
+ " with torch.no_grad():\n",
482
+ " out = model.generate(\n",
483
+ " **inputs,\n",
484
+ " max_new_tokens=max_new_tokens,\n",
485
+ " do_sample=False,\n",
486
+ " temperature=0.0,\n",
487
+ " )\n",
488
+ " return tokenizer.decode(\n",
489
+ " out[0][inputs[\"input_ids\"].shape[1] :], skip_special_tokens=True\n",
490
+ " )\n",
491
+ "\n",
492
+ "\n",
493
+ "def run_one_episode(task: str, seed: int) -> tuple[float, int, int]:\n",
494
+ " env = make_env()\n",
495
+ " obs = env.reset(task_name=task, seed=seed, max_ticks=EPISODE_TICKS)\n",
496
+ " cumulative = 0.0\n",
497
+ " parsed = 0\n",
498
+ " failed = 0\n",
499
+ " for _ in range(EPISODE_TICKS):\n",
500
+ " completion = _hf_chat(SYSTEM_PROMPT, serialize_observation(obs))\n",
501
+ " action = parse_action(completion)\n",
502
+ " if action is None:\n",
503
+ " failed += 1\n",
504
+ " action = parse_failure_marker()\n",
505
+ " else:\n",
506
+ " parsed += 1\n",
507
+ " result = env.step(CrisisworldcortexAction(action=action))\n",
508
+ " obs = result.observation if hasattr(result, \"observation\") else result\n",
509
+ " cumulative += obs.reward if obs.reward is not None else 0.0\n",
510
+ " if obs.done:\n",
511
+ " break\n",
512
+ " return cumulative, parsed, failed\n",
513
+ "\n",
514
+ "\n",
515
+ "for task in TASKS:\n",
516
+ " cum, parsed, failed = run_one_episode(task, seed=0)\n",
517
+ " print(\n",
518
+ " f\"task={task:<18} cum_reward={cum:+.3f} parsed={parsed:2d} parse_failures={failed:2d}\"\n",
519
+ " )\n",
520
+ "\n",
521
+ "print(\"\\nMini-demo complete.\")"
522
+ ]
523
+ }
524
+ ],
525
+ "metadata": {
526
+ "kernelspec": {
527
+ "display_name": "Python 3",
528
+ "language": "python",
529
+ "name": "python3"
530
+ },
531
+ "language_info": {
532
+ "name": "python",
533
+ "version": "3.10"
534
+ }
535
+ },
536
+ "nbformat": 4,
537
+ "nbformat_minor": 5
538
+ }
openenv.yaml CHANGED
@@ -1,7 +1,7 @@
1
- spec_version: 1
2
- name: CrisisWorldCortex
3
- type: space
4
- runtime: fastapi
5
- app: server.app:app
6
- port: 8000
7
-
 
1
+ spec_version: 1
2
+ name: CrisisWorldCortex
3
+ type: space
4
+ runtime: fastapi
5
+ app: server.app:app
6
+ port: 8000
7
+
openenv_CrisisWorldCortex.egg-info/PKG-INFO CHANGED
@@ -5,8 +5,7 @@ Summary: Crisisworldcortex environment for OpenEnv
5
  Requires-Python: >=3.10
6
  Requires-Dist: openenv-core[core]==0.2.3
7
  Requires-Dist: openai<3.0,>=2.0
8
- Requires-Dist: datasets>=4.0.0
9
- Requires-Dist: huggingface-hub>=1.0.0
10
  Provides-Extra: dev
11
  Requires-Dist: pre-commit>=4.0.0; extra == "dev"
12
  Requires-Dist: pytest>=8.0.0; extra == "dev"
 
5
  Requires-Python: >=3.10
6
  Requires-Dist: openenv-core[core]==0.2.3
7
  Requires-Dist: openai<3.0,>=2.0
8
+ Requires-Dist: python-dotenv>=1.0.0
 
9
  Provides-Extra: dev
10
  Requires-Dist: pre-commit>=4.0.0; extra == "dev"
11
  Requires-Dist: pytest>=8.0.0; extra == "dev"
openenv_CrisisWorldCortex.egg-info/SOURCES.txt CHANGED
@@ -7,7 +7,6 @@ pyproject.toml
7
  ./__init__.py
8
  ./client.py
9
  ./inference.py
10
- ./mm.py
11
  ./models.py
12
  openenv_CrisisWorldCortex.egg-info/PKG-INFO
13
  openenv_CrisisWorldCortex.egg-info/SOURCES.txt
@@ -21,20 +20,14 @@ server/app.py
21
  tests/test_actions_round_trip.py
22
  tests/test_baseline_b1.py
23
  tests/test_baseline_b2.py
24
- tests/test_baseline_b3.py
25
  tests/test_cortex_brain_executive.py
26
  tests/test_cortex_brain_smoke.py
27
- tests/test_cortex_council.py
28
- tests/test_cortex_council_smoke.py
29
  tests/test_cortex_lenses.py
30
- tests/test_cortex_metacognition.py
31
  tests/test_cortex_perception.py
32
- tests/test_cortex_routing_policy.py
33
  tests/test_cortex_subagents.py
34
  tests/test_env_reset_kwargs.py
35
  tests/test_env_step_reward_wiring.py
36
  tests/test_import_graph.py
37
- tests/test_inference_agent_dispatch.py
38
  tests/test_legal_constraint_enforcement.py
39
  tests/test_llm_client.py
40
  tests/test_observation_no_latent_leak.py
@@ -51,9 +44,5 @@ tests/test_smoke_env.py
51
  tests/test_stdout_format.py
52
  tests/test_synthetic_rejection_payload.py
53
  tests/test_training_eval_metrics.py
54
- tests/test_training_multi_model_skeleton.py
55
  tests/test_training_reward_shaping.py
56
- tests/test_training_rollout_buffer.py
57
- tests/test_training_scripts_b1.py
58
- tests/test_training_scripts_collect_sft.py
59
- tests/test_training_scripts_sft.py
 
7
  ./__init__.py
8
  ./client.py
9
  ./inference.py
 
10
  ./models.py
11
  openenv_CrisisWorldCortex.egg-info/PKG-INFO
12
  openenv_CrisisWorldCortex.egg-info/SOURCES.txt
 
20
  tests/test_actions_round_trip.py
21
  tests/test_baseline_b1.py
22
  tests/test_baseline_b2.py
 
23
  tests/test_cortex_brain_executive.py
24
  tests/test_cortex_brain_smoke.py
 
 
25
  tests/test_cortex_lenses.py
 
26
  tests/test_cortex_perception.py
 
27
  tests/test_cortex_subagents.py
28
  tests/test_env_reset_kwargs.py
29
  tests/test_env_step_reward_wiring.py
30
  tests/test_import_graph.py
 
31
  tests/test_legal_constraint_enforcement.py
32
  tests/test_llm_client.py
33
  tests/test_observation_no_latent_leak.py
 
44
  tests/test_stdout_format.py
45
  tests/test_synthetic_rejection_payload.py
46
  tests/test_training_eval_metrics.py
 
47
  tests/test_training_reward_shaping.py
48
+ tests/test_training_rollout_buffer.py
 
 
 
openenv_CrisisWorldCortex.egg-info/requires.txt CHANGED
@@ -1,7 +1,6 @@
1
  openenv-core[core]==0.2.3
2
  openai<3.0,>=2.0
3
- datasets>=4.0.0
4
- huggingface-hub>=1.0.0
5
 
6
  [dev]
7
  pre-commit>=4.0.0
 
1
  openenv-core[core]==0.2.3
2
  openai<3.0,>=2.0
3
+ python-dotenv>=1.0.0
 
4
 
5
  [dev]
6
  pre-commit>=4.0.0
server/__init__.py CHANGED
@@ -1,11 +1,11 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- # All rights reserved.
3
- #
4
- # This source code is licensed under the BSD-style license found in the
5
- # LICENSE file in the root directory of this source tree.
6
-
7
- """Crisisworldcortex environment server components."""
8
-
9
- from .CrisisWorldCortex_environment import CrisisworldcortexEnvironment
10
-
11
- __all__ = ["CrisisworldcortexEnvironment"]
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the BSD-style license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Crisisworldcortex environment server components."""
8
+
9
+ from .CrisisWorldCortex_environment import CrisisworldcortexEnvironment
10
+
11
+ __all__ = ["CrisisworldcortexEnvironment"]
server/requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- openenv[core]>=0.2.0
2
- fastapi>=0.115.0
3
- uvicorn>=0.24.0
4
-
5
-
6
-
 
1
+ openenv[core]>=0.2.0
2
+ fastapi>=0.115.0
3
+ uvicorn>=0.24.0
4
+
5
+
6
+
tests/CLAUDE.md CHANGED
@@ -1,48 +1,48 @@
1
- # tests/CLAUDE.md
2
-
3
- Test surface per subsystem. Smoke bar first, boundary tests next, coverage last.
4
-
5
- ## Belongs here
6
-
7
- - `conftest.py` β€” repo root on `sys.path` for bare-name imports.
8
- - One test module per subsystem boundary (table below).
9
-
10
- ## Does not belong here
11
-
12
- Helpers that mutate real graders, simulator state, or disk. Fixtures that hit the live HF Space β€” mock or `pytest.skip`.
13
-
14
- ## Run commands
15
-
16
- ```bash
17
- uv run python -m pytest tests/ -v # all
18
- uv run python -m pytest tests/test_smoke_env.py::test_reset_returns_valid_observation -v # one
19
- uv run python -m pytest --cov tests/ # coverage
20
- ```
21
-
22
- ## Required tests β€” each maps to exactly one subsystem contract
23
-
24
- | File | Scope | Asserts |
25
- |---|---|---|
26
- | `test_package_exports.py` | wire package | Root `__init__` re-exports `CrisisworldcortexAction/Observation/Env`. |
27
- | `test_smoke_env.py` | `server/` env | `reset()` / `step()` return a valid `CrisisworldcortexObservation`. |
28
- | `test_actions_round_trip.py` | `server/` env | 6 MVP outer actions round-trip; `public_communication` is rejected at runtime. |
29
- | `test_reward_shape.py` | `server/graders/` | Every grader returns values in `[0.0, 1.0]`. |
30
- | `test_reward_non_constancy.py` | `server/graders/` | Grader output varies across β‰₯ 2 synthetic episodes. |
31
- | `test_anti_hivemind_protocol.py` | `cortex/` | 5 protocol steps fire in order; caps enforced (2 rounds, 1 cross-brain challenge, 1 Critic/brain/tick). |
32
- | `test_collapse_detector.py` | `cortex/` | Metacognition flags when all brains recommend the same action. |
33
- | `test_import_graph.py` | repo-wide | No `import server` under `cortex/**`; no `import cortex` under `server/**`; no `import server.simulator` under `training/**`. |
34
- | `test_baselines_smoke.py` | `baselines/` | B1 / B2 / B3 each run one episode on `outbreak_easy`. |
35
- | `test_training_smoke.py` | `training/` | `train_router.main()` runs one episode against a mocked env under 5 s. |
36
-
37
- ## Binding rules
38
-
39
- - Every public API in a subsystem's CLAUDE.md has β‰₯ 1 test here.
40
- - Coverage target: 80% per subsystem; 100% for `server/graders/` and `cortex/anti_hivemind.py`.
41
- - No test may take > 10 s unless marked `@pytest.mark.slow` and gated behind `--runslow`.
42
- - `test_import_graph.py` uses a fresh subprocess import, not `sys.modules` monkey-patching β€” the latter passes under contamination.
43
-
44
- ## Common failure modes
45
-
46
- - Smoke test asserting on current-echo values β€” breaks when real env logic lands. Assert on shape, not value.
47
- - Module-scope env instantiation in tests β€” slows collection and hides init errors until runtime.
48
- - Tests that hit the HF Space without a skip guard β€” CI flakes on rate limits.
 
1
+ # tests/CLAUDE.md
2
+
3
+ Test surface per subsystem. Smoke bar first, boundary tests next, coverage last.
4
+
5
+ ## Belongs here
6
+
7
+ - `conftest.py` β€” repo root on `sys.path` for bare-name imports.
8
+ - One test module per subsystem boundary (table below).
9
+
10
+ ## Does not belong here
11
+
12
+ Helpers that mutate real graders, simulator state, or disk. Fixtures that hit the live HF Space β€” mock or `pytest.skip`.
13
+
14
+ ## Run commands
15
+
16
+ ```bash
17
+ uv run python -m pytest tests/ -v # all
18
+ uv run python -m pytest tests/test_smoke_env.py::test_reset_returns_valid_observation -v # one
19
+ uv run python -m pytest --cov tests/ # coverage
20
+ ```
21
+
22
+ ## Required tests β€” each maps to exactly one subsystem contract
23
+
24
+ | File | Scope | Asserts |
25
+ |---|---|---|
26
+ | `test_package_exports.py` | wire package | Root `__init__` re-exports `CrisisworldcortexAction/Observation/Env`. |
27
+ | `test_smoke_env.py` | `server/` env | `reset()` / `step()` return a valid `CrisisworldcortexObservation`. |
28
+ | `test_actions_round_trip.py` | `server/` env | 6 MVP outer actions round-trip; `public_communication` is rejected at runtime. |
29
+ | `test_reward_shape.py` | `server/graders/` | Every grader returns values in `[0.0, 1.0]`. |
30
+ | `test_reward_non_constancy.py` | `server/graders/` | Grader output varies across β‰₯ 2 synthetic episodes. |
31
+ | `test_anti_hivemind_protocol.py` | `cortex/` | 5 protocol steps fire in order; caps enforced (2 rounds, 1 cross-brain challenge, 1 Critic/brain/tick). |
32
+ | `test_collapse_detector.py` | `cortex/` | Metacognition flags when all brains recommend the same action. |
33
+ | `test_import_graph.py` | repo-wide | No `import server` under `cortex/**`; no `import cortex` under `server/**`; no `import server.simulator` under `training/**`. |
34
+ | `test_baselines_smoke.py` | `baselines/` | B1 / B2 / B3 each run one episode on `outbreak_easy`. |
35
+ | `test_training_smoke.py` | `training/` | `train_router.main()` runs one episode against a mocked env under 5 s. |
36
+
37
+ ## Binding rules
38
+
39
+ - Every public API in a subsystem's CLAUDE.md has β‰₯ 1 test here.
40
+ - Coverage target: 80% per subsystem; 100% for `server/graders/` and `cortex/anti_hivemind.py`.
41
+ - No test may take > 10 s unless marked `@pytest.mark.slow` and gated behind `--runslow`.
42
+ - `test_import_graph.py` uses a fresh subprocess import, not `sys.modules` monkey-patching β€” the latter passes under contamination.
43
+
44
+ ## Common failure modes
45
+
46
+ - Smoke test asserting on current-echo values β€” breaks when real env logic lands. Assert on shape, not value.
47
+ - Module-scope env instantiation in tests β€” slows collection and hides init errors until runtime.
48
+ - Tests that hit the HF Space without a skip guard β€” CI flakes on rate limits.
training/CLAUDE.md CHANGED
@@ -1,64 +1,64 @@
1
- # training/CLAUDE.md
2
-
3
- GRPO training for the routing policy. Two Colab notebooks required for hackathon compliance.
4
-
5
- ## Belongs here
6
-
7
- - `train_router.py` β€” manual PyTorch GRPO loop for the MLP router (Option B primary).
8
- - `rollout_buffer.py` β€” trajectory collection + router-step serialization.
9
- - `reward_shaping.py` β€” composes the 4-term training reward from `server.graders` outputs.
10
- - `train_router_colab.ipynb` β€” Colab-runnable notebook for the MLP router. **Required.**
11
- - `train_flat_agent_trl.ipynb` β€” TRL `GRPOTrainer` + Unsloth LoRA on a flat agent against `outbreak_easy`. **Required** for finale Unsloth/TRL compliance (design Β§E.3).
12
- - `configs/grpo_config.yaml` β€” GRPO hyperparameters.
13
- - `configs/tasks.yaml` β€” task-curriculum config (easy β†’ medium β†’ hard).
14
-
15
- ## Does not belong here
16
-
17
- Baseline agents (β†’ `baselines/`). Plotting / reward curves (β†’ `demo/`, `scripts/`). SEIR dynamics (β†’ `server/simulator/`).
18
-
19
- ## Allowed imports
20
-
21
- - `models`, `client`.
22
- - `cortex.routing_policy`, `cortex.council`, `cortex.schemas`, `cortex.metacognition`.
23
- - `server.graders` β€” **reward-name constants only** (e.g. the `training_reward` dict keys). Do not import `server.simulator`. Do not instantiate the env in-process.
24
- - Torch, TRL, Unsloth (compliance notebook only).
25
-
26
- ## Forbidden imports
27
-
28
- - `server.simulator/*` β€” training hits the env over HTTP like production.
29
- - `baselines/*`, `demo/*`.
30
-
31
- ## Binding contracts
32
-
33
- - **Training-data rows = router steps**, not ticks or rounds. One row per `RoutingAction` emission.
34
- - **Training reward = exactly the 4 terms returned by `server.graders.training_reward`** (see `server/CLAUDE.md` for the dict schema). Never mix in eval-only metrics.
35
- - Training episode length = 10–12 ticks. Eval episode length = 20 ticks (only if training is stable; otherwise eval also runs at 12).
36
- - Temperature > 0 on LLM subagents during rollouts (exploration); temperature = 0 during eval (reproducibility).
37
- - Pin the OpenEnv version in `pyproject.toml` before training runs β€” finale requires "latest release" at submission.
38
-
39
- ## Colab notebook contracts
40
-
41
- - `train_router_colab.ipynb`: imports CrisisWorld as a local Python module (no Docker in Colab). Runs end-to-end on a fresh Colab T4.
42
- - `train_flat_agent_trl.ipynb`: uses `trl.GRPOTrainer(environment_factory=CrisisworldcortexEnv, ...)` + `unsloth.FastLanguageModel` LoRA wrapper. Runs end-to-end on a fresh Colab T4. **This notebook's absence disqualifies the submission.**
43
-
44
- ## Public APIs (owned here)
45
-
46
- - `train_router.main(config_path: str) -> None`
47
- - `RolloutBuffer.add(router_step: RouterStep) -> None`
48
- - `RolloutBuffer.sample(batch_size: int) -> list[RouterStep]`
49
- - `shape_reward(trajectory: Trajectory) -> float` β€” weighted combination of the 4 training terms.
50
-
51
- ## Testing requirements
52
-
53
- - `shape_reward` returns a scalar in `[0.0, 1.0]`.
54
- - `RolloutBuffer` round-trips synthetic router steps without data loss.
55
- - `train_router.py` runs 1 episode end-to-end against a mocked env in under 5 seconds (CI smoke).
56
- - Both Colab notebooks execute to completion for β‰₯ a few hundred training steps on Colab T4 pre-onsite.
57
-
58
- ## Common failure modes
59
-
60
- - Logging ticks as training rows β€” collapses router's action granularity; GRPO credit assignment breaks.
61
- - Mixing eval metrics into the training reward β€” inflates the headline curve for reasons the paper can't defend.
62
- - Widening training episodes past 12 ticks β€” rollouts stop fitting in the GRPO update window; wall-clock explodes.
63
- - Training-reward dict keys drifting from `server.graders.training_reward` β€” shape-only tests miss this; trainer silently optimizes the wrong signal.
64
- - Missing the TRL compliance notebook at submission β€” automatic finale failure.
 
1
+ # training/CLAUDE.md
2
+
3
+ GRPO training for the routing policy. Two Colab notebooks required for hackathon compliance.
4
+
5
+ ## Belongs here
6
+
7
+ - `train_router.py` β€” manual PyTorch GRPO loop for the MLP router (Option B primary).
8
+ - `rollout_buffer.py` β€” trajectory collection + router-step serialization.
9
+ - `reward_shaping.py` β€” composes the 4-term training reward from `server.graders` outputs.
10
+ - `train_router_colab.ipynb` β€” Colab-runnable notebook for the MLP router. **Required.**
11
+ - `train_flat_agent_trl.ipynb` β€” TRL `GRPOTrainer` + Unsloth LoRA on a flat agent against `outbreak_easy`. **Required** for finale Unsloth/TRL compliance (design Β§E.3).
12
+ - `configs/grpo_config.yaml` β€” GRPO hyperparameters.
13
+ - `configs/tasks.yaml` β€” task-curriculum config (easy β†’ medium β†’ hard).
14
+
15
+ ## Does not belong here
16
+
17
+ Baseline agents (β†’ `baselines/`). Plotting / reward curves (β†’ `demo/`, `scripts/`). SEIR dynamics (β†’ `server/simulator/`).
18
+
19
+ ## Allowed imports
20
+
21
+ - `models`, `client`.
22
+ - `cortex.routing_policy`, `cortex.council`, `cortex.schemas`, `cortex.metacognition`.
23
+ - `server.graders` β€” **reward-name constants only** (e.g. the `training_reward` dict keys). Do not import `server.simulator`. Do not instantiate the env in-process.
24
+ - Torch, TRL, Unsloth (compliance notebook only).
25
+
26
+ ## Forbidden imports
27
+
28
+ - `server.simulator/*` β€” training hits the env over HTTP like production.
29
+ - `baselines/*`, `demo/*`.
30
+
31
+ ## Binding contracts
32
+
33
+ - **Training-data rows = router steps**, not ticks or rounds. One row per `RoutingAction` emission.
34
+ - **Training reward = exactly the 4 terms returned by `server.graders.training_reward`** (see `server/CLAUDE.md` for the dict schema). Never mix in eval-only metrics.
35
+ - Training episode length = 10–12 ticks. Eval episode length = 20 ticks (only if training is stable; otherwise eval also runs at 12).
36
+ - Temperature > 0 on LLM subagents during rollouts (exploration); temperature = 0 during eval (reproducibility).
37
+ - Pin the OpenEnv version in `pyproject.toml` before training runs β€” finale requires "latest release" at submission.
38
+
39
+ ## Colab notebook contracts
40
+
41
+ - `train_router_colab.ipynb`: imports CrisisWorld as a local Python module (no Docker in Colab). Runs end-to-end on a fresh Colab T4.
42
+ - `train_flat_agent_trl.ipynb`: uses `trl.GRPOTrainer(environment_factory=CrisisworldcortexEnv, ...)` + `unsloth.FastLanguageModel` LoRA wrapper. Runs end-to-end on a fresh Colab T4. **This notebook's absence disqualifies the submission.**
43
+
44
+ ## Public APIs (owned here)
45
+
46
+ - `train_router.main(config_path: str) -> None`
47
+ - `RolloutBuffer.add(router_step: RouterStep) -> None`
48
+ - `RolloutBuffer.sample(batch_size: int) -> list[RouterStep]`
49
+ - `shape_reward(trajectory: Trajectory) -> float` β€” weighted combination of the 4 training terms.
50
+
51
+ ## Testing requirements
52
+
53
+ - `shape_reward` returns a scalar in `[0.0, 1.0]`.
54
+ - `RolloutBuffer` round-trips synthetic router steps without data loss.
55
+ - `train_router.py` runs 1 episode end-to-end against a mocked env in under 5 seconds (CI smoke).
56
+ - Both Colab notebooks execute to completion for β‰₯ a few hundred training steps on Colab T4 pre-onsite.
57
+
58
+ ## Common failure modes
59
+
60
+ - Logging ticks as training rows β€” collapses router's action granularity; GRPO credit assignment breaks.
61
+ - Mixing eval metrics into the training reward β€” inflates the headline curve for reasons the paper can't defend.
62
+ - Widening training episodes past 12 ticks β€” rollouts stop fitting in the GRPO update window; wall-clock explodes.
63
+ - Training-reward dict keys drifting from `server.graders.training_reward` β€” shape-only tests miss this; trainer silently optimizes the wrong signal.
64
+ - Missing the TRL compliance notebook at submission β€” automatic finale failure.