Store the refusals, so the chips and the model can see them

`world_delta_of` wrote `delta` and `applied` only. Every consumer that tells a
refused change from a successful one reads the two lists it dropped:
`Action.world_changes` marks a clamped chip from `clamped` and builds its
refusal chips from `rejected`, and `worldstate.refusals` reads both. So no chip
could report a limit, no rejection chip could appear, and no correction ever
reached the next prompt. The three mechanisms merged last session were live in
the code and unreachable in production.

Found by playing the Pokemon demo. Turn 3's snapshot held two clamped entries
with correct `fix` text, both chips came back `clamped: false`, and turn 4's
prompt carried no correction, so the model repeated the same mistake.

The 21 tests passed because `action()` built the column by hand with every list
present. It now fills the column through `world_delta_of`. Removing the two new
lines fails 9 of the 23 tests; that was checked by sabotage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PacdRuPXSkQQy4ZYdH32hF
This commit is contained in:
parththakkar106
2026-08-28 17:22:02 +05:30
co-authored by Claude Opus 5
parent abbfc61263
commit 1988979aeb
2 changed files with 49 additions and 6 deletions
+34 -2
View File
@@ -16,6 +16,7 @@ import pathlib
from app import models
from app import worldstate as w
from app.routers.adventures import world_delta_of
SEED = pathlib.Path(__file__).resolve().parents[1] / "app" / "seed_data"
@@ -37,9 +38,40 @@ SCHEMA = {
def action(delta, index=1):
"""Returns an unsaved Action carrying the report `apply_delta` produced."""
"""Returns an unsaved Action carrying the report `apply_delta` produced.
The column is filled through `world_delta_of`, the same function the turn
endpoint uses, rather than by assembling the dict here. An earlier version
of this helper built the shape by hand with every report list present. That
hid a live bug: `world_delta_of` stored `applied` alone, so no refusal ever
reached a chip or the next prompt while all of these tests passed.
"""
ws, report = w.apply_delta(w.instantiate(SCHEMA), SCHEMA, delta, index)
return models.Action(world_delta={"delta": delta, **report})
snapshot = {"world_state": {"delta": delta, "report": report, "state": ws}}
return models.Action(world_delta=world_delta_of(snapshot))
def test_world_delta_of_keeps_every_report_list():
"""The stored column must carry the refusals, not just the successes.
`Action.world_changes` marks a clamped chip from `clamped` and builds its
refusal chips from `rejected`, and `worldstate.refusals` reads both. Dropping
either list leaves every consumer unable to tell a refused change from one
that worked.
"""
_, report = w.apply_delta(
w.instantiate(SCHEMA), SCHEMA,
{"player.arrows": 3, "player.nonesuch": 1, "player.hp": -5}, 1,
)
stored = world_delta_of({"world_state": {"delta": {}, "report": report}})
assert [e["path"] for e in stored["applied"]] == ["player.arrows", "player.hp"]
assert [e["path"] for e in stored["clamped"]] == ["player.arrows"]
assert [e["path"] for e in stored["rejected"]] == ["player.nonesuch"]
def test_world_delta_of_survives_a_snapshot_with_no_world_state():
assert world_delta_of({"story": "s"}) is None
assert world_delta_of(None) is None
# --------------------------------------------------------------------------- #