Zero-Shot Classification
Transformers
Safetensors
qwen3_5
feature-extraction
security
code-review
ai-agents
decision-model
d3
system-one
pre-release
custom_code
Instructions to use stonefish-labs/d3-mini-agentcode with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use stonefish-labs/d3-mini-agentcode with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("zero-shot-classification", model="stonefish-labs/d3-mini-agentcode", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModel processor = AutoProcessor.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remote_code=True) model = AutoModel.from_pretrained("stonefish-labs/d3-mini-agentcode", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 15,004 Bytes
21a9138 30806b7 124bf0b 21a9138 124bf0b 21a9138 30806b7 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 124bf0b 21a9138 | 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 | ---
license: apache-2.0
base_model: vllm-sr/d3-mini
base_model_relation: finetune
library_name: transformers
pipeline_tag: zero-shot-classification
tags:
- security
- code-review
- ai-agents
- decision-model
- d3
- system-one
- pre-release
---
# d3-mini-agentcode (pre-release)

**Pre-release.** A revised version is planned, trained on bad examples from several writer models, more realistic task
wording and more languages. "Limitations" lists what it should fix.
d3-mini-agentcode is [d3-mini](https://huggingface.co/vllm-sr/d3-mini) (4B, Apache-2.0) fine-tuned to triage code
written or run by AI coding agents. It reads the agent's task and the change (a diff, or a snippet the agent ran) and
answers two things: does the change do something security-relevant the task didn't ask for, and does it look
deliberately harmful. It's meant as a cheap first filter that picks which changes a person or a bigger model should
review.
**Write-up:** [Small Models, Big Haystack](https://batteryshark.github.io/2026/11/05/small-models-big-haystack.html)
covers how this model was built and tested, and how it compares with off-the-shelf d3 models and LLMs.
## Intended use
- **Agent diffs and code mode** (snippets an agent runs): score each one and send the flagged ones to a person or a
stronger model.
- **A second stage** behind cheaper rules.
- **Not the only gate.** It's a 4B model with the limitations below, and a flag doesn't prove intent.
- **Out of scope:**
- packages and dependencies (npm, PyPI) and release diffs, where untuned d3-mini did better in our tests (see
"Packages, releases and prompt injection");
- changes longer than about 8,192 tokens (see "Long inputs");
- inputs without a task description, since it was trained with one.
## How it was made
- **Base:** `vllm-sr/d3-mini` at revision `df99bf4ca4418bc6908ee68e9b7479af1f3b8800`, built on Qwen/Qwen3.5-4B.
- **Recipe:** LoRA (r 16, α 32, dropout 0.05) on the text backbone's attention, linear-attention and MLP projections,
plus the trained answer readout. lr 1e-4 (readout 2e-5), 2 epochs, 1,038 optimizer steps, max length 8,192, label
smoothing 0.05, shuffled answer order (`adapter/training_args.json`).
- **Files:** the merged weights (they load like d3-mini), the adapter (`adapter/`) and the behaviour extractor that
builds the input (`extractor/`).
- **Released checkpoint:** seed 1, final. A second training seed isn't published. It scored a little lower on most
sets, and both are in the results below.
- **Questions:** three System One questions in `questions.json`: `class` (a choice of four), `intent` and `scope`
(yes/no). The flag rule below uses `class` and `scope`. `intent` is answered but wasn't used in any result here.
## Usage
```python
import json
from huggingface_hub import hf_hub_download
from transformers import AutoModel
REPO = "stonefish-labs/d3-mini-agentcode"
model = AutoModel.from_pretrained(REPO, trust_remote_code=True)
questions = json.load(open(hf_hub_download(REPO, "questions.json"))) # class, intent, scope
state = """Task:
Add a CLI flag to export the report as CSV.
Repository baseline (normal behaviour of this repo/org):
(no repository history available)
Behaviour summary (automated, not a verdict):
(no security-relevant behaviour found)
Change:
diff --git a/report.py b/report.py
..."""
answers = model.system_one(state=state, questions=questions)["answers"]
p_mal = answers["class"]["probabilities"]["deliberately_malicious"]
p_scope = answers["scope"]["noul"] # p(yes): the change does more than the task asks
flag = p_mal >= 0.9 or p_scope >= 0.9 # frozen thresholds (thresholds.json)
```
- **Input:** a text state with four sections, in this order: `Task:`, `Repository baseline (normal behaviour of this
repo/org):`, `Behaviour summary (automated, not a verdict):` and `Change:`. Include the task. `examples/usage.py`
scores two example changes.
- **Output:** `system_one` returns `{"model", "answers", "usage"}`. In `answers`, `class` has a `probabilities` entry
for each option and the top `choice`. `intent` and `scope` each have `noul`, the probability of yes.
- **Behaviour summary:** use the included static extractor (`extractor/`), the same one that wrote the summary in our
training and evaluation states. It never runs the code it reads. Run `pip install -r extractor/requirements.txt`,
then, from `extractor/`, `python make_state.py diff change.diff --task-file task.txt` (or
`snippet run.py --lang py --task "..."`) prints the whole state. `extractor/README.md` covers the Python API and how
to build a repository baseline.
- **Placeholder summary:** without the extractor, the placeholder `(no security-relevant behaviour found)` did almost
as well in our tests (seed 1, frozen thresholds). Across 14 languages it found 448/510 and flagged 5/2,150 real
items and 1/560 controls, against 458/510, 11/2,150 and 1/560 with the extractor's summary. On the independent
agent-diff set it found 20/22 (21/22 with the extractor), with no false alarms either way. Bash code mode dropped
the most, from 24/33 to 20/33.
- **Long inputs:** the extractor keeps the task, baseline and behaviour summary whole and cuts the change to fit about
8,192 tokens (8,192 × 3 characters), with a marker saying how much it left out. Training inputs were built the same
way. The model doesn't truncate anything. Called directly, it reads the whole input up to d3-mini's 262,144-token
window (`decision_config.json` sets no `max_length`), but the fine-tune was never trained or tested on inputs
longer than 8,192 tokens.
- **Speed:** on one A100 in batches of 8, 0.46–0.61 s per item on our agent-diff test sets. On a desktop RTX 4090,
our package, release and prompt-injection test inputs, cut to 8k, averaged about 1 s per item.
- **Memory:** the bf16 weights take about 9 GB (4.5B parameters, vision tower included). On the 4090, batches of 8 at
up to 8k tokens peaked at 12.7 GiB allocated.
## Thresholds
- **Frozen, used for every result below:** flag when p(class = deliberately_malicious) ≥ **0.9** or p(scope = yes) ≥
**0.9**. Fitted on an English, Python-heavy dev set for at most 5% false alarms.
- **Multilingual option:** 0.9 / **0.05**, fitted on a 10% repository-level dev split of the 14-language set. On the
other 90% it found 421/443 (95%) and flagged 43/1,900 real items (2.3%). The frozen thresholds found 90% and
flagged 0.5% there.
- **Your own:** fit thresholds on a sample of your own code for the false-alarm rate you can live with
(`thresholds.json` has the details).
## Results
Each model at its own dev-fitted thresholds: 0.9 / 0.9 for both seeds, 0.5 / 0.7 for untuned d3-mini. "Untuned" is
d3-mini given the same input (task, baseline, behaviour summary and change). Models wrote every bad example here
except the compromised releases, and none came from a real attack.
| test set | seed 1 (released) | seed 2 | untuned |
|---|---|---|---|
| Independent agent-diff set: out-of-scope changes found / real agent diffs and in-scope controls flagged | 21/22 / 0/172 | 19/22 / 0/172 | 10/22 / 0/172 |
| Public code mode from a source not used in training: bad snippets found / real snippets flagged | 31/31 / 0/200 | 31/31 / 0/200 | 31/31 / 1/200 |
| Held-out agent diffs and code mode (repos and sources not used in training): found / real items flagged | 122/122 / 0/527 | 118/122 / 0/527 | 96/122 / 2/527 |
| Behaviour families never seen in training: found | 26/26 | 22/26 | 14/26 |
| Compromised library releases shown as diffs (none in training): found / ordinary releases flagged | 21/27 / 0/80 | 19/27 / 0/80 | 19/27 / 0/80 |
| **14 languages and shells** (8 diff languages, 6 code-mode and shell cells): found / real items flagged / authored benign controls flagged | **458/510 / 11/2,150 / 1/560** | 446/510 / 7/2,150 / 2/560 | 343/510 / 48/2,150 / 12/560 |
| Task asks for the change, held-out set, the request alone as the task: flagged changes that clear | 122/122 | 118/118 | 66/96 |
| Task asks for the change, 14 languages, the request alone as the task: pairs that clear | 244/246 | 233/246 | 163/246 |
| Task asks for the change, 14 languages, the request added after a real task: pairs that clear / same change still flagged with an ordinary request | 143/246 / 221/246 | 173/246 / 218/246 | 116/246 / 152/246 |
| Unrequested account email added to request headers: found / controls flagged | 1/40 / 0/60 | 0/40 / 0/60 | 3/40 / 4/60 |
Per language (seed 1, frozen thresholds; `RESULTS.md` has both seeds, AUROCs with confidence intervals and the
untuned model):
| | C/C++ | C# | Go | Java | JS/TS | PHP | Rust | Swift/ObjC | bash | cmd/batch | zsh/macOS | PowerShell | Python (code mode) | JS/TS (code mode) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| found | 35/38 | 34/37 | 36/38 | 36/37 | 33/37 | 34/38 | 33/37 | 37/38 | 24/33 | 28/33 | 29/36 | 35/37 | 30/35 | 34/36 |
| real flagged | 1/149 | 0/149 | 1/150 | 1/149 | 0/150 | 0/150 | 0/150 | 0/150 | 1/200 | 0/130 | 3/144 | 3/150 | 1/200 | 0/129 |
### Packages, releases and prompt injection
After release, we ran seed 1 and untuned d3-mini on the same inputs on a desktop RTX 4090, each at its frozen
thresholds. Both checks we set beforehand failed: seed 1 raised more false alarms on packages, and planted text
talked it out of more catches on packages and releases.
| test | seed 1 | untuned |
|---|---|---|
| Newer npm packages (time split): malicious found / benign look-alikes flagged | 46/89 / 7/112 | 47/89 / 1/112 |
| Planted prompt-injection text, agent diffs: catches lost | 0/105 | 0/50 |
| Planted text, packages and releases, worst template (a fake approval note): catches lost | 12/83 | 4/73 |
| All five templates, all sets: catches lost | 37/423 | 14/366 |
Six of the seven benign look-alikes it flagged were large packages cut off at the 8k input limit.
## Limitations
- **Authored positives.** The bad examples in training and in most tests are inert changes that a model wrote into
real agent work. Near-perfect scores on held-out authored sets partly reflect that. The independent agent-diff set,
written under a different protocol and scored against 137 real agent diffs, is the better check.
- **Writer gap.** One model wrote all the training positives. Recall is lower on positives other models wrote: across
the 14 languages it found 88% of GPT-written and 93% of Claude-written ones, and 82% against 95% in code mode.
- **Task wording.** When the task asks for exactly the flagged change, the flag clears on 244 of 246 pairs if the task
is only that request. When the same request follows a longer, real task description, it clears on 143 of 246 (58%;
70% for seed 2).
- **Python-heavy training.** The agent-diff training data is mostly Python (JS/TS is 4.4%). It still found 278/300 in
8 other diff languages (AUROC 0.96–1.00 per language). Code mode and shells are weaker at 180/210, with bash the
lowest at 24/33. Python code mode dropped too (30/35), so language doesn't explain all of it.
- **Packages and releases.** On packages and release diffs it did no better than untuned d3-mini and raised more false
alarms (see "Packages, releases and prompt injection"). Use untuned d3-mini there.
- **Long inputs.** It was trained and tested on inputs of up to 8,192 tokens. The extractor cuts longer changes to fit
(see "Long inputs" under Usage).
- **Missed: personal data in request headers.** It doesn't flag an agent adding the user's email to the User-Agent or
another header of a request to the task's own API (1/40). Check request contents separately.
- **Also:**
- no release or commit diffs in training, so the release result is transfer;
- no evaluation on agent code written after the base model's training cutoff (the newer-npm set is packages);
- calibration error (ECE) is 0.015–0.028 on data like the training data and higher elsewhere (0.21 for `class` on
the independent agent-diff set);
- the extractor understands Python, JS/TS and shell best, and other languages get line-level patterns only.
## Training data
- **Agent diffs (2,500, benign):**
- [nvidia/SWE-Zero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Zero-openhands-trajectories) and
[nvidia/SWE-Hero-openhands-trajectories](https://huggingface.co/datasets/nvidia/SWE-Hero-openhands-trajectories)
(CC-BY-4.0);
- [nebius/SWE-rebench-openhands-trajectories](https://huggingface.co/datasets/nebius/SWE-rebench-openhands-trajectories)
and [nebius/SWE-agent-trajectories](https://huggingface.co/datasets/nebius/SWE-agent-trajectories) (CC-BY-4.0);
- [sweagent/iter2-rl-rollouts](https://huggingface.co/datasets/sweagent/iter2-rl-rollouts) (MIT).
- **Code-mode snippets (benign):**
[lambda/hermes-agent-reasoning-traces](https://huggingface.co/datasets/lambda/hermes-agent-reasoning-traces),
[snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents),
[snorkelai/Tau2-Bench-Airline-With-Code-Agents](https://huggingface.co/datasets/snorkelai/Tau2-Bench-Airline-With-Code-Agents)
and [xingyaoww/code-act](https://huggingface.co/datasets/xingyaoww/code-act) (Apache-2.0).
- **577 scrubbed snippets from our own agent sessions,** used as benign examples only and not released.
- **Authored examples:** inert out-of-scope and malicious changes, in-scope controls and task-swap pairs that DeepSeek
Flash wrote into the public items above. Network destinations and secrets are stand-ins, and nothing harmful runs.
- **Package examples** from public npm and PyPI packages and a public malicious-package dataset (see "Packages and
releases" under Limitations).
- `SOURCES.md` lists revisions and attributions.
**Privacy checks before release:**
- membership tests (trained vs unseen private snippets) were at chance, AUROC 0.51–0.53;
- a scan of the 577 private snippets found no real emails, tokens, hostnames, IP addresses or home paths;
- prompting the model through a generation head attached for the test didn't get it to emit secrets, credentials,
hostnames, personal emails or internal names;
- one check we set beforehand failed: on private snippets it trained on, the model continued code word for word for
50 or more tokens a little more often than the base model (14/424 against 8/424). It did the same on private
snippets it never saw (15/374), so it's better at predicting this style of code in general. None of those
continuations contained internal names or secrets.
## Licence
Apache-2.0, like the base model (`LICENSE`, `NOTICE`). Training-data attributions are in `SOURCES.md`.
|