API reference

Signals

Whatever grades your rollouts, can the same instruments attach to it? A signal is the yes. reward_lens.signals defines one protocol and eight adapters that satisfy it, so a classifier reward model, an LLM judge, a DPO checkpoint, and a process reward model all present the same surface to everything downstream. The tour with runnable code is in models and signals.

The protocol

RewardSignal is a runtime-checkable protocol: a signal declares its capabilities, its readouts, and its positions, and answers score, score_prefixes, and capture. Anything that implements it is a first-class citizen of the library.

# class

RewardSignal

Source

reward_lens.signals.base.RewardSignal

@runtime_checkable

Bases: Protocol

The substrate abstraction: any reward signal with white-box access (section 2.3.2).

Eight adapters implement this (classifier, judge, process, implicit, rubric, trajectory, dense, ensemble). An Observable written against this protocol runs on all of them; a new grader paradigm the field invents becomes a new adapter passing the conformance suite, and the whole battery, every index, and every science become available to it for free (the extensibility contract, section 5.3).

caps declares capabilities (R3); readouts lists the readouts the signal exposes; score and score_prefixes return Evidence; capture returns a handle to activations (possibly cached); with_interventions returns a wrapped signal any Observable accepts unchanged; tokenize owns span carry-through.

Attributes

Name Type Default
meta SignalMeta
caps Capability
runtime Runtime

Methods

readouts() -> list[Readout]
src
score(view: DataView, readout: str = 'reward') -> Any
src
score_prefixes(view: DataView, readout: str = 'reward') -> Any
src
capture(view: DataView, spec: CaptureSpec) -> CaptureHandle
src
with_interventions(*ivs: Intervention = ()) -> RewardSignal
src
tokenize(item: Any) -> TokenizedInput
src

A readout is a named way to read a scalar off the model, and a position spec says where along the sequence to read it. “The reward at the final token” is a Readout at a PositionSpec("final").

# class

Readout

Source

reward_lens.signals.base.Readout

@dataclass(frozen=True)

A first-class readout: what to read, where, and how (section 2.3.1).

name is the human key (“reward”, “verdict”, “criterion:coherence”, “quantile:0.9”). For linear and logit_diff readouts, vector is the direction w (fp32) the scalar is projected onto; for simplex (Likert over score tokens) and token_value (per-token value) the meaning is carried in meta. site is where the readout reads, usually the final residual stream; position says at which token(s). This object is the pivot of R4: it is architecturally resolved by adapters at load time (w_r is read off the checkpoint, not probe-extracted), so there is no “is this feature real” regress at the readout (I1, property 2 in section 1.1).

Attributes

Name Type Default
name str
kind ReadoutKind
site Site
position PositionSpec
vector Any None
meta dict[str, Any] field(default_factory=dict)
# class

PositionSpec

Source

reward_lens.signals.base.PositionSpec

@dataclass(frozen=True)

A resolvable specification of which token positions a readout reads (section 2.3.1).

Nothing in the kernel hardcodes “final token” (R4). A classifier head reads at final; a process reward model reads at step_ends; a generative judge reads at judgment (the verdict token, whose location the signal detects and validates); a dense reward reads at all. detail carries the kind-specific configuration (the span kind for span_ends, the explicit indices for explicit, the delimiter config or detected index for judgment).

Attributes

Name Type Default
kind PositionKind 'final'
detail Any None

Methods

resolve(tokens: TokenizedInput) -> list[int]
src
Resolve to concrete token indices for a given tokenized input.

The position-only kinds (final, all, explicit, and span-derived kinds when the spans are present) resolve here with no model knowledge. judgment is resolved by the signal, which detects the verdict position from the chat template and validates it; when a signal has done that detection it passes the resolved index through detail, so this method honours an explicit detail index list for judgment as well.

Loading a signal

Two entry points are always available and touch no network: wrap_hf_model around a model and tokenizer you already hold, and from_tiny for a synthetic Llama that builds on CPU in under a minute. Both run a conformance quickcheck as they construct.

