Give every memory a node, and show only the ones on your path
A hand-written memory used to carry a NULL depth, described in the model as "belongs to the adventure rather than to a path". That sounds harmless and is not: a NULL is a coordinate no fork can cap, so a note typed on one line followed the reader onto branches whose story it never described. It takes the head now — the story you were reading when you wrote it — and obeys exactly the rule a summarised memory obeys. The unanchored escape clause in lineage.Path.clause existed for that single case and is deleted rather than left unused. Its docstring argued that a capped depth would drop a typed memory the moment its branch stopped being the newest entry; anchoring answers the same worry better, because the memory is not exempt from the path, it is on one. The drawer now shows the path being read and nothing else, filtered by the clause retrieval itself uses, so the bank you can see is the bank the model can see. Nothing is stranded: a memory lives on a branch, switching to that branch shows it, and deleting the branch deletes it. Pinning decides order, the path decides existence. Migration 62 lands existing NULL-depth memories at depth 0 of their branch rather than at the tip. 0 is at or before every fork point, so every memory stays visible from exactly the paths it is visible from today — nobody's bank loses a row on deploy. The tip is the tidier-sounding choice and would have emptied them out of every branch forked earlier than they were typed. This supersedes the on_path flag and the "another branch" badge from earlier today; anchoring makes them redundant, and they are removed. Four tests changed because they asserted the old contract, not because they broke. The one worth reading is the pair replacing test_a_hand_written_memory_is_not_lost_at_the_first_fork: typed on shared trunk it still survives a fork, and typed on ground the fork never travelled it no longer follows you. 402 tests. Verified on tools/branch_fixture.py: each branch's drawer holds its own memory and not the other's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H5qiyiR7gtFQaoDphHZ3g
This commit is contained in:
committed by
Parth
co-authored by
Claude Opus 5
parent
1d1367ce3a
commit
2d38a162d4
+28
-21
@@ -769,29 +769,36 @@ live, redraws the bar, swaps the story to the other take (`hp -5`, not `hp -40`)
|
||||
repoints Insights at the other path — "History: 5 of 5 actions", carrying the scratch and
|
||||
not the beating.
|
||||
|
||||
**The memory drawer says which memories the model can actually see.** Retrieval has been
|
||||
path-scoped since SP3, so a memory on a branch this story never travelled is never sent.
|
||||
The drawer still lists the whole bank — hiding rows would leave memories impossible to
|
||||
find and delete, in a phase whose rule is that nothing is removed automatically — but
|
||||
listing them *alike* was its own lie: it told the player the model remembers something it
|
||||
cannot see. `MemoryOut.on_path` marks them, and the row is set back and labelled *another
|
||||
branch*. The flag is computed from **the predicate retrieval itself uses**
|
||||
(`path_of(...).clause(Memory, unanchored=True)`), not a second spelling of it, because two
|
||||
spellings drift and the failure would be a badge claiming the opposite of what the model
|
||||
gets. Pinning does not override it — the path clause runs before pinning is considered.
|
||||
**Every memory is now a memory of a story, not of an adventure — migration 62.** A
|
||||
hand-written memory used to carry a NULL depth, described as "belongs to the adventure
|
||||
rather than to a path". That reads as harmless and is not: a NULL is a coordinate no fork
|
||||
can cap, so a note typed on one line followed the reader onto branches whose events it
|
||||
never described. `tree.place_memory` anchors it at the head instead — *the story you were
|
||||
reading when you wrote it* — and it then obeys exactly the rule a summarised memory obeys.
|
||||
|
||||
**The relationship is asymmetric, and a test now says so.** A fork borrows its ancestors,
|
||||
so a memory written on the parent is on the fork's path too; the reverse is never true.
|
||||
Worth knowing before anyone "fixes" it into a symmetric check.
|
||||
The whole `unanchored` escape clause in `lineage.Path.clause` existed for that one case
|
||||
and is **deleted**, not merely unused. Its docstring argued a capped `depth <= fork` would
|
||||
drop a typed memory "the moment its branch stopped being the newest entry"; anchoring is
|
||||
the better answer to the same worry, because the memory is not exempt from the path, it is
|
||||
*on* one.
|
||||
|
||||
**One known sharp edge, inherited rather than introduced.** A *hand-written* memory has a
|
||||
branch but no depth, and `unanchored=True` keeps it whatever the lineage cap says — so
|
||||
one typed on the parent follows you onto a fork whose events it may not describe. That is
|
||||
SP3's deliberate choice (`test_a_hand_written_memory_is_not_lost_at_the_first_fork`): the
|
||||
alternative is a memory vanishing at the first fork after it was typed. Auto-summarized
|
||||
memories carry a depth and *are* capped at the fork, so they behave as expected. If this
|
||||
ever bites, the fix is to anchor a hand-written memory at the head depth when it is
|
||||
created rather than to change the clause.
|
||||
**The drawer shows the path being read, and nothing else.** Same clause as retrieval, so
|
||||
the bank you can see is the bank the model can see — one question, one answer. The earlier
|
||||
attempt at this shipped an adventure-wide list with an `on_path` flag and an *another
|
||||
branch* badge; anchoring makes that redundant, and the field, the badge and its CSS are
|
||||
gone. Nothing is stranded by hiding: a memory lives on a branch, switching to that branch
|
||||
shows it, and deleting the branch deletes it
|
||||
(`test_deleting_a_branch_deletes_the_memories_written_on_it`).
|
||||
|
||||
Pinning is unchanged and still path-scoped: it decides *order*, the path decides
|
||||
*existence*. A pinned memory on a branch you are not reading is not sent, because the path
|
||||
clause runs before pinning is considered.
|
||||
|
||||
**Migration 62 anchors existing NULL-depth memories at depth 0 of their branch**, not at
|
||||
the tip. 0 is at or before every fork point, so every memory stays visible from exactly
|
||||
the paths it is visible from today — the anchor takes nothing out of anybody's bank on
|
||||
deploy. Anchoring at the tip would have emptied them out of every branch forked earlier
|
||||
than they were typed, on a database with real users on it.
|
||||
|
||||
**The scroll path was driven, and it holds.** Three prepends on the 602-action fixture,
|
||||
60 actions and ~16,200 px each. The same DOM node stayed at viewport top 792 → 787 — a
|
||||
|
||||
+19
-9
@@ -166,15 +166,25 @@ That is now four bugs on this frontend found by exercising it rather than by tes
|
||||
two of them in paths that had just shipped. The pattern is not subtle any more: **this
|
||||
frontend has no test runner, so anything not driven by hand is unverified.**
|
||||
|
||||
**The memory drawer now says what the model can see.** Retrieval has been path-scoped
|
||||
since SP3, but the drawer listed every memory alike — so on a fork you read "Fell down
|
||||
the cellar stairs" and reasonably concluded the AI knew it, when that memory is never
|
||||
retrieved on that branch. Rows off the current path are set back and labelled *another
|
||||
branch*, still fully editable and deletable. The flag comes from the predicate retrieval
|
||||
itself uses, so the badge cannot drift from the behaviour. Note the asymmetry before
|
||||
changing it: a fork borrows its ancestors, so a parent's memory *is* on the fork's path;
|
||||
the reverse never is. And a hand-written memory has no depth, so it survives the fork cap
|
||||
by design — see SP7's entry in `plan/14` for the one case where that surprises.
|
||||
**A memory is now attached to a node, always — migration 62.** Hand-written ones used to
|
||||
carry a NULL depth ("belongs to the adventure, not to a path"), which is a coordinate no
|
||||
fork can cap, so a note typed on one line followed you onto branches whose story it never
|
||||
described. They anchor at the head now, and the `unanchored` escape clause in
|
||||
`lineage.Path.clause` is deleted rather than left to rot.
|
||||
|
||||
**And the drawer shows only the path being read**, filtered by the same clause retrieval
|
||||
uses: the bank you can see is the bank the model can see. Nothing is stranded — a memory
|
||||
lives on a branch, switching to it shows the memory, and deleting the branch deletes it.
|
||||
Pinning decides *order*, the path decides *existence*.
|
||||
|
||||
Migration 62 lands existing NULL-depth memories at **depth 0 of their branch**, which is
|
||||
at or before every fork point, so nobody's bank loses a row on deploy. The tip would have
|
||||
been the tidier-sounding choice and would have emptied them out of every branch forked
|
||||
earlier than they were typed.
|
||||
|
||||
An earlier pass this session shipped the other design — adventure-wide list, `on_path`
|
||||
flag, *another branch* badge. Anchoring made it redundant and it was removed. Worth
|
||||
knowing if the phrase turns up in an older commit message.
|
||||
|
||||
**The scroll path is finally driven.** 602-action fixture, three prepends of ~16,200 px
|
||||
each: the same DOM node held viewport top 792 → 787, and the view stayed 48,174 px from
|
||||
|
||||
Reference in New Issue
Block a user