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
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 (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 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-miniat revisiondf99bf4ca4418bc6908ee68e9b7479af1f3b8800, 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),intentandscope(yes/no). The flag rule below usesclassandscope.intentis answered but wasn't used in any result here.
Usage
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):andChange:. Include the task.examples/usage.pyscores two example changes. - Output:
system_onereturns{"model", "answers", "usage"}. Inanswers,classhas aprobabilitiesentry for each option and the topchoice.intentandscopeeach havenoul, 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. Runpip install -r extractor/requirements.txt, then, fromextractor/,python make_state.py diff change.diff --task-file task.txt(orsnippet run.py --lang py --task "...") prints the whole state.extractor/README.mdcovers 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.jsonsets nomax_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.jsonhas 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
classon 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 and nvidia/SWE-Hero-openhands-trajectories (CC-BY-4.0);
- nebius/SWE-rebench-openhands-trajectories and nebius/SWE-agent-trajectories (CC-BY-4.0);
- sweagent/iter2-rl-rollouts (MIT).
- Code-mode snippets (benign): lambda/hermes-agent-reasoning-traces, snorkelai/Tau2-Bench-Verified-Airline-With-Code-Agents, snorkelai/Tau2-Bench-Airline-With-Code-Agents and 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.mdlists 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.
- Downloads last month
- 21