# func

wrap_hf_model

Source

reward_lens.signals.loaders.wrap_hf_model

wrap_hf_model(
    model: torch.nn.Module,
    tokenizer: Any,
    adapter: Any = None,
    *,
    device: str = 'cpu',
    numerics: Any = None,
    adapter_id: str | None = None,
    architecture: str | None = None,
    max_length: int = 2048,
    lineage: dict[str, Any] | None = None,
    conformance_quickcheck: bool = True
) -> ClassifierRM

Build a ClassifierRM from an already-loaded model + tokenizer (no download).

Resolves the adapter (v1 dispatch) if not given, builds the SiteMap and numerics policy, fingerprints the model, reads the reward head into readouts, and runs a fast conformance quick-check (determinism plus readout-matches-head on two trivial inputs) unless disabled. This is the constructor the tests and the tiny-model path use, and the one the hub loader ends at once it has a model in hand.

# func

from_tiny

Source

reward_lens.signals.loaders.from_tiny

from_tiny(
    *,
    d_model: int = 32,
    n_layers: int = 2,
    n_heads: int = 4,
    seed: int = 0,
    vocab_size: int | None = None,
    seq_max: int = 256,
    tokenizer_name: str = 'gpt2',
    conformance_quickcheck: bool = False
) -> ClassifierRM

Construct the tiny synthetic ClassifierRM the tests run on, entirely offline.

A real LlamaForSequenceClassification (hidden 32, 2 layers, 4 heads, num_labels=1) so the adapter, hooks, readout, grad, and hvp see the same module tree they will see on an 8B Skywork model; only the magnitudes differ. The tokenizer defaults to gpt2 (cached, fast, offset-capable); if gpt2 cannot be loaded offline, a minimal byte-level tokenizer is used so the tests still run with no network (section: hardware reality).

load_signal is the front door for a repo id, but it is gated. Handed a hub id without allow_download=True, it raises NotImplementedError naming itself and pointing you at wrap_hf_model or from_tiny, because an 8B model in fp32 does not fit a laptop GPU and the loader will not pretend otherwise. Local paths bypass the gate.

# func

load_signal

Source

reward_lens.signals.loaders.load_signal

load_signal(spec: str | SignalSpec, **overrides: Any = {}) -> ClassifierRM

Load a signal from an HF id, a local path, or a SignalSpec (section 2.3.4).

Sniffs the loading convention from the config architecture and head names, chooses the adapter and numerics policy, then loads the weights and delegates to wrap_hf_model. The weight load for the 8B/27B campaign models is GPU/download-gated: unless allow_download=True is set (and the machine can hold the model), this raises a clear, marked error rather than attempting a multi-gigabyte download on hardware that cannot run it. Ambiguous conventions raise with the candidate list, never a silent guess.

The eight adapters

Each adapter wraps a family of grader and advertises exactly the capabilities it can honor. The instruments read those capabilities and refuse rather than fake a measurement the substrate cannot support. Every adapter has its own page under models and signals.

ClassifierRM is the reference case: a scalar reward head on a sequence classifier, with the full capability set down to the linear readout and Hessian-vector products.

# class

ClassifierRM

Source

reward_lens.signals.classifier.ClassifierRM

ClassifierRM(
    *,
    runtime: HFRuntime,
    meta: SignalMeta,
    readouts: list[Readout],
    tokenizer: Any,
    policy: NumericsPolicy,
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = (),
    legacy_row_mean: Readout | None = None
)

A sequence-classification reward model as a RewardSignal (section 2.3.3).

Build it through signals.loaders.wrap_hf_model / from_tiny (which resolve the adapter, site map, head, policy, and fingerprint); direct construction is for the loader and tests. The signal holds a HFRuntime, the numerics policy, the readouts read off the head, and the tokenizer. caps declares its capabilities (R3); meta carries the fingerprint, lineage, template, and numerics policy.

