pub struct Session {
pub loaded: BTreeMap<String, Record>,
pub last_prompt: String,
pub recent_prompts: Vec<String>,
pub updated: u64,
}Fields§
§loaded: BTreeMap<String, Record>skill id -> how it got into context (and at what confidence).
last_prompt: StringThe most recent user prompt in this conversation. Stashed by the hook
only when telemetry is on, so a later self-load seen by ski observe
(a recall miss — the model loaded a skill we never recommended) can be
tied back to the prompt that was active. Empty otherwise; never serialized
when empty, so the non-telemetry hot path leaves the file unchanged.
recent_prompts: Vec<String>Recent user prompts in this conversation, oldest-first, bounded. Drives
query-side context enrichment: a vague follow-up (“now do the other one”)
is disambiguated by the turns that preceded it. Maintained only when the
context feature is enabled (Config::context_depth > 0), so the default
hot path neither writes nor carries it. #[serde(default)] + skip-when-empty
keeps it invisible to indexes/sessions written before it existed.
updated: u64Unix seconds of the last write (diagnostics only).
Implementations§
Source§impl Session
impl Session
Sourcepub fn load(path: &Path) -> Session
pub fn load(path: &Path) -> Session
Load state for a session, or an empty session if the file is missing or unreadable. Never errors.
Sourcepub fn save(&self, path: &Path) -> Result<()>
pub fn save(&self, path: &Path) -> Result<()>
Persist state, stamping updated. Best-effort; callers in the hot path
should ignore the result so state IO can’t block a prompt.
Writes a per-process temp file then atomically renames it over the target,
so a concurrent reader (another hook/observe process sharing the
session_id) never observes a half-written file — a torn read used to
silently reset the session and re-arm dedup. The lost-update window — two
writers racing the load→mutate→save and one dropping the other’s mark —
remains; it costs at most a missed dedup (a re-injection), never
corruption, and closing it would need an advisory lock.
pub fn is_loaded(&self, id: &str) -> bool
pub fn get(&self, id: &str) -> Option<&Record>
Sourcepub fn save_merged(&self, path: &Path) -> Result<()>
pub fn save_merged(&self, path: &Path) -> Result<()>
Persist like save, but first merge the loaded ledger
with whatever is on disk now, so a mark written by a concurrent process
survives. The hook loads its session snapshot, then spends the better part
of a second embedding/reranking before saving — ample time for ski observe to record a model self-load that a plain save would overwrite
(the lost mark re-arms dedup and the skill gets re-injected).
Merge rules, per skill id (dedup-safety errs toward suppression):
- present only on disk → kept (that’s the concurrent writer’s mark);
ModelbeatsSkiregardless of side (a used skill stays used);- both
Ski→ the higher recorded confidence wins (matchesshould_recommend’s “no repeat after a HIGH showing”).
Prompt fields (last_prompt, recent_prompts) are taken from self:
the hook is their only writer, and there is at most one hook per prompt.
Callers that intentionally wipe state (the compaction re-arm) must use
the plain save, or the merge would resurrect the ledger.
The load→rename window still exists but shrinks from the whole hook runtime to microseconds; closing it fully would need an advisory lock.
Sourcepub fn should_recommend(&self, id: &str, new_conf: f32, high: f32) -> bool
pub fn should_recommend(&self, id: &str, new_conf: f32, high: f32) -> bool
Whether id should be recommended now, at new_conf, given what we
already know. The two dedup rules:
- a used skill (
Source::Model) is never recommended again; - a recommended-but-unused skill (
Source::Ski) is re-recommended only when it newly reacheshighconfidence (it was shown belowhighbefore — a clearer prompt earns one stronger nudge; after a HIGH showing, never).
Sourcepub fn mark_recommended(&mut self, id: &str, confidence: f32)
pub fn mark_recommended(&mut self, id: &str, confidence: f32)
Record that we recommended id at confidence. Stores the confidence we
just showed (so the next-turn should_recommend test is accurate), but
never downgrades a Model load — once the model used a skill it stays
used.
Sourcepub fn mark_used(&mut self, id: &str)
pub fn mark_used(&mut self, id: &str)
Record that the model loaded id itself. Always wins (the strongest
signal); keeps any confidence we’d previously shown for diagnostics.
Sourcepub fn mark(&mut self, id: &str, source: Source)
pub fn mark(&mut self, id: &str, source: Source)
Generic mark, kept for callers/tests that don’t carry a confidence:
Model via [mark_used], Ski as a confidence-0 first sighting that
never overwrites an existing entry.
Sourcepub fn push_prompt(&mut self, prompt: &str, max: usize)
pub fn push_prompt(&mut self, prompt: &str, max: usize)
Append prompt to the rolling context window, keeping at most max of the
most recent prompts (oldest dropped first). A blank prompt, or one identical
to the immediately previous entry (a resubmit), is ignored so the window
holds distinct conversational turns. max == 0 disables the window entirely
(the feature-off path).
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Session
impl<'de> Deserialize<'de> for Session
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for Session
impl RefUnwindSafe for Session
impl Send for Session
impl Sync for Session
impl Unpin for Session
impl UnsafeUnpin for Session
impl UnwindSafe for Session
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<R, P> ReadPrimitive<R> for P
impl<R, P> ReadPrimitive<R> for P
Source§fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
ReadEndian::read_from_little_endian().