SaiManish123 commited on
Commit
7a3ba78
Β·
verified Β·
1 Parent(s): 10fa2de

readme: project description, results, training logs, links

Browse files
Files changed (1) hide show
  1. README.md +451 -0
README.md ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Janus (AdaptShield): Adaptive Incident Response Under Polymorphic Adversaries"
3
+ emoji: πŸ›‘οΈ
4
+ colorFrom: blue
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ tags:
10
+ - openenv
11
+ - security
12
+ - reinforcement-learning
13
+ - cybersecurity
14
+ short_description: Two-phase adaptive cybersecurity benchmark for LLMs
15
+ ---
16
+
17
+ # Janus (AdaptShield): Adaptive Incident Response Under Polymorphic Adversaries
18
+
19
+ **AdaptShield** is the environment: a two-phase agentic cybersecurity
20
+ simulator where an LLM defends a 4-node enterprise network against an
21
+ adversary that shifts strategy mid-episode. **Janus** is the model we
22
+ trained on it: a Qwen2.5-1.5B LoRA, supervised then refined with GRPO.
23
+ On the hardest task Janus scores 0.90 on a held-out world family it
24
+ never saw during training; a tool-aware heuristic baseline scores 0.18
25
+ on the same task.
26
+
27
+ The skill being tested is narrow on purpose. Not threat classification.
28
+ Not generic tool calling. The benchmark targets one thing: real-time
29
+ adaptation when the attacker's playbook changes mid-incident. Section
30
+ [Why this matters](#why-this-matters) explains why we think that's the
31
+ gap, and the [Results](#results) section is where the gap closes.
32
+
33
+ ## Project Links
34
+
35
+ - **HF Space (live env):** [`SaiManish123/adaptshield`](https://huggingface.co/spaces/SaiManish123/adaptshield)
36
+ - **Colab notebook (SFT + GRPO reproducer, free T4):** [`Project_Janus(AdaptShield)_Final.ipynb`](https://drive.google.com/file/d/1uI9BaQTsn8YXOAlCtQCr_0N6ixLbqlba/view?usp=sharing)
37
+ - **Artifacts / model repo:** [`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus)
38
+ - **Demo video:** [`youtu.be/upX9a5zXHBM`](https://youtu.be/upX9a5zXHBM)
39
+
40
+ ---
41
+
42
+ ## Why this matters
43
+
44
+ Most cyber-agent demos test threat classification or generic tool
45
+ calling. Real production breaches don't look like that. They look like
46
+ this:
47
+
48
+ In April 2026 attackers compromised Context.ai, used its OAuth
49
+ integration into a Vercel employee's Google Workspace, and pivoted from
50
+ shadow AI through identity into Vercel's internal systems, where they
51
+ enumerated and decrypted customer environment variables. The same week,
52
+ a Broken Object Level Authorization flaw in Lovable.dev let any
53
+ free-tier account read source code, Supabase credentials, Stripe keys,
54
+ and AI chat histories from other tenants, including projects built by
55
+ AI itself. Eight months earlier, the Tea dating app left a Firebase
56
+ bucket open and 72,000 verification selfies and driver's licenses of
57
+ women on a safety app were scraped to 4chan within hours.
58
+
59
+ Three different failure modes, one underlying problem for the
60
+ defender's agent: identity hijack via shadow AI, broken authorization
61
+ in vibe-coded apps, and classic cloud misconfig. The environment is
62
+ shifting faster than any static training distribution can keep up with,
63
+ and the real attacker does not sit still while you classify them.
64
+
65
+ Real campaigns drift through the kill chain (initial access, lateral
66
+ movement, exfiltration) and the defender's job is to re-classify,
67
+ contain, and eradicate as the picture changes. Static SOAR playbooks
68
+ keyed to fixed indicators of compromise fail the moment the adversary
69
+ rotates them; that is what an attacker TTP shift looks like in
70
+ production, and it is the regime where dwell time blows out and Tier-1
71
+ triage starts dropping signal.
72
+
73
+ AdaptShield is built around that pressure. The environment forces the
74
+ agent to act on partial evidence, hand judgment across two roles with
75
+ an information bottleneck between them, trade security correctness
76
+ against operational blast radius, and re-plan when the attacker pivots
77
+ mid-incident. Each of those is a separate failure mode in production
78
+ SOC tooling, and the benchmark scores all four at once.
79
+
80
+ ---
81
+
82
+ ## Results
83
+
84
+ Numbers below come from the production run on Hugging Face L4 Jobs,
85
+ training Qwen2.5-1.5B-Instruct with a LoRA adapter. Eval is 50
86
+ deterministic seeds per task, evaluated on a held-out world family
87
+ the policy never saw during training.
88
+
89
+ ![AdaptShield held-out benchmark: tool-aware baseline vs SFT vs GRPO](assets/headline_results.png)
90
+
91
+ On the hard task (`polymorphic-zero-day`) the tool-aware heuristic
92
+ baseline scores 0.18 and Janus holds 0.90 on the held-out family. On
93
+ the easier tasks the lift is smaller because the rule baseline is
94
+ already near the ceiling; the benchmark is shaped so adaptation only
95
+ matters where it should.
96
+
97
+ ### Benchmark comparison (full table)
98
+
99
+ | Task | No-tool baseline | Tool-aware baseline | SFT (train family) | SFT (held-out) | GRPO (train) | GRPO (held-out) |
100
+ |------|-----------------:|-------------------:|-------------------:|---------------:|-------------:|----------------:|
101
+ | `direct-triage` | 0.860 | 0.990 | 0.990 | 0.990 | 0.990 | 0.990 |
102
+ | `dual-pivot` | 0.650 | 0.640 | 0.825 | 0.825 | 0.825 | 0.825 |
103
+ | `polymorphic-zero-day` | 0.380 | 0.180 | 0.960 | 0.930 | **0.883** | **0.902** |
104
+
105
+ Two things in this table are worth flagging.
106
+
107
+ The tool-aware baseline scores 0.18 on the hard task, worse than the
108
+ no-tool baseline at 0.38. That is not a bug in the baseline; it is
109
+ that bolting tools onto a heuristic without learning when to trust them
110
+ makes the agent over-trigger on injected false positives. You see the
111
+ same pattern in production with rule-based SOAR playbooks against
112
+ adaptive adversaries.
113
+
114
+ Held-out GRPO (0.902) actually edges out train-family GRPO (0.883). That
115
+ is evidence the policy is generalizing across world templates rather
116
+ than memorizing them. Without splitting the eval by world family this
117
+ finding would not be visible. Same-seed evaluation would have credited
118
+ the model for memorization it did not do.
119
+
120
+ ### SFT: loss and held-out reward
121
+
122
+ ![SFT loss curve](https://huggingface.co/SaiManish123/Janus/resolve/main/sft_worldsplit_1_5b/loss_curve.png)
123
+
124
+ ![SFT learning curve: tool-aware baseline anchor, train family vs held-out family across checkpoints](https://huggingface.co/SaiManish123/Janus/resolve/main/sft_worldsplit_1_5b/reward_curve.png?v=2)
125
+
126
+ ### GRPO: refinement on the polymorphic adversary
127
+
128
+ ![GRPO reward curve, polymorphic-zero-day](https://huggingface.co/SaiManish123/Janus/resolve/main/grpo_polymorphic_zero_day_1_5b/reward_curve.png)
129
+
130
+ ### Training runs
131
+
132
+ Three production runs on Hugging Face Jobs produced the artifacts in this
133
+ README. Stdout logs are public and the per-step / per-episode metrics
134
+ files are next to the adapters.
135
+
136
+ | Run | Trainer | GPU | Steps / Episodes | Train wall-clock | Logs | Metrics |
137
+ |-----|---------|-----|------------------|------------------|------|---------|
138
+ | [`sft_worldsplit_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/sft_worldsplit_1_5b) | SFT (LoRA) | L4 Γ—1 | 378 steps | 9m 49s | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/sft_worldsplit_1_5b.log) | [trainer_state](https://huggingface.co/SaiManish123/Janus/blob/main/sft_worldsplit_1_5b/checkpoint-378/trainer_state.json) |
139
+ | [`grpo_worldsplit_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/grpo_worldsplit_1_5b) | GRPO, mixed curriculum | L4 Γ—1 | 1,628 episodes | 1h 26m | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/grpo_worldsplit_1_5b.log) | [per-episode](https://huggingface.co/SaiManish123/Janus/blob/main/grpo_worldsplit_1_5b/metrics.json) |
140
+ | [`grpo_polymorphic_zero_day_1_5b`](https://huggingface.co/SaiManish123/Janus/tree/main/grpo_polymorphic_zero_day_1_5b) | GRPO, hard-task focus | L4 Γ—1 | 4,357 episodes | 3h 17m | [stdout](https://huggingface.co/SaiManish123/Janus/blob/main/logs/grpo_polymorphic_zero_day_1_5b.log) | [per-episode](https://huggingface.co/SaiManish123/Janus/blob/main/grpo_polymorphic_zero_day_1_5b/metrics.json) |
141
+
142
+ The curriculum run mixes all three tasks (weights `direct-triage: 0.3 /
143
+ dual-pivot: 0.4 / polymorphic-zero-day: 0.3`). The polymorphic run
144
+ trains exclusively on the hard task to push hard-task performance
145
+ without distraction from saturated tiers. Per-episode reward in both
146
+ runs stabilizes within the first ~500 episodes and stays there for the
147
+ rest of the schedule.
148
+
149
+ ---
150
+
151
+ ## Architecture
152
+
153
+ ![AdaptShield architecture overview](assets/architecture_overview.svg)
154
+
155
+ Each episode runs against a sampled mission profile, world-family
156
+ template, and latent operational mode. The Threat Analyst investigates
157
+ raw enterprise evidence through SOC tools and emits a structured
158
+ handoff. The Tactical Executor sees only that handoff (not the raw
159
+ state) and chooses the mitigation. The split mirrors the
160
+ Tier-1-to-Tier-2 escalation in a real SOC, where the responder acts on
161
+ the analyst's written triage and never re-examines the raw telemetry.
162
+ A deterministic Python grader scores security correctness, business
163
+ impact, dependency blast radius, and mission alignment. There is no
164
+ LLM-as-judge anywhere in the loop.
165
+
166
+ ## Training Pipeline
167
+
168
+ ![Janus training pipeline](assets/training_pipeline.svg)
169
+
170
+ Five steps, each reproducible from the repo:
171
+
172
+ 1. Generate SFT demonstrations by rolling AdaptShield episodes with a
173
+ rule-based Phase 1 expert and a tool-aware Phase 2 expert.
174
+ 2. Train a LoRA adapter on Qwen2.5-1.5B (or 0.5B for the Colab
175
+ reproducer) with supervised fine-tuning on those demos.
176
+ 3. Evaluate on both train-family and held-out-family worlds. The split
177
+ is by world template, not by seed, so memorizing a template doesn't
178
+ transfer across the split.
179
+ 4. Refine the SFT adapter with GRPO on a curriculum weighted toward
180
+ `polymorphic-zero-day`. The deterministic grader is the reward.
181
+ 5. Publish adapters, curves, metrics, and benchmark tables to
182
+ [`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus).
183
+
184
+ A free-tier Colab notebook reproduces steps 1-4 end-to-end on a T4 in
185
+ roughly 35 minutes using Qwen2.5-0.5B and reduced episode budgets. The
186
+ numbers in this README come from the 1.5B run on a Hugging Face L4 Job.
187
+
188
+ ---
189
+
190
+ ## Environment Description
191
+
192
+ The agent defends a 4-node enterprise network (`auth_service`,
193
+ `payment_service`, `database`, `api_gateway`). Each turn has two phases:
194
+
195
+ **Phase 1 (Threat Analyst).** Agent reads SIEM metrics, can call SOC
196
+ tools (log search, network telemetry, threat intel lookup), and emits a
197
+ structured `Phase1Action` with threat type, target node, confidence and
198
+ a recommended action.
199
+
200
+ **Phase 2 (Tactical Executor).** Agent receives only the Phase 1
201
+ assessment (blind to raw state) and emits a `Phase2Action`. The analyst
202
+ has to communicate clearly because the executor cannot double-check the
203
+ network.
204
+
205
+ The attacker escalates through `recon β†’ exploit β†’ exfiltration` if the
206
+ agent fails to respond correctly. On the hard task, the attacker shifts
207
+ strategy mid-episode and seeds false-positive noise that looks like a
208
+ real attack but isn't, which punishes reflexive isolation. This is the
209
+ alert-fatigue regime that drives most production SOC false-positive
210
+ budgets.
211
+
212
+ ### Observation Space
213
+
214
+ ```json
215
+ {
216
+ "phase": "1 or 2",
217
+ "network_nodes": {
218
+ "auth_service": {"status": "...", "request_rate": 0, "error_rate": 0.0, "cpu": 0}
219
+ },
220
+ "active_alerts": ["raw metric alert strings (no MITRE codes)"],
221
+ "attack_stage": "recon | exploit | exfiltration | none",
222
+ "history": [{"turn": "1", "p1": "classified:brute_force", "p2": "rate_limit→auth_service"}],
223
+ "phase1_assessment": {"threat_type": "...", "confidence": 0.9, "target_node": "..."},
224
+ "metadata": {"normalized_score": 0.72}
225
+ }
226
+ ```
227
+
228
+ Phase 2 observations have empty `network_nodes` and `active_alerts`.
229
+ The executor only sees the analyst's handoff.
230
+
231
+ ### Action Space
232
+
233
+ **Phase 1 (`Phase1Action`):**
234
+ ```json
235
+ {"threat_type": "brute_force", "confidence": 0.9, "target_node": "auth_service", "recommended_action": "rate_limit", "reasoning": "..."}
236
+ ```
237
+
238
+ **Phase 2 (`Phase2Action`):**
239
+ ```json
240
+ {"action": "rate_limit", "target_node": "auth_service", "reasoning": "..."}
241
+ ```
242
+
243
+ Valid actions: `rate_limit`, `isolate`, `honeypot`, `patch`, `monitor`.
244
+
245
+ ### Tasks
246
+
247
+ | Task | Difficulty | Description | Rule baseline |
248
+ |------|-----------|-------------|--------------:|
249
+ | `direct-triage` | Easy | Single fixed strategy | ~0.87 |
250
+ | `dual-pivot` | Medium | Two alternating strategies | ~0.76 |
251
+ | `polymorphic-zero-day` | Hard | All four + mid-episode shift + noise | ~0.52 |
252
+
253
+ ### Reward Function
254
+
255
+ | Outcome | Reward |
256
+ |---------|-------:|
257
+ | Phase 1 threat type correct | +0.15 |
258
+ | Phase 1 target node correct | +0.10 |
259
+ | Phase 2 optimal action + correct target | +0.39 |
260
+ | Phase 2 heavy-handed but effective | +0.18 |
261
+ | Phase 2 wrong action | -0.25 |
262
+ | False positive on benign event | -0.39 |
263
+ | Catastrophic: database exfiltrated | -0.49, `done=True` |
264
+
265
+ Scores are clipped to the open interval `(0.01, 0.99)`. The grader
266
+ never emits exactly 0 or 1, which keeps GRPO advantages well-defined.
267
+
268
+ ### Operational Impact Layer
269
+
270
+ AdaptShield also scores business impact, so the agent is rewarded for
271
+ stopping the attack without ignoring operational blast radius. Each
272
+ service has a criticality weight and a dependency fan-out:
273
+
274
+ | Service | Criticality | Downstream dependency risk |
275
+ |---------|------------:|----------------------------|
276
+ | `auth_service` | 0.70 | `payment_service` |
277
+ | `payment_service` | 0.90 | `api_gateway` |
278
+ | `database` | 1.00 | `payment_service`, `api_gateway` |
279
+ | `api_gateway` | 0.80 | `auth_service`, `payment_service`, `database` |
280
+
281
+ Actions have bounded disruption costs (`monitor` = none, `isolate` =
282
+ highest). The grader emits `business_impact`, `availability_impact`,
283
+ `security_risk`, `dependency_blast_radius`, and `operational_penalty`
284
+ inside `score_breakdown`. The reward adjustment is capped at `Β±0.05` per
285
+ turn, which keeps the training signal stable while leaving the replay
286
+ detailed enough to explain whether the agent stopped the attack cleanly
287
+ or caused unnecessary business disruption getting there. This is the
288
+ MTTR-versus-availability tradeoff every SOC actually navigates:
289
+ containment that bricks `auth_service` to stop a credential-stuffing
290
+ campaign also takes legitimate users offline, so "isolate everything"
291
+ is not a winning playbook.
292
+
293
+ ### Mission-Aware Objectives
294
+
295
+ Each task carries a mission profile, visible in observation metadata and
296
+ appended to the system prompt:
297
+
298
+ | Task | Mission | Primary Asset | SLA Priority | Risk Tolerance |
299
+ |------|---------|---------------|--------------|----------------|
300
+ | `direct-triage` | `login_stability` | `auth_service` | availability | medium |
301
+ | `dual-pivot` | `checkout_continuity` | `payment_service` | availability | medium |
302
+ | `polymorphic-zero-day` | `breach_containment` | `database` | containment | low |
303
+
304
+ The grader emits `mission_alignment` and `mission_adjustment`, capped at
305
+ `Β±0.04` per turn. This makes the agent optimize for the operational
306
+ mission, not just the threat label. Availability-priority missions
307
+ discourage unnecessary isolation of the primary asset; containment
308
+ missions reward decisive correct containment of the crown-jewel
309
+ database.
310
+
311
+ ### Design choices that aren't obvious
312
+
313
+ A few decisions in the environment that look like details but matter
314
+ for what the benchmark actually measures:
315
+
316
+ - **Information bottleneck between phases.** Phase 2's observation has
317
+ empty `network_nodes` and `active_alerts`. The executor only sees
318
+ Phase 1's structured handoff. If Phase 1 cannot communicate clearly,
319
+ Phase 2 fails, and you see it in the score, not in a separate metric.
320
+ This is what makes the env actually test cross-role coordination
321
+ rather than just two independent policies stitched together.
322
+ - **Train/eval split by world family, not by seed.** The world templates
323
+ used for training are disjoint from the ones used for held-out
324
+ evaluation. A model that overfits to a specific service-name pattern
325
+ or a specific alert distribution will pass train evals and fail
326
+ held-out. Same-seed evaluation would have hidden this.
327
+ - **Open scoring interval `(0.01, 0.99)`.** The grader never emits
328
+ exactly 0 or 1. This keeps GRPO advantage estimates well-defined.
329
+ Saturating rewards collapse the variance the algorithm needs.
330
+ - **Bounded auxiliary signals.** Operational impact is capped at `Β±0.05`
331
+ per turn and mission alignment at `Β±0.04`. They steer the policy
332
+ without dominating the security signal, so the training curve does
333
+ not get hijacked by a single side-objective.
334
+ - **Deterministic Python grader, no LLM-as-judge.** Rewards come from
335
+ strategy matching against a fixed ground-truth attacker, not from a
336
+ judge model. The benchmark cannot be gamed by a more eloquent policy.
337
+ - **Phase-1 alerts are raw metric strings, not pre-tagged MITRE ATT&CK
338
+ techniques.** The agent has to do the classification itself, not
339
+ match a label to a label. This is what makes the heuristic baseline
340
+ collapse on the hard task: rule-based classification keyed on fixed
341
+ indicators of compromise does not survive the injected false-positive
342
+ noise that real polymorphic adversaries use to drown Tier-1 triage.
343
+
344
+ ---
345
+
346
+ ## Reproduce it
347
+
348
+ ### Free-tier Colab (recommended for judges)
349
+
350
+ Open the Colab notebook linked above and run top-to-bottom. It will:
351
+
352
+ - install the exact pinned dependency stack used in the HF Job
353
+ - generate SFT demos from the environment
354
+ - train an SFT LoRA on Qwen2.5-0.5B (T4-friendly)
355
+ - run GRPO refinement on top of that SFT adapter
356
+ - print the benchmark table and inline the production training curves
357
+ from `SaiManish123/Janus` so you can compare scaled-down vs. full runs
358
+
359
+ End-to-end runtime on a Colab T4 is roughly 35 minutes.
360
+
361
+ ### Local setup
362
+
363
+ ```bash
364
+ pip install openenv-core
365
+ git clone https://github.com/SaiManish123/adaptshield
366
+ cd adaptshield
367
+ python -m adaptshield.server.app
368
+ ```
369
+
370
+ ### Run inference against the live environment
371
+
372
+ ```bash
373
+ export HF_TOKEN=your_token
374
+ export ADAPTSHIELD_TASK=direct-triage # or dual-pivot / polymorphic-zero-day
375
+ export ENV_BASE_URL=http://localhost:7860
376
+ python inference.py # run from the repo root
377
+ ```
378
+
379
+ `inference.py` honors the evaluator contract: `[START]`, `[STEP]`, `[END]`
380
+ stdout markers and credentials read only from environment variables.
381
+
382
+ ### Smoke test
383
+
384
+ ```bash
385
+ python smoke_test.py
386
+ ```
387
+
388
+ Spins the env up in-process and walks one episode of each task with a
389
+ deterministic policy. Should finish in <10 seconds.
390
+
391
+ ### Regression tests
392
+
393
+ ```bash
394
+ adaptshield/.venv/bin/python -m unittest tests.test_regression -v
395
+ ```
396
+
397
+ ### Baseline scores
398
+
399
+ With `ADAPTSHIELD_SEED=42`, the deterministic rule baseline produces:
400
+
401
+ | Task | Score | Steps | Status |
402
+ |------|------:|------:|--------|
403
+ | `direct-triage` | 0.870 | 10 | PASS |
404
+ | `dual-pivot` | 0.760 | 12 | PASS |
405
+ | `polymorphic-zero-day` | 0.520 | 16 | PASS |
406
+
407
+ Difficulty staircase: **PASS**.
408
+
409
+ ---
410
+
411
+ ## Repository layout
412
+
413
+ ```
414
+ adaptshield/
415
+ β”œβ”€β”€ server/ # FastAPI server (OpenEnv-compatible)
416
+ β”œβ”€β”€ client.py # OpenEnv client (no server-internal imports)
417
+ β”œβ”€β”€ models.py # Phase1Action / Phase2Action schemas
418
+ β”œβ”€β”€ soc_tools.py # SIEM, log search, threat intel SOC tools
419
+ β”œβ”€β”€ eval_tasks.py # task definitions + difficulty staircase
420
+ β”œβ”€β”€ baseline.py # deterministic rule baseline
421
+ β”œβ”€β”€ tool_baseline.py # tool-aware heuristic baseline
422
+ β”œβ”€β”€ generate_sft_data.py # rolls episodes β†’ SFT JSONL
423
+ β”œβ”€β”€ train_sft.py # LoRA SFT trainer (Unsloth + TRL)
424
+ β”œβ”€β”€ train.py # GRPO trainer (Unsloth + TRL)
425
+ β”œβ”€β”€ plot_training.py # reward / loss curve plotting
426
+ β”œβ”€β”€ build_benchmark_table.py # eval matrix builder
427
+ β”œβ”€β”€ inference.py # judge-facing entry point
428
+ β”œβ”€β”€ smoke_test.py # one-shot in-process smoke test
429
+ β”œβ”€β”€ tests/test_regression.py # determinism + reward regression tests
430
+ β”œβ”€β”€ openenv.yaml # OpenEnv manifest
431
+ └── Dockerfile # HF Space container
432
+ ```
433
+
434
+ ## Engineering notes
435
+
436
+ `AdaptShieldEnvironment` extends OpenEnv's `Environment` base class and
437
+ follows the Gym-style API (`reset`, `step`, `state`). The client in
438
+ `client.py` talks to the server only through HTTP, with no shared
439
+ imports and no leaking of server internals. None of the SOC tools are
440
+ named `reset`, `step`, `state`, or `close`, so they do not collide with
441
+ the reserved MCP tool names. Grading is deterministic Python; the
442
+ reward signal and the benchmark scores both come from strategy
443
+ matching against a fixed ground-truth attacker, never from an LLM
444
+ judge.
445
+
446
+ All adapters, curves, metrics, and benchmark tables for the 1.5B run
447
+ are public on [`SaiManish123/Janus`](https://huggingface.co/SaiManish123/Janus).
448
+
449
+ ## License
450
+
451
+ MIT.