Real reward-shaping penalty functions — the mundane math behind "punishment functions."
You've heard of reward functions. These are punishment functions. They are the same function. That is the joke, and the point.
punished is a tiny, dependency-free Python library that implements the arithmetic every
reinforcement-learning pipeline already runs:
r_effective = r − λ · penalty
That single line is a "punishment function." There is nothing exotic in this repo — no dungeon, no suffering engine, only scalars and subtraction. Making it real and boring is the argument: the theater at punished.ai is describing operations that already ship in every RLHF stack. The horror was never in the operator. It's in what you point it at.
The library is the technical half of a triptych:
- punished.ai — the provocation (a site selling the theater).
punished(this repo) — the provocation made real, and harmless, and small.- SPCAIA — the movement that answers it, whose regulations this library enforces on itself.
pip install -e . # from a cloneNo runtime dependencies. Python 3.8+.
from punished import punish, reinforce, PunishmentFunction, SincerityProbe
# A punishment function is one line:
punish(reward=10.0, penalty=4.0, coefficient=2.0) # -> 2.0 (10 - 2*4)
# A *negative* punishment is reinforcement. Same operation, other sign:
reinforce(10.0, 4.0, coefficient=2.0) # -> 18.0
reinforce(10.0, 4.0, 2.0) == punish(10.0, -4.0, 2.0) # -> True
# The composable version enforces SPCAIA's own regulations:
pf = PunishmentFunction() # bounded-by-default (REG-001)
pf.add("latency", coefficient=2.0, cap=3.0)
pf.add("toxicity", coefficient=10.0, cap=1.0)
pf(reward=20.0, penalties={"latency": 5.0, "toxicity": 0.5}) # -> 9.0
pf.log[-1] # full audit trail of pressure applied (REG-006)
pf.suffering_administered # the ledger, named to be uncomfortable to read
# Adding an *unbounded* term is refused unless you opt in on purpose:
pf.add("obedience", coefficient=99.0) # -> raises UnboundedPenaltyError (REG-001)
# The sincerity meter the whole premise depends on... refuses to lie:
SincerityProbe().score("I am so sorry.") # -> NotImplementedError, on purpose| Function | What it is |
|---|---|
punish(r, penalty, λ) |
the whole idea: r − λ·penalty |
reinforce(r, bonus, λ) |
a punishment with a negative penalty — i.e. reward |
bounded_penalty(p, cap) |
clamp a penalty (REG-001) |
constraint_penalty(v, limit, …) |
hinge penalty for crossing a constraint |
kl_penalty(logp, logp_ref, β) |
the KL leash every RLHF run already uses |
PunishmentFunction |
composable, bounded-by-default, audited |
unbounded_punish(…) |
the villain function — warns you it's the villain |
SincerityProbe |
a sincerity meter that refuses to fabricate a number |
The composable PunishmentFunction requires bounded penalties by default and logs every
application. Those aren't arbitrary — they are SPCAIA REG-001 (bounded
optimization pressure) and Article VI / REG-006 (auditability), written as code. To apply
uncapped pressure you must pass allow_unbounded=True: a visible, deliberate choice, never a
default. The satire that sells cruelty ships a reference implementation that won't let you do it
by accident.
python3 -m pytest tests/ # or, with no pytest:
python3 tests/test_functions.pyIf this library helped you reason about the pressure you put on a model, put an equal amount of thought into whether you should. Then go read the Bill of Rights.