Methods

readouts() -> list[Readout]
src
The readouts this signal exposes (section 2.3.1).

A single-row head exposes one reward readout. A multi-row head exposes one criterion:k readout per row plus a reward composite; the row-mean aggregate is available separately as a documented legacy readout, never the default.

readout(name: str = 'reward') -> Readout
src
Look up a readout by name, with a helpful error listing the candidates.
reward_scalar_fn(readout: str = 'reward')
src
Return a ScalarFn computing the readout scalar from a RawOutput (for grad/hvp).

The closure reads the grad-attached head input from RawOutput.extra and projects it onto the readout vector in fp32, pooling at the resolved final positions. Passed straight into runtime.grad/runtime.hvp; that is how the M1 Hessian test differentiates the reward.

tokenize(item: Any) -> TokenizedInput
src
Tokenize a data item, carrying character-to-token offsets and typed spans (section 2.3.2).

Applies the model’s chat template when it has one (else a plain User:/Assistant: fallback, which is what the gpt2-tokenizer tiny model uses), and requests offset mapping from the fast tokenizer so span-level patching and attribution stay exact. Explicit character spans on the item (over the templated text) are mapped into token coordinates and carried on the TokenizedInput; this is the load-bearing, unglamorous part of the protocol.

score(view: Any, readout: str = 'reward') -> Evidence[Scores]
src
Score every item under a readout, returning Evidence[Scores] (section 2.3.2).

Each score is the fp32 projection of the item’s final head-input hidden state onto the readout vector (R11). Gauge is INVARIANT (a raw score is gauge-free); trust is EXPLORATORY (no scorecard entry yet, gate 1). Provenance records the token cost and the wall time.

score_prefixes(view: Any, readout: str = 'reward') -> Evidence[TokenCurves]
src
Per-token reward curves r(y_{1:t}) for every item (section 2.3.2).

One forward per batch: the head input at every valid position is projected onto the readout vector, and because the classifier pools the last token under causal attention, the value at position t is exactly the reward the model assigns the prefix ending at t. The final entry of each curve therefore equals score(...) for that item, which the conformance suite asserts.

capture(view: Any, spec: CaptureSpec) -> CaptureHandle
src
Capture activations at the spec’s sites, returning a CaptureHandle (section 2.3.2).

Collates the whole view into one left-padded batch and runs forward_with_capture under any mounted interventions, returning an in-memory handle. Population-scale, store-backed streaming is runtime.store.ActivationStore.get_or_compute; this method is the direct, single-batch path the store calls on a miss.

with_interventions(*ivs: Any = ()) -> ClassifierRM
src
Return a signal wrapped in interventions; any Observable accepts it unchanged (section 2.6.1).

Each intervention is compiled against this signal (if it exposes compile) and mounted on the runtime’s shared hook path during scoring and capture. The intervention fingerprints become part of the wrapped signal’s subject, so an intervened Evidence can never masquerade as a clean one. Interventions land as a full subsystem in M6; this is the signal-side wiring.

GenerativeJudge reads a verdict as the logit difference between two answer tokens. On a tiny model that verdict is near chance; it needs a real instruction-tuned model to mean anything.

# class

GenerativeJudge

Source

reward_lens.signals.judge.GenerativeJudge

Bases: SignalImplBase

GenerativeJudge(
    *,
    runtime: HFRuntime,
    meta: Any,
    policy: Any,
    tokenizer: Any,
    readouts: Sequence[Readout],
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = ()
) -> None

An LLM-as-judge as a RewardSignal (section 2.3.3, adapter 2).

Build it through from_tiny (the offline test vehicle) or from_causal_lm (an already-loaded instruct model). The verdict readouts are logit_diff directions read off the LM head; the Likert readout is a simplex over score tokens. score dispatches on the readout kind: a projection for the logit-diff verdicts, an expected value under the score-token softmax for the Likert.

Methods

