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
Card fact-check: usage snippet, long inputs, 4090 follow-up, limits, privacy, sources
124bf0b verified |
Download README.md from stonefish-labs/d3-mini-agentcode: direct link, hf CLI and curl.
- Browser
- Download file 15 kB
-
https://huggingface.co/stonefish-labs/d3-mini-agentcode/resolve/main/README.md
- Command line
-
hf download hf://stonefish-labs/d3-mini-agentcode/README.md
-
curl -L -o README.md https://huggingface.co/stonefish-labs/d3-mini-agentcode/resolve/main/README.md
15 kB
| 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`. | |