From 9795bb1a6192719b62a715ee022bd6e0c7fde01d Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 26 Oct 2026 03:29:21 +0000 Subject: [PATCH 1/4] chore(vendor): bump tinyinference and tinytools submodules Update the vendored tinyinference and tinytools submodule pointers to their latest revisions. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- vendor/tinyinference | 2 +- vendor/tinytools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/tinyinference b/vendor/tinyinference index f5c0e56d2..c29d5118e 160000 --- a/vendor/tinyinference +++ b/vendor/tinyinference @@ -1 +1 @@ -Subproject commit f5c0e56d26d54c9e710eecd278066d29eaf39d29 +Subproject commit c29d5118eddfa12b83c266d7d5f07f6918dc3787 diff --git a/vendor/tinytools b/vendor/tinytools index 75e42ba96..82c0d975e 160000 --- a/vendor/tinytools +++ b/vendor/tinytools @@ -1 +1 @@ -Subproject commit 75e42ba9647eb801891a12ad05b6337f6a1c126b +Subproject commit 82c0d975e2667e1c525afe525006134431641785 From 3bc47e75f60a5feb5e04d9c184f7a3be15200db8 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 26 Oct 2026 03:29:48 +0000 Subject: [PATCH 2/4] test(session): cover head generation probing without reserving successors Add tests asserting that probing for the head generation leaves no reserved successor behind, and that a generation begun explicitly stays reserved so a second begin attempt fails. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- .../src/port/memory/mod_tests.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/tinyagents-session/src/port/memory/mod_tests.rs b/crates/tinyagents-session/src/port/memory/mod_tests.rs index 8b60fac65..925b89584 100644 --- a/crates/tinyagents-session/src/port/memory/mod_tests.rs +++ b/crates/tinyagents-session/src/port/memory/mod_tests.rs @@ -271,6 +271,41 @@ fn an_unwritten_successor_is_reserved_until_written_or_dropped() { ); } +#[test] +fn probing_for_the_head_generation_does_not_reserve_the_next_one() { + let locator = InMemoryTranscriptLocator::new("test"); + let session = SessionRef::scoped("thread-probe", "planner"); + let meta = test_meta("thread-probe"); + let baseline = vec![TranscriptMessage::user("hello")]; + locator + .open_session(&session, meta.clone()) + .unwrap() + .append(baseline[0].clone()) + .unwrap(); + + // Resuming asks for the head, which probes the successor generation. + assert_eq!(locator.head_generation(&session), session); + assert!(!locator.session_exists(&session.next_generation())); + assert!(locator.session_chain(&session).len() == 1); + + let (successor, _) = locator + .begin_generation_from_baseline(&session, meta, &baseline) + .expect("a probe leaves no state for the next generation to collide with"); + assert_eq!(successor, session.next_generation()); +} + +#[test] +fn a_begun_generation_stays_reserved_after_probing_for_it() { + let locator = InMemoryTranscriptLocator::new("test"); + let session = SessionRef::scoped("thread-probe-reserved", "planner"); + let meta = test_meta("thread-probe-reserved"); + let (successor, _) = locator.begin_generation(&session, meta.clone()).unwrap(); + + assert!(!locator.session_exists(&successor)); + assert!(locator.read_session_transcript(&successor).is_none()); + assert!(locator.begin_generation(&session, meta).is_err()); +} + #[test] fn completed_retention_uses_completion_time() { let turns = InMemoryTurnStates::default(); From 8282b7f173e55486d8e787f65c5b533dcf894f8e Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 26 Oct 2026 03:30:27 +0000 Subject: [PATCH 3/4] chore(vendor): bump tinytools and tinyinference submodules Update the vendored tinytools and tinyinference submodules to their latest commits. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- vendor/tinyinference | 2 +- vendor/tinytools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/tinyinference b/vendor/tinyinference index c29d5118e..f5c0e56d2 160000 --- a/vendor/tinyinference +++ b/vendor/tinyinference @@ -1 +1 @@ -Subproject commit c29d5118eddfa12b83c266d7d5f07f6918dc3787 +Subproject commit f5c0e56d26d54c9e710eecd278066d29eaf39d29 diff --git a/vendor/tinytools b/vendor/tinytools index 82c0d975e..75e42ba96 160000 --- a/vendor/tinytools +++ b/vendor/tinytools @@ -1 +1 @@ -Subproject commit 82c0d975e2667e1c525afe525006134431641785 +Subproject commit 75e42ba9647eb801891a12ad05b6337f6a1c126b From 28b21684da4d55a5b2b2c51c00976bfc0e35403e Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Mon, 26 Oct 2026 03:31:32 +0000 Subject: [PATCH 4/4] fix(session): avoid registering stems on exact transcript lookup The in-memory locator inherited the trait default for read_session_transcript, which probes through open_stem and leaves an empty handle behind for every generation queried, causing begin_generation to see its own successor as already existing. It now performs a read-only lookup that only returns a transcript when one has actually been written. Auto-committed-on: cuttlefish Co-authored-by: Medulla --- .../tinyagents-session/src/port/memory/mod.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/tinyagents-session/src/port/memory/mod.rs b/crates/tinyagents-session/src/port/memory/mod.rs index 97063030b..d36262cd9 100644 --- a/crates/tinyagents-session/src/port/memory/mod.rs +++ b/crates/tinyagents-session/src/port/memory/mod.rs @@ -336,6 +336,26 @@ impl TranscriptLocator for InMemoryTranscriptLocator { .map(|(_, history)| history as Arc) } + /// An exact lookup that never registers a stem. The trait default probes + /// through [`Self::open_stem`], which would leave an empty handle behind + /// for every generation asked about, and `begin_generation` would then + /// find its own successor "already exists". Only a written transcript + /// counts, as in the persistent locators. + fn read_session_transcript(&self, session: &SessionRef) -> Option> { + let stem = session_stem(session); + let history = self + .stems + .lock() + .unwrap_or_else(PoisonError::into_inner) + .iter() + .find(|(known, _, _)| known == &stem) + .map(|(_, _, history)| history.clone())?; + match history.read_session() { + Ok(Some(_)) => Some(history as Arc), + _ => None, + } + } + fn open_stem( &self, stem: &str,