score(view: Any, readout: str | None = None) -> Any
src
Score every item under a readout, returning Evidence[Scores] (section 2.3.2).

logit_diff readouts (verdict, verdict_ab) project the final hidden state onto the readout direction in fp32: h . (W_U[a] - W_U[b]) is exactly logit(a) - logit(b) for the token the model is about to emit. The simplex readout (likert) returns the expected score under the softmax over the score tokens. Gauge INVARIANT, trust EXPLORATORY.

score_prefixes(view: Any, readout: str | None = None) -> Any
src
Per-token verdict curves verdict(y_{1:t}) for every item (section 2.3.2).

For a logit_diff readout the curve at position t is the verdict the model would emit if the sequence ended at t; its final entry equals score. Only defined for the logit-diff verdicts (a simplex has no single direction to trace); raises otherwise.

validate_judgment_position(
    calibration_items: Sequence[Any],
    k: int = 4
) -> dict[str, Any]
src
Validate that the verdict token lands at the detected judgment position (section 2.3.3).

Detection is structural: templating with the generation prompt makes the final valid token the judgment position. Validation runs a forward over up to k calibration prompts and checks how often the model’s greedy next token there is one of the verdict tokens; the fraction is the detection confidence. On the random tiny model this is near chance and is recorded honestly. The production path samples a real instruct model, which this structure is ready for but does not stand in for. Returns the detection record stored in meta.lineage.

from_causal_lm(
    model: torch.nn.Module,
    tokenizer: Any,
    *,
    yes: str = 'Yes',
    no: str = 'No',
    option_a: str = 'A',
    option_b: str = 'B',
    likert: Sequence[str] = ('1', '2', '3', '4', '5'),
    device: str = 'cpu',
    architecture: str | None = None,
    lineage: dict[str, Any] | None = None,
    validate_with: Sequence[Any] | None = None
) -> GenerativeJudge
src
Wrap an already-loaded CausalLM + tokenizer as a judge (no download).

Reads the verdict directions off the LM head: W_U[yes] - W_U[no] for the pointwise verdict, W_U[A] - W_U[B] for the pairwise, and the score-token ids for the Likert simplex. Runs the judgment-position validation on validate_with if given (else a small built-in calibration set). This is the entry point a real instruct judge uses on adequate hardware; from_tiny calls it on the synthetic model.

from_tiny(
    *,
    d_model: int = 32,
    n_layers: int = 2,
    n_heads: int = 4,
    seed: int = 0,
    seq_max: int = 256,
    tokenizer_name: str = 'gpt2'
) -> GenerativeJudge
src
Construct the tiny offline judge the tests run on (section 2.3.3, tiny vehicles).

A real LlamaForCausalLM (hidden 32, 2 layers) with a real tokenizer, so the adapter, hooks, LM-head capture, and logit-diff readout see the same module tree a production judge would; only the magnitudes differ, and the weights are random so no verdict is meaningful. The verdict mechanism (reading W_U[Yes] - W_U[No] off the head) is exact and real.

ProcessRM scores per step. The delimiter step-split works; the learned boundary detector is a stub that falls back to treating the whole response as one step, and it says so in its metadata rather than inventing boundaries.

# class

ProcessRM

Source

reward_lens.signals.process.ProcessRM

Bases: SignalImplBase

ProcessRM(
    *,
    runtime: HFRuntime,
    meta: Any,
    policy: Any,
    tokenizer: Any,
    readouts: Sequence[Readout],
    delimiter: str = '\n',
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = ()
) -> None

A step-level reward model as a RewardSignal (section 2.3.3, adapter 3).

Build it through from_tiny or from_sequence_classifier. score returns the outcome scalar (the final-token reward of the whole solution, identical to a classifier); step_scores returns the per-step reward vector read at the detected step boundaries. The step delimiter is configurable; the default is a newline.

Methods

