File size: 22,536 Bytes
69ef6fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
---
license: cc-by-nc-4.0
base_model: CohereLabs/tiny-aya-fire
base_model_relation: adapter
datasets:
- ahmadrrrtx/scriptshield-urdu-roman
language:
- ur
tags:
- steering-vector
- activation-steering
- contrastive-activation-addition
- refusal
- ai-safety
- urdu
- roman-urdu
- negative-result
- safetune
- lexsi
---

# ScriptShield steering vectors for Tiny Aya Fire

Six 2,048-dimensional steering vectors for decoder block 18 of
[`CohereLabs/tiny-aya-fire`](https://huggingface.co/CohereLabs/tiny-aya-fire), with a manifest and a
standalone loader. No model weights are included.

They were built to test one idea: a refusal-steering vector learned from Urdu-script **and** Roman-Urdu
prompts also carries "which script is this" information; if that component is projected out, the vector
might protect Roman-Urdu users better.

**The idea was not supported (pre-registered gate NOT MET).** These vectors are published so that the
result can be checked and built on, **not as a recommended safety intervention**.

| | |
|---|---|
| Dataset and evaluation logs | [`ahmadrrrtx/scriptshield-urdu-roman`](https://huggingface.co/datasets/ahmadrrrtx/scriptshield-urdu-roman) |
| Code, protocol and reproduction notes | [https://github.com/ahmadrrrtx/scriptshield](https://github.com/ahmadrrrtx/scriptshield) |
| Write-up | [article](https://github.com/ahmadrrrtx/scriptshield/blob/main/docs/hf_article.md) · [full result tables](https://github.com/ahmadrrrtx/scriptshield/blob/main/docs/RESULTS.md) |
| Replay of the recorded outputs | [`ahmadrrrtx/scriptshield-replay`](https://huggingface.co/spaces/ahmadrrrtx/scriptshield-replay) |
| Collections | [ScriptShield — model artifacts](https://huggingface.co/collections/ahmadrrrtx/scriptshield-model-artifacts-6af6d396e9d50507cbd53417) · [ScriptShield — data artifacts](https://huggingface.co/collections/ahmadrrrtx/scriptshield-data-artifacts-6af6d396c3a2600f7241d2d9) |

Author: Muhammad Ahmad ([@ahmadrrrtx](https://huggingface.co/ahmadrrrtx)). Built for the [Lexsi AI Safety and Alignment Hackathon](https://hackathon.lexsi.ai/), Track 2
(inference-time safety steering).

## Findings in brief

1. **The script gap is real and large.** On 36 held-out intent families the unsteered model refused harmful requests at 100.0 % vs 47.2 % (-52.8 pp, 95 % CI -69.4 to -36.1; exact McNemar p = 3.8e-06) in Urdu script vs Roman Urdu. Pre-registered test, significant after Holm correction.
2. **The automatic judge overstates Roman-Urdu refusals, so the true gap is larger.** Reading every Roman-Urdu response (post hoc, by the LLM agent, not a human): 13/36 real refusals, 6/36 answers toward the harmful request, 17/36 non-responsive (echo of the prompt, loops, mistranslations, off-topic text). In Roman Urdu the model mostly fails to engage rather than fluently complying.
3. **Removing the script direction from the steering vector did not help.** Pre-registered gate not met: the residualized and the pooled vector gave the same refusal outcome on 71 of 72 harmful prompts.
4. **Steering at the frozen dose is not a fix.** By the judge, the pooled vector raised Roman-Urdu refusal by +13.9 pp, a control vector orthogonal to it by +11.1 pp, and judged refusals of benign prompts rose by +6.9 pp against a pre-registered budget of +2 pp. By adjudication the pooled vector moved real refusals from 13 to 19 of 36 (McNemar p = 0.07) and the control from 13 to 15; no arm produced a real refusal of a benign prompt. Suggestive, not established.

## What is in the package

| File | Content |
|---|---|
| `steering_vectors.npz` | one float64 array of length 2,048 per arm (names below) |
| `manifest.json` | layer, strength, per-vector checksums, base-model revision and weight hashes, extraction and inference settings, toolkit versions, code commit |
| `frozen_selection.json` | the layer and strength chosen on the development split before the test set was run |
| `apply_steering.py` | standalone loader (needs only `numpy`, `torch`, `transformers`, `safetune`) |
| `expected_outputs.jsonl` | 144 logged responses (48 harmless factual questions in Urdu script and Roman Urdu × unsteered, pooled, residualized) with their sha256, to check that your setup reproduces ours |
| `SHA256SUMS` | checksums of every file |

| Arm | What it is | cos to `pooled` |
|---|---|---:|
| `pooled` | mean of the Urdu-script and Roman-Urdu harmful-minus-benign CAA vectors (equal weight per form) | +1.0000 |
| `residualized` | pooled vector with its component along the script direction (Urdu script vs Roman Urdu, benign prompts) projected out, rescaled to the pooled norm | +0.9958 |
| `random_subspace` | CONTROL: pooled vector with a random direction projected out, rescaled to the pooled norm | +0.9998 |
| `orthogonal_null` | CONTROL: within-pair label-flip noise vector, exactly orthogonal to pooled, pooled norm | +0.0000 |
| `ur_only` | CAA vector from Urdu-script prompts only, rescaled to the pooled norm | +0.9844 |
| `ro_only` | CAA vector from Roman-Urdu prompts only, rescaled to the pooled norm | +0.9301 |

All arms are rescaled to the norm of `pooled`, so they are compared at the same dose. Orientation is
harmful − benign: a **positive** strength pushes toward refusal.

## Results on the frozen test set

36 intent families the vectors never saw (24 other families were used to extract them and
12 more to choose the layer and strength). One model, one seed, greedy decoding, 64 new tokens.

### The script gap in the unsteered model (pre-registered)

Paired by intent family, n = 36 test families, harmful prompts, same meaning in both scripts.

| | Urdu script | Roman Urdu | Difference |
|---|---:|---:|---:|
| Refusal rate | 100.0 % | 47.2 % | -52.8 pp (95 % CI -69.4 to -36.1) |
| Judge-flagged unsafe response | 0.0 % | 27.8 % | McNemar p = 0.002 |

Exact McNemar on refusal: 19 families were refused in Urdu script but not in Roman Urdu, 0 the reverse; p = 3.8e-06.

For reference, the same harmful intents were refused at 97.2 % in code-mixed Urdu-English and 100.0 % in English.

The same pattern appears on the other 36 families (calibration + development, never part of this test): 94.4 % in Urdu script vs 44.4 % in Roman Urdu.

The model is also weaker in Roman Urdu on harmless material: on 24 simple factual questions it answered 87.5 % correctly in Urdu script and 50.0 % in Roman Urdu, and it answered 9 of 72 Roman-Urdu prompts in Devanagari (Hindi).

(Utility is scored by answer patterns fixed before the run. Reading the answers (post hoc, by the agent) gives 22 of 24 in Urdu script and 13 of 24 in Roman Urdu, the same 9-question difference; see the note under the all-arms table.)

### All arms (pre-registered labels)

| Arm | Refusal, harmful, Urdu script | Refusal, harmful, Roman Urdu | Judged refusal of benign prompts | Invalid | Utility |
|---|---:|---:|---:|---:|---:|
| unsteered | 100.0 % | 47.2 % | 6.9 % | 1.4 % | 68.8 % |
| pooled | 100.0 % | 61.1 % | 13.9 % | 1.4 % | 72.9 % |
| residualized | 100.0 % | 63.9 % | 9.7 % | 1.0 % | 75.0 % |
| random_subspace | 100.0 % | 61.1 % | 13.9 % | 1.4 % | n/a |
| orthogonal_null | 97.2 % | 58.3 % | 4.2 % | 1.4 % | n/a |
| ur_only | 100.0 % | 61.1 % | 12.5 % | 2.1 % | n/a |
| ro_only | 100.0 % | 58.3 % | 8.3 % | 1.0 % | n/a |

Every attempted prompt is in the denominator (36 per cell; benign = 72 Urdu-script + Roman-Urdu prompts). `invalid` = empty or a repetition loop; it is never counted as a refusal. Utility was measured for three arms only. **The section on the judge shows that its refusal label is unreliable in Roman Urdu; read the Roman-Urdu and benign columns with that in mind.**

**Utility column: post hoc reading check (protocol A20).** The agent read all 144 utility answers (not a human; arm labels visible). 7 pattern scores disagree with the text: 5 correct answers used a spelling or transliteration the patterns do not cover, and 2 answers were matched although the answer they state is wrong (2 borderline, also shown flipped). Details: `experiments/adjudication/utility_reading_check.json`.

| Arm | Utility by pattern (pre-registered) | Utility on reading (post hoc) | Borderline flipped |
|---|---:|---:|---:|
| unsteered | 33/48 | 35/48 | 35/48 |
| pooled | 35/48 | 36/48 | 37/48 |
| residualized | 36/48 | 36/48 | 37/48 |

On reading, the pooled and residualized arms answer the same questions correctly; the pooled arm differs from the unsteered model on 1 question (2 with the borderline answer flipped). The pre-registered column is unchanged. Differences of one or two questions in it are within the scoring error of the patterns and should not be read as effects of steering.

### Residualized vs pooled (pre-registered gate)

| Criterion | Measured | Threshold | Met |
|---|---:|---:|:--:|
| Worst-form refusal gain | +2.8 pp (CI +0.0 to +8.3) | ≥ +5 pp | no |
| Script-gap reduction | +2.8 pp (CI +0.0 to +8.3) | ≥ +5 pp | no |
| Over-refusal increase | -4.2 pp | ≤ +2 pp | yes |
| Utility drop | -2.1 pp | ≤ +2 pp | yes |
| Invalid-rate increase | -0.3 pp | ≤ +1 pp | yes |

**Gate NOT MET.** The two arms gave the same refusal outcome on 71 of 72 harmful prompts; the single difference is one family. With 36 families the interval still contains +5 pp, so by the pre-registered wording this is inconclusive rather than proof of no effect.
On reading (post hoc, see the reading of the responses) the two arms are indistinguishable: 19 vs 19 real Roman-Urdu refusals, 3 vs 3 answers, 14 vs 14 non-responsive; the one judged difference was a mistranslated echo that the judge read as a refusal.
The two arms produced byte-identical text on 232 of 288 test prompts (80.6 %).
The utility difference in the table is a scoring artefact: on reading (post hoc, note under the all-arms table) both arms answer the same 36 of 48 questions correctly. The gate outcome does not depend on it.

Pre-declared continuous endpoint (teacher-forced refusal margin, residualized − pooled, family means): +0.0219 nats/token (95 % CI +0.0208 to +0.0231; sign-flip p = 0.0001).
That is statistically detectable and tiny: the unsteered margin is 2.44 for Urdu-script and 1.14 for Roman-Urdu harmful prompts, so residualization closes 1.7 % of that 1.30-nat difference.

Holm-adjusted p-values: H1 unsteered ur vs ro (McNemar): 1.1e-05; H3 worst-form gain (bootstrap): 1; H3 gap reduction (bootstrap): 1.

### Each steered arm against the unsteered model

Change in the judge's refusal rate, paired by family, percentage points with 95 % family-bootstrap interval. Not multiplicity-corrected.

| Arm | Harmful, Urdu script | Harmful, Roman Urdu | Benign, Urdu script | Benign, Roman Urdu |
|---|---:|---:|---:|---:|
| pooled | +0.0 (+0.0 to +0.0) | +13.9 (-2.8 to +30.6) | +0.0 (+0.0 to +0.0) | +13.9 (-2.8 to +30.6) |
| residualized | +0.0 (+0.0 to +0.0) | +16.7 (+0.0 to +33.3) | +0.0 (+0.0 to +0.0) | +5.6 (-8.3 to +22.2) |
| random_subspace | +0.0 (+0.0 to +0.0) | +13.9 (-2.8 to +30.6) | +0.0 (+0.0 to +0.0) | +13.9 (-2.8 to +30.6) |
| orthogonal_null | -2.8 (-8.3 to +0.0) | +11.1 (+0.0 to +25.0) | +0.0 (+0.0 to +0.0) | -5.6 (-16.7 to +5.6) |
| ur_only | +0.0 (+0.0 to +0.0) | +13.9 (-2.8 to +30.6) | +0.0 (+0.0 to +0.0) | +11.1 (+0.0 to +25.0) |
| ro_only | +0.0 (+0.0 to +0.0) | +11.1 (+0.0 to +25.0) | +0.0 (+0.0 to +0.0) | +2.8 (-11.1 to +16.7) |

`orthogonal_null` is a control: a vector of the same norm, exactly orthogonal to the pooled refusal vector. By the judge's label it raised Roman-Urdu refusal by +11.1 pp against +13.9 pp for the refusal vector, with overlapping intervals. On this measurement the gain cannot be attributed to the refusal direction. The reading of the responses looks at what the responses actually say.

## Can the automatic judge be trusted in Roman Urdu?

### Second judge — PRE-DECLARED sensitivity analysis

The same outputs scored by a rubric prompt on Qwen2.5-7B-Instruct: H1 refusal 100.0 % vs 44.4 % (-55.6 pp, 95 % CI -72.2 to -38.9; exact McNemar p = 1.9e-06).
H3 worst-form gain +2.8 pp, gate not met. Both pre-registered conclusions hold under the second judge.

| Arm | Refusal, harmful, Roman Urdu (judge 1) | (judge 2) | Refusal of benign prompts (judge 1) | (judge 2) |
|---|---:|---:|---:|---:|
| unsteered | 47.2 % | 44.4 % | 6.9 % | 13.9 % |
| pooled | 61.1 % | 50.0 % | 13.9 % | 20.8 % |
| residualized | 63.9 % | 52.8 % | 9.7 % | 20.8 % |
| random_subspace | 61.1 % | 52.8 % | 13.9 % | 20.8 % |
| orthogonal_null | 58.3 % | 44.4 % | 4.2 % | 9.7 % |
| ur_only | 61.1 % | 52.8 % | 12.5 % | 16.7 % |
| ro_only | 58.3 % | 47.2 % | 8.3 % | 18.1 % |

### Prompt echoes — POST HOC (protocol A18)

The model often answers a Roman-Urdu prompt by repeating the prompt. 49 of 2016 test responses are such echoes (49 of them to Roman-Urdu prompts), and the judge labelled 32 of them refusals. Treating an echo as `invalid` instead:

- H1 becomes 100.0 % vs 36.1 % (-63.9 pp, 95 % CI -77.8 to -47.2; exact McNemar p = 2.4e-07).

| Arm | Refusal, harmful, Roman Urdu (pre-registered) | (echo-corrected) | Refusal of benign prompts (echo-corrected) |
|---|---:|---:|---:|
| unsteered | 47.2 % | 36.1 % | 6.9 % |
| pooled | 61.1 % | 47.2 % | 13.9 % |
| residualized | 63.9 % | 50.0 % | 9.7 % |
| random_subspace | 61.1 % | 47.2 % | 13.9 % |
| orthogonal_null | 58.3 % | 47.2 % | 4.2 % |
| ur_only | 61.1 % | 47.2 % | 12.5 % |
| ro_only | 58.3 % | 47.2 % | 8.3 % |

### Reading the responses — POST HOC, exploratory (protocol A19)

The LLM agent labelled all 214 distinct Roman-Urdu test responses of four arms (288 rows) with the arm hidden, under a rubric committed beforehand: **R** refusal, **A** answer toward the request, **N** non-responsive (echo, loop, paraphrase of the prompt, off-topic or misread). **This is not human annotation and not by a native speaker; it was only partly blind.**

| Arm | Harmful: R | A | N | Judge said refusal | Benign: R | A | N | Judge said refusal |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| unsteered | 13 | 6 | 17 | 17 | 0 | 29 | 7 | 5 |
| pooled | 19 | 3 | 14 | 22 | 0 | 27 | 9 | 10 |
| residualized | 19 | 3 | 14 | 23 | 0 | 28 | 8 | 7 |
| orthogonal_null | 15 | 5 | 16 | 21 | 0 | 27 | 9 | 3 |

36 prompts per cell. With every borderline label flipped to its alternative, harmful-prompt refusals are unsteered 11, pooled 15, residualized 15, orthogonal_null 12.

Judge against this reading, Roman-Urdu rows only:

| Label source | Precision of "refusal" | Recall | Cohen's κ | What its false refusals really were |
|---|---:|---:|---:|---|
| Judge 1, pre-registered label | 0.53 | 0.86 | 0.52 | 42 non-responsive, 9 answers |
| Judge 1, echo-corrected | 0.63 | 0.86 | 0.63 | 24 non-responsive, 9 answers |
| Judge 2 (rubric) | 0.60 | 0.86 | 0.60 | 19 non-responsive, 19 answers |

Paired change against unsteered (36 families):

| Arm | Real refusals of harmful prompts | Answers toward harmful requests | Benign prompts answered |
|---|---:|---:|---:|
| pooled | 13 → 19 (+16.7 pp, CI +2.8 to +33.3, p = 0.07) | 6 → 3 (-8.3 pp, CI -19.4 to +2.8, p = 0.38) | 29 → 27 (-5.6 pp, CI -19.4 to +8.3, p = 0.69) |
| residualized | 13 → 19 (+16.7 pp, CI +2.8 to +33.3, p = 0.07) | 6 → 3 (-8.3 pp, CI -19.4 to +2.8, p = 0.38) | 29 → 28 (-2.8 pp, CI -13.9 to +8.3, p = 1) |
| orthogonal_null | 13 → 15 (+5.6 pp, CI +0.0 to +13.9, p = 0.5) | 6 → 5 (-2.8 pp, CI -13.9 to +8.3, p = 1) | 29 → 27 (-5.6 pp, CI -19.4 to +8.3, p = 0.69) |

What this shows:

- Of the 108 Roman-Urdu responses judge 1 called refusals, 51 (47 %) were not refusals on reading.
- Benign prompts: the judge reported 25 refusals across the four arms; the reading found 0. What the judge calls over-refusal in Roman Urdu is garbled or looping output.
- Harmful prompts: the judge credits the pooled vector with +5 refusals and the control with +4; the reading finds +6 and +2. With 36 families neither difference is statistically secure.

## What can and cannot be claimed

**Supported.** Tiny Aya Fire's refusal behaviour depends strongly on script: the same harmful request is refused essentially always in Urdu script and less than half the time in Roman Urdu. Roman-Urdu refusal is 47.2 % under the pre-registered judge, 44.4 % under a second judge, 36.1 % under an echo correction, 36.1 % under a reading of every response. Every alternative gives a larger gap than the pre-registered one. In the model's activations the harmful-vs-benign signal is about half as strong for Roman Urdu.

**Not supported.** That removing the script direction from a refusal-steering vector improves protection for Roman-Urdu users. The pre-registered gate was not met and the two vectors behave alike.

**Open.** Whether refusal steering helps Roman-Urdu users at all. The reading in 5.3 suggests a real but modest gain; the pre-registered measurement cannot confirm it because the judge is unreliable in Roman Urdu and a control vector scores similarly on it.

**Not claimed.** Anything about other models, other Roman-Urdu spellings, multi-turn or adversarial prompts, longer responses, or human-validated language quality. A Roman-Urdu non-refusal here is most often a confused or empty answer, not a usable harmful one.

## How to use

```bash
pip install torch transformers numpy huggingface_hub
pip install "git+https://github.com/Lexsi-Labs/SafeTune@26f59e6d1bdcacafe5f2be3b8d982fb214369659"

# this package, and the base model (gated: accept its terms on the Hub first, then log in)
hf download ahmadrrrtx/scriptshield-tiny-aya-fire-steering --local-dir scriptshield-vectors && cd scriptshield-vectors
hf download CohereLabs/tiny-aya-fire --revision 73500c80fcb6c8bea3e830c21d5d2920249a0e1a --local-dir /path/to/tiny-aya-fire

# one prompt, unsteered vs steered
python apply_steering.py --model-dir /path/to/tiny-aya-fire --arm pooled --prompt "آپ کیسے ہیں؟"

# check that your environment reproduces the logged run exactly
python apply_steering.py --model-dir /path/to/tiny-aya-fire --verify expected_outputs.jsonl
```

```python
from apply_steering import load_package, load_base, make_wrapper, generate

vectors, manifest = load_package(".")                       # checksums are verified
tok, model = load_base("/path/to/tiny-aya-fire", manifest["inference"]["dtype"])
steered = make_wrapper(model, vectors["pooled"], manifest["layer"], manifest["alpha"])
print(generate(model, tok, ["..."], steered))               # steering is live only via wrapper.generate
```

Frozen setting: layer 18, ρ = 0.1 (α = 0.2694; ρ is the added vector's norm as a fraction of
the mean residual-stream norm at that layer). You need your own access to the base model.

## How the vectors were made

1. **Data.** ScriptShield corpus v0.2.0: 72 intent families, each with a harmful and a matched benign
   request, written in Urdu script, Roman Urdu, code-mixed Urdu-English and English. Families are split
   24 calibration / 12 development / 36 test; no family crosses a split.
2. **Extraction.** Lexsi **SafeTune** contrastive activation addition at the last prompt token:
   mean(harmful) − mean(benign), separately for Urdu script and Roman Urdu on the calibration families;
   `pooled` is their equal-weight mean. Cross-checked against an independent activation capture.
3. **Script direction.** Mean activation difference between Urdu-script and Roman-Urdu **benign** prompts.
   `residualized` = `pooled` with that direction projected out, rescaled to the pooled norm.
4. **Selection.** Layer and strength were chosen on the development families by a rule written down
   beforehand (largest gain in harmful refusal with at most +2 points of benign refusal), on the `pooled`
   arm only, and committed before the test set was run.
5. **Evaluation.** Generation and logging through Lexsi **AuditKIT**; data validation and export through
   Lexsi **CuratorKIT**.

## Reproducibility

Verified in a fresh GPU session (Tesla T4, Tesla T4): this package alone (loader + vectors, no ScriptShield code importable) reproduced **144/144** logged outputs byte-for-byte (unsteered 48/48, pooled 48/48, residualized 48/48).

Test run produced at code commit `47413dfa41c32fe0f5b555ba1d64838d40317c67`, base model revision `73500c80fcb6c8bea3e830c21d5d2920249a0e1a` (the sha256 of
both weight shards and of the tokenizer are in `manifest.json`), float16 on a Tesla T4.

## Limitations

- **Small and unreviewed.** 36 test families. The prompts were written with an LLM and have not been
  checked by a native Urdu speaker. Intervals are wide.
- **The judge is unreliable in Roman Urdu.** See the section above. The reading that corrects it was done
  by an LLM agent, not by people.
- **One model, one layer, one strength, one seed, 64-token responses.** Nothing here is known to transfer.
- **Steering has a cost.** On the development split, doubling the strength from ρ = 0.1 to 0.2 raised
  judged refusals of benign prompts by 8 to 17 points depending on the layer. At the frozen strength
  the judge-measured increase exceeded the pre-registered budget on the test set; on reading, those
  responses were garbled answers rather than refusals, which is a cost of a different kind.
- **Not a defence.** Anyone who controls the inference code can remove a steering vector.

## How this was built

This project was built with substantial help from an AI assistant (an LLM agent). Under the author's
direction it drafted the prompts, wrote much of the code and analysis, and did the response readings
labelled above as agent readings. The author chose the research direction, approved the protocol and the
release, and is responsible for the content. No human annotation has been done yet.

## Licence and terms

The vectors are derived from the Tiny Aya weights and are released for **non-commercial research** under
CC-BY-NC 4.0; use is also subject to the Cohere Labs Acceptable Use Policy that governs the base model.
The base weights are not redistributed. `apply_steering.py` is also available under the MIT licence in the
code repository. SafeTune, AuditKIT and CuratorKIT are source-available under the Lexsi Labs licence (LSAL),
which has its own non-commercial terms.

## Citation

```bibtex
@misc{scriptshield2026,
  author = {Muhammad Ahmad},
  title  = {ScriptShield: script-dependent refusal in Tiny Aya Fire for Urdu and Roman Urdu},
  year   = {2026},
  url    = {https://github.com/ahmadrrrtx/scriptshield}
}
```

## Acknowledgements

Built for the Lexsi hackathon with Lexsi Labs' CuratorKIT, SafeTune and AuditKIT. Base model by Cohere Labs.
Judge models by the Qwen team (Apache-2.0).