Give a memory a word ceiling, after measuring one

Ran the memory prompt end to end against a real model as a controlled
A/B: one story generated through the app's own build_context a turn at
a time, then both prompts run over the same blocks, so the story is held
constant and the prompt is the only variable. Fresh process per call, so
neither arm sees the other and the model is never told what is being
tested. The control is the exact prompt from 9cdcb55.

The reported fault reproduced. Two consecutive memories written from one
story minutes apart came back in two different persons — "You crept low
through the mist" and "The player asked Gwen to". With the cast brief
both named Kaelen. The control also inverted who acted on a move whose
player text was "grab her wrist and pull her down", which is the failure
the brief predicts: with no cast there is nothing to say whose wrist
"her wrist" was.

It also found something the prompt review had not. "1-2 plain sentences"
is not a length, and the same model wrote 34 words for one block and 105
for the next. A 105-word memory is a paragraph, and `memory_top_k`
injects five every turn, so the bank's running cost was set by a number
nobody had ever stated.

MEMORY_MAX_WORDS states it, and the prompt now says which details to
keep when trimming: the ones a later scene could turn on. Re-run over
the identical story, the same two blocks came back at 32 and 58 words,
still named, still third person, still carrying the camp map, the
strongbox behind the second tent, and the strap frayed near through.
Variance is the real gain — 34..105 became 32..58.

Overshooting 50 slightly is expected. Models exceed word budgets, which
is why builder.length_hint already carries a buffer for the same reason.

What this does not show: the run used a Claude model, and the app talks
to an OpenAI-compatible endpoint whose weaker models are why
worldstate/parse.py tolerates trailing commas. The prompt is followable
and the brief supplies the missing information; a weaker model is not
proven to comply as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPyQN926gkZTAYfgugcaok
This commit is contained in:
Claude
2026-08-31 15:03:20 +05:30
committed by Parth
parent 1b5e4c56cd
commit 07192767d8
2 changed files with 73 additions and 14 deletions
+11 -1
View File
@@ -63,6 +63,14 @@ MAX_CAST_MEMBERS = 8
CAST_ENTRY_CHARS = 240
SETTING_TOKENS = 300 # of `adventure.memory`, the plot essentials
# A ceiling on one memory, in words. Measured, not guessed: with only
# "1-2 plain sentences" to go on, a real model wrote 34 words for one block and
# 105 for the next, and a 105-word memory is a paragraph. Five of those are
# injected per turn at the default `memory_top_k`, so the bank's cost is set
# here. A stated number also holds the length steady between memories, which is
# the same consistency the framing rule buys for the wording.
MEMORY_MAX_WORDS = 50
# The framing rule is the larger half of this prompt, and it is worth the
# tokens. Without it the model chooses a person per call, so one bank ends up
# holding "You entered the crypt", "The player entered the crypt" and "He
@@ -77,7 +85,9 @@ SETTING_TOKENS = 300 # of `adventure.memory`, the plot essentials
MEMORY_SYSTEM_PROMPT = (
"You compress interactive-fiction story excerpts into memories. Respond with "
"1-2 plain sentences in past tense stating the concrete facts and events "
"(names, places, items, promises, injuries).\n\n"
f"(names, places, items, promises, injuries), in at most {MEMORY_MAX_WORDS} "
"words. Keep the details a later scene could turn on — a name, a promise, an "
"injury, where something is — and drop the ones it could not.\n\n"
"Write in the third person. The excerpt is written in the second person: "
'"you" is the protagonist, who is named in the Cast. Refer to the '
'protagonist by that name, never as "you". If the Cast gives no name for '