Repository navigation
engine: MN-REQ-05.5 session cap evicts least-recently-touched saveable session - #220
Merged
Merged
Conversation
…e session; rid lease_lost/reserved; reads/open/load touch; ship flags
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Engine build of MN-REQ-05.5 SessionCapEvictLru, as modelled in #219 (merged b2636ab). Do not merge without owner review.
What changed:
sessions_unsaveable_held. If nothing can be evicted, the refusal islimit_exceededwith "all sessions are in use".config_conflict. With no dir set, the cap refuses.sessions_evicted,sessions_unsaveable_heldandlast_evicted_product. Never a session id.restored=true, the same ACL, no lease, a fresh TTL and a fresh touch_seq.mutate rid=:lease_lostif the caller no longer holds the lease,reservedif another llm_id holds it. With no rid, mutate is unchanged.evictShippedandcommitRefusesLostLeaseset to true, the satisfy link added, the xfails removed.Tests: 688 passed, 1 skipped (already skipped before this change), 0 xpassed. ruff is clean. Built by Claude haiku; Claude sonnet review: CORRECT.
Reviewer nits, not fixed:
serve_statuscan now raise an unhandledconfig_conflictif the two dirs differ. Serve already refuses to start in that case._is_owned()relies on a private CPython RLock API.Builder gap notes
Branch engine/mn-req-05.5-evict. Model unchanged in design; the entries below are where the
model or a test was silent, ambiguous, or contradicted, and what was built.
A. Model and test contradictions (test updated to the model; flagged)
Refusal text (
sessions|n/max→all sessions are in use). Model R4 says the refusal isthe plain message "all sessions are in use". These pre-ship assertions were updated:
test_max_sessions_override,test_cli_close_decrements_so_open_can_minttest_every_cap_and_write_proof(max_sessionswire)test_unsaveable_expiry_counts_against_session_cap,test_live_unsaveable_expiry_keeps_warns_cap_and_closeOpen stamps the first touch (model: open is the first touch,
modified_at = created_at).tests/test_session_modified.py
test_modified_at_updates_on_readassertedmodified_at is Noneafter open. Now asserts
== created_at.Load is a touch (model: session_load is a touch with a fresh TTL). tests/test_snapshot.py
test_snapshot_preserves_modified_atasserted the loadedmodified_atequals the saved one.Now: a default load's
modified_atis no earlier than the saved one, andload_snapshot(..., preserve_clocks=True)(upgrade restore path) keeps the saved clock.test_touch_seq_not_in_snapshot_fresh_on_load (tests/test_session_evict.py). With cap 3
and b, c, d live, the load of the evicted id evicts b (oldest). The test then looked up
get_entry(b).touch_seq, which is None. Changed the comparison to the live sessions (c, d).test_reserve_lease_is_not_an_exemption (tests/test_session_evict.py). Its setup used
get_session(a)at minute 2. That is a read and a touch (READ-SURFACES), so a became thenewest and b, not a, was the correct victim; the test expects a. Setup now uses
SessionStore(a)(a handle, no touch). The lease intent is unchanged.B. Model silent or ambiguous (chosen reading)
New open while a victim is mid-save. Task text: "new open waits". Model and
test_cap_two_concurrent_opens_one_victim_per_slot: the picking open waits for its own save;
a late open is refused
limit_exceededat once, without queuing. Built the model/test reading.Ingest touch deferred to a committed write.
_ingest_cmd(mutate, add, update, ...) nolonger touches before the gate. A session is touched only when the gate commits (
mark_written).Needed so a refused
lease_lostorreservedcommit is not a touch. Side effect: any refusedor no-op ingest (for example an empty batch) is not a touch either. Other session-scoped writes
(
reserve,release,delete, ...) still touch on entry as before.rid with no llm_id on a live lease. Model covers "a different llm_id" (
reserved) and"not held" (
lease_lost). No llm_id at all: built asno_llm_id(the existing code for ananonymous commit on a leased row).
Admin "name or tag" of the evicted session. Sessions have no name. serve_status reports
last_evicted_product: the gate-side--productlabel of the most recent eviction, or null.Never an mn_ id. Also
sessions_evictedandsessions_unsaveable_heldas specified.serve_status with TCP serve running. The counters are the MCP process's (in-process
registry). When TCP serve is up, the eviction counters come from this process, not the serve
process. Not covered by the model; flagged for ops.
snap_model pre-check unchanged. tests/ and catalog_snap
_precheck_planstill refusewhen live + plan > max (message
sessions|n/max), without evicting first. The model coversopen and load only. Left as is so a snap never evicts its own catalog mid-plan. Refusal is still
limit_exceeded.Snapshot dir alias.
MEMNET_SNAPSHOT_DIRis an alias; the same path under both names isaccepted (compared by realpath).
Caps()raisesconfig_conflictfor any construction, andrun_servecallsCaps()at start so serve refuses to start.Victim lock on the calling thread. An RLock held by the calling thread would pass a plain
try-lock. The pick also checks
entry.lock._is_owned()(private RLock API, present in CPython).Without it an open on a thread that holds a session lock could evict that session.
restoredflag. Set on everyload_snapshothandle, not only on a reopen of an evictedsession. The test only checks loaded handles.
Upgrade restore.
load_snapshot_text(preserve_clocks=True)keeps saved clocks; it stillclaims a slot. The upgrade path refuses a non-empty registry first, so it never evicts.
C. Doc and ship wording
Model status wording ("proposed, not shipped", "xfail until shipped") updated to shipped,
unreleased in requirements, verify, behaviour, deploy comments, sysml-models/README and
CHANGELOG. The ship-step paragraph in requirements is kept as written. README and AGENTS had no
evict wording, so only CHANGELOG changed there.
Verify MN-VER-05-S04 now also cites
test_failed_evict_save_is_not_a_touch(built per Elon item 2). The new sync tests (engine save map, no rule flag read) are in
tests/test_sysml_session_evict.py, which is not a runtime file, so they are not verify-cited.