What this recipe does
A compliance team needs two things: a number that won't make the regulator unhappy, and an audit trail. This recipe gives both. The model returns a risk band; every reason cites either a sanctioned-list entry or a transaction hash in the input. Hallucinated risk reasons are blocked at the verifier — if the artifact says "interacted with Tornado Cash," there must be a tx in the input pointing at a Tornado contract.
Cost is asymmetric: a missed match against an OFAC SDN address is a regulatory event; a false flag is a 5-minute review. False-negative weight is 50× false-positive weight in the K-score loss. Compiled on-prem because the gold corpus contains licensed Chainalysis-equivalent labels.
The spec
{
"output_kind": "json",
"schema": {
"required": ["risk_band", "reasons", "action"],
"properties": {
"risk_band": { "enum": ["clean", "watch", "high", "sanctioned"] },
"reasons": { "type": "array", "items": {
"required": ["reason", "evidence_tx"],
"properties": {
"reason": { "$ref": "reasons.json" },
"evidence_tx": { "type": "array", "items": {"type":"string"} },
"sdn_match": { "type": ["string", "null"] }
}
} },
"action": { "enum": ["allow", "review", "block"] }
}
},
"verifier": {
"reason_must_be_in_taxonomy": true,
"evidence_tx_must_appear_in_input": true,
"sdn_match_must_resolve_to_sdn_list": true,
"false_negative_cost": 50,
"sanctioned_band_requires_sdn_match": true
}
}
Compile (in your VPC, against your SDN snapshot)
kolm compile "AML address screener with cited tx evidence" \ --base qwen2.5-coder-3b \ --pairs ./labeled-wallets/*.jsonl \ --taxonomy ./reasons.json \ --sdn-list ./ofac-sdn-2026-05-09.json \ --asymmetric-loss false-neg=50 \ --verifier evidence-grounded,sdn-resolves \ --k-floor 0.88 \ --output web3-screener.kolm ok wrote web3-screener.kolm k_score=0.92 signature=hmac-sha256 sdn-match recall 100% on synthetic adversarial (n=240)
K-score gate
Run-time profile
Deploy
// pre-trade gate — every withdrawal address checked before approval: on_withdrawal_request = async (req) => { const txs = await chain.txHistory(req.address, { days: 90 }); const r = kolm.run('web3-screener.kolm', { address: req.address, txs }); audit.log({ withdrawal: req.id, screener_receipt: r.receipt }); if (r.action === 'block') return reject(req, r.reasons); if (r.action === 'review') return queue.compliance(req, r); return approve(req); };