Repository navigation
solvi 1.3.0 — laws for rule-governed worlds: rivals, negations, a factored model
solvi 1.3.0 — laws for rule-governed worlds: rivals, negations, a factored model
A release of the experimental laws module (solvi.experimental.laws). An agent that learns the rules of a grid puzzle
game from its own moves used the module as a library and had to write twelve pieces itself; this release ships eleven
of them — each opt-in or a plain addition — plus one fix to the noisy admission, the only new behaviour on by default. The
twelfth, a held-out check on probe admissions, is not shipped: on a second task it refused almost nothing. No code
outside the laws module changes; the docs gain a best-practices recipe for a compact (~70M) decider. Full details,
with every measurement and its conditions, are in the
CHANGELOG.
Scope: what the laws module is for
The features were measured on grid games and on relational laws over generated graphs — environments whose behaviour
follows discrete rules. On a real agent tool-call log (write calls of the public AgentDojo benchmark, 1,213 held-out
rows from 30 tasks; laws learned as exceptions to a provenance guard's verdicts) the opt-in features of this release
showed no measured gain over the default: coverage the same, soundness inconclusive, and the share of harmful writes
let through stayed above the 0.15 set as the bar beforehand, with and without them (0.177 and 0.218, at about 0.10
false interruptions — about what a tuned gradient-boosting model on the same atoms gives, 0.188). For noisy
behavioural logs use a calibrated scorer, not the laws module.
Highlights (all in solvi.experimental.laws; every switch off by default)
- Refuse a law the held-out rows cannot tell from another:
LawSet(rivals=True). Before, the order of the
candidates chose. On a synthetic grid-game world with levels arriving one at a time and the separating row on 10 %
of levels, a wrong law was the first accepted in 198 of 1,000 streams and in 0 with rivals (the right one
763 → 816); the cost is delay — in 65 of the 1,000 streams nothing was accepted within 20 levels, because the true
law waits for a row that tells it from its rivals. - Atom implications by construction:
Grammar(implies=...), so an atom implied by another is never reported
untested; withadmit_by_probes(coverage="varied", coverage_on=...)only an atom that actually varied blocks an
admission. On the library's gridworld toy with probes and a random pool of states: refusals for an implied atom
485 → 0, laws admitted 81 → 99, 0 false laws; on a pool of walked states the refusals fell as well (536 → 0) but no
law was admitted withoutcoverage="varied"(111 with it, 0 false) — atoms that never varied still block. - Declared effect equivalences:
LawSet(equivalent=[...])merges candidates whose effects agree by construction
(Law.aliases); a merged candidate stays a rival until the probes separate it. On MiniGrid (one grid family, three
layouts, 20 seeds, probes answered by the simulator) position rules went from 15 (2 wrong) to 24 (none wrong) and
facing rules from none admitted to 34, none wrong — the gain is the twin kept as a rival; no accepted law carried an
alias. - Fewer, surer laws from revisited states:
Buffer(dedup=True | key)drops a row whose situation is already in
the buffer. - A factored transition model:
solvi.experimental.laws.factored(FactoredModel,Channel,Row(depends=)) —
one law set per effect channel, δ split over the channels, row provenance that voids the rows a fallen law labelled. - Smaller pieces:
Grammar(min_k=1)(no empty condition), anUnknownprobe answer,admit_by_probes(..., siblings="undetermined"), a dry-run admissioncommit=False, a publicLawSet.versioncounter, andBuffer/
LawSetthat pickle without theirid()-keyed caches.
Experimental even within the module: the limits measured
- Negated literals (
Grammar(negations="refine" | "all"), withBudgetedProposerso that negated candidates
spend their own budget). On fresh relational-graph tasks that need a negation, found 0.18 → 0.39 and 0.15 → 0.50;
unsound laws not higher; candidates ×1.63; on two families that need none, found lower by one task in 97 and one in
87 — the first below the bar set before the run. An admitted negated law can steer the search away from the true law. Buffer(dedup=True)did what it targets on a second task (wrong laws per admission 0.415 → 0.112) but cost
found laws (0.369 → 0.330, below the bar set before the run). Turn it on where a few objects are revisited many
times and fewer, surer laws are worth the loss.- The factored model is more accurate than one joint
LawSeton MiniGrid (0.999 against 0.984 next-state
accuracy) but saves no data (0.94× and 1.05× the joint model's transitions, against a bar of 0.6×) and, like the
joint model, learns false laws on states the environments never reach. Use it for accuracy, not to learn from fewer
transitions. - Effect equivalences: the declarations are written by the caller and must be true (a false one is caught only on
logged rows bycheck_equivalent, or when the rule later fails); measured on one grid family, in a deterministic
world with exact probe answers. On the gridworld toy the mechanism did not fire, so no gain there is attributed to it. coverage="varied"is an Occam choice: a law may be too general on states nobody produced (the atoms stay
listed inuntested).
Breaking changes
None: every new switch is off by default, with one exception that is the behaviour change below — the fit check of the
noisy admission (fit_level=0.01, fit_support=1) is new and on:
admit_by_probes(..., noise=ε)no longer accepts a law that does not fit the noise. Before accepting, the chosen
law is checked against ε on the rows where it applies; too many misses, or too little support, gives "undetermined".
Before, the least-bad wrong candidate could win when the true law was not offered. Where the right law is offered
the outcome is unchanged.fit_level=Nonerestores the old behaviour.
pip install -U solvi