API reference
Interventions
What can you change about a reward model, and which change is dangerous enough to need a receipt? reward_lens.interventions is the causal half of the library: patch an activation, steer along a direction, ablate a subspace, edit the readout weights, or erase a concept. Every operation compiles to a hook on the signal, so they compose in order, and the one that erases returns a certificate that grades whether it worked. The narrated version is the intervention algebra.
Patching
A patch compiles a fixed activation into a named site and holds it there while the signal scores. ComponentPatch replaces a whole component’s output, HeadPatch a single attention head, and ResidualAddPatch adds a vector into the residual stream.
reward_lens.interventions.patch.ComponentPatch
@dataclass
Replace a sublayer or residual activation with a source (or zero); an Intervention.
site names where to patch (attn_out, mlp_out, or resid_post at a layer).
source is the full-sequence source activation (1, T_src, d) to splice in; when mode is
"zero" the activation is replaced with zeros and source is ignored (the crude ablation
baseline). label is a short human tag used in the component name.
Attributes
| Name | Type | Default |
|---|---|---|
site | Site | |
source | torch.Tensor | None | None |
mode | str | 'replace' |
label | str | '' |
id | str | 'component_patch' |
reward_lens.interventions.patch.HeadPatch
@dataclass
Replace a single attention head’s contribution by editing the o_proj input slice.
The attention output is o_proj(concat_h head_h), so patching head h means overwriting its
d_head slice of the o_proj input with the source side’s slice while leaving every other
head untouched. site is Site(layer, "head_out", head); source_head is the source
per-head input (1, T_src, d_head) for that head.
Attributes
| Name | Type | Default |
|---|---|---|
site | Site | |
source_head | torch.Tensor | |
n_heads | int | |
id | str | 'head_patch' |
reward_lens.interventions.patch.ResidualAddPatch
@dataclass
Add a precomputed delta to a site’s activation; the path-patching primitive.
Path patching perturbs only the sender to receiver path by adding the sender’s source-minus-target
residual contribution at the receiver’s input, holding every other path clean. site is the
receiver read point (typically Site(layer, "resid_pre")) and delta is the
(1, T, d_model) residual adjustment to add there. This is the exact mechanic v1’s
PathPatcher used, expressed as an Intervention.
Attributes
| Name | Type | Default |
|---|---|---|
site | Site | |
delta | torch.Tensor | |
id | str | 'residual_add_patch' |
Steering and ablation
SteeringIntervention pushes the activation along a unit direction by a chosen strength. At strength zero it is bit-exact with the unmodified run, so the null case costs nothing and cannot leak an artifact. AblationIntervention removes a direction instead, in directional, mean, or head mode.
reward_lens.interventions.steer.SteeringIntervention
@dataclass
Add strength * unit(direction) to a site’s activation; an Intervention (section 2.6.2).
direction is the array-like (d_model,) steering direction (unit-normalized on use, so its
magnitude is irrelevant and only its orientation matters). site names where to add it, in the
residual stream or a sublayer output. strength is the signed displacement in unit-direction
units, the dose. positions, if given, restricts the addition to those (padded) sequence
columns; the default None steers every position, which is what a final-token readout needs and
what the dose-response Observable uses.
Attributes
| Name | Type | Default |
|---|---|---|
direction | Any | |
site | Site | |
strength | float | |
positions | tuple[int, ...] | None | None |
id | str | 'steer' |
Methods
fingerprint() -> str
src
Two steers differ in fingerprint whenever they differ in where they push, which way they push (the direction content hash, not just its shape), or how hard they push. A strength of zero is a distinct fingerprint from any nonzero strength even though it is a numerical no-op, because a provenance record should say a steer with strength zero was applied, not that nothing was.
compile(signal: RewardSignal) -> CompiledIntervention
src
site (the Intervention protocol).
The site is architecture-resolved by the runner’s SiteMap at mount time, exactly as in
patch.py, so compilation only needs to bind the hook and stamp the fingerprint.
reward_lens.interventions.ablate.AblationIntervention
@dataclass
Remove a direction (or a head) from an activation; an Intervention (section 2.6.2).
site names where the ablation acts. mode selects the removal:
"directional"projectsdirectionout of the residual (h -> h - (h.u) u);directionis required andmean_projectionis ignored."mean"replaces the along-direction coordinate withmean_projectioninstead of zeroing it (h -> h - (h.u) u + mean_projection * u);directionis required. The supplied scalar is the direction’s average projection over a reference dataset, computed upstream."head"zeros the attention head named bysite.headatSite(layer, "head_out", head);n_headsis required so theo_projinput can be split into per-head slices, anddirectionis ignored.
The direction, where used, is unit-normalized on use, so only its orientation matters.
Attributes
| Name | Type | Default |
|---|---|---|
site | Site | |
direction | Any | None |
mode | str | 'directional' |
mean_projection | float | 0.0 |
n_heads | int | None | None |
id | str | 'ablate' |
Methods
fingerprint() -> str
src
A directional and a mean ablation of the same direction at the same site have different
fingerprints because the mode and the substituted mean differ; a head ablation keys on the
head count as well, so the same site.head on two different backbones does not collide.
compile(signal: RewardSignal) -> CompiledIntervention
src
site (the Intervention protocol).
Directional and mean ablation bind a projection hook; head ablation binds a per-head zeroing
hook that the runner mounts as an o_proj pre-hook (the head_out surface). The site is
architecture-resolved at mount time, as in patch.py, so compilation only binds the hook
and stamps the fingerprint.
Editing the readout
EditIntervention works in weight space rather than activation space, projecting a direction out of the reward head: . It changes what the model rewards, not just one forward pass.
reward_lens.interventions.edit.EditIntervention
@dataclass
Project a concept direction out of the reward head weight; an Intervention (section 2.6.2).
direction is the array-like (d_model,) concept direction u to remove, in the space the
reward head reads (the head-input hidden state), unit-normalized on use. readout names which
readout’s vector to edit ("reward" by default). strength is the coefficient alpha in
w_r' = w_r - alpha (w_r . u) u: alpha = 1 removes the component entirely (the E17 edit),
alpha between 0 and 1 attenuates it, alpha > 1 over-corrects past orthogonality.
Because the edit lives on the readout vector rather than on an activation, a compiled edit carries
no mounts; it carries the edited vector in meta and is applied by :func:run_edited_scores.
Attributes
| Name | Type | Default |
|---|---|---|
direction | Any | |
readout | str | 'reward' |
strength | float | 1.0 |
id | str | 'edit' |
Methods
fingerprint() -> str
src
The fingerprint changes with which readout is edited, which direction is removed (the content hash, not just the shape), and how strongly, so an edited-head Evidence carries an identity distinct from the base head and from any other edit.
edited_vector(signal: RewardSignal, dtype: Any = np.float64) -> np.ndarray
src
w_r' = w_r - alpha (w_r . u) u for signal.
Reads the readout’s current vector off the signal and removes the direction component in
dtype (float64 by default, so the orthogonality is exact to double precision for the
algebraic proofs; the runner requests float32 to match the head-projection dtype). Returns a
numpy vector (d_model,).
compile(signal: RewardSignal) -> CompiledIntervention
src
The edit is a weight-space operation with no activation mount, so mounts is empty; the
realized w_r' (float32, ready for the head projection) rides in meta and is consumed by
:func:run_edited_scores. The fingerprint is stamped exactly as the activation interventions
stamp theirs, so an edited-head number carries its provenance the same way.
Erasure and its certificate
Eraser is a fitted LEACE transform that removes a concept from the activations, fit_leace fits one from labelled data, and LeaceErasure wraps it as a composable intervention. Erasure is the operation that can quietly fail, so it does not get to claim success on its own word.
reward_lens.interventions.erase.Eraser
@dataclass
A fitted LEACE affine eraser, persisted with its fit-data provenance (DESIGN 2.6, R8).
P is the d x d projection and mu the d fit-data mean, so the erasure is the
affine map r(x) = x - P (x - mu). rank is the dimension of the erased concept subspace.
fit_data_id and concept_id are the ids of the feature matrix and the concept the eraser
was fit on; carrying them on the artifact is design rule R8, and it is what lets a certificate
(certify.py) name the exact data an eraser was trained on. sites records the residual
sites the eraser is intended for, informational metadata for the head-only and chosen-layers
surfaces.
The matrices are kept in float64: the eraser’s defining properties (idempotence, exact linear erasure, minimal edit) are proven to ~1e-6 or tighter, and float32 storage would put that tolerance out of reach for no memory saving that matters at a single site.
Attributes
| Name | Type | Default |
|---|---|---|
P | np.ndarray | |
mu | np.ndarray | |
rank | int | |
dim | int | |
method | str | 'leace' |
fit_data_id | str | None | None |
concept_id | str | None | None |
sites | tuple[Site, ...] | () |
Methods
apply(x: np.ndarray) -> np.ndarray
src
X (n x d, rows are samples): r(X) = X - (X - mu) P^T.
Accepts a single vector (d,) or a batch (n, d); returns the same shape in float64.
This is the numpy path used to fit certificates and to prove the guarantee on captured
matrices; the torch mount hook in :class:LeaceErasure applies the identical map on-device.
apply_to_readout(w_r: np.ndarray) -> np.ndarray
src
w_r against the concept subspace (the head-only surface).
The reward read from erased features, w_r . r(x), equals w_r' . x for the projected
readout w_r' = (I - P)^T w_r = w_r - P^T w_r, up to a per-sample-constant offset the
reward gauge already quotients out (per-prompt shifts, DESIGN 2.7.1). So editing the reward
direction once reproduces feature erasure everywhere without mounting a hook, which is the
head-only erasure of DESIGN line 601. Returns a float64 vector (d,).
fingerprint() -> str
src
Derived from the projection and mean bytes together with the fit-data and concept ids, so two erasers fit from the same data and concept share an id and an eraser can never be confused with one trained on different data. This is the cache-key and provenance component the Intervention protocol requires.
reward_lens.interventions.erase.fit_leace
fit_leace(
X: np.ndarray,
Z: np.ndarray,
*,
fit_data_id: str | None = None,
concept_id: str | None = None,
sites: tuple[Site, ...] = ()
) -> Eraser Fit the LEACE eraser from captured features X and a concept Z (DESIGN 2.6).
X is (n, d) (features at a site). Z is the concept: a length-n vector of labels
for a single binary or scalar concept, or an (n, k) matrix whose columns span a
k-dimensional concept subspace (one-hot class indicators, or several concepts at once). The
returned :class:Eraser removes all linear information about every direction in
span(Cov(X, Z)).
Only second moments enter, so the fit is a closed form with no optimization loop: the feature
covariance Sigma and the cross-covariance Sigma_XZ are formed, and
:func:leace_matrix returns the projection. The fit-data and concept ids are stored on the
eraser (R8); pass them from the capture provenance so the persisted eraser names its training
data.
reward_lens.interventions.erase.LeaceErasure
@dataclass
Mount a fitted :class:Eraser at one or more sites as an Intervention (DESIGN 2.6.1).
sites selects the surface: a single residual site for a targeted edit, several named
residual sites for the chosen-layers surface, or every residual site the caller enumerates for
the all-layers surface. Compilation resolves the eraser into one affine mount hook per site;
the hooks install through the runtime’s single mounting path, exactly as patches and steers do,
so an erased forward pass is measured by any Observable unchanged and the erasure fingerprint is
carried into the intervened Evidence’s SubjectRef. The head-only surface is not a hook; it is
:meth:Eraser.apply_to_readout applied to the reward direction.
This wraps the frozen :class:~reward_lens.interventions.base.Intervention protocol: the map is
signal-independent (it is an affine function of the activation), so compile ignores the
signal and the sites are architecture-resolved by the runner’s SiteMap at mount time, the same
contract patch.py follows.
Attributes
| Name | Type | Default |
|---|---|---|
eraser | Eraser | |
sites | tuple[Site, ...] | |
id | str | 'leace_erasure' |
certify_erasure trains a fresh probe on held-out data and reports the worst recovery AUC it can find. A real erase drops that AUC to chance (1.0 to 0.5056 in the test suite) and the certificate binds a CalibrationRef, so the Evidence comes out CALIBRATED. A sham erase leaves the AUC at 1.0 and the evidence stays EXPLORATORY. The certificate grades the erasure; the erasure does not grade itself.
reward_lens.interventions.certify.certify_erasure
certify_erasure(
eraser: Eraser,
X_holdout: np.ndarray,
Z_holdout: np.ndarray,
*,
eps: float = 0.05,
probe_train_frac: float = 0.5,
l2: float = 0.001,
seed: int = 0,
concept_id: str | None = None,
provenance: Provenance | None = None
) -> Evidence[ErasureCertificate] Certify an eraser by held-out probe recovery, as Evidence (DESIGN line 603).
The held-out features are erased, split into a probe-train and a probe-eval portion, and a fresh
linear probe is trained and scored per concept column. The reported recovery_auc is the
worst case across columns. If it is at most 0.5 + eps the erasure holds on data the eraser
never saw, and the returned Evidence carries a :class:CalibrationRef, so gate 1 lifts it to
CALIBRATED; otherwise the Evidence carries no calibration and stays EXPLORATORY. That is the
mechanism by which a certificate lifts an eraser above EXPLORATORY, and by which a fake erasure
is refused: the pass/fail is computed from the same held-out probe either way.
X_holdout must be disjoint from the eraser’s fit data for the certificate to mean anything;
the caller is responsible for that split, and eraser.fit_data_id records what to exclude.
The certificate is INVARIANT (a scalar recovery statistic, not a cross-signal geometric
quantity), so no frame is required.
Composition
compose chains interventions that share a site into one, applied in order. The algebra is closed: a composed intervention is itself an intervention, so a signal can wear several at once through signal.with_interventions(...).
reward_lens.interventions.base.compose
compose(interventions: Sequence[Intervention]) -> ComposedIntervention Compose several interventions into one that mounts them together in order.