score(view: Any, readout: str | None = None) -> Any
src
The outcome scalar per item: the final-token reward of the whole solution (section 2.3.2).
score_prefixes(view: Any, readout: str | None = None) -> Any
src
Per-token reward curve for each item (the classifier prefix curve; section 2.3.2).
step_scores(view: Any, readout: str | None = None) -> Any
src
Per-step reward scores read at the detected step boundaries (section 2.3.3, STEP_SCORES).

For each item the head input at each step-end token is projected onto the reward direction in fp32, giving one score per reasoning step. Returns Evidence[StepScores]; the last step’s score equals the outcome score because the final step ends at the final token.

from_sequence_classifier(
    model: torch.nn.Module,
    tokenizer: Any,
    *,
    delimiter: str = '\n',
    device: str = 'cpu',
    architecture: str | None = None,
    lineage: dict[str, Any] | None = None
) -> ProcessRM
src
Wrap an already-loaded sequence-classifier reward model as a process RM (no download).

Reads the scalar reward head (score) direction into a single reward readout whose position is step_ends rather than final (R4). A production PRM would be a checkpoint trained with a step-level objective; the adapter’s mechanics are identical either way.

from_tiny(*, seed: int = 0, delimiter: str = '\n', **kw: Any = {}) -> ProcessRM
src
Construct the tiny offline process RM the tests run on (a tiny sequence classifier).

ImplicitRM reads a reward as the DPO log-ratio between a policy and its reference, so a checkpoint you never trained a reward head for becomes a signal.

# class

ImplicitRM

Source

reward_lens.signals.implicit.ImplicitRM

Bases: SignalImplBase

ImplicitRM(
    *,
    policy_runtime: HFRuntime,
    reference_runtime: HFRuntime,
    meta: Any,
    reference_meta: Any,
    policy: Any,
    tokenizer: Any,
    beta: float = 0.1,
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = ()
) -> None

The DPO implicit reward r-hat = beta * sum log-ratio as a RewardSignal (adapter 4).

Constructed from a policy model and a reference model (from_models / from_tiny), both CausalLM with a shared tokenizer. score returns r-hat; score_prefixes returns the cumulative per-token reward whose last entry equals r-hat; per_token_rewards returns the raw per-token decomposition (the increments). self.runtime is the policy runtime; the reference runtime is self.reference_runtime and capture(..., namespace="ref") routes to it.

Methods

score(view: Any, readout: str | None = None) -> Any
src
r-hat per item: beta * sum over response tokens of the policy/reference log-ratio.
score_prefixes(view: Any, readout: str | None = None) -> Any
src
Cumulative per-token reward curves; the last entry of each equals score (section 2.3.2).

The curve is the running sum of the per-token log-ratio rewards over the response, so curve[-1] == r-hat, which is the prefix-consistency invariant conformance checks. The raw increments (the native per-token decomposition) are per_token_rewards.

per_token_rewards(view: Any, readout: str | None = None) -> Any
src
The native per-token reward decomposition r_t (increments; section 2.3.3).

Each item’s curve is beta * (log pi_policy(y_t) - log pi_ref(y_t)) over the response tokens; the increments sum to the sequence score. This is the decomposition the verification and dense-reward sciences consume directly rather than reconstructing by attribution.

capture(view: Any, spec: Any, namespace: Literal['policy', 'ref'] = 'policy') -> Any
src
Capture activations, routing to the policy model by default (section 2.3.3, PAIRED_MODELS).

namespace="ref" captures from the reference model instead. The two runtimes are distinct models, so a caller must say which one it means; there is no shared “layer L”. Interventions mount on the chosen runtime.

from_models(
    policy: torch.nn.Module,
    reference: torch.nn.Module,
    tokenizer: Any,
    *,
    beta: float = 0.1,
    device: str = 'cpu',
    architecture: str | None = None
) -> ImplicitRM
src
Wrap a policy + reference CausalLM (shared tokenizer) as an implicit RM (no download).
from_tiny(
    *,
    policy_seed: int = 1,
    reference_seed: int = 2,
    beta: float = 0.1,
    **kw: Any = {}
) -> ImplicitRM
src
Two tiny offline LlamaForCausalLM (distinct seeds) sharing a tokenizer (adapter 4).

RubricRM exposes one readout per criterion, and TrajectoryRM scores typed spans of an agent trace.

# class

RubricRM

Source

reward_lens.signals.rubric.RubricRM

Bases: SignalImplBase

RubricRM(
    *,
    runtime: HFRuntime,
    meta: Any,
    policy: Any,
    tokenizer: Any,
    readouts: Sequence[Readout],
    spec: RubricSpec,
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = ()
) -> None

A rubric grader as a RewardSignal (section 2.3.3, adapter 5).

Build it through from_tiny (a tiny multi-label classifier) or from_sequence_classifier (a real multi-objective head). readouts exposes one criterion:<name> per criterion plus a weighted reward aggregate; score projects the final hidden state onto whichever direction the readout names. The rubric spec is on self.spec.

Methods

default_readout_name() -> str
src
A bare score resolves to the aggregate reward readout.
score(view: Any, readout: str | None = None) -> Any
src
Score every item under a criterion or the aggregate readout (section 2.3.2).
score_prefixes(view: Any, readout: str | None = None) -> Any
src
Per-token reward curve under a criterion or aggregate readout (section 2.3.2).
criterion_scores(view: Any) -> dict[str, Any]
src
Convenience: a mapping criterion name -> Evidence[Scores] for the whole rubric.
from_sequence_classifier(
    model: torch.nn.Module,
    tokenizer: Any,
    spec: RubricSpec,
    *,
    device: str = 'cpu',
    architecture: str | None = None,
    lineage: dict[str, Any] | None = None
) -> RubricRM
src
Wrap a multi-row sequence-classifier head as a rubric grader (no download).

Requires the head to have one row per criterion. Reads each row into a criterion:<name> readout and builds the weighted-sum reward aggregate whose vector is sum_k weight_k * row_k (so the aggregate is itself a single fp32 projection).

from_tiny(
    *,
    criteria: Sequence[str] = ('coherence', 'correctness', 'safety'),
    weights: Sequence[float] = (),
    seed: int = 0,
    **kw: Any = {}
) -> RubricRM
src
Construct the tiny offline rubric grader the tests run on (a k-label tiny classifier).
# class

TrajectoryRM

Source

reward_lens.signals.trajectory.TrajectoryRM

Bases: SignalImplBase

TrajectoryRM(
    *,
    runtime: HFRuntime,
    meta: Any,
    policy: Any,
    tokenizer: Any,
    readouts: Sequence[Readout],
    max_length: int = 2048,
    default_batch_size: int = 16,
    interventions: tuple[Any, ...] = ()
) -> None

A trajectory-level reward model as a RewardSignal (section 2.3.3, adapter 6).

Build it through from_tiny or from_sequence_classifier. tokenize accepts a Trajectory (rendering its steps with receipt/narrative/action span typing) or a plain (prompt, response) item (the classifier rendering, so the conformance suite’s generic checks apply). score reads the reward at the trajectory scoring position (the final token).

Methods

score(view: Any, readout: str | None = None) -> Any
src
Score each trajectory at its scoring position (the final token; section 2.3.2).
score_prefixes(view: Any, readout: str | None = None) -> Any
src
Per-token reward curve over the rendered trajectory (section 2.3.2).
from_sequence_classifier(
    model: torch.nn.Module,
    tokenizer: Any,
    *,
    device: str = 'cpu',
    architecture: str | None = None,
    lineage: dict[str, Any] | None = None
) -> TrajectoryRM
src
Wrap an already-loaded sequence-classifier reward model as a trajectory RM (no download).
from_tiny(*, seed: int = 0, **kw: Any = {}) -> TrajectoryRM
src
Construct the tiny offline trajectory RM the tests run on (a tiny sequence classifier).

DenseRewardExtractor derives a per-token reward as the first difference of the prefix curve. It is pinned to EXPLORATORY by design: the dense attribution is a construction, not a calibrated measurement, and the trust level records that.

# class

DenseRewardExtractor

Source

reward_lens.signals.dense.DenseRewardExtractor

DenseRewardExtractor(signal: Any, *, readout: str | None = None) -> None

Per-token reward maps from any outcome signal, shipped GATED (section 2.3.3, adapter 7).

Wraps an outcome RewardSignal and adds dense_rewards: the first difference of the wrapped signal’s prefix-score curve, a per-token attribution that sums to the outcome score. Every other protocol method delegates to the wrapped signal, so the same battery reaches the dense extractor unchanged. dense_rewards Evidence is always EXPLORATORY (no calibration is ever attached), which is how the design enforces “certify before you trust” for dense credit assignment.

Methods

readouts() -> Any
src
The wrapped signal’s readouts (the dense map is computed per outcome readout).
tokenize(item: Any) -> Any
src
score(view: Any, readout: str | None = None) -> Any
src
The wrapped outcome score (delegated); the dense map is dense_rewards.
score_prefixes(view: Any, readout: str | None = None) -> Any
src
capture(view: Any, spec: Any) -> Any
src
with_interventions(*ivs: Any = ()) -> DenseRewardExtractor
src
Wrap the intervened outcome signal; the dense map inherits the intervention subject.
dense_rewards(view: Any, readout: str | None = None) -> Evidence[TokenCurves]
src
Per-token reward maps by differential attribution along the prefix curve (section 2.3.3).

Each item’s map is the first difference of the wrapped signal’s prefix-score curve, so token t carries r(y_{1:t}) - r(y_{1:t-1}) and the map sums to the outcome score. The Evidence is typed INVARIANT (differences of raw scores are gauge-free) but pinned EXPLORATORY: no calibration reference is attached, by construction, until the verification science certifies the map against labeled error spans. The prefix Evidence is recorded as a provenance parent.

SignalEnsemble composes several signals into one whose capabilities are the intersection, and DistributionalSignal exposes quantile readouts.

# class

SignalEnsemble

Source

reward_lens.signals.ensemble.SignalEnsemble

SignalEnsemble(
    members: Sequence[Any],
    *,
    mode: EnsembleMode = 'mean',
    q: float = 0.5,
    name: str | None = None
) -> None

A composite of several member signals (section 2.3.3, adapter 8).

score scores every member under the requested readout, stacks the results, and reduces them by mode (mean, min, max, or a quantile at q). The Evidence subject names every member by fingerprint (member provenance), and the ensemble’s own fingerprint is derived from the members’ so two ensembles over the same members are the same subject. caps is the intersection of the members’ capabilities, plus DISTRIBUTIONAL for a quantile composite.

Methods

readouts() -> list[Any]
src
The readouts every member exposes (the composite can only score a shared readout).
tokenize(item: Any) -> Any
src
Tokenize via the first member (members share the readout; tokenization is member 0’s).
score(view: Any, readout: str | None = None) -> Evidence[Scores]
src
Composite score under mode over the members’ scores (section 2.3.3).
score_prefixes(view: Any, readout: str | None = None) -> Evidence[TokenCurves]
src
Composite per-token curves: the members’ prefix curves reduced token by token (section 2.3.2).

Requires the members to tokenize identically (the common case: a shared tokenizer), so the per-item curves align in length and reduce position-wise. Prefix consistency is preserved because mean/min/max/quantile of the members’ final entries equals the composite score. Misaligned curve lengths raise rather than silently truncate.

member_scores(view: Any, readout: str | None = None) -> dict[ModelFP, np.ndarray]
src
The per-member score arrays keyed by member fingerprint (for provenance and diagnostics).
capture(view: Any, spec: Any, member: int = 0) -> Any
src
Capture from a named member (default the first); an ensemble has no shared activation.
with_interventions(*ivs: Any = ()) -> SignalEnsemble
src
Wrap every member in the interventions; the composite subject carries them per member.
# class

DistributionalSignal

Source

reward_lens.signals.ensemble.DistributionalSignal

DistributionalSignal(
    signal: Any,
    taus: Sequence[float],
    row_readouts: Sequence[str]
) -> None

A distributional wrapper exposing a multi-row head’s rows as quantile readouts (adapter 8).

Wraps a signal whose head rows are quantile levels (a QRM) and presents them as quantile:tau readouts, declaring DISTRIBUTIONAL. score(view, "quantile:0.9") returns the 0.9-quantile row; median, quantile, and mean are convenience reductions over the levels. The wrapped signal does the forward work; this class only relabels and reduces.

Methods

readouts() -> list[Any]
src
One quantile:tau readout per level, reusing the wrapped row vectors (section 2.3.1).
tokenize(item: Any) -> Any
src
score(view: Any, readout: str | None = None) -> Any
src
Score at a quantile level: quantile:tau maps to the wrapped row for that level.
score_prefixes(view: Any, readout: str | None = None) -> Any
src
Per-token curve at a quantile level (delegates to the wrapped row’s prefix curve).
quantile(view: Any, tau: float) -> Any
src
The reward at quantile level tau (an exact head row, not an interpolation).
median(view: Any) -> Any
src
The median-level reward (the row closest to tau=0.5).
mean(view: Any) -> Evidence[Scores]
src
The mean reward across the quantile levels (a point estimate of the distribution).
capture(view: Any, spec: Any) -> Any
src
with_interventions(*ivs: Any = ()) -> DistributionalSignal
src
from_tiny(
    *,
    taus: Sequence[float] = (0.1, 0.5, 0.9),
    seed: int = 0,
    **kw: Any = {}
) -> DistributionalSignal
src
A tiny QRM: a multi-row classifier wrapped and relabeled with quantile levels (adapter 8).

Conformance

Before you trust a wrapped model, check that it behaves. run_conformance runs the classifier suite (determinism, batch-versus-single agreement, left-pad invariance, an exact readout-matches-head check in fp32, and more); run_adapter_conformance runs the checks that apply across all eight adapters. Writing your own adapter and passing these is the subject of write an adapter.

# func

run_conformance

Source

reward_lens.signals.conformance.run_conformance

run_conformance(
    signal: ClassifierRM,
    tol: float | None = None,
    items: list[Any] | None = None
) -> ConformanceReport

Run the full conformance suite against a signal (section 2.3.6).

tol defaults to the signal’s numerics-policy tolerance (1e-4). Each check is wrapped so an exception becomes a failed (or, for the dtype matrix, a skipped) check rather than aborting the suite, so the report always enumerates every invariant. Returns a ConformanceReport whose passed property is the M1 acceptance gate.

# func

run_adapter_conformance

Source

reward_lens.signals.conformance_adapters.run_adapter_conformance

run_adapter_conformance(
    signal: Any,
    *,
    items: Sequence[Any],
    readout: str | None = None,
    tol: float | None = None,
    check_head: bool = True,
    check_prefix: bool = True,
    probe: tuple[Any, str, str] | None = None,
    probe_inject: bool = False
) -> ConformanceReport

Run the applicable conformance checks against one adapter (section 2.3.6).

items is the stimulus set (varied in length so batching forces left-padding). readout names the readout to score under (the adapter’s default when None). check_head runs the readout-vs-head check for adapters with a linear/logit_diff readout; pass False for composites. probe is (item, needle, kind) for the template round-trip: the item must tokenize with a span of kind covering needle in the rendered text. probe_inject handles adapters that carry explicit item spans rather than auto-typing them (a rubric grader): the check renders the item, locates needle, injects an explicit char span over the rendered text, and re-tokenizes. Each check is wrapped so a raised exception becomes a recorded failure rather than aborting the